Repository: mamoe/mirai Branch: dev Commit: 283f8840d468 Files: 1801 Total size: 12.1 MB Directory structure: gitextract_8c_sgdaa/ ├── .editorconfig ├── .gitattributes ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug.yml │ │ ├── config.yml │ │ └── feature.md │ └── workflows/ │ ├── build.yml │ ├── check-publishing.yml │ ├── doc.yml │ └── release.yml ├── .gitignore ├── .run/ │ ├── Check Binary Compatibility.run.xml │ ├── Compile everything.run.xml │ ├── Compile mirai-console.run.xml │ ├── Compile mirai-core for JVM.run.xml │ ├── Dump API Changes for mirai-console-frontend-base.run.xml │ ├── Dump API Changes for mirai-console.run.xml │ ├── Dump API Changes for mirai-core-api.run.xml │ ├── Publish deps test artifacts.run.xml │ ├── Publish local artifacts.run.xml │ ├── Run IDE.run.xml │ ├── Run core tests.run.xml │ ├── RunMessageDecodingRecorderKt.run.xml │ ├── RunRecorderKt.run.xml │ ├── Test everything.run.xml │ ├── Test mirai-console.run.xml │ └── Test mirai-core for JVM.run.xml ├── CONTRIBUTING.md ├── LICENSE ├── README-eng.md ├── README.md ├── build.gradle.kts ├── buildSrc/ │ ├── build.gradle.kts │ ├── settings.gradle.kts │ └── src/ │ └── main/ │ ├── kotlin/ │ │ ├── Android.kt │ │ ├── BinaryCompatibilityConfigurator.kt │ │ ├── DependencyDumper.kt │ │ ├── HmppConfigure.kt │ │ ├── JvmDependencies.kt │ │ ├── JvmPublishing.kt │ │ ├── KotlinMetadataPatcher.kt │ │ ├── LocalProperties.kt │ │ ├── Mpp.kt │ │ ├── MppPublishing.kt │ │ ├── ProjectConfigure.kt │ │ ├── PublishingHelpers.kt │ │ ├── Stdlib.kt │ │ ├── TestDependencies.kt │ │ ├── UpdateSnapshotPage.kt │ │ ├── Versions.kt │ │ ├── analyzes/ │ │ │ ├── AndroidApiLevelCheck.kt │ │ │ ├── AsmUtil.kt │ │ │ ├── CompiledCodeVerify.kt │ │ │ └── NoSuchMethodAnalyzer.kt │ │ ├── explicit-api.gradle.kts │ │ ├── keys/ │ │ │ └── SecretKeys.kt │ │ ├── shadow/ │ │ │ ├── Relocation.kt │ │ │ ├── RelocationConfig.kt │ │ │ └── Shadow.kt │ │ └── utils.kt │ └── resources/ │ ├── androidutil/ │ │ └── api-versions.xml │ ├── binary-compatibility-validator-build.txt │ └── binary-compatibility-validator-ignore.txt ├── ci-release-helper/ │ ├── .gitignore │ ├── build.gradle.kts │ ├── changelogs/ │ │ ├── 2.10.1.md │ │ ├── 2.10.2.md │ │ ├── 2.11.0-M1.md │ │ ├── 2.11.0-RC.md │ │ ├── 2.11.0.md │ │ ├── 2.12.0-RC.md │ │ ├── 2.12.0.md │ │ ├── 2.12.1.md │ │ ├── 2.12.2.md │ │ ├── 2.12.3.md │ │ ├── 2.13.0-M1.md │ │ ├── 2.13.0-RC.md │ │ ├── 2.13.0-RC2.md │ │ ├── 2.13.0.md │ │ ├── 2.13.1.md │ │ ├── 2.13.2.md │ │ ├── 2.13.3.md │ │ ├── 2.13.4.md │ │ ├── 2.14.0-RC.md │ │ ├── 2.14.0.md │ │ ├── 2.15.0-M1.md │ │ └── README.md │ ├── scripts/ │ │ └── kill-java.js │ └── src/ │ ├── CiHelper.kt │ ├── buildIndex/ │ │ ├── Index.kt │ │ └── SnapshotVersions.kt │ └── package.kt ├── docs/ │ ├── .conf/ │ │ └── nav.js │ ├── Bots.md │ ├── ConciseAPI.md │ ├── ConfiguringMultiplatformProjects.md │ ├── ConfiguringProjects.md │ ├── ConsoleTerminal.md │ ├── Contacts.md │ ├── CoreAPI.md │ ├── DebuggingNetwork.md │ ├── EventList.md │ ├── Events.md │ ├── Evolution.md │ ├── KotlinAndJava.md │ ├── Messages.md │ ├── MigrationFrom1x.md │ ├── Preparations.md │ ├── Questions.md │ ├── README.md │ ├── UserManual.md │ ├── UsingSnapshots.md │ ├── contributing/ │ │ ├── ImplementingProtocol.md │ │ ├── README.md │ │ ├── SimpleInstructions.md │ │ ├── VerifyingABI.md │ │ ├── building/ │ │ │ ├── BuildingCoreAndroid.md │ │ │ └── README.md │ │ └── mock/ │ │ └── SpaceAllocation.md │ ├── files/ │ │ └── install-20210412.cmd │ ├── mirai-ecology.md │ ├── mocking/ │ │ └── Mocking.md │ └── src/ │ ├── Contacts.mermaid.md │ └── Messages.mermaid.md ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── install.sh ├── logging/ │ ├── README.md │ ├── mirai-logging-log4j2/ │ │ ├── build.gradle.kts │ │ ├── resources/ │ │ │ └── META-INF/ │ │ │ └── services/ │ │ │ └── net.mamoe.mirai.utils.MiraiLogger$Factory │ │ ├── src/ │ │ │ └── MiraiLog4JFactory.kt │ │ └── test/ │ │ └── MiraiLog4JAdapterTest.kt │ ├── mirai-logging-slf4j/ │ │ ├── build.gradle.kts │ │ └── test/ │ │ └── MiraiSlf4JAdapterTest.kt │ ├── mirai-logging-slf4j-logback/ │ │ ├── build.gradle.kts │ │ └── test/ │ │ └── MiraiSlf4JLogbackAdapterTest.kt │ └── mirai-logging-slf4j-simple/ │ ├── build.gradle.kts │ └── test/ │ └── MiraiSlf4JSimpleAdapterTest.kt ├── mirai-bom/ │ └── build.gradle.kts ├── mirai-console/ │ ├── .gitignore │ ├── .gitmodules │ ├── README.md │ ├── backend/ │ │ ├── codegen/ │ │ │ ├── README.md │ │ │ ├── build.gradle.kts │ │ │ └── src/ │ │ │ ├── Codegen.kt │ │ │ ├── MessageScopeCodegen.kt │ │ │ ├── ValuePluginDataCodegen.kt │ │ │ ├── old/ │ │ │ │ ├── JSettingCodegen.kt │ │ │ │ ├── SettingValueUseSiteCodegen.kt │ │ │ │ ├── ValueImplCodegen.kt │ │ │ │ └── ValuesCodegen.kt │ │ │ └── util.kt │ │ ├── integration-test/ │ │ │ ├── README.md │ │ │ ├── build.gradle.kts │ │ │ ├── src/ │ │ │ │ ├── AbstractTestPoint.kt │ │ │ │ ├── AbstractTestPointAsPlugin.kt │ │ │ │ ├── IntegrationTestBootstrap.kt │ │ │ │ ├── MiraiConsoleIntegrationTestLauncher.kt │ │ │ │ └── utils.kt │ │ │ ├── test/ │ │ │ │ ├── MiraiConsoleIntegrationTestBootstrap.kt │ │ │ │ └── testpoints/ │ │ │ │ ├── DoNothingPoint.kt │ │ │ │ ├── MCITBSelfAssertions.kt │ │ │ │ ├── PluginSharedLibraries.kt │ │ │ │ └── plugin/ │ │ │ │ ├── PluginDataRenameToIdTest.kt │ │ │ │ ├── PluginDependOnErrorPlugin.kt │ │ │ │ ├── PluginOnDisableCalledOnlyOnceTest.kt │ │ │ │ └── PluginWithExceptionTest.kt │ │ │ └── testers/ │ │ │ ├── .gitignore │ │ │ ├── MCITSelfTestPlugin/ │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── MCITSelfTestPlugin.kt │ │ │ ├── README.md │ │ │ ├── mirai-plugin-compatibility/ │ │ │ │ ├── .module-group.txt │ │ │ │ ├── mirai-jar-after-2_11/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── src/ │ │ │ │ │ └── After211.kt │ │ │ │ ├── mirai-jar-after-2_11-without-new/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── src/ │ │ │ │ │ └── After211NoNew.kt │ │ │ │ ├── mirai-jar-before-2_11/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── src/ │ │ │ │ │ └── Before211.kt │ │ │ │ ├── same-pkg-1/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── src/ │ │ │ │ │ └── P.kt │ │ │ │ └── same-pkg-2/ │ │ │ │ ├── .nested-module.txt │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── P.kt │ │ │ ├── never-override-jdk-modules/ │ │ │ │ ├── module-jdk-module/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── mvn.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── javax/ │ │ │ │ │ └── xml/ │ │ │ │ │ └── parsers/ │ │ │ │ │ └── SAXParser.kt │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── NeverOverrideJdkModules.kt │ │ │ ├── options-properties/ │ │ │ │ ├── independent-plugin/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── src/ │ │ │ │ │ └── Independent.kt │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ ├── mirai-console-plugin/ │ │ │ │ │ │ └── options.properties │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── OptionsProperties.kt │ │ │ ├── plugin-can-depends-on-mirai-console/ │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── PluginCanDependsOnMiraiConsole.kt │ │ │ ├── plugin-dep-dependon-dep-issue-2054/ │ │ │ │ ├── module-moda/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── mvn.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── ModuleA.kt │ │ │ │ ├── module-modb/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── mvn.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── ModuleB.kt │ │ │ │ ├── module-private-issue2108/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── mvn.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── PrivateModule.kt │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ ├── second-plugin/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── src/ │ │ │ │ │ └── PDepDependOnDepSec.kt │ │ │ │ └── src/ │ │ │ │ └── PDepDependOnDep.kt │ │ │ ├── plugin-depend-on-other/ │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ ├── PluginDependOnOther.kt │ │ │ │ └── issue1920/ │ │ │ │ ├── OtherClass1.kt │ │ │ │ └── OtherClass2.kt │ │ │ ├── plugin-dynamic-dependencies-download/ │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ ├── mirai-console-plugin/ │ │ │ │ │ │ ├── dependencies-private.txt │ │ │ │ │ │ └── dependencies-shared.txt │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── P.kt │ │ │ ├── plugin-resolve-self-dependencies-over-console-ones/ │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ ├── mirai-console-plugin/ │ │ │ │ │ │ └── dependencies-private.txt │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── PluginResolveSelfDependenciesOverConsoleOnes.kt │ │ │ ├── plugin-use-console-deps-fallback/ │ │ │ │ ├── resources/ │ │ │ │ │ └── META-INF/ │ │ │ │ │ └── services/ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ └── src/ │ │ │ │ └── PluginUseConsoleDepsFallback.kt │ │ │ ├── plugin-with-pluginyml/ │ │ │ │ ├── resources/ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── plugin.yml │ │ │ │ └── src/ │ │ │ │ └── PluginWithPluginYml.kt │ │ │ ├── plugin-with-pluginyml-can-use-libraries-while-clinit/ │ │ │ │ ├── clinit-library/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── mvn.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── PluginLibrary.kt │ │ │ │ ├── resources/ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ ├── mirai-console-plugin/ │ │ │ │ │ │ │ └── dependencies-private.txt │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── plugin.yml │ │ │ │ └── src/ │ │ │ │ └── PluginWithPluginYmlClinitTest.kt │ │ │ ├── service-loader/ │ │ │ │ ├── module-service-loader-impl/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ │ └── net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef │ │ │ │ │ │ └── test-res.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── ServiceImpl.kt │ │ │ │ ├── module-service-loader-typedef/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ └── test-res.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── ServiceTypedef.kt │ │ │ │ ├── resources/ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ └── test-res.txt │ │ │ │ ├── service-loader-2dep-plugin/ │ │ │ │ │ ├── .nested-module.txt │ │ │ │ │ ├── resources/ │ │ │ │ │ │ ├── META-INF/ │ │ │ │ │ │ │ └── services/ │ │ │ │ │ │ │ ├── net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef │ │ │ │ │ │ │ └── net.mamoe.mirai.console.plugin.jvm.JvmPlugin │ │ │ │ │ │ └── test-res.txt │ │ │ │ │ └── src/ │ │ │ │ │ └── PMain.kt │ │ │ │ └── src/ │ │ │ │ └── PMain.kt │ │ │ └── tester.template.gradle.kts │ │ └── mirai-console/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.gradle.kts │ │ ├── compatibility-validation/ │ │ │ └── jvm/ │ │ │ └── api/ │ │ │ └── jvm.api │ │ ├── resources/ │ │ │ └── net/ │ │ │ └── mamoe/ │ │ │ └── mirai/ │ │ │ └── console/ │ │ │ └── internal/ │ │ │ └── enduserreadme/ │ │ │ └── readme.txt │ │ ├── src/ │ │ │ ├── MiraiConsole.kt │ │ │ ├── MiraiConsoleFrontEndDescription.kt │ │ │ ├── MiraiConsoleImplementation.kt │ │ │ ├── command/ │ │ │ │ ├── AbstractCommand.kt │ │ │ │ ├── BuiltInCommands.kt │ │ │ │ ├── Command.kt │ │ │ │ ├── CommandContext.kt │ │ │ │ ├── CommandExecuteResult.kt │ │ │ │ ├── CommandExecutionException.kt │ │ │ │ ├── CommandManager.kt │ │ │ │ ├── CommandOwner.kt │ │ │ │ ├── CommandPermissionDeniedException.kt │ │ │ │ ├── CommandSender.kt │ │ │ │ ├── CompositeCommand.kt │ │ │ │ ├── IllegalCommandArgumentException.kt │ │ │ │ ├── RawCommand.kt │ │ │ │ ├── SimpleCommand.kt │ │ │ │ ├── descriptor/ │ │ │ │ │ ├── CommandArgumentContext.kt │ │ │ │ │ ├── CommandArgumentParserBuiltins.kt │ │ │ │ │ ├── CommandParameter.kt │ │ │ │ │ ├── CommandSignature.kt │ │ │ │ │ ├── CommandValueArgumentParser.kt │ │ │ │ │ ├── Exceptions.kt │ │ │ │ │ ├── ExperimentalCommandDescriptors.kt │ │ │ │ │ └── TypeVariant.kt │ │ │ │ ├── java/ │ │ │ │ │ ├── JCompositeCommand.kt │ │ │ │ │ ├── JRawCommand.kt │ │ │ │ │ └── JSimpleCommand.kt │ │ │ │ ├── parse/ │ │ │ │ │ ├── CommandCall.kt │ │ │ │ │ ├── CommandCallParser.kt │ │ │ │ │ ├── CommandValueArgument.kt │ │ │ │ │ └── SpaceSeparatedCommandCallParser.kt │ │ │ │ └── resolve/ │ │ │ │ ├── BuiltInCommandCallResolver.kt │ │ │ │ ├── CommandCallInterceptor.kt │ │ │ │ ├── CommandCallResolver.kt │ │ │ │ └── ResolvedCommandCall.kt │ │ │ ├── data/ │ │ │ │ ├── AbstractPluginData.kt │ │ │ │ ├── AutoSavePluginConfig.kt │ │ │ │ ├── AutoSavePluginData.kt │ │ │ │ ├── AutoSavePluginDataHolder.kt │ │ │ │ ├── PluginConfig.kt │ │ │ │ ├── PluginData.kt │ │ │ │ ├── PluginDataExtensions.kt │ │ │ │ ├── PluginDataHolder.kt │ │ │ │ ├── PluginDataStorage.kt │ │ │ │ ├── ReadOnlyPluginConfig.kt │ │ │ │ ├── ReadOnlyPluginData.kt │ │ │ │ ├── Value.kt │ │ │ │ ├── ValueDescription.kt │ │ │ │ ├── ValueName.kt │ │ │ │ └── java/ │ │ │ │ ├── JAutoSavePluginConfig.kt │ │ │ │ ├── JAutoSavePluginData.kt │ │ │ │ ├── JavaAutoSavePluginConfig.kt │ │ │ │ └── JavaAutoSavePluginData.kt │ │ │ ├── enduserreadme/ │ │ │ │ └── EndUserReadme.kt │ │ │ ├── events/ │ │ │ │ ├── AutoLoginEvent.kt │ │ │ │ ├── CommandExecutionEvent.kt │ │ │ │ ├── ConsoleEvent.kt │ │ │ │ ├── EndUserReadmeInitializeEvent.kt │ │ │ │ └── StartupEvent.kt │ │ │ ├── extension/ │ │ │ │ ├── ComponentStorage.kt │ │ │ │ ├── Extension.kt │ │ │ │ ├── ExtensionException.kt │ │ │ │ ├── ExtensionPoint.kt │ │ │ │ ├── ExtensionRegistry.kt │ │ │ │ └── PluginComponentStorage.kt │ │ │ ├── extensions/ │ │ │ │ ├── BotConfigurationAlterer.kt │ │ │ │ ├── CommandCallInterceptorProvider.kt │ │ │ │ ├── CommandCallParserProvider.kt │ │ │ │ ├── CommandCallResolverProvider.kt │ │ │ │ ├── PermissionServiceProvider.kt │ │ │ │ ├── PluginLoaderProvider.kt │ │ │ │ ├── PostStartupExtension.kt │ │ │ │ └── SingletonExtensionSelector.kt │ │ │ ├── fontend/ │ │ │ │ ├── DefaultLoggingProcessProgress.kt │ │ │ │ └── ProcessProgress.kt │ │ │ ├── internal/ │ │ │ │ ├── MiraiConsoleBuildConstants.kt.template │ │ │ │ ├── MiraiConsoleImplementationBridge.kt │ │ │ │ ├── auth/ │ │ │ │ │ ├── ConsoleBotAuthorization.kt │ │ │ │ │ └── ConsoleSecretsCalculator.kt │ │ │ │ ├── command/ │ │ │ │ │ ├── CommandManagerImpl.kt │ │ │ │ │ ├── CommandReflector.kt │ │ │ │ │ ├── CommnadConfig.kt │ │ │ │ │ ├── builtin/ │ │ │ │ │ │ └── LoginCommandImpl.kt │ │ │ │ │ └── internal.kt │ │ │ │ ├── data/ │ │ │ │ │ ├── CompositeValueImpl.kt │ │ │ │ │ ├── MemoryPluginDataStorageImpl.kt │ │ │ │ │ ├── MultiFilePluginDataStorageImpl.kt │ │ │ │ │ ├── PluginDataImpl.kt │ │ │ │ │ ├── _PluginData.value.kt │ │ │ │ │ ├── _PrimitiveValueDeclarations.kt │ │ │ │ │ ├── builtins/ │ │ │ │ │ │ ├── AutoLoginConfig.kt │ │ │ │ │ │ ├── ConsoleDataScopeImpl.kt │ │ │ │ │ │ ├── EndUserReadmeData.kt │ │ │ │ │ │ ├── LoggerConfig.kt │ │ │ │ │ │ └── PluginDependenciesConfig.kt │ │ │ │ │ ├── collectionUtil.kt │ │ │ │ │ ├── reflectionUtils.kt │ │ │ │ │ ├── serializerHelper.kt │ │ │ │ │ └── valueFromKTypeImpl.kt │ │ │ │ ├── enduserreadme/ │ │ │ │ │ └── EndUserReadmeProcessor.kt │ │ │ │ ├── extension/ │ │ │ │ │ ├── ComponentStorageInternal.kt │ │ │ │ │ └── SingletonExtensionSelectorImpl.kt │ │ │ │ ├── logging/ │ │ │ │ │ ├── LazyInitMiraiLogger.kt │ │ │ │ │ ├── LoggerControllerImpl.kt │ │ │ │ │ ├── MiraiConsoleLogger.kt │ │ │ │ │ └── externalbind/ │ │ │ │ │ └── slf4j/ │ │ │ │ │ ├── MiraiConsoleSLF4JService.kt │ │ │ │ │ └── SLF4JAdapterLogger.kt │ │ │ │ ├── permission/ │ │ │ │ │ ├── AbstractConcurrentPermissionService.kt │ │ │ │ │ ├── BuiltInPermissionServices.kt │ │ │ │ │ └── parseFromStringImpl.kt │ │ │ │ ├── plugin/ │ │ │ │ │ ├── AllDependenciesClassesHolder.kt │ │ │ │ │ ├── BuiltInJvmPluginLoaderImpl.kt │ │ │ │ │ ├── Exceptions.kt │ │ │ │ │ ├── ExportManagerImpl.kt │ │ │ │ │ ├── JvmPluginClassLoader.kt │ │ │ │ │ ├── JvmPluginDependencyDownload.kt │ │ │ │ │ ├── JvmPluginInternal.kt │ │ │ │ │ ├── JvmPluginTesting.kt │ │ │ │ │ ├── MiraiConsoleAsPlugin.kt │ │ │ │ │ ├── NotYetLoadedJvmPlugin.kt │ │ │ │ │ ├── PluginDescriptionUtil.kt │ │ │ │ │ └── PluginManagerImpl.kt │ │ │ │ ├── shutdown/ │ │ │ │ │ └── ShutdownDaemon.kt │ │ │ │ └── util/ │ │ │ │ ├── CommonUtils.kt │ │ │ │ ├── ConsoleInputImpl.kt │ │ │ │ ├── JavaPluginSchedulerImpl.kt │ │ │ │ ├── PluginServiceHelper.kt │ │ │ │ └── semver/ │ │ │ │ ├── RequirementInternal.kt │ │ │ │ ├── RequirementParser.kt │ │ │ │ └── SemVersionInternal.kt │ │ │ ├── logging/ │ │ │ │ ├── AbstractLoggerController.kt │ │ │ │ └── LoggerController.kt │ │ │ ├── permission/ │ │ │ │ ├── Permission.kt │ │ │ │ ├── PermissionId.kt │ │ │ │ ├── PermissionIdNamespace.kt │ │ │ │ ├── PermissionImplementation.kt │ │ │ │ ├── PermissionRegistryConflictException.kt │ │ │ │ ├── PermissionService.kt │ │ │ │ ├── Permittee.kt │ │ │ │ └── PermitteeId.kt │ │ │ ├── plugin/ │ │ │ │ ├── NotYetLoadedPlugin.kt │ │ │ │ ├── Plugin.kt │ │ │ │ ├── PluginFileExtensions.kt │ │ │ │ ├── PluginManager.kt │ │ │ │ ├── ResourceContainer.kt │ │ │ │ ├── center/ │ │ │ │ │ └── PluginCenter.kt │ │ │ │ ├── description/ │ │ │ │ │ ├── IllegalPluginDescriptionException.kt │ │ │ │ │ ├── PluginDependency.kt │ │ │ │ │ └── PluginDescription.kt │ │ │ │ ├── jvm/ │ │ │ │ │ ├── AbstractJvmPlugin.kt │ │ │ │ │ ├── ExportManager.kt │ │ │ │ │ ├── JavaPlugin.kt │ │ │ │ │ ├── JavaPluginScheduler.kt │ │ │ │ │ ├── JvmPlugin.kt │ │ │ │ │ ├── JvmPluginClasspath.kt │ │ │ │ │ ├── JvmPluginDescription.kt │ │ │ │ │ ├── JvmPluginLoader.kt │ │ │ │ │ └── KotlinPlugin.kt │ │ │ │ └── loader/ │ │ │ │ ├── FilePluginLoader.kt │ │ │ │ ├── PluginLoadException.kt │ │ │ │ └── PluginLoader.kt │ │ │ └── util/ │ │ │ ├── Annotations.kt │ │ │ ├── AnsiMessageBuilder.kt │ │ │ ├── ConsoleInput.kt │ │ │ ├── ContactUtils.kt │ │ │ ├── CoroutineScopeUtils.kt │ │ │ ├── MemoryFormat.kt │ │ │ ├── MessageScope.kt │ │ │ ├── MessageUtils.kt │ │ │ ├── SemVersion.kt │ │ │ ├── StandardUtils.kt │ │ │ └── retryCatching.kt │ │ ├── test/ │ │ │ ├── TestMiraiConosle.kt │ │ │ ├── command/ │ │ │ │ ├── AbstractCommandTest.kt │ │ │ │ ├── CommandContextTest.kt │ │ │ │ ├── CommandValueArgumentContextTest.kt │ │ │ │ ├── InstanceTestCommand.kt │ │ │ │ ├── JSimpleTest.java │ │ │ │ └── commanTestingUtil.kt │ │ │ ├── configuration/ │ │ │ │ └── AutoLoginTest.kt │ │ │ ├── data/ │ │ │ │ ├── JAutoSavePluginDataTest.kt │ │ │ │ ├── JavaPluginDescriptionTests.kt │ │ │ │ ├── MultiFilePluginDataStorageImplTests.kt │ │ │ │ ├── PluginDataTest.kt │ │ │ │ └── PluginMovingTests.kt │ │ │ ├── extension/ │ │ │ │ └── GlobalComponentStorageTest.kt │ │ │ ├── logging/ │ │ │ │ ├── TestAbstractLoggerController.kt │ │ │ │ └── TestAbstractLoggerController_PathBased.kt │ │ │ ├── permission/ │ │ │ │ ├── PermissionServiceTest.kt │ │ │ │ └── PermissionsBasicsTest.kt │ │ │ ├── plugin/ │ │ │ │ ├── BuiltInJvmPluginLoaderImplTest.kt │ │ │ │ └── PluginLoadingOrderTest.kt │ │ │ ├── testFramework/ │ │ │ │ ├── AbstractConsoleInstanceTest.kt │ │ │ │ ├── MockConsoleImplementation.kt │ │ │ │ └── test/ │ │ │ │ └── FrameworkInstanceTest.kt │ │ │ └── util/ │ │ │ ├── TestCoroutineUtils.kt │ │ │ └── TestSemVersion.kt │ │ └── testResources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── org.slf4j.spi.SLF4JServiceProvider │ ├── docs/ │ │ ├── .conf/ │ │ │ └── nav.js │ │ ├── Appendix.md │ │ ├── BuiltInCommands.md │ │ ├── Commands.md │ │ ├── ConfiguringProjects.md │ │ ├── Contributing.md │ │ ├── Extensions.md │ │ ├── FrontEnd.md │ │ ├── Logging.md │ │ ├── Permissions.md │ │ ├── PluginData.md │ │ ├── QA.md │ │ ├── README.md │ │ ├── Run.md │ │ └── plugin/ │ │ ├── JVMPlugin-Appendix.md │ │ ├── JVMPlugin-DataExchange.md │ │ ├── JVMPlugin-Debug.md │ │ ├── JVMPlugin.md │ │ └── Plugins.md │ ├── frontend/ │ │ ├── mirai-android/ │ │ │ ├── .github/ │ │ │ │ ├── ISSUE_TEMPLATE/ │ │ │ │ │ └── bug_report.md │ │ │ │ └── workflows/ │ │ │ │ └── android.yml │ │ │ ├── .gitignore │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ ├── app/ │ │ │ │ ├── .gitignore │ │ │ │ ├── build.gradle │ │ │ │ ├── libs/ │ │ │ │ │ ├── d8.jar │ │ │ │ │ ├── giteeman-0.2.3.jar │ │ │ │ │ ├── mirai-console-0.5.2.jar │ │ │ │ │ └── mirai-js-1.0.0.jar │ │ │ │ ├── proguard-rules.pro │ │ │ │ └── src/ │ │ │ │ ├── androidTest/ │ │ │ │ │ └── java/ │ │ │ │ │ └── io/ │ │ │ │ │ └── github/ │ │ │ │ │ └── mzdluo123/ │ │ │ │ │ └── mirai/ │ │ │ │ │ └── android/ │ │ │ │ │ ├── TestWithIdleResources.kt │ │ │ │ │ ├── activity/ │ │ │ │ │ │ └── NavTest.kt │ │ │ │ │ ├── console/ │ │ │ │ │ │ ├── ConsoleIntentTest.kt │ │ │ │ │ │ └── ConsoleTest.kt │ │ │ │ │ ├── script/ │ │ │ │ │ │ └── ScriptManageTest.kt │ │ │ │ │ └── utils.kt │ │ │ │ └── main/ │ │ │ │ ├── AndroidManifest.xml │ │ │ │ ├── aidl/ │ │ │ │ │ └── io/ │ │ │ │ │ └── github/ │ │ │ │ │ └── mzdluo123/ │ │ │ │ │ └── mirai/ │ │ │ │ │ └── android/ │ │ │ │ │ └── IbotAidlInterface.aidl │ │ │ │ ├── java/ │ │ │ │ │ ├── io/ │ │ │ │ │ │ └── github/ │ │ │ │ │ │ └── mzdluo123/ │ │ │ │ │ │ └── mirai/ │ │ │ │ │ │ └── android/ │ │ │ │ │ │ ├── AppSettings.kt │ │ │ │ │ │ ├── BotApplication.kt │ │ │ │ │ │ ├── IdleResources.kt │ │ │ │ │ │ ├── NotificationFactory.kt │ │ │ │ │ │ ├── activity/ │ │ │ │ │ │ │ ├── CaptchaActivity.kt │ │ │ │ │ │ │ ├── MainActivity.kt │ │ │ │ │ │ │ ├── PluginImportActivity.kt │ │ │ │ │ │ │ └── UnsafeLoginActivity.kt │ │ │ │ │ │ ├── crash/ │ │ │ │ │ │ │ ├── MiraiAndroidReportSender.kt │ │ │ │ │ │ │ └── MiraiAndroidReportSenderFactory.kt │ │ │ │ │ │ ├── miraiconsole/ │ │ │ │ │ │ │ ├── AndroidLoginSolver.kt │ │ │ │ │ │ │ └── AndroidMiraiConsole.kt │ │ │ │ │ │ ├── receiver/ │ │ │ │ │ │ │ ├── BootReceiver.kt │ │ │ │ │ │ │ └── PushMsgReceiver.kt │ │ │ │ │ │ ├── script/ │ │ │ │ │ │ │ ├── JavaScriptHost.kt │ │ │ │ │ │ │ ├── LuaScriptHost.kt │ │ │ │ │ │ │ ├── ScriptHost.kt │ │ │ │ │ │ │ ├── ScriptHostFactory.kt │ │ │ │ │ │ │ └── ScriptManager.kt │ │ │ │ │ │ ├── service/ │ │ │ │ │ │ │ ├── BotService.kt │ │ │ │ │ │ │ └── ServiceConnector.kt │ │ │ │ │ │ ├── ui/ │ │ │ │ │ │ │ ├── about/ │ │ │ │ │ │ │ │ └── AboutFragment.kt │ │ │ │ │ │ │ ├── console/ │ │ │ │ │ │ │ │ └── ConsoleFragment.kt │ │ │ │ │ │ │ ├── plugin/ │ │ │ │ │ │ │ │ ├── PluginFragment.kt │ │ │ │ │ │ │ │ └── PluginViewModel.kt │ │ │ │ │ │ │ ├── script/ │ │ │ │ │ │ │ │ ├── ScriptCenterFragment.kt │ │ │ │ │ │ │ │ ├── ScriptCenterListAdapter.kt │ │ │ │ │ │ │ │ ├── ScriptCenterViewModel.kt │ │ │ │ │ │ │ │ ├── ScriptFragment.kt │ │ │ │ │ │ │ │ ├── ScriptInfoDialogFragment.kt │ │ │ │ │ │ │ │ ├── ScriptListAdapter.kt │ │ │ │ │ │ │ │ └── ScriptViewModel.kt │ │ │ │ │ │ │ └── setting/ │ │ │ │ │ │ │ └── SettingFragment.kt │ │ │ │ │ │ └── utils/ │ │ │ │ │ │ ├── CommandFastRegister.kt │ │ │ │ │ │ ├── DeviceStatus.java │ │ │ │ │ │ ├── DexCompiler.java │ │ │ │ │ │ ├── LoopQueue.java │ │ │ │ │ │ ├── MiraiAndroidStatus.kt │ │ │ │ │ │ ├── TextSharer.kt │ │ │ │ │ │ ├── dnsQuery.kt │ │ │ │ │ │ ├── fileUtils.kt │ │ │ │ │ │ └── pasteBin.kt │ │ │ │ │ └── java/ │ │ │ │ │ └── awt/ │ │ │ │ │ └── image/ │ │ │ │ │ └── BufferedImage.java │ │ │ │ └── res/ │ │ │ │ ├── color/ │ │ │ │ │ └── color_drawer_item.xml │ │ │ │ ├── drawable/ │ │ │ │ │ ├── ic_add_white_24dp.xml │ │ │ │ │ ├── ic_android_24.xml │ │ │ │ │ ├── ic_baseline_folder_24.xml │ │ │ │ │ ├── ic_baseline_insert_drive_file_24.xml │ │ │ │ │ ├── ic_baseline_keyboard_arrow_up_24.xml │ │ │ │ │ ├── ic_baseline_publish_24.xml │ │ │ │ │ ├── ic_battery_alert_24.xml │ │ │ │ │ ├── ic_chat_bubble_black_24dp.xml │ │ │ │ │ ├── ic_check_white_24dp.xml │ │ │ │ │ ├── ic_delete_black_24dp.xml │ │ │ │ │ ├── ic_desktop_windows_black_24dp.xml │ │ │ │ │ ├── ic_edit_black_24dp.xml │ │ │ │ │ ├── ic_exit_to_app_24dp.xml │ │ │ │ │ ├── ic_extension_black_24dp.xml │ │ │ │ │ ├── ic_info_black_24dp.xml │ │ │ │ │ ├── ic_insert_drive_file_black_24dp.xml │ │ │ │ │ ├── ic_keyboard_arrow_down_black_24dp.xml │ │ │ │ │ ├── ic_keyboard_return_black_24dp.xml │ │ │ │ │ ├── ic_launcher_background.xml │ │ │ │ │ ├── ic_local_printshop_24.xml │ │ │ │ │ ├── ic_new_launcher_foreground.xml │ │ │ │ │ ├── ic_refresh_black_24dp.xml │ │ │ │ │ ├── ic_restore_24.xml │ │ │ │ │ ├── ic_save_black_24dp.xml │ │ │ │ │ ├── ic_settings_black_24dp.xml │ │ │ │ │ ├── ic_share_24.xml │ │ │ │ │ ├── ic_store_white_24.xml │ │ │ │ │ ├── icon.xml │ │ │ │ │ ├── loading_background.xml │ │ │ │ │ └── side_nav_bar.xml │ │ │ │ ├── drawable-v24/ │ │ │ │ │ └── ic_launcher_foreground.xml │ │ │ │ ├── layout/ │ │ │ │ │ ├── activity_captcha.xml │ │ │ │ │ ├── activity_main.xml │ │ │ │ │ ├── activity_plugin_import.xml │ │ │ │ │ ├── activity_unsafe_login.xml │ │ │ │ │ ├── app_bar_main.xml │ │ │ │ │ ├── content_main.xml │ │ │ │ │ ├── dialog_ask_filename.xml │ │ │ │ │ ├── dialog_autologin.xml │ │ │ │ │ ├── dialog_script_info.xml │ │ │ │ │ ├── fragment_about.xml │ │ │ │ │ ├── fragment_home.xml │ │ │ │ │ ├── fragment_plugin.xml │ │ │ │ │ ├── fragment_script.xml │ │ │ │ │ ├── fragment_script_center.xml │ │ │ │ │ ├── fragment_script_center_empty.xml │ │ │ │ │ ├── fragment_script_empty.xml │ │ │ │ │ ├── item_plugin.xml │ │ │ │ │ ├── item_script.xml │ │ │ │ │ ├── item_script_center_list.xml │ │ │ │ │ └── nav_header_main.xml │ │ │ │ ├── menu/ │ │ │ │ │ ├── menu_activity_main_drawer.xml │ │ │ │ │ ├── menu_console.xml │ │ │ │ │ ├── menu_script.xml │ │ │ │ │ ├── menu_script_center.xml │ │ │ │ │ ├── plugin_add.xml │ │ │ │ │ ├── plugin_manage.xml │ │ │ │ │ └── unsafe_menu.xml │ │ │ │ ├── mipmap-anydpi-v26/ │ │ │ │ │ ├── ic_launcher.xml │ │ │ │ │ ├── ic_launcher_round.xml │ │ │ │ │ ├── ic_new_launcher.xml │ │ │ │ │ └── ic_new_launcher_round.xml │ │ │ │ ├── navigation/ │ │ │ │ │ └── mobile_navigation.xml │ │ │ │ ├── values/ │ │ │ │ │ ├── colors.xml │ │ │ │ │ ├── dimens.xml │ │ │ │ │ ├── ic_new_launcher_background.xml │ │ │ │ │ ├── strings.xml │ │ │ │ │ └── styles.xml │ │ │ │ ├── values-night/ │ │ │ │ │ └── colors-night.xml │ │ │ │ ├── values-v21/ │ │ │ │ │ └── styles.xml │ │ │ │ ├── values-v29/ │ │ │ │ │ └── styles.xml │ │ │ │ └── xml/ │ │ │ │ ├── path_script.xml │ │ │ │ └── setting_screen.xml │ │ │ ├── build.gradle │ │ │ ├── gradle/ │ │ │ │ └── wrapper/ │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ │ ├── gradle.properties │ │ │ ├── gradlew │ │ │ ├── gradlew.bat │ │ │ └── settings.gradle │ │ ├── mirai-console-frontend-base/ │ │ │ ├── build.gradle.kts │ │ │ ├── compatibility-validation/ │ │ │ │ └── jvm/ │ │ │ │ └── api/ │ │ │ │ └── jvm.api │ │ │ ├── resources/ │ │ │ │ └── META-INF/ │ │ │ │ └── services/ │ │ │ │ └── org.slf4j.spi.SLF4JServiceProvider │ │ │ ├── src/ │ │ │ │ ├── AbstractMiraiConsoleFrontendImplementation.kt │ │ │ │ ├── FrontendBase.kt │ │ │ │ ├── RepipedMessageForward.kt │ │ │ │ ├── logging/ │ │ │ │ │ └── LogRecorder.kt │ │ │ │ └── package.kt │ │ │ └── test/ │ │ │ ├── RepipedMessageForwardTest.kt │ │ │ └── package.kt │ │ └── mirai-console-terminal/ │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── src/ │ │ │ ├── BufferedOutputStream.kt │ │ │ ├── ConsoleInputImpl.kt │ │ │ ├── ConsoleTerminalSettings.kt │ │ │ ├── ConsoleThread.kt │ │ │ ├── JLineInputDaemon.kt │ │ │ ├── LoggingService.kt │ │ │ ├── MiraiConsoleImplementationTerminal.kt │ │ │ ├── MiraiConsoleTerminalLoader.kt │ │ │ ├── TerminalProcessProgress.kt │ │ │ ├── net/ │ │ │ │ └── mamoe/ │ │ │ │ └── mirai/ │ │ │ │ └── console/ │ │ │ │ └── pure/ │ │ │ │ └── MiraiConsolePureLoader.kt │ │ │ └── noconsole/ │ │ │ └── NoConsole.kt │ │ └── test/ │ │ └── RunTerminal.kt │ └── tools/ │ ├── compiler-annotations/ │ │ ├── README.md │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── androidMain/ │ │ │ └── AndroidManifest.xml │ │ └── commonMain/ │ │ └── kotlin/ │ │ ├── CheckerConstants.kt │ │ ├── ResolveContext.kt │ │ └── RestrictedScope.kt │ ├── compiler-common/ │ │ ├── README.md │ │ ├── build.gradle.kts │ │ └── src/ │ │ ├── diagnostics/ │ │ │ ├── MiraiConsoleErrors.kt │ │ │ └── MiraiConsoleErrorsRendering.kt │ │ ├── resolve/ │ │ │ ├── resolveCommon.kt │ │ │ └── resolveTypes.kt │ │ └── utilCommon.kt │ ├── gradle-plugin/ │ │ ├── .gitignore │ │ ├── README.md │ │ ├── build.gradle.kts │ │ ├── gradle.properties │ │ ├── src/ │ │ │ ├── integTest/ │ │ │ │ └── kotlin/ │ │ │ │ ├── AbstractTest.kt │ │ │ │ ├── KotlinTransitiveDependenciesIntegrationTest.kt │ │ │ │ ├── TestBuildPlugin.kt │ │ │ │ └── TestPluginApply.kt │ │ │ └── main/ │ │ │ └── kotlin/ │ │ │ ├── BuildMiraiPluginTask.kt │ │ │ ├── BuildMiraiPluginV2.kt │ │ │ ├── IGNORED_DEPENDENCIES_IN_SHADOW.kt │ │ │ ├── MiraiConsoleExtension.kt │ │ │ ├── MiraiConsoleGradlePlugin.kt │ │ │ ├── VersionConstants.kt.template │ │ │ ├── dsl.kt │ │ │ └── publishing.kt │ │ └── test/ │ │ └── net/ │ │ └── mamoe/ │ │ └── mirai/ │ │ └── console/ │ │ └── gradle/ │ │ ├── AbstractTest.groovy │ │ └── TestPluginApply.groovy │ └── intellij-plugin/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ ├── libs/ │ │ └── ide-common.jar │ ├── resources/ │ │ ├── META-INF/ │ │ │ └── plugin.xml │ │ ├── fileTemplates/ │ │ │ └── code/ │ │ │ ├── .gitignore.ft │ │ │ ├── .gitignore.html │ │ │ ├── Gradle gradle-wrapper.properties.ft │ │ │ ├── Gradle gradle-wrapper.properties.html │ │ │ ├── Gradle gradle.properties.ft │ │ │ ├── Gradle gradle.properties.html │ │ │ ├── Plugin build.gradle.ft │ │ │ ├── Plugin build.gradle.html │ │ │ ├── Plugin build.gradle.kts.ft │ │ │ ├── Plugin build.gradle.kts.html │ │ │ ├── Plugin main class Java.java.ft │ │ │ ├── Plugin main class Java.java.ft.back │ │ │ ├── Plugin main class Java.java.html │ │ │ ├── Plugin main class Kotlin.kt.ft │ │ │ ├── Plugin main class Kotlin.kt.html │ │ │ ├── Plugin main service.txt.ft │ │ │ ├── Plugin main service.txt.html │ │ │ ├── Plugin settings.gradle.ft │ │ │ ├── Plugin settings.gradle.html │ │ │ ├── Plugin settings.gradle.kts.ft │ │ │ ├── Plugin settings.gradle.kts.html │ │ │ ├── RunTerminal.run.xml.ft │ │ │ ├── RunTerminal.run.xml.html │ │ │ ├── account.properties.ft │ │ │ └── account.properties.html │ │ ├── inspectionDescriptions/ │ │ │ ├── PluginMainServiceNotConfigured.html │ │ │ ├── ResourceNotClosed.html │ │ │ └── UsingStringPlusMessage.html │ │ ├── intentionDescriptions/ │ │ │ ├── WrapWithResourceUseCallIntention/ │ │ │ │ ├── after.receiver.template │ │ │ │ ├── before.receiver.template │ │ │ │ └── description.html │ │ │ └── WrapWithResourceUseCallJavaIntention/ │ │ │ ├── after.action.template │ │ │ ├── before.action.template │ │ │ └── description.html │ │ └── messages/ │ │ ├── InspectionGadgetsBundle.properties │ │ ├── InspectionsBundle.properties │ │ ├── MiraiProjectWizardBundle.properties │ │ └── MiraiProjectWizardBundle_zh.properties │ ├── run/ │ │ └── projects/ │ │ ├── .gitignore │ │ └── test-project/ │ │ ├── build.gradle.kts │ │ ├── gradle/ │ │ │ └── wrapper/ │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradle.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle.kts │ │ └── src/ │ │ ├── main/ │ │ │ ├── java/ │ │ │ │ └── test/ │ │ │ │ ├── ResourceNotClosedInspectionTestJava.java │ │ │ │ └── TestJavaPlugin.java │ │ │ └── kotlin/ │ │ │ └── org/ │ │ │ └── example/ │ │ │ └── myplugin/ │ │ │ ├── AbstractMessageKeysUsages.kt │ │ │ ├── ConsoleCommandOwnerCheck.kt │ │ │ ├── MyPluginMain.kt │ │ │ ├── MySimpleCommand.kt │ │ │ ├── ReadOnlyPluginDataVar.kt │ │ │ ├── ResourceNotClosedInspectionTest.kt │ │ │ ├── StringPlusMessageInspectionTest.kt │ │ │ ├── UsingDerivedMap.kt │ │ │ └── WrapWithResourceUseCallIntentionTest.kt │ │ └── test/ │ │ └── kotlin/ │ │ └── RunConsole.kt │ ├── src/ │ │ ├── IDEContainerContributor.kt │ │ ├── QuickFixRegistrar.kt │ │ ├── assets/ │ │ │ ├── Assets.kt │ │ │ └── FileTemplateRegistrar.kt │ │ ├── diagnostics/ │ │ │ ├── CommandDeclarationChecker.kt │ │ │ ├── ContextualParametersChecker.kt │ │ │ ├── MessageChainGetCallChecker.kt │ │ │ ├── PluginDataValuesChecker.kt │ │ │ ├── PluginMainServiceNotConfiguredInspection.kt │ │ │ ├── QuickFixUtils.kt │ │ │ ├── ResourceNotClosedInspection.kt │ │ │ ├── TaskUtils.kt │ │ │ ├── UsingStringPlusMessageInspection.kt │ │ │ ├── diagnosticsUtil.kt │ │ │ └── fix/ │ │ │ ├── AbuseYellowIntention.kt │ │ │ ├── AddSerializerFix.kt │ │ │ ├── ConfigurePluginMainServiceFix.kt │ │ │ ├── ConvertToValFix.kt │ │ │ ├── ProvideDefaultValueFix.kt │ │ │ ├── TypeProjectionFix.kt │ │ │ └── WrapWithResourceUseCallIntention.kt │ │ ├── line/ │ │ │ └── marker/ │ │ │ ├── CommandDeclarationLineMarkerProvider.kt │ │ │ └── PluginMainLineMarkerProvider.kt │ │ ├── resolve/ │ │ │ ├── FunctionSignature.kt │ │ │ ├── ReceiverExpression.kt │ │ │ └── resolveIdea.kt │ │ ├── util/ │ │ │ ├── RequirementHelper.kt │ │ │ ├── RequirementParser.kt │ │ │ └── RunIgnoringErrors.kt │ │ └── wizard/ │ │ ├── BuildSystemType.kt │ │ ├── KotlinStdlibVersion.kt │ │ ├── LanguageType.kt │ │ ├── MiraiModuleBuilder.kt │ │ ├── MiraiModuleType.kt │ │ ├── MiraiProjectModel.kt │ │ ├── MiraiProjectWizardInitialStep.kt │ │ ├── MiraiValidations.kt │ │ ├── MiraiVersion.kt │ │ ├── MiraiWizardBundle.kt │ │ └── ProjectAssetsProvider.kt │ └── test/ │ ├── creator/ │ │ ├── MiraiVersionKindTest.kt │ │ └── tasks/ │ │ └── TaskUtilsKtTest.kt │ └── package.kt ├── mirai-core/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── compatibility-validation/ │ │ ├── android/ │ │ │ └── api/ │ │ │ └── android.api │ │ └── jvm/ │ │ └── api/ │ │ └── jvm.api │ └── src/ │ ├── androidInstrumentedTest/ │ │ └── kotlin/ │ │ ├── package.kt │ │ ├── test/ │ │ │ └── initializeTestJvm.kt │ │ └── testFramework/ │ │ └── currentPlatform.kt │ ├── androidMain/ │ │ ├── AndroidManifest.xml │ │ └── kotlin/ │ │ ├── package.kt │ │ └── utils/ │ │ └── crypto/ │ │ └── EcdhAndroid.kt │ ├── androidUnitTest/ │ │ └── kotlin/ │ │ ├── package.kt │ │ ├── test/ │ │ │ ├── Logger.kt │ │ │ └── initializeTestJvm.kt │ │ └── testFramework/ │ │ └── currentPlatform.kt │ ├── commonMain/ │ │ ├── kotlin/ │ │ │ ├── AbstractBot.kt │ │ │ ├── BotAccount.kt │ │ │ ├── BotFactory.kt │ │ │ ├── MiraiImpl.kt │ │ │ ├── QQAndroidBot.kt │ │ │ ├── contact/ │ │ │ │ ├── AbstractContact.kt │ │ │ │ ├── AbstractMember.kt │ │ │ │ ├── AbstractUser.kt │ │ │ │ ├── AnonymousMemberImpl.kt │ │ │ │ ├── ContactAware.kt │ │ │ │ ├── FriendImpl.kt │ │ │ │ ├── GroupImpl.kt │ │ │ │ ├── GroupSendMessageImpl.kt │ │ │ │ ├── GroupSettingsImpl.kt │ │ │ │ ├── MemberActiveImpl.kt │ │ │ │ ├── NormalMemberImpl.kt │ │ │ │ ├── OnlineAnnouncementImpl.kt │ │ │ │ ├── OtherClientImpl.kt │ │ │ │ ├── SendMessageHandler.kt │ │ │ │ ├── StrangerImpl.kt │ │ │ │ ├── active/ │ │ │ │ │ ├── GroupActiveImpl.kt │ │ │ │ │ └── GroupActiveProtocol.kt │ │ │ │ ├── announcement/ │ │ │ │ │ ├── AnnouncementsImpl.kt │ │ │ │ │ └── GroupAnnouncement.kt │ │ │ │ ├── essence/ │ │ │ │ │ ├── EssencesImpl.kt │ │ │ │ │ └── GroupDigestProtocol.kt │ │ │ │ ├── file/ │ │ │ │ │ ├── AbsoluteFileImpl.kt │ │ │ │ │ ├── AbsoluteFolderImpl.kt │ │ │ │ │ ├── AbstractAbsoluteFileFolder.kt │ │ │ │ │ ├── FileProtocol.kt │ │ │ │ │ └── RemoteFilesImpl.kt │ │ │ │ ├── friendgroup/ │ │ │ │ │ ├── FriendGroupImpl.kt │ │ │ │ │ └── FriendGroupsImpl.kt │ │ │ │ ├── info/ │ │ │ │ │ ├── FriendGroupInfo.kt │ │ │ │ │ ├── FriendInfoImpl.kt │ │ │ │ │ ├── GroupInfoImpl.kt │ │ │ │ │ ├── MemberInfoImpl.kt │ │ │ │ │ └── StrangerInfoImpl.kt │ │ │ │ ├── roaming/ │ │ │ │ │ ├── AbstractRoamingMessages.kt │ │ │ │ │ ├── RoamingMessagesImplFriend.kt │ │ │ │ │ ├── RoamingMessagesImplGroup.kt │ │ │ │ │ └── TimeBasedRoamingMessagesImpl.kt │ │ │ │ └── util.kt │ │ │ ├── event/ │ │ │ │ ├── EventChannelImpl.kt │ │ │ │ ├── EventChannelToEventDispatcherAdapter.kt │ │ │ │ ├── EventListeners.kt │ │ │ │ ├── GlobalEventChannelProviderImpl.kt │ │ │ │ ├── SafeListener.kt │ │ │ │ └── package.kt │ │ │ ├── message/ │ │ │ │ ├── ReceiveMessageHandler.kt │ │ │ │ ├── RefinableMessage.kt │ │ │ │ ├── atImpl.kt │ │ │ │ ├── contextualBugReportException.kt │ │ │ │ ├── data/ │ │ │ │ │ ├── FileMessageImpl.kt │ │ │ │ │ ├── LongMessageInternal.kt │ │ │ │ │ ├── MarketFaceImpl.kt │ │ │ │ │ ├── MessageChainBuilderExt.kt │ │ │ │ │ ├── MessageSourceExt.kt │ │ │ │ │ ├── MultiMsgUploader.kt │ │ │ │ │ ├── UnsupportedMessageImpl.kt │ │ │ │ │ ├── audio.kt │ │ │ │ │ ├── lightApp.kt │ │ │ │ │ └── shortVideo.kt │ │ │ │ ├── faceImpl.kt │ │ │ │ ├── flags/ │ │ │ │ │ └── InternalFlagOnlyMessage.kt │ │ │ │ ├── image/ │ │ │ │ │ ├── AbstractImage.kt │ │ │ │ │ ├── ImageDecoder.kt │ │ │ │ │ ├── ImageInfo.kt │ │ │ │ │ ├── ImageUrlAware.kt │ │ │ │ │ ├── InternalImageProtocolImpl.kt │ │ │ │ │ ├── InternalShortVideoProtocolImpl.kt │ │ │ │ │ ├── OfflineImage.kt │ │ │ │ │ ├── OnlineImage.kt │ │ │ │ │ └── jceData.kt │ │ │ │ ├── imagesImpl.kt │ │ │ │ ├── messageToElems.kt │ │ │ │ ├── protocol/ │ │ │ │ │ ├── MessageProtocol.kt │ │ │ │ │ ├── MessageProtocolFacade.kt │ │ │ │ │ ├── decode/ │ │ │ │ │ │ ├── MessageDecoder.kt │ │ │ │ │ │ └── MessageDecoderPipeline.kt │ │ │ │ │ ├── encode/ │ │ │ │ │ │ ├── MessageEncoder.kt │ │ │ │ │ │ └── MessageEncoderPipeline.kt │ │ │ │ │ ├── impl/ │ │ │ │ │ │ ├── AudioProtocol.kt │ │ │ │ │ │ ├── CustomMessageProtocol.kt │ │ │ │ │ │ ├── FaceProtocol.kt │ │ │ │ │ │ ├── FileMessageProtocol.kt │ │ │ │ │ │ ├── FlashImageProtocol.kt │ │ │ │ │ │ ├── ForwardMessageProtocol.kt │ │ │ │ │ │ ├── GeneralMessageSenderProtocol.kt │ │ │ │ │ │ ├── IgnoredMessagesProtocol.kt │ │ │ │ │ │ ├── ImageProtocol.kt │ │ │ │ │ │ ├── LongMessageProtocol.kt │ │ │ │ │ │ ├── MarketFaceProtocol.kt │ │ │ │ │ │ ├── MusicShareProtocol.kt │ │ │ │ │ │ ├── PokeMessageProtocol.kt │ │ │ │ │ │ ├── PttMessageProtocol.kt │ │ │ │ │ │ ├── QuoteReplyProtocol.kt │ │ │ │ │ │ ├── RichMessageProtocol.kt │ │ │ │ │ │ ├── ShortVideoProtocol.kt │ │ │ │ │ │ ├── SuperFaceProtocol.kt │ │ │ │ │ │ ├── TextProtocol.kt │ │ │ │ │ │ ├── UnsupportedMessageProtocol.kt │ │ │ │ │ │ └── VipFaceProtocol.kt │ │ │ │ │ ├── outgoing/ │ │ │ │ │ │ ├── HighwayUploader.kt │ │ │ │ │ │ ├── MessageProtocolStrategy.kt │ │ │ │ │ │ ├── OutgoingMessagePipeline.kt │ │ │ │ │ │ ├── OutgoingMessagePipelineProcessor.kt │ │ │ │ │ │ └── OutgoingMessageProcessor.kt │ │ │ │ │ └── serialization/ │ │ │ │ │ └── MessageSerializer.kt │ │ │ │ ├── rich/ │ │ │ │ │ └── package.kt │ │ │ │ ├── source/ │ │ │ │ │ ├── MessageSourceInternal.kt │ │ │ │ │ ├── incomingSourceImpl.kt │ │ │ │ │ ├── offlineSourceImpl.kt │ │ │ │ │ └── outgoingSourceImpl.kt │ │ │ │ └── visitor/ │ │ │ │ └── MessageVisitorEx.kt │ │ │ ├── network/ │ │ │ │ ├── ContactListCache.kt │ │ │ │ ├── DebuggingProperties.kt │ │ │ │ ├── Packet.kt │ │ │ │ ├── QQAndroidClient.kt │ │ │ │ ├── Ticket.kt │ │ │ │ ├── auth/ │ │ │ │ │ ├── AuthControl.kt │ │ │ │ │ ├── BotAuthSessionInternal.kt │ │ │ │ │ ├── DefaultBotAuthorizationFactoryImpl.kt │ │ │ │ │ └── SafeBotAuthSession.kt │ │ │ │ ├── component/ │ │ │ │ │ ├── ComponentKey.kt │ │ │ │ │ ├── ComponentStorage.kt │ │ │ │ │ ├── ComponentStorageDelegate.kt │ │ │ │ │ ├── ConcurrentComponentStorage.kt │ │ │ │ │ ├── MutableComponentStorage.kt │ │ │ │ │ └── NoSuchComponentException.kt │ │ │ │ ├── components/ │ │ │ │ │ ├── AccountSecretsManager.kt │ │ │ │ │ ├── BdhSessionSyncer.kt │ │ │ │ │ ├── BotClientHolder.kt │ │ │ │ │ ├── BotInitProcessor.kt │ │ │ │ │ ├── BotOfflineEventMonitor.kt │ │ │ │ │ ├── CacheValidator.kt │ │ │ │ │ ├── ClockComponent.kt │ │ │ │ │ ├── ConfigPushProcessor.kt │ │ │ │ │ ├── ConfigPushSyncer.kt │ │ │ │ │ ├── ContactCacheService.kt │ │ │ │ │ ├── ContactUpdater.kt │ │ │ │ │ ├── EcdhInitialPublicKeyUpdater.kt │ │ │ │ │ ├── EncryptServiceHolder.kt │ │ │ │ │ ├── EventDispatcher.kt │ │ │ │ │ ├── HeartbeatProcessor.kt │ │ │ │ │ ├── HeartbeatScheduler.kt │ │ │ │ │ ├── HttpClientProvider.kt │ │ │ │ │ ├── KeyRefreshProcessor.kt │ │ │ │ │ ├── MessageSvcSyncer.kt │ │ │ │ │ ├── NetworkHandlerReference.kt │ │ │ │ │ ├── NoticeProcessorPipeline.kt │ │ │ │ │ ├── OtherClientUpdater.kt │ │ │ │ │ ├── PacketCodec.kt │ │ │ │ │ ├── PacketHandler.kt │ │ │ │ │ ├── PacketLoggingStrategy.kt │ │ │ │ │ ├── QRCodeLoginProcessor.kt │ │ │ │ │ ├── ServerList.kt │ │ │ │ │ ├── SsoProcessor.kt │ │ │ │ │ ├── SsoProcessorContext.kt │ │ │ │ │ ├── SyncController.kt │ │ │ │ │ └── package.kt │ │ │ │ ├── handler/ │ │ │ │ │ ├── CommonNetworkHandler.kt │ │ │ │ │ ├── NetworkHandler.kt │ │ │ │ │ ├── NetworkHandlerContext.kt │ │ │ │ │ ├── NetworkHandlerFactory.kt │ │ │ │ │ ├── NetworkHandlerSupport.kt │ │ │ │ │ ├── selector/ │ │ │ │ │ │ ├── AbstractKeepAliveNetworkHandlerSelector.kt │ │ │ │ │ │ ├── ExceptionInSelectorResumeException.kt │ │ │ │ │ │ ├── MaxAttemptsReachedException.kt │ │ │ │ │ │ ├── NetworkChannelException.kt │ │ │ │ │ │ ├── NetworkException.kt │ │ │ │ │ │ ├── NetworkHandlerSelector.kt │ │ │ │ │ │ ├── NoServerAvailableException.kt │ │ │ │ │ │ ├── PacketTimeoutException.kt │ │ │ │ │ │ ├── SelectorNetworkHandler.kt │ │ │ │ │ │ └── SelectorRequireReconnectException.kt │ │ │ │ │ └── state/ │ │ │ │ │ ├── CombinedStateObserver.kt │ │ │ │ │ ├── ExceptionInStateObserverException.kt │ │ │ │ │ ├── JobAttachStateObserver.kt │ │ │ │ │ ├── LoggingStateObserver.kt │ │ │ │ │ ├── SafeStateObserver.kt │ │ │ │ │ ├── StateChangedObserver.kt │ │ │ │ │ └── StateObserver.kt │ │ │ │ ├── highway/ │ │ │ │ │ ├── ChunkedFlowSession.kt │ │ │ │ │ ├── Highway.kt │ │ │ │ │ └── Http.kt │ │ │ │ ├── impl/ │ │ │ │ │ ├── HeartbeatFailedException.kt │ │ │ │ │ ├── ServerClosedException.kt │ │ │ │ │ └── package.kt │ │ │ │ ├── keys.kt │ │ │ │ ├── notice/ │ │ │ │ │ ├── NewContactSupport.kt │ │ │ │ │ ├── PrivateContactSupport.kt │ │ │ │ │ ├── TraceLoggingNoticeProcessor.kt │ │ │ │ │ ├── UnconsumedNoticesAlerter.kt │ │ │ │ │ ├── decoders/ │ │ │ │ │ │ ├── GroupNotificationDecoder.kt │ │ │ │ │ │ └── MsgInfoDecoder.kt │ │ │ │ │ ├── group/ │ │ │ │ │ │ ├── GroupMessageProcessor.kt │ │ │ │ │ │ ├── GroupNotificationProcessor.kt │ │ │ │ │ │ ├── GroupOrMemberListNoticeProcessor.kt │ │ │ │ │ │ └── GroupRecallProcessor.kt │ │ │ │ │ └── priv/ │ │ │ │ │ ├── FriendGroupNoticeProcessor.kt │ │ │ │ │ ├── FriendNoticeProcessor.kt │ │ │ │ │ ├── OtherClientNoticeProcessor.kt │ │ │ │ │ └── PrivateMessageProcessor.kt │ │ │ │ ├── protocol/ │ │ │ │ │ ├── LoginType.kt │ │ │ │ │ ├── SyncingCacheList.kt │ │ │ │ │ ├── data/ │ │ │ │ │ │ ├── jce/ │ │ │ │ │ │ │ ├── ChangeFriendNameReq.kt │ │ │ │ │ │ │ ├── ConfigPush.kt │ │ │ │ │ │ │ ├── DeviceItemDes.kt │ │ │ │ │ │ │ ├── FriendList.kt │ │ │ │ │ │ │ ├── GroupMngReq.kt │ │ │ │ │ │ │ ├── InstanceInfo.kt │ │ │ │ │ │ │ ├── MoveGroupMemPack.kt │ │ │ │ │ │ │ ├── MsgType0x210.kt │ │ │ │ │ │ │ ├── OnlinePushPack.kt │ │ │ │ │ │ │ ├── PushNotifyPack.kt │ │ │ │ │ │ │ ├── ReqPushStatus.kt │ │ │ │ │ │ │ ├── ReqSummaryCard.kt │ │ │ │ │ │ │ ├── RequestMSFForceOffline.kt │ │ │ │ │ │ │ ├── RequestPacket.kt │ │ │ │ │ │ │ ├── RequestPushForceOffline.kt │ │ │ │ │ │ │ ├── SetGroupPack.kt │ │ │ │ │ │ │ ├── SvcDevLoginInfo.kt │ │ │ │ │ │ │ ├── SvcReqMSFLoginNotifyData.kt │ │ │ │ │ │ │ ├── SvcReqRegister.kt │ │ │ │ │ │ │ ├── SvcRequestPushReadedNotify.kt │ │ │ │ │ │ │ ├── SvcRespRegister.kt │ │ │ │ │ │ │ ├── SvcRspGetDevLoginInfo.kt │ │ │ │ │ │ │ └── TroopList.kt │ │ │ │ │ │ ├── proto/ │ │ │ │ │ │ │ ├── Cmd0x346.kt │ │ │ │ │ │ │ ├── Cmd0x352.kt │ │ │ │ │ │ │ ├── Cmd0x388.kt │ │ │ │ │ │ │ ├── Cmd0x857.kt │ │ │ │ │ │ │ ├── Cmd0x858.kt │ │ │ │ │ │ │ ├── Define.kt │ │ │ │ │ │ │ ├── Exciting.kt │ │ │ │ │ │ │ ├── FrdSysMsg.kt │ │ │ │ │ │ │ ├── FriendListCommon.kt │ │ │ │ │ │ │ ├── Group.kt │ │ │ │ │ │ │ ├── GroupFileCommon.kt │ │ │ │ │ │ │ ├── Highway.kt │ │ │ │ │ │ │ ├── HummerCommelem.kt │ │ │ │ │ │ │ ├── HummerResv12.kt │ │ │ │ │ │ │ ├── HummerResv21.kt │ │ │ │ │ │ │ ├── HummerResv3.kt │ │ │ │ │ │ │ ├── HummerResv6.kt │ │ │ │ │ │ │ ├── ImageRequest.kt │ │ │ │ │ │ │ ├── ImgExtPbResvAttrCommon.kt │ │ │ │ │ │ │ ├── LongMsg.kt │ │ │ │ │ │ │ ├── Msg.kt │ │ │ │ │ │ │ ├── MsgCommon.kt │ │ │ │ │ │ │ ├── MsgRevokeUserDef.kt │ │ │ │ │ │ │ ├── MsgSvc.kt │ │ │ │ │ │ │ ├── MsgTransmit.kt │ │ │ │ │ │ │ ├── MultiMsg.kt │ │ │ │ │ │ │ ├── OIDB.kt │ │ │ │ │ │ │ ├── Oidb0x6d6.kt │ │ │ │ │ │ │ ├── Oidb0x6d7.kt │ │ │ │ │ │ │ ├── Oidb0x6d8.kt │ │ │ │ │ │ │ ├── Oidb0x6d9.kt │ │ │ │ │ │ │ ├── Oidb0x769.kt │ │ │ │ │ │ │ ├── Oidb0xeac.kt │ │ │ │ │ │ │ ├── OidbCmd0xb77.kt │ │ │ │ │ │ │ ├── OnlinePush.kt │ │ │ │ │ │ │ ├── PbReserve.kt │ │ │ │ │ │ │ ├── PttShortVideo.kt │ │ │ │ │ │ │ ├── SSOReserveField.kt │ │ │ │ │ │ │ ├── StatSvcGetOnline.kt │ │ │ │ │ │ │ ├── StatSvcSimpleGet.kt │ │ │ │ │ │ │ ├── StructMsg.kt │ │ │ │ │ │ │ ├── SyncCookie.kt │ │ │ │ │ │ │ ├── msgType0x210.kt │ │ │ │ │ │ │ └── msgType0x211.kt │ │ │ │ │ │ └── richstatus/ │ │ │ │ │ │ └── RichStatus.kt │ │ │ │ │ └── packet/ │ │ │ │ │ ├── EncryptMethod.kt │ │ │ │ │ ├── OutgoingPacket.kt │ │ │ │ │ ├── PacketFactory.kt │ │ │ │ │ ├── Tlv.kt │ │ │ │ │ ├── chat/ │ │ │ │ │ │ ├── ChatType.kt │ │ │ │ │ │ ├── GroupFile.kt │ │ │ │ │ │ ├── MultiMsg.kt │ │ │ │ │ │ ├── MusicSharePacket.kt │ │ │ │ │ │ ├── NewContact.kt │ │ │ │ │ │ ├── NudgePacket.kt │ │ │ │ │ │ ├── PbMessageSvc.kt │ │ │ │ │ │ ├── SendMessageMultiProtocol.kt │ │ │ │ │ │ ├── TroopEssenceMsgManager.kt │ │ │ │ │ │ ├── TroopManagement.kt │ │ │ │ │ │ ├── image/ │ │ │ │ │ │ │ ├── ImgStore.kt │ │ │ │ │ │ │ └── LongConn.kt │ │ │ │ │ │ ├── receive/ │ │ │ │ │ │ │ ├── MessageSvc.PbDeleteMsg.kt │ │ │ │ │ │ │ ├── MessageSvc.PbGetGroupMsg.kt │ │ │ │ │ │ │ ├── MessageSvc.PbGetMsg.kt │ │ │ │ │ │ │ ├── MessageSvc.PbGetRoamMsgReq.kt │ │ │ │ │ │ │ ├── MessageSvc.PbSendMsg.kt │ │ │ │ │ │ │ ├── MessageSvc.PushForceOffline.kt │ │ │ │ │ │ │ ├── MessageSvc.PushNotify.kt │ │ │ │ │ │ │ ├── MessageSvc.PushReaded.kt │ │ │ │ │ │ │ ├── MessageSvc.RequestPushStatus.kt │ │ │ │ │ │ │ ├── OnlinePush.PbC2CMsgSync.kt │ │ │ │ │ │ │ ├── OnlinePush.PbPushGroupMsg.kt │ │ │ │ │ │ │ ├── OnlinePush.PbPushTransMsg.kt │ │ │ │ │ │ │ ├── OnlinePush.ReqPush.kt │ │ │ │ │ │ │ └── OnlinePush.SidExpired.kt │ │ │ │ │ │ ├── shortvideo/ │ │ │ │ │ │ │ └── PttCenterSvr.kt │ │ │ │ │ │ └── voice/ │ │ │ │ │ │ └── PttStore.kt │ │ │ │ │ ├── list/ │ │ │ │ │ │ ├── FriendList.kt │ │ │ │ │ │ ├── ProfileService.kt │ │ │ │ │ │ └── StrangerList.kt │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── ConfigPushSvc.kt │ │ │ │ │ │ ├── Heartbeat.kt │ │ │ │ │ │ ├── StatSvc.kt │ │ │ │ │ │ ├── WtLogin.kt │ │ │ │ │ │ └── wtlogin/ │ │ │ │ │ │ ├── WtLogin10.kt │ │ │ │ │ │ ├── WtLogin15.kt │ │ │ │ │ │ ├── WtLogin2.kt │ │ │ │ │ │ ├── WtLogin20.kt │ │ │ │ │ │ ├── WtLogin7.kt │ │ │ │ │ │ ├── WtLogin8.kt │ │ │ │ │ │ ├── WtLogin9.kt │ │ │ │ │ │ └── WtLoginExt.kt │ │ │ │ │ ├── sso/ │ │ │ │ │ │ └── TRpcRawPacket.kt │ │ │ │ │ └── summarycard/ │ │ │ │ │ ├── FriendRemark.kt │ │ │ │ │ └── SummaryCard.kt │ │ │ │ └── qimei/ │ │ │ │ └── Qimei.kt │ │ │ ├── pipeline/ │ │ │ │ └── ProcessorPipeline.kt │ │ │ ├── spi/ │ │ │ │ └── EncryptService.kt │ │ │ └── utils/ │ │ │ ├── AtomicIntSeq.kt │ │ │ ├── BotConfigurationExt.kt │ │ │ ├── ExternalResourceImpl.kt │ │ │ ├── FileSystem.kt │ │ │ ├── FragmentedMsgParsingCache.kt │ │ │ ├── GuidSource.kt │ │ │ ├── ImagePatcher.kt │ │ │ ├── MiraiCoreServices.kt │ │ │ ├── MiraiProtocolInternal.kt │ │ │ ├── MiraiUtilsLogger.kt │ │ │ ├── NetworkType.kt │ │ │ ├── PlatformDatagramChannel.kt │ │ │ ├── PlatformSocket.kt │ │ │ ├── ScheduledJob.kt │ │ │ ├── SingleEntrantLock.kt │ │ │ ├── SubLogger.kt │ │ │ ├── collection.kt │ │ │ ├── crypto/ │ │ │ │ ├── AES.kt │ │ │ │ ├── Ecdh.kt │ │ │ │ ├── QQEcdh.kt │ │ │ │ ├── RSA.kt │ │ │ │ └── TEA.kt │ │ │ ├── flags.kt │ │ │ ├── io/ │ │ │ │ ├── ProtocolStruct.kt │ │ │ │ ├── output.kt │ │ │ │ └── serialization/ │ │ │ │ ├── tars/ │ │ │ │ │ ├── Tars.kt │ │ │ │ │ ├── TarsId.kt │ │ │ │ │ └── internal/ │ │ │ │ │ ├── TarsDecoder.kt │ │ │ │ │ ├── TarsInput.kt │ │ │ │ │ ├── TarsOld.kt │ │ │ │ │ └── TarsTag.kt │ │ │ │ └── utils.kt │ │ │ ├── numbers.kt │ │ │ ├── printStructure.kt │ │ │ ├── retryWithServers.kt │ │ │ ├── runCoroutineInPlace.kt │ │ │ ├── string.kt │ │ │ └── type.kt │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ ├── net.mamoe.mirai.IMirai │ │ │ ├── net.mamoe.mirai.auth.DefaultBotAuthorizationFactory │ │ │ ├── net.mamoe.mirai.event.InternalGlobalEventChannelProvider │ │ │ ├── net.mamoe.mirai.internal.message.protocol.MessageProtocol │ │ │ ├── net.mamoe.mirai.message.data.InternalImageProtocol │ │ │ ├── net.mamoe.mirai.message.data.InternalShortVideoProtocol │ │ │ ├── net.mamoe.mirai.message.data.OfflineAudio$Factory │ │ │ └── net.mamoe.mirai.utils.InternalProtocolDataExchange │ │ └── emoji-pattern.regex │ ├── commonTest/ │ │ ├── kotlin/ │ │ │ ├── AbstractTestWithMiraiImpl.kt │ │ │ ├── BotFactoryTest.kt │ │ │ ├── MockBot.kt │ │ │ ├── PlatformUtilsTest.kt │ │ │ ├── ScheduledJobTest.kt │ │ │ ├── TypeConversionTest.kt │ │ │ ├── contact/ │ │ │ │ └── file/ │ │ │ │ └── AbsoluteFolderTest.kt │ │ │ ├── event/ │ │ │ │ ├── AbstractEventTest.kt │ │ │ │ ├── CancelScopeTest.kt │ │ │ │ ├── EventChannelFlowTest.kt │ │ │ │ ├── EventChannelTest.kt │ │ │ │ ├── EventTests.kt │ │ │ │ ├── NextEventTest.kt │ │ │ │ └── StepUtil.kt │ │ │ ├── message/ │ │ │ │ ├── CleanupRubbishMessageElementsTest.kt │ │ │ │ ├── ImageBuilderTest.kt │ │ │ │ ├── ImageReadingTest.kt │ │ │ │ ├── InternalImageProtocolImplTest.kt │ │ │ │ ├── RefineContextTest.kt │ │ │ │ ├── code/ │ │ │ │ │ └── TestMiraiCode.kt │ │ │ │ ├── data/ │ │ │ │ │ ├── AudioTest.kt │ │ │ │ │ ├── ContentEqualsTest.kt │ │ │ │ │ ├── ForwardRefineTest.kt │ │ │ │ │ ├── MessageReceiptTest.kt │ │ │ │ │ ├── MessageRefineTest.kt │ │ │ │ │ └── MessageSerializationTest.kt │ │ │ │ ├── protocol/ │ │ │ │ │ ├── MessageProtocolFacadeTest.kt │ │ │ │ │ └── impl/ │ │ │ │ │ ├── AbstractMessageProtocolTest.kt │ │ │ │ │ ├── CustomMessageProtocolTest.kt │ │ │ │ │ ├── EqualityAsserter.kt │ │ │ │ │ ├── FaceProtocolTest.kt │ │ │ │ │ ├── FileMessageProtocolTest.kt │ │ │ │ │ ├── FlashImageProtocolTest.kt │ │ │ │ │ ├── ForwardMessageProtocolTest.kt │ │ │ │ │ ├── GeneralMessageSenderProtocolTest.kt │ │ │ │ │ ├── ImageProtocolTest.kt │ │ │ │ │ ├── LongMessageProtocolTest.kt │ │ │ │ │ ├── MarketFaceProtocolTest.kt │ │ │ │ │ ├── MusicShareProtocolTest.kt │ │ │ │ │ ├── PokeMessageProtocolTest.kt │ │ │ │ │ ├── QuoteReplyProtocolTest.kt │ │ │ │ │ ├── RichMessageProtocolTest.kt │ │ │ │ │ ├── SuperFaceProtocolTest.kt │ │ │ │ │ ├── TextProtocolTest.kt │ │ │ │ │ └── VipFaceProtocolTest.kt │ │ │ │ └── serialization/ │ │ │ │ └── AbstractMessageSerializationTest.kt │ │ │ ├── network/ │ │ │ │ ├── AwaitStateTest.kt │ │ │ │ ├── PacketCodecTest.kt │ │ │ │ ├── ServerListTest.kt │ │ │ │ ├── auth/ │ │ │ │ │ ├── AbstractBotAuthTest.kt │ │ │ │ │ ├── AuthorizationReasonTest.kt │ │ │ │ │ └── BotAuthorizationTest.kt │ │ │ │ ├── component/ │ │ │ │ │ ├── AbstractMutableComponentStorageTest.kt │ │ │ │ │ ├── BotAuthControlTest.kt │ │ │ │ │ ├── BotInitProcessorTest.kt │ │ │ │ │ ├── CombinedStorageTest.kt │ │ │ │ │ └── EventDispatcherTest.kt │ │ │ │ ├── framework/ │ │ │ │ │ ├── AbstractCommonNHTest.kt │ │ │ │ │ ├── AbstractCommonNHTestWithSelector.kt │ │ │ │ │ ├── AbstractMockNetworkHandlerTest.kt │ │ │ │ │ ├── AbstractNetworkHandlerTest.kt │ │ │ │ │ ├── AbstractRealNetworkHandlerTest.kt │ │ │ │ │ ├── AbstractRealTimeActionTestUnit.kt │ │ │ │ │ ├── ITestNetworkHandler.kt │ │ │ │ │ ├── PacketReplier.kt │ │ │ │ │ ├── SynchronizedStdoutLogger.kt │ │ │ │ │ ├── TestNetworkHandler.kt │ │ │ │ │ ├── TestNetworkHandlerContext.kt │ │ │ │ │ ├── components/ │ │ │ │ │ │ ├── TestEventDispatcherImpl.kt │ │ │ │ │ │ ├── TestImagePatcher.kt │ │ │ │ │ │ └── TestSsoProcessor.kt │ │ │ │ │ ├── networkUtils.kt │ │ │ │ │ ├── sessionUtils.kt │ │ │ │ │ └── test/ │ │ │ │ │ └── FrameworkEventTest.kt │ │ │ │ ├── handler/ │ │ │ │ │ ├── KeepAliveNetworkHandlerSelectorTest.kt │ │ │ │ │ ├── SelectorHeartbeatRecoveryTest.kt │ │ │ │ │ ├── SelectorLoginRecoveryTest.kt │ │ │ │ │ ├── StandaloneSelectorTests.kt │ │ │ │ │ └── StateObserverTest.kt │ │ │ │ └── impl/ │ │ │ │ └── common/ │ │ │ │ ├── AccountSecretsTest.kt │ │ │ │ ├── BotLifecycleTest.kt │ │ │ │ ├── CommonNHAddressChangedTest.kt │ │ │ │ ├── CommonNHBotNormalLoginTest.kt │ │ │ │ ├── CommonNHEventTest.kt │ │ │ │ ├── ResumeConnectionTest.kt │ │ │ │ ├── SendPacketTest.kt │ │ │ │ └── SetStateTest.kt │ │ │ ├── notice/ │ │ │ │ └── processors/ │ │ │ │ ├── AbstractNoticeProcessorTest.kt │ │ │ │ ├── BotInvitedJoinTest.kt │ │ │ │ ├── FriendNickChangeTest.kt │ │ │ │ ├── GroupRetrieveTest.kt │ │ │ │ ├── GroupTransferTest.kt │ │ │ │ ├── MemberAdminChangeTest.kt │ │ │ │ ├── MemberJoinTest.kt │ │ │ │ ├── MemberQuitTest.kt │ │ │ │ ├── MessageSyncTest.kt │ │ │ │ ├── MessageTest.kt │ │ │ │ ├── MuteTest.kt │ │ │ │ └── RecallTest.kt │ │ │ ├── samples/ │ │ │ │ └── CustomMessageSamples.kt │ │ │ ├── test/ │ │ │ │ ├── events.kt │ │ │ │ ├── initPlatform.common.kt │ │ │ │ ├── printing.kt │ │ │ │ └── utils.kt │ │ │ ├── testFramework/ │ │ │ │ ├── DebugProbes.kt │ │ │ │ ├── DynamicTest.kt │ │ │ │ ├── Platform.kt │ │ │ │ ├── message/ │ │ │ │ │ └── TestMessageSourceSequenceIdAwaiter.kt │ │ │ │ ├── package.kt │ │ │ │ └── rules/ │ │ │ │ └── DisabledOnJvmLikePlatform.kt │ │ │ └── utils/ │ │ │ ├── FileSystemTest.kt │ │ │ ├── crypto/ │ │ │ │ ├── AESTest.kt │ │ │ │ ├── EcdhTest.kt │ │ │ │ └── RSATest.kt │ │ │ └── io/ │ │ │ └── serialization/ │ │ │ ├── ReadJceStructTest.kt │ │ │ └── tars/ │ │ │ └── internal/ │ │ │ └── DebugLoggerTest.kt │ │ └── resources/ │ │ ├── META-INF/ │ │ │ └── services/ │ │ │ └── net.mamoe.mirai.internal.message.source.MessageSourceSequenceIdAwaiter │ │ └── recording/ │ │ └── configs/ │ │ ├── desensitization.yml │ │ └── test.desensitization.yml │ ├── jvmBaseMain/ │ │ └── kotlin/ │ │ ├── MiraiImpl.kt │ │ ├── contact/ │ │ │ ├── GroupImpl.kt │ │ │ ├── active/ │ │ │ │ └── GroupActiveImpl.kt │ │ │ └── file/ │ │ │ └── AbsoluteFolderImpl.kt │ │ ├── message/ │ │ │ └── protocol/ │ │ │ └── impl/ │ │ │ └── TextProtocol.kt │ │ ├── network/ │ │ │ ├── component/ │ │ │ │ └── ComponentKey.kt │ │ │ ├── handler/ │ │ │ │ ├── NetworkHandlerFactory.kt │ │ │ │ ├── SocketAddress.kt │ │ │ │ └── selector/ │ │ │ │ └── SelectorRequireReconnectException.kt │ │ │ ├── impl/ │ │ │ │ ├── netty/ │ │ │ │ │ ├── NettyChannelException.kt │ │ │ │ │ ├── NettyNetworkHandler.kt │ │ │ │ │ ├── NettyNetworkHandlerFactory.kt │ │ │ │ │ └── nettyUtils.kt │ │ │ │ └── package.kt │ │ │ ├── package.kt │ │ │ └── protocol/ │ │ │ └── data/ │ │ │ └── richstatus/ │ │ │ └── RichStatus.kt │ │ ├── package.kt │ │ └── utils/ │ │ ├── BotConfigurationExt.kt │ │ ├── ExternalResourceImpl.kt │ │ ├── PlatformSocket.kt │ │ ├── RemoteFileImpl.kt │ │ └── crypto/ │ │ ├── AES.kt │ │ ├── JceEcdh.kt │ │ ├── JceEcdhWithProvider.kt │ │ ├── QQEcdhJvm.kt │ │ └── RSA.kt │ ├── jvmBaseTest/ │ │ ├── kotlin/ │ │ │ ├── event/ │ │ │ │ ├── EventChannelJavaTest.java │ │ │ │ ├── EventLaunchUndispatchedTest.kt │ │ │ │ ├── JvmMethodEventsTest.kt │ │ │ │ ├── JvmMethodEventsTestJava.kt │ │ │ │ └── SimpleListenerHostTestJava.kt │ │ │ ├── network/ │ │ │ │ ├── component/ │ │ │ │ │ ├── ComponentKeyTest.kt │ │ │ │ │ └── ConcurrentComponentStorageToStringTest.kt │ │ │ │ ├── framework/ │ │ │ │ │ ├── AbstractCommonNHTest.kt │ │ │ │ │ ├── AbstractNettyNHTest.kt │ │ │ │ │ └── NettyNHTestChannel.kt │ │ │ │ └── impl/ │ │ │ │ └── netty/ │ │ │ │ └── CommonNHUtilsTest.kt │ │ │ ├── package.kt │ │ │ ├── test/ │ │ │ │ ├── AbstractTest.kt │ │ │ │ └── NativeTestWrapper.kt │ │ │ ├── testFramework/ │ │ │ │ ├── DebugProbes.kt │ │ │ │ ├── DynamicTest.kt │ │ │ │ ├── codegen/ │ │ │ │ │ ├── RemoveDefaultValuesVisitor.kt │ │ │ │ │ ├── ValueDescAnalyzer.kt │ │ │ │ │ ├── descriptors/ │ │ │ │ │ │ ├── ClassValueDesc.kt │ │ │ │ │ │ ├── CollectionLikeValueDesc.kt │ │ │ │ │ │ ├── CollectionValueDesc.kt │ │ │ │ │ │ ├── MapValueDesc.kt │ │ │ │ │ │ ├── ObjectArrayValueDesc.kt │ │ │ │ │ │ ├── PlainValueDesc.kt │ │ │ │ │ │ ├── PrimitiveArrayValueDesc.kt │ │ │ │ │ │ └── ValueDesc.kt │ │ │ │ │ ├── test/ │ │ │ │ │ │ ├── IndenterTest.kt │ │ │ │ │ │ ├── OptimizeByteArrayAsHexStringTransformerTest.kt │ │ │ │ │ │ └── visitors/ │ │ │ │ │ │ ├── ValueDescAnalyzerTest.kt │ │ │ │ │ │ └── ValueDescToStringRendererTest.kt │ │ │ │ │ ├── visitor/ │ │ │ │ │ │ ├── ValueDescTransformer.kt │ │ │ │ │ │ ├── ValueDescVisitor.kt │ │ │ │ │ │ └── ValueDescVisitorUnit.kt │ │ │ │ │ └── visitors/ │ │ │ │ │ ├── AnalyzeDefaultValuesMappingVisitor.kt │ │ │ │ │ ├── ClassFormatter.kt │ │ │ │ │ ├── Indenter.kt │ │ │ │ │ ├── OptimizeByteArrayAsHexStringTransformer.kt │ │ │ │ │ └── ValueDescToStringRenderer.kt │ │ │ │ ├── desensitizer/ │ │ │ │ │ └── Desensitizer.kt │ │ │ │ ├── message/ │ │ │ │ │ └── protocol/ │ │ │ │ │ └── MessageDecodingRecorder.kt │ │ │ │ ├── notice/ │ │ │ │ │ └── RecordingNoticeHandler.kt │ │ │ │ ├── rules/ │ │ │ │ │ └── DisabledOnPlatform.kt │ │ │ │ └── test/ │ │ │ │ └── RecordingNoticeProcessorTest.kt │ │ │ └── utils/ │ │ │ ├── CombinedExternalResourceTest.kt │ │ │ ├── StructureToStringTransformerNew.kt │ │ │ └── test/ │ │ │ └── StructureToStringTransformerNewTest.kt │ │ └── resources/ │ │ └── META-INF/ │ │ └── services/ │ │ └── net.mamoe.mirai.utils.StructureToStringTransformer │ ├── jvmMain/ │ │ └── kotlin/ │ │ ├── package.kt │ │ └── utils/ │ │ └── crypto/ │ │ └── EcdhJvmDesktop.kt │ └── jvmTest/ │ ├── README.md │ ├── kotlin/ │ │ ├── AtomicResizeCacheListTest.kt │ │ ├── JavaApiTests.java │ │ ├── bootstrap/ │ │ │ ├── RunMessageDecodingRecorder.kt │ │ │ └── RunNoticeRecorder.kt │ │ ├── directboot/ │ │ │ ├── DebugRunHelper.kt │ │ │ └── envprepare.kt │ │ ├── netinternalkit/ │ │ │ ├── Ansi.kt │ │ │ ├── LogCapture.kt │ │ │ └── NetReplayHelper.kt │ │ ├── package.kt │ │ ├── test/ │ │ │ └── initializeTestJvm.kt │ │ └── testFramework/ │ │ └── currentPlatform.kt │ └── resources/ │ └── account.yml ├── mirai-core-all/ │ └── build.gradle.kts ├── mirai-core-api/ │ ├── README.md │ ├── build.gradle.kts │ ├── compatibility-validation/ │ │ ├── android/ │ │ │ └── api/ │ │ │ └── android.api │ │ └── jvm/ │ │ └── api/ │ │ └── jvm.api │ └── src/ │ ├── androidMain/ │ │ ├── AndroidManifest.xml │ │ └── kotlin/ │ │ ├── package.kt │ │ └── utils/ │ │ ├── LoginSolver.android.kt │ │ ├── PlatformLogger.android.kt │ │ └── SingleFileLogger.android.kt │ ├── commonMain/ │ │ └── kotlin/ │ │ ├── Bot.kt │ │ ├── BotFactory.kt │ │ ├── IMirai.kt │ │ ├── LowLevelApiAccessor.kt │ │ ├── auth/ │ │ │ ├── BotAuthorization.kt │ │ │ └── QRCodeLoginListener.kt │ │ ├── contact/ │ │ │ ├── AnonymousMember.kt │ │ │ ├── AudioSupported.kt │ │ │ ├── AvatarSpec.kt │ │ │ ├── Contact.kt │ │ │ ├── ContactList.kt │ │ │ ├── ContactOrBot.kt │ │ │ ├── Exceptions.kt │ │ │ ├── FileSupported.kt │ │ │ ├── Friend.kt │ │ │ ├── Group.kt │ │ │ ├── Member.kt │ │ │ ├── MemberPermission.kt │ │ │ ├── NormalMember.kt │ │ │ ├── OtherClient.kt │ │ │ ├── Stranger.kt │ │ │ ├── TempUser.kt │ │ │ ├── User.kt │ │ │ ├── UserOrBot.kt │ │ │ ├── active/ │ │ │ │ ├── ActiveChart.kt │ │ │ │ ├── ActiveHonorInfo.kt │ │ │ │ ├── ActiveHonorList.kt │ │ │ │ ├── ActiveRankRecord.kt │ │ │ │ ├── ActiveRecord.kt │ │ │ │ ├── GroupActive.kt │ │ │ │ ├── MemberActive.kt │ │ │ │ ├── MemberMedalInfo.kt │ │ │ │ └── MemberMedalType.kt │ │ │ ├── announcement/ │ │ │ │ ├── Announcement.kt │ │ │ │ ├── AnnouncementImage.kt │ │ │ │ ├── AnnouncementParameters.kt │ │ │ │ ├── AnnouncementParametersBuilder.kt │ │ │ │ ├── Announcements.kt │ │ │ │ ├── OfflineAnnouncement.kt │ │ │ │ └── OnlineAnnouncement.kt │ │ │ ├── essence/ │ │ │ │ ├── EssenceMessageRecord.kt │ │ │ │ └── Essences.kt │ │ │ ├── file/ │ │ │ │ ├── AbsoluteFile.kt │ │ │ │ ├── AbsoluteFileFolder.kt │ │ │ │ ├── AbsoluteFolder.kt │ │ │ │ └── RemoteFiles.kt │ │ │ ├── friendgroup/ │ │ │ │ ├── FriendGroup.kt │ │ │ │ └── FriendGroups.kt │ │ │ └── roaming/ │ │ │ ├── RoamingMessageFilter.kt │ │ │ ├── RoamingMessages.kt │ │ │ └── RoamingSupported.kt │ │ ├── data/ │ │ │ ├── FriendInfo.kt │ │ │ ├── GroupHonorType.kt │ │ │ ├── GroupInfo.kt │ │ │ ├── MemberInfo.kt │ │ │ ├── OnlineStatus.kt │ │ │ ├── Profile.kt │ │ │ ├── RequestEventData.kt │ │ │ ├── StrangerInfo.kt │ │ │ ├── UserInfo.kt │ │ │ └── UserProfile.kt │ │ ├── event/ │ │ │ ├── Event.kt │ │ │ ├── EventChannel.kt │ │ │ ├── EventChannelKotlinExtensions.kt │ │ │ ├── ExceptionInEventChannelFilterException.kt │ │ │ ├── Extensions.kt │ │ │ ├── GlobalEventChannel.kt │ │ │ ├── JvmMethodListeners.kt │ │ │ ├── Listener.kt │ │ │ ├── MessageSelectBuilderUnit.kt │ │ │ ├── MessageSubscribersBuilder.kt │ │ │ ├── deprecated.nextEvent.kt │ │ │ ├── deprecated.nextEventAsync.kt │ │ │ ├── deprecated.syncFromEvent.kt │ │ │ ├── events/ │ │ │ │ ├── EventCancelledException.kt │ │ │ │ ├── ImageUploadEvent.kt │ │ │ │ ├── MessageEvent.kt │ │ │ │ ├── MessagePostSendEvent.kt │ │ │ │ ├── MessagePreSendEvent.kt │ │ │ │ ├── MessageRecallEvent.kt │ │ │ │ ├── MessageSyncEvent.kt │ │ │ │ ├── NudgeEvent.kt │ │ │ │ ├── ShortVideoUploadEvent.kt │ │ │ │ ├── SignEvent.kt │ │ │ │ ├── bot.kt │ │ │ │ ├── friend.kt │ │ │ │ ├── group.kt │ │ │ │ ├── otherClient.kt │ │ │ │ ├── stranger.kt │ │ │ │ └── types.kt │ │ │ ├── select.kt │ │ │ └── subscribeMessages.kt │ │ ├── internal/ │ │ │ ├── event/ │ │ │ │ ├── JvmMethodListenersInternal.kt │ │ │ │ ├── VerboseEvent.kt │ │ │ │ └── messageSubscribersInternal.kt │ │ │ ├── message/ │ │ │ │ ├── AbstractPolymorphicSerializer.kt │ │ │ │ └── MessageSerializersImpl.kt │ │ │ ├── network/ │ │ │ │ └── Packet.kt │ │ │ └── utils/ │ │ │ ├── ExternalResourceImpls.kt │ │ │ ├── ExternalResourceLeakObserver.kt │ │ │ ├── LoggerAdapterImpls.kt │ │ │ ├── MarkedMiraiLogger.kt │ │ │ ├── Marker.kt │ │ │ └── StdoutLogger.kt │ │ ├── message/ │ │ │ ├── MessageReceipt.kt │ │ │ ├── MessageSerializers.kt │ │ │ ├── action/ │ │ │ │ ├── AsyncRecallResult.kt │ │ │ │ └── Nudge.kt │ │ │ ├── code/ │ │ │ │ ├── CodableMessage.kt │ │ │ │ ├── MiraiCode.kt │ │ │ │ └── internal/ │ │ │ │ └── impl.kt │ │ │ ├── data/ │ │ │ │ ├── At.kt │ │ │ │ ├── AtAll.kt │ │ │ │ ├── Audio.kt │ │ │ │ ├── CombinedMessage.kt │ │ │ │ ├── ConstrainSingle.kt │ │ │ │ ├── CustomMessage.kt │ │ │ │ ├── Deprecated.kt │ │ │ │ ├── Dice.kt │ │ │ │ ├── Face.kt │ │ │ │ ├── FileMessage.kt │ │ │ │ ├── FlashImage.kt │ │ │ │ ├── ForwardMessage.kt │ │ │ │ ├── HummerMessage.kt │ │ │ │ ├── Image.kt │ │ │ │ ├── MarketFace.kt │ │ │ │ ├── Message.kt │ │ │ │ ├── MessageChain.kt │ │ │ │ ├── MessageChainBuilder.kt │ │ │ │ ├── MessageKey.kt │ │ │ │ ├── MessageOrigin.kt │ │ │ │ ├── MessageSource.kt │ │ │ │ ├── MessageSourceBuilder.kt │ │ │ │ ├── MusicShare.kt │ │ │ │ ├── OfflineMessageSource.kt │ │ │ │ ├── OnlineMessageSource.kt │ │ │ │ ├── PlainText.kt │ │ │ │ ├── PokeMessage.kt │ │ │ │ ├── QuoteReply.kt │ │ │ │ ├── README.md │ │ │ │ ├── RichMessage.kt │ │ │ │ ├── RockPaperScissors.kt │ │ │ │ ├── ShortVideo.kt │ │ │ │ ├── ShowImageFlag.kt │ │ │ │ ├── SingleMessage.kt │ │ │ │ ├── SuperFace.kt │ │ │ │ ├── UnsupportedMessage.kt │ │ │ │ ├── VipFace.kt │ │ │ │ ├── Voice.kt │ │ │ │ ├── impl.kt │ │ │ │ └── visitor/ │ │ │ │ └── MessageVisitor.kt │ │ │ └── utils.kt │ │ ├── network/ │ │ │ ├── ForceOfflineException.kt │ │ │ └── LoginFailedException.kt │ │ ├── spi/ │ │ │ ├── AudioToSilkService.kt │ │ │ └── SPIServiceLoader.kt │ │ └── utils/ │ │ ├── AbstractBotConfiguration.kt │ │ ├── AbstractExternalResource.kt │ │ ├── Annotations.kt │ │ ├── BotConfiguration.kt │ │ ├── DeviceInfo.kt │ │ ├── DeviceInfoBuilder.kt │ │ ├── DeviceInfoManager.kt │ │ ├── DeviceInfoV1LegacySerializer.kt │ │ ├── ExternalResource.kt │ │ ├── FileCacheStrategy.kt │ │ ├── FileLogger.kt │ │ ├── LoggerAdapters.kt │ │ ├── LoginSolver.kt │ │ ├── MiraiLogger.kt │ │ ├── MiraiLoggerFactoryImplementationBridge.kt │ │ ├── MiraiUtilsLogger.kt │ │ ├── OverFileSizeMaxException.kt │ │ ├── ProgressionCallback.kt │ │ ├── RemoteFile.kt │ │ ├── SingleFileLogger.kt │ │ └── Streamable.kt │ ├── commonTest/ │ │ ├── kotlin/ │ │ │ ├── logging/ │ │ │ │ ├── AbstractLoggingTest.kt │ │ │ │ ├── Log4j2LoggingTest.kt │ │ │ │ └── LoggingCompatibilityTest.kt │ │ │ ├── message.data/ │ │ │ │ ├── CombinedMessageTest.kt │ │ │ │ ├── ConstrainSingleHelperTest.kt │ │ │ │ ├── ConstrainSingleTest.kt │ │ │ │ ├── ImageTest.kt │ │ │ │ ├── LinearMessageChainImplTest.kt │ │ │ │ ├── MessageChainBuilderTest.kt │ │ │ │ ├── MessageChainImmutableTest.kt │ │ │ │ ├── MessageChainImplTest.kt │ │ │ │ ├── MessageKeyTest.kt │ │ │ │ ├── MessageUtilsTest.kt │ │ │ │ ├── MessageVisitorTest.kt │ │ │ │ └── TestMessageChainDelegate.kt │ │ │ ├── package.kt │ │ │ ├── test/ │ │ │ │ └── TestDSL.kt │ │ │ └── utils/ │ │ │ ├── DeviceInfoTest.kt │ │ │ ├── JvmDeviceInfoTest.kt │ │ │ └── TimeTest.kt │ │ └── resources/ │ │ └── log4j.properties │ ├── jvmMain/ │ │ └── kotlin/ │ │ ├── package.kt │ │ └── utils/ │ │ ├── LoginSolver.TxCaptchaHelper.kt │ │ ├── LoginSolver.jvm.kt │ │ ├── PlatformLogger.jvm.kt │ │ └── SingleFileLogger.jvm.kt │ └── jvmTest/ │ └── kotlin/ │ ├── message/ │ │ └── data/ │ │ └── MessageChainImmutableTestJdk8.kt │ └── package.kt ├── mirai-core-mock/ │ ├── README.md │ ├── build.gradle.kts │ ├── src/ │ │ ├── MockActions.kt │ │ ├── MockBot.kt │ │ ├── MockBotDSL.kt │ │ ├── MockBotFactory.kt │ │ ├── contact/ │ │ │ ├── MockAnonymousMember.kt │ │ │ ├── MockContact.kt │ │ │ ├── MockContactOrBot.kt │ │ │ ├── MockFriend.kt │ │ │ ├── MockGroup.kt │ │ │ ├── MockGroupControlPane.kt │ │ │ ├── MockMember.kt │ │ │ ├── MockMsgSyncSupport.kt │ │ │ ├── MockNormalMember.kt │ │ │ ├── MockOtherClient.kt │ │ │ ├── MockStranger.kt │ │ │ ├── MockUser.kt │ │ │ ├── MockUserOrBot.kt │ │ │ ├── active/ │ │ │ │ ├── MockGroupActive.kt │ │ │ │ └── MockMemberActive.kt │ │ │ ├── announcement/ │ │ │ │ └── MockAnnouncements.kt │ │ │ └── essence/ │ │ │ └── MockEssences.kt │ │ ├── database/ │ │ │ └── MessageDatabase.kt │ │ ├── internal/ │ │ │ ├── MockBotFactoryImpl.kt │ │ │ ├── MockBotImpl.kt │ │ │ ├── MockMiraiImpl.kt │ │ │ ├── components/ │ │ │ │ └── MockEventDispatcherImpl.kt │ │ │ ├── contact/ │ │ │ │ ├── AbstractMockContact.kt │ │ │ │ ├── MockAnnouncementsImpl.kt │ │ │ │ ├── MockAnonymousMemberImpl.kt │ │ │ │ ├── MockFriendImpl.kt │ │ │ │ ├── MockGroupImpl.kt │ │ │ │ ├── MockNormalMemberImpl.kt │ │ │ │ ├── MockStrangerImpl.kt │ │ │ │ ├── active/ │ │ │ │ │ ├── MockGroupActive.kt │ │ │ │ │ └── MockMemberActiveImpl.kt │ │ │ │ ├── essence/ │ │ │ │ │ └── MockEssences.kt │ │ │ │ ├── friendfroup/ │ │ │ │ │ ├── MockFriendGroup.kt │ │ │ │ │ └── MockFriendGroups.kt │ │ │ │ ├── roaming/ │ │ │ │ │ └── MockRoamingMessages.kt │ │ │ │ └── util.kt │ │ │ ├── contactbase/ │ │ │ │ ├── ContactDatabase.kt │ │ │ │ └── ContactInfo.kt │ │ │ ├── db/ │ │ │ │ └── MsgDatabaseImpl.kt │ │ │ ├── msgsrc/ │ │ │ │ └── OnlineMsgSrc.kt │ │ │ ├── remotefile/ │ │ │ │ ├── absolutefile/ │ │ │ │ │ ├── MockAbsoluteFile.kt │ │ │ │ │ ├── MockAbsoluteFolder.kt │ │ │ │ │ └── MockRemoteFiles.kt │ │ │ │ └── remotefile/ │ │ │ │ └── MockRemoteFile.kt │ │ │ └── serverfs/ │ │ │ ├── MockServerFileDiskImpl.kt │ │ │ └── TmpResourceServerImpl.kt │ │ ├── package.kt │ │ ├── resserver/ │ │ │ ├── MockServerFileDisk.kt │ │ │ ├── MockServerFileSystem.kt │ │ │ ├── MockServerRemoteFile.kt │ │ │ └── TmpResourceServer.kt │ │ ├── userprofile/ │ │ │ ├── UserProfileService.kt │ │ │ └── contactinfos.kt │ │ └── utils/ │ │ ├── AvatarGenerator.kt │ │ ├── MemberInfo.kt │ │ ├── MockActionsScope.kt │ │ ├── MockConversions.kt │ │ ├── NameGenerator.kt │ │ ├── NudgeDsl.kt │ │ ├── event.kt │ │ ├── http.kt │ │ ├── image.kt │ │ └── mockdsl.kt │ └── test/ │ ├── AbsoluteFileTest.kt │ ├── DslTest.kt │ ├── FsServerTest.kt │ ├── ImageUploadTest.kt │ ├── MockBotTestBase.kt │ ├── MsgDbTest.kt │ ├── TestBase.kt │ ├── TxFsDiskTest.kt │ ├── mock/ │ │ ├── MessageSerializationTest.kt │ │ ├── MessagingTest.kt │ │ ├── MockBotBaseTest.kt │ │ ├── MockBotEventTest.kt │ │ ├── MockFriendGroupsTest.kt │ │ ├── MockFriendTest.kt │ │ ├── MockGroupTest.kt │ │ ├── MockMemberTest.kt │ │ └── MockStrangerTest.kt │ └── package.kt ├── mirai-core-utils/ │ ├── README.md │ ├── build.gradle.kts │ └── src/ │ ├── androidInstrumentedTest/ │ │ └── kotlin/ │ │ └── AndroidUnwrapTest.kt │ ├── androidMain/ │ │ ├── AndroidManifest.xml │ │ └── kotlin/ │ │ └── Actuals.kt │ ├── commonMain/ │ │ └── kotlin/ │ │ ├── Annotations.kt │ │ ├── Arrays.kt │ │ ├── AtomicInteger.kt │ │ ├── Base64.kt │ │ ├── ByteArrayOp.kt │ │ ├── ByteArrayPool.kt │ │ ├── Bytes.kt │ │ ├── CheckableResult.kt │ │ ├── Clock.kt │ │ ├── Closeable.kt │ │ ├── CollectionDiff.kt │ │ ├── Collections.kt │ │ ├── Conversions.kt │ │ ├── CoroutineUtils.kt │ │ ├── Either.kt │ │ ├── ExceptionCollector.kt │ │ ├── File.kt │ │ ├── Files.kt │ │ ├── HtmlEntity.kt │ │ ├── IO.kt │ │ ├── JsonStruct.kt │ │ ├── LateinitMutableProperty.kt │ │ ├── Numbers.kt │ │ ├── RandomUtils.kt │ │ ├── Resources.kt │ │ ├── ResultExtensions.kt │ │ ├── SecretsProtection.kt │ │ ├── Serialization.kt │ │ ├── Services.kt │ │ ├── SizedCache.kt │ │ ├── StandardUtils.kt │ │ ├── Strings.kt │ │ ├── StructureToStringTransformer.kt │ │ ├── Symbol.kt │ │ ├── TimeUtils.kt │ │ ├── TlvMap.kt │ │ ├── TypeSafeMap.kt │ │ ├── UnsafeMutableNonNullProperty.kt │ │ ├── UtilsLogger.kt │ │ ├── annotations/ │ │ │ └── Range.kt │ │ ├── channels/ │ │ │ ├── ChannelState.kt │ │ │ ├── IllegalChannelStateException.kt │ │ │ ├── OnDemandChannelImpl.kt │ │ │ ├── OnDemandSendChannel.kt │ │ │ └── ProducerFailureException.kt │ │ ├── package.kt │ │ └── systemProp.kt │ ├── commonTest/ │ │ └── kotlin/ │ │ └── net/ │ │ └── mamoe/ │ │ └── mirai/ │ │ └── utils/ │ │ ├── CommonByteArrayOpTest.kt │ │ ├── EitherTest.kt │ │ ├── ExceptionCollectorTest.kt │ │ ├── ExternalImageTest.kt │ │ ├── HexToBytesTest.kt │ │ ├── HtmlEscapeTest.kt │ │ ├── ImageIdConversionTest.kt │ │ ├── LateinitMutablePropertyTest.kt │ │ ├── ResourceAccessLockTest.kt │ │ ├── SizedCacheTest.kt │ │ ├── TlvMapTest.kt │ │ ├── TrySafelyTest.kt │ │ ├── TypeSafeMapTest.kt │ │ ├── channels/ │ │ │ └── OnDemandChannelTest.kt │ │ └── testFramework/ │ │ └── AssertNoCoroutineSuspension.kt │ ├── jvmBaseMain/ │ │ └── kotlin/ │ │ ├── ByteArrayOp.kt │ │ ├── Clock.kt │ │ ├── Closeable.kt │ │ ├── Collections.kt │ │ ├── ConcurrentLinkedQueue.kt │ │ ├── CoroutineUtils.kt │ │ ├── ExceptionCollector.kt │ │ ├── Files.kt │ │ ├── IO.jvm.shared.kt │ │ ├── JvmNioBuffer.kt │ │ ├── MiraiFile.kt │ │ ├── Reflections.kt │ │ ├── Resources.kt │ │ ├── SecretsProtection.kt │ │ ├── Serialization.kt │ │ ├── Services.kt │ │ ├── StandardUtils.kt │ │ ├── Streams.kt │ │ ├── StructureToStringTransformer.kt │ │ ├── StructureToStringTransformerLegacy.kt │ │ ├── ThreadLocal.kt │ │ ├── TimeUtils.kt │ │ ├── WeakRef.kt │ │ ├── annotations/ │ │ │ └── Range.kt │ │ ├── package.kt │ │ └── systemProp.kt │ ├── jvmBaseTest/ │ │ └── kotlin/ │ │ ├── ByteArrayOpTest.kt │ │ ├── KotlinFlowToJdkStreamTest.kt │ │ ├── LateinitMutablePropertyTestJvm.kt │ │ └── package.kt │ ├── jvmMain/ │ │ └── kotlin/ │ │ ├── Actuals.kt │ │ └── IO.jvm.kt │ └── jvmTest/ │ └── kotlin/ │ ├── AndroidUnwrapTest.kt │ └── SecretsProtectionTest.kt ├── mirai-deps-test/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── gradle.properties │ └── test/ │ ├── AbstractTest.kt │ ├── CoreDependencyResolutionTest.kt │ ├── CoreShadowRelocationTest.kt │ └── package.kt ├── mirai-dokka/ │ ├── .gitignore │ ├── README.md │ ├── build.gradle.kts │ ├── frontend/ │ │ └── ext.js │ └── src/ │ ├── BuildVersionList.kt │ ├── DeployToGitHub.kt │ ├── Prepare.kt │ └── system.kt └── settings.gradle.kts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*] charset = utf-8 end_of_line = lf indent_size = 4 indent_style = space insert_final_newline = false max_line_length = 120 tab_width = 4 ij_continuation_indent_size = 8 ij_formatter_off_tag = @formatter:off ij_formatter_on_tag = @formatter:on ij_formatter_tags_enabled = true ij_smart_tabs = false ij_visual_guides = none ij_wrap_on_typing = false [*.java] ij_java_align_consecutive_assignments = false ij_java_align_consecutive_variable_declarations = false ij_java_align_group_field_declarations = false ij_java_align_multiline_annotation_parameters = false ij_java_align_multiline_array_initializer_expression = false ij_java_align_multiline_assignment = false ij_java_align_multiline_binary_operation = false ij_java_align_multiline_chained_methods = false ij_java_align_multiline_extends_list = false ij_java_align_multiline_for = true ij_java_align_multiline_method_parentheses = false ij_java_align_multiline_parameters = true ij_java_align_multiline_parameters_in_calls = false ij_java_align_multiline_parenthesized_expression = false ij_java_align_multiline_records = true ij_java_align_multiline_resources = true ij_java_align_multiline_ternary_operation = false ij_java_align_multiline_text_blocks = false ij_java_align_multiline_throws_list = false ij_java_align_subsequent_simple_methods = false ij_java_align_throws_keyword = false ij_java_annotation_parameter_wrap = off ij_java_array_initializer_new_line_after_left_brace = false ij_java_array_initializer_right_brace_on_new_line = false ij_java_array_initializer_wrap = off ij_java_assert_statement_colon_on_next_line = false ij_java_assert_statement_wrap = off ij_java_assignment_wrap = off ij_java_binary_operation_sign_on_next_line = false ij_java_binary_operation_wrap = off ij_java_blank_lines_after_anonymous_class_header = 0 ij_java_blank_lines_after_class_header = 0 ij_java_blank_lines_after_imports = 1 ij_java_blank_lines_after_package = 1 ij_java_blank_lines_around_class = 1 ij_java_blank_lines_around_field = 0 ij_java_blank_lines_around_field_in_interface = 0 ij_java_blank_lines_around_initializer = 1 ij_java_blank_lines_around_method = 1 ij_java_blank_lines_around_method_in_interface = 1 ij_java_blank_lines_before_class_end = 0 ij_java_blank_lines_before_imports = 1 ij_java_blank_lines_before_method_body = 0 ij_java_blank_lines_before_package = 0 ij_java_block_brace_style = end_of_line ij_java_block_comment_at_first_column = true ij_java_builder_methods = none ij_java_call_parameters_new_line_after_left_paren = false ij_java_call_parameters_right_paren_on_new_line = false ij_java_call_parameters_wrap = off ij_java_case_statement_on_separate_line = true ij_java_catch_on_new_line = false ij_java_class_annotation_wrap = split_into_lines ij_java_class_brace_style = end_of_line ij_java_class_count_to_use_import_on_demand = 5 ij_java_class_names_in_javadoc = 1 ij_java_do_not_indent_top_level_class_members = false ij_java_do_not_wrap_after_single_annotation = false ij_java_do_while_brace_force = never ij_java_doc_add_blank_line_after_description = true ij_java_doc_add_blank_line_after_param_comments = false ij_java_doc_add_blank_line_after_return = false ij_java_doc_add_p_tag_on_empty_lines = true ij_java_doc_align_exception_comments = true ij_java_doc_align_param_comments = true ij_java_doc_do_not_wrap_if_one_line = false ij_java_doc_enable_formatting = true ij_java_doc_enable_leading_asterisks = true ij_java_doc_indent_on_continuation = false ij_java_doc_keep_empty_lines = true ij_java_doc_keep_empty_parameter_tag = true ij_java_doc_keep_empty_return_tag = true ij_java_doc_keep_empty_throws_tag = true ij_java_doc_keep_invalid_tags = true ij_java_doc_param_description_on_new_line = false ij_java_doc_preserve_line_breaks = false ij_java_doc_use_throws_not_exception_tag = true ij_java_else_on_new_line = false ij_java_entity_dd_suffix = EJB ij_java_entity_eb_suffix = Bean ij_java_entity_hi_suffix = Home ij_java_entity_lhi_prefix = Local ij_java_entity_lhi_suffix = Home ij_java_entity_li_prefix = Local ij_java_entity_pk_class = java.lang.String ij_java_entity_vo_suffix = VO ij_java_enum_constants_wrap = off ij_java_extends_keyword_wrap = off ij_java_extends_list_wrap = off ij_java_field_annotation_wrap = split_into_lines ij_java_finally_on_new_line = false ij_java_for_brace_force = never ij_java_for_statement_new_line_after_left_paren = false ij_java_for_statement_right_paren_on_new_line = false ij_java_for_statement_wrap = off ij_java_generate_final_locals = false ij_java_generate_final_parameters = false ij_java_if_brace_force = never ij_java_imports_layout = *, |, javax.**, java.**, |, $* ij_java_indent_case_from_switch = true ij_java_insert_inner_class_imports = false ij_java_insert_override_annotation = true ij_java_keep_blank_lines_before_right_brace = 2 ij_java_keep_blank_lines_between_package_declaration_and_header = 2 ij_java_keep_blank_lines_in_code = 2 ij_java_keep_blank_lines_in_declarations = 2 ij_java_keep_builder_methods_indents = false ij_java_keep_control_statement_in_one_line = true ij_java_keep_first_column_comment = true ij_java_keep_indents_on_empty_lines = false ij_java_keep_line_breaks = true ij_java_keep_multiple_expressions_in_one_line = false ij_java_keep_simple_blocks_in_one_line = false ij_java_keep_simple_classes_in_one_line = false ij_java_keep_simple_lambdas_in_one_line = false ij_java_keep_simple_methods_in_one_line = false ij_java_label_indent_absolute = false ij_java_label_indent_size = 0 ij_java_lambda_brace_style = end_of_line ij_java_layout_static_imports_separately = true ij_java_line_comment_add_space = false ij_java_line_comment_at_first_column = true ij_java_message_dd_suffix = EJB ij_java_message_eb_suffix = Bean ij_java_method_annotation_wrap = split_into_lines ij_java_method_brace_style = end_of_line ij_java_method_call_chain_wrap = off ij_java_method_parameters_new_line_after_left_paren = false ij_java_method_parameters_right_paren_on_new_line = false ij_java_method_parameters_wrap = off ij_java_modifier_list_wrap = false ij_java_names_count_to_use_import_on_demand = 3 ij_java_new_line_after_lparen_in_record_header = false ij_java_packages_to_use_import_on_demand = java.awt.*, javax.swing.* ij_java_parameter_annotation_wrap = off ij_java_parentheses_expression_new_line_after_left_paren = false ij_java_parentheses_expression_right_paren_on_new_line = false ij_java_place_assignment_sign_on_next_line = false ij_java_prefer_longer_names = true ij_java_prefer_parameters_wrap = false ij_java_record_components_wrap = normal ij_java_repeat_synchronized = true ij_java_replace_instanceof_and_cast = false ij_java_replace_null_check = true ij_java_replace_sum_lambda_with_method_ref = true ij_java_resource_list_new_line_after_left_paren = false ij_java_resource_list_right_paren_on_new_line = false ij_java_resource_list_wrap = off ij_java_rparen_on_new_line_in_record_header = false ij_java_session_dd_suffix = EJB ij_java_session_eb_suffix = Bean ij_java_session_hi_suffix = Home ij_java_session_lhi_prefix = Local ij_java_session_lhi_suffix = Home ij_java_session_li_prefix = Local ij_java_session_si_suffix = Service ij_java_space_after_closing_angle_bracket_in_type_argument = false ij_java_space_after_colon = true ij_java_space_after_comma = true ij_java_space_after_comma_in_type_arguments = true ij_java_space_after_for_semicolon = true ij_java_space_after_quest = true ij_java_space_after_type_cast = true ij_java_space_before_annotation_array_initializer_left_brace = false ij_java_space_before_annotation_parameter_list = false ij_java_space_before_array_initializer_left_brace = false ij_java_space_before_catch_keyword = true ij_java_space_before_catch_left_brace = true ij_java_space_before_catch_parentheses = true ij_java_space_before_class_left_brace = true ij_java_space_before_colon = true ij_java_space_before_colon_in_foreach = true ij_java_space_before_comma = false ij_java_space_before_do_left_brace = true ij_java_space_before_else_keyword = true ij_java_space_before_else_left_brace = true ij_java_space_before_finally_keyword = true ij_java_space_before_finally_left_brace = true ij_java_space_before_for_left_brace = true ij_java_space_before_for_parentheses = true ij_java_space_before_for_semicolon = false ij_java_space_before_if_left_brace = true ij_java_space_before_if_parentheses = true ij_java_space_before_method_call_parentheses = false ij_java_space_before_method_left_brace = true ij_java_space_before_method_parentheses = false ij_java_space_before_opening_angle_bracket_in_type_parameter = false ij_java_space_before_quest = true ij_java_space_before_switch_left_brace = true ij_java_space_before_switch_parentheses = true ij_java_space_before_synchronized_left_brace = true ij_java_space_before_synchronized_parentheses = true ij_java_space_before_try_left_brace = true ij_java_space_before_try_parentheses = true ij_java_space_before_type_parameter_list = false ij_java_space_before_while_keyword = true ij_java_space_before_while_left_brace = true ij_java_space_before_while_parentheses = true ij_java_space_inside_one_line_enum_braces = false ij_java_space_within_empty_array_initializer_braces = false ij_java_space_within_empty_method_call_parentheses = false ij_java_space_within_empty_method_parentheses = false ij_java_spaces_around_additive_operators = true ij_java_spaces_around_assignment_operators = true ij_java_spaces_around_bitwise_operators = true ij_java_spaces_around_equality_operators = true ij_java_spaces_around_lambda_arrow = true ij_java_spaces_around_logical_operators = true ij_java_spaces_around_method_ref_dbl_colon = false ij_java_spaces_around_multiplicative_operators = true ij_java_spaces_around_relational_operators = true ij_java_spaces_around_shift_operators = true ij_java_spaces_around_type_bounds_in_type_parameters = true ij_java_spaces_around_unary_operator = false ij_java_spaces_within_angle_brackets = false ij_java_spaces_within_annotation_parentheses = false ij_java_spaces_within_array_initializer_braces = false ij_java_spaces_within_braces = false ij_java_spaces_within_brackets = false ij_java_spaces_within_cast_parentheses = false ij_java_spaces_within_catch_parentheses = false ij_java_spaces_within_for_parentheses = false ij_java_spaces_within_if_parentheses = false ij_java_spaces_within_method_call_parentheses = false ij_java_spaces_within_method_parentheses = false ij_java_spaces_within_parentheses = false ij_java_spaces_within_record_header = false ij_java_spaces_within_switch_parentheses = false ij_java_spaces_within_synchronized_parentheses = false ij_java_spaces_within_try_parentheses = false ij_java_spaces_within_while_parentheses = false ij_java_special_else_if_treatment = true ij_java_subclass_name_suffix = Impl ij_java_ternary_operation_signs_on_next_line = false ij_java_ternary_operation_wrap = off ij_java_test_name_suffix = Test ij_java_throws_keyword_wrap = off ij_java_throws_list_wrap = off ij_java_use_external_annotations = false ij_java_use_fq_class_names = false ij_java_use_relative_indents = false ij_java_use_single_class_imports = true ij_java_variable_annotation_wrap = off ij_java_visibility = public ij_java_while_brace_force = never ij_java_while_on_new_line = false ij_java_wrap_comments = false ij_java_wrap_first_method_in_call_chain = false ij_java_wrap_long_lines = false [.editorconfig] ij_editorconfig_align_group_field_declarations = false ij_editorconfig_space_after_colon = false ij_editorconfig_space_after_comma = true ij_editorconfig_space_before_colon = false ij_editorconfig_space_before_comma = false ij_editorconfig_spaces_around_assignment_operators = true [{*.ant,*.fxml,*.jhm,*.jnlp,*.jrxml,*.pom,*.qrc,*.rng,*.tld,*.wadl,*.wsdd,*.wsdl,*.xjb,*.xml,*.xsd,*.xsl,*.xslt,*.xul}] ij_xml_align_attributes = true ij_xml_align_text = false ij_xml_attribute_wrap = normal ij_xml_block_comment_at_first_column = true ij_xml_keep_blank_lines = 2 ij_xml_keep_indents_on_empty_lines = false ij_xml_keep_line_breaks = true ij_xml_keep_line_breaks_in_text = true ij_xml_keep_whitespaces = false ij_xml_keep_whitespaces_around_cdata = preserve ij_xml_keep_whitespaces_inside_cdata = false ij_xml_line_comment_at_first_column = true ij_xml_space_after_tag_name = false ij_xml_space_around_equals_in_attribute = false ij_xml_space_inside_empty_tag = false ij_xml_text_wrap = normal ij_xml_use_custom_settings = false [{*.bash,*.sh,*.zsh}] indent_size = 2 tab_width = 2 ij_shell_binary_ops_start_line = false ij_shell_keep_column_alignment_padding = false ij_shell_minify_program = false ij_shell_redirect_followed_by_space = false ij_shell_switch_cases_indented = false ij_shell_use_unix_line_separator = true [{*.kt,*.kts}] ij_kotlin_align_in_columns_case_branch = false ij_kotlin_align_multiline_binary_operation = false ij_kotlin_align_multiline_extends_list = false ij_kotlin_align_multiline_method_parentheses = false ij_kotlin_align_multiline_parameters = true ij_kotlin_align_multiline_parameters_in_calls = false ij_kotlin_allow_trailing_comma = true ij_kotlin_allow_trailing_comma_on_call_site = false ij_kotlin_assignment_wrap = normal ij_kotlin_blank_lines_after_class_header = 0 ij_kotlin_blank_lines_around_block_when_branches = 0 ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1 ij_kotlin_block_comment_at_first_column = true ij_kotlin_call_parameters_new_line_after_left_paren = true ij_kotlin_call_parameters_right_paren_on_new_line = true ij_kotlin_call_parameters_wrap = on_every_item ij_kotlin_catch_on_new_line = false ij_kotlin_class_annotation_wrap = split_into_lines ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL ij_kotlin_continuation_indent_for_chained_calls = false ij_kotlin_continuation_indent_for_expression_bodies = false ij_kotlin_continuation_indent_in_argument_lists = false ij_kotlin_continuation_indent_in_elvis = false ij_kotlin_continuation_indent_in_if_conditions = false ij_kotlin_continuation_indent_in_parameter_lists = false ij_kotlin_continuation_indent_in_supertype_lists = false ij_kotlin_else_on_new_line = false ij_kotlin_enum_constants_wrap = off ij_kotlin_extends_list_wrap = normal ij_kotlin_field_annotation_wrap = split_into_lines ij_kotlin_finally_on_new_line = false ij_kotlin_if_rparen_on_new_line = true ij_kotlin_import_nested_classes = false ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^ ij_kotlin_insert_whitespaces_in_simple_one_line_method = true ij_kotlin_keep_blank_lines_before_right_brace = 2 ij_kotlin_keep_blank_lines_in_code = 2 ij_kotlin_keep_blank_lines_in_declarations = 2 ij_kotlin_keep_first_column_comment = true ij_kotlin_keep_indents_on_empty_lines = false ij_kotlin_keep_line_breaks = true ij_kotlin_lbrace_on_next_line = false ij_kotlin_line_comment_add_space = false ij_kotlin_line_comment_at_first_column = true ij_kotlin_method_annotation_wrap = split_into_lines ij_kotlin_method_call_chain_wrap = normal ij_kotlin_method_parameters_new_line_after_left_paren = true ij_kotlin_method_parameters_right_paren_on_new_line = true ij_kotlin_method_parameters_wrap = on_every_item ij_kotlin_name_count_to_use_star_import = 5 ij_kotlin_name_count_to_use_star_import_for_members = 3 ij_kotlin_packages_to_use_import_on_demand = java.util.*, kotlinx.android.synthetic.**, io.ktor.** ij_kotlin_parameter_annotation_wrap = off ij_kotlin_space_after_comma = true ij_kotlin_space_after_extend_colon = true ij_kotlin_space_after_type_colon = true ij_kotlin_space_before_catch_parentheses = true ij_kotlin_space_before_comma = false ij_kotlin_space_before_extend_colon = true ij_kotlin_space_before_for_parentheses = true ij_kotlin_space_before_if_parentheses = true ij_kotlin_space_before_lambda_arrow = true ij_kotlin_space_before_type_colon = false ij_kotlin_space_before_when_parentheses = true ij_kotlin_space_before_while_parentheses = true ij_kotlin_spaces_around_additive_operators = true ij_kotlin_spaces_around_assignment_operators = true ij_kotlin_spaces_around_equality_operators = true ij_kotlin_spaces_around_function_type_arrow = true ij_kotlin_spaces_around_logical_operators = true ij_kotlin_spaces_around_multiplicative_operators = true ij_kotlin_spaces_around_range = false ij_kotlin_spaces_around_relational_operators = true ij_kotlin_spaces_around_unary_operator = false ij_kotlin_spaces_around_when_arrow = true ij_kotlin_variable_annotation_wrap = off ij_kotlin_while_on_new_line = false ij_kotlin_wrap_elvis_expressions = 1 ij_kotlin_wrap_expression_body_functions = 1 ij_kotlin_wrap_first_method_in_call_chain = false [{*.cjs,*.js}] ij_continuation_indent_size = 4 ij_javascript_align_imports = false ij_javascript_align_multiline_array_initializer_expression = false ij_javascript_align_multiline_binary_operation = false ij_javascript_align_multiline_chained_methods = false ij_javascript_align_multiline_extends_list = false ij_javascript_align_multiline_for = true ij_javascript_align_multiline_parameters = true ij_javascript_align_multiline_parameters_in_calls = false ij_javascript_align_multiline_ternary_operation = false ij_javascript_align_object_properties = 0 ij_javascript_align_union_types = false ij_javascript_align_var_statements = 0 ij_javascript_array_initializer_new_line_after_left_brace = false ij_javascript_array_initializer_right_brace_on_new_line = false ij_javascript_array_initializer_wrap = off ij_javascript_assignment_wrap = off ij_javascript_binary_operation_sign_on_next_line = false ij_javascript_binary_operation_wrap = off ij_javascript_blacklist_imports = rxjs/Rx, node_modules/**, **/node_modules/**, @angular/material, @angular/material/typings/** ij_javascript_blank_lines_after_imports = 1 ij_javascript_blank_lines_around_class = 1 ij_javascript_blank_lines_around_field = 0 ij_javascript_blank_lines_around_function = 1 ij_javascript_blank_lines_around_method = 1 ij_javascript_block_brace_style = end_of_line ij_javascript_call_parameters_new_line_after_left_paren = false ij_javascript_call_parameters_right_paren_on_new_line = false ij_javascript_call_parameters_wrap = off ij_javascript_catch_on_new_line = false ij_javascript_chained_call_dot_on_new_line = true ij_javascript_class_brace_style = end_of_line ij_javascript_comma_on_new_line = false ij_javascript_do_while_brace_force = never ij_javascript_else_on_new_line = false ij_javascript_enforce_trailing_comma = keep ij_javascript_extends_keyword_wrap = off ij_javascript_extends_list_wrap = off ij_javascript_field_prefix = _ ij_javascript_file_name_style = relaxed ij_javascript_finally_on_new_line = false ij_javascript_for_brace_force = never ij_javascript_for_statement_new_line_after_left_paren = false ij_javascript_for_statement_right_paren_on_new_line = false ij_javascript_for_statement_wrap = off ij_javascript_force_quote_style = false ij_javascript_force_semicolon_style = false ij_javascript_function_expression_brace_style = end_of_line ij_javascript_if_brace_force = never ij_javascript_import_merge_members = global ij_javascript_import_prefer_absolute_path = global ij_javascript_import_sort_members = true ij_javascript_import_sort_module_name = false ij_javascript_import_use_node_resolution = true ij_javascript_imports_wrap = on_every_item ij_javascript_indent_case_from_switch = true ij_javascript_indent_chained_calls = true ij_javascript_indent_package_children = 0 ij_javascript_jsx_attribute_value = braces ij_javascript_keep_blank_lines_in_code = 2 ij_javascript_keep_first_column_comment = true ij_javascript_keep_indents_on_empty_lines = false ij_javascript_keep_line_breaks = true ij_javascript_keep_simple_blocks_in_one_line = false ij_javascript_keep_simple_methods_in_one_line = false ij_javascript_line_comment_add_space = true ij_javascript_line_comment_at_first_column = false ij_javascript_method_brace_style = end_of_line ij_javascript_method_call_chain_wrap = off ij_javascript_method_parameters_new_line_after_left_paren = false ij_javascript_method_parameters_right_paren_on_new_line = false ij_javascript_method_parameters_wrap = off ij_javascript_object_literal_wrap = on_every_item ij_javascript_parentheses_expression_new_line_after_left_paren = false ij_javascript_parentheses_expression_right_paren_on_new_line = false ij_javascript_place_assignment_sign_on_next_line = false ij_javascript_prefer_as_type_cast = false ij_javascript_prefer_explicit_types_function_expression_returns = false ij_javascript_prefer_explicit_types_function_returns = false ij_javascript_prefer_explicit_types_vars_fields = false ij_javascript_prefer_parameters_wrap = false ij_javascript_reformat_c_style_comments = false ij_javascript_space_after_colon = true ij_javascript_space_after_comma = true ij_javascript_space_after_dots_in_rest_parameter = false ij_javascript_space_after_generator_mult = true ij_javascript_space_after_property_colon = true ij_javascript_space_after_quest = true ij_javascript_space_after_type_colon = true ij_javascript_space_after_unary_not = false ij_javascript_space_before_async_arrow_lparen = true ij_javascript_space_before_catch_keyword = true ij_javascript_space_before_catch_left_brace = true ij_javascript_space_before_catch_parentheses = true ij_javascript_space_before_class_lbrace = true ij_javascript_space_before_class_left_brace = true ij_javascript_space_before_colon = true ij_javascript_space_before_comma = false ij_javascript_space_before_do_left_brace = true ij_javascript_space_before_else_keyword = true ij_javascript_space_before_else_left_brace = true ij_javascript_space_before_finally_keyword = true ij_javascript_space_before_finally_left_brace = true ij_javascript_space_before_for_left_brace = true ij_javascript_space_before_for_parentheses = true ij_javascript_space_before_for_semicolon = false ij_javascript_space_before_function_left_parenth = true ij_javascript_space_before_generator_mult = false ij_javascript_space_before_if_left_brace = true ij_javascript_space_before_if_parentheses = true ij_javascript_space_before_method_call_parentheses = false ij_javascript_space_before_method_left_brace = true ij_javascript_space_before_method_parentheses = false ij_javascript_space_before_property_colon = false ij_javascript_space_before_quest = true ij_javascript_space_before_switch_left_brace = true ij_javascript_space_before_switch_parentheses = true ij_javascript_space_before_try_left_brace = true ij_javascript_space_before_type_colon = false ij_javascript_space_before_unary_not = false ij_javascript_space_before_while_keyword = true ij_javascript_space_before_while_left_brace = true ij_javascript_space_before_while_parentheses = true ij_javascript_spaces_around_additive_operators = true ij_javascript_spaces_around_arrow_function_operator = true ij_javascript_spaces_around_assignment_operators = true ij_javascript_spaces_around_bitwise_operators = true ij_javascript_spaces_around_equality_operators = true ij_javascript_spaces_around_logical_operators = true ij_javascript_spaces_around_multiplicative_operators = true ij_javascript_spaces_around_relational_operators = true ij_javascript_spaces_around_shift_operators = true ij_javascript_spaces_around_unary_operator = false ij_javascript_spaces_within_array_initializer_brackets = false ij_javascript_spaces_within_brackets = false ij_javascript_spaces_within_catch_parentheses = false ij_javascript_spaces_within_for_parentheses = false ij_javascript_spaces_within_if_parentheses = false ij_javascript_spaces_within_imports = false ij_javascript_spaces_within_interpolation_expressions = false ij_javascript_spaces_within_method_call_parentheses = false ij_javascript_spaces_within_method_parentheses = false ij_javascript_spaces_within_object_literal_braces = false ij_javascript_spaces_within_object_type_braces = true ij_javascript_spaces_within_parentheses = false ij_javascript_spaces_within_switch_parentheses = false ij_javascript_spaces_within_type_assertion = false ij_javascript_spaces_within_union_types = true ij_javascript_spaces_within_while_parentheses = false ij_javascript_special_else_if_treatment = true ij_javascript_ternary_operation_signs_on_next_line = false ij_javascript_ternary_operation_wrap = off ij_javascript_union_types_wrap = on_every_item ij_javascript_use_chained_calls_group_indents = false ij_javascript_use_double_quotes = true ij_javascript_use_path_mapping = always ij_javascript_use_public_modifier = false ij_javascript_use_semicolon_after_statement = true ij_javascript_var_declaration_wrap = normal ij_javascript_while_brace_force = never ij_javascript_while_on_new_line = false ij_javascript_wrap_comments = false [{*.ft,*.vm,*.vsl}] ij_vtl_keep_indents_on_empty_lines = false [{*.gant,*.gradle,*.groovy,*.gson,*.gy}] ij_groovy_align_group_field_declarations = false ij_groovy_align_multiline_array_initializer_expression = false ij_groovy_align_multiline_assignment = false ij_groovy_align_multiline_binary_operation = false ij_groovy_align_multiline_chained_methods = false ij_groovy_align_multiline_extends_list = false ij_groovy_align_multiline_for = true ij_groovy_align_multiline_list_or_map = true ij_groovy_align_multiline_method_parentheses = false ij_groovy_align_multiline_parameters = true ij_groovy_align_multiline_parameters_in_calls = false ij_groovy_align_multiline_resources = true ij_groovy_align_multiline_ternary_operation = false ij_groovy_align_multiline_throws_list = false ij_groovy_align_named_args_in_map = true ij_groovy_align_throws_keyword = false ij_groovy_array_initializer_new_line_after_left_brace = false ij_groovy_array_initializer_right_brace_on_new_line = false ij_groovy_array_initializer_wrap = off ij_groovy_assert_statement_wrap = off ij_groovy_assignment_wrap = off ij_groovy_binary_operation_wrap = off ij_groovy_blank_lines_after_class_header = 0 ij_groovy_blank_lines_after_imports = 1 ij_groovy_blank_lines_after_package = 1 ij_groovy_blank_lines_around_class = 1 ij_groovy_blank_lines_around_field = 0 ij_groovy_blank_lines_around_field_in_interface = 0 ij_groovy_blank_lines_around_method = 1 ij_groovy_blank_lines_around_method_in_interface = 1 ij_groovy_blank_lines_before_imports = 1 ij_groovy_blank_lines_before_method_body = 0 ij_groovy_blank_lines_before_package = 0 ij_groovy_block_brace_style = end_of_line ij_groovy_block_comment_at_first_column = true ij_groovy_call_parameters_new_line_after_left_paren = false ij_groovy_call_parameters_right_paren_on_new_line = false ij_groovy_call_parameters_wrap = off ij_groovy_catch_on_new_line = false ij_groovy_class_annotation_wrap = split_into_lines ij_groovy_class_brace_style = end_of_line ij_groovy_class_count_to_use_import_on_demand = 5 ij_groovy_do_while_brace_force = never ij_groovy_else_on_new_line = false ij_groovy_enum_constants_wrap = off ij_groovy_extends_keyword_wrap = off ij_groovy_extends_list_wrap = off ij_groovy_field_annotation_wrap = split_into_lines ij_groovy_finally_on_new_line = false ij_groovy_for_brace_force = never ij_groovy_for_statement_new_line_after_left_paren = false ij_groovy_for_statement_right_paren_on_new_line = false ij_groovy_for_statement_wrap = off ij_groovy_if_brace_force = never ij_groovy_import_annotation_wrap = 2 ij_groovy_imports_layout = *, |, javax.**, java.**, |, $* ij_groovy_indent_case_from_switch = true ij_groovy_indent_label_blocks = true ij_groovy_insert_inner_class_imports = false ij_groovy_keep_blank_lines_before_right_brace = 2 ij_groovy_keep_blank_lines_in_code = 2 ij_groovy_keep_blank_lines_in_declarations = 2 ij_groovy_keep_control_statement_in_one_line = true ij_groovy_keep_first_column_comment = true ij_groovy_keep_indents_on_empty_lines = false ij_groovy_keep_line_breaks = true ij_groovy_keep_multiple_expressions_in_one_line = false ij_groovy_keep_simple_blocks_in_one_line = false ij_groovy_keep_simple_classes_in_one_line = true ij_groovy_keep_simple_lambdas_in_one_line = true ij_groovy_keep_simple_methods_in_one_line = true ij_groovy_label_indent_absolute = false ij_groovy_label_indent_size = 0 ij_groovy_lambda_brace_style = end_of_line ij_groovy_layout_static_imports_separately = true ij_groovy_line_comment_add_space = false ij_groovy_line_comment_at_first_column = true ij_groovy_method_annotation_wrap = split_into_lines ij_groovy_method_brace_style = end_of_line ij_groovy_method_call_chain_wrap = off ij_groovy_method_parameters_new_line_after_left_paren = false ij_groovy_method_parameters_right_paren_on_new_line = false ij_groovy_method_parameters_wrap = off ij_groovy_modifier_list_wrap = false ij_groovy_names_count_to_use_import_on_demand = 3 ij_groovy_parameter_annotation_wrap = off ij_groovy_parentheses_expression_new_line_after_left_paren = false ij_groovy_parentheses_expression_right_paren_on_new_line = false ij_groovy_prefer_parameters_wrap = false ij_groovy_resource_list_new_line_after_left_paren = false ij_groovy_resource_list_right_paren_on_new_line = false ij_groovy_resource_list_wrap = off ij_groovy_space_after_assert_separator = true ij_groovy_space_after_colon = true ij_groovy_space_after_comma = true ij_groovy_space_after_comma_in_type_arguments = true ij_groovy_space_after_for_semicolon = true ij_groovy_space_after_quest = true ij_groovy_space_after_type_cast = true ij_groovy_space_before_annotation_parameter_list = false ij_groovy_space_before_array_initializer_left_brace = false ij_groovy_space_before_assert_separator = false ij_groovy_space_before_catch_keyword = true ij_groovy_space_before_catch_left_brace = true ij_groovy_space_before_catch_parentheses = true ij_groovy_space_before_class_left_brace = true ij_groovy_space_before_closure_left_brace = true ij_groovy_space_before_colon = true ij_groovy_space_before_comma = false ij_groovy_space_before_do_left_brace = true ij_groovy_space_before_else_keyword = true ij_groovy_space_before_else_left_brace = true ij_groovy_space_before_finally_keyword = true ij_groovy_space_before_finally_left_brace = true ij_groovy_space_before_for_left_brace = true ij_groovy_space_before_for_parentheses = true ij_groovy_space_before_for_semicolon = false ij_groovy_space_before_if_left_brace = true ij_groovy_space_before_if_parentheses = true ij_groovy_space_before_method_call_parentheses = false ij_groovy_space_before_method_left_brace = true ij_groovy_space_before_method_parentheses = false ij_groovy_space_before_quest = true ij_groovy_space_before_switch_left_brace = true ij_groovy_space_before_switch_parentheses = true ij_groovy_space_before_synchronized_left_brace = true ij_groovy_space_before_synchronized_parentheses = true ij_groovy_space_before_try_left_brace = true ij_groovy_space_before_try_parentheses = true ij_groovy_space_before_while_keyword = true ij_groovy_space_before_while_left_brace = true ij_groovy_space_before_while_parentheses = true ij_groovy_space_in_named_argument = true ij_groovy_space_in_named_argument_before_colon = false ij_groovy_space_within_empty_array_initializer_braces = false ij_groovy_space_within_empty_method_call_parentheses = false ij_groovy_spaces_around_additive_operators = true ij_groovy_spaces_around_assignment_operators = true ij_groovy_spaces_around_bitwise_operators = true ij_groovy_spaces_around_equality_operators = true ij_groovy_spaces_around_lambda_arrow = true ij_groovy_spaces_around_logical_operators = true ij_groovy_spaces_around_multiplicative_operators = true ij_groovy_spaces_around_regex_operators = true ij_groovy_spaces_around_relational_operators = true ij_groovy_spaces_around_shift_operators = true ij_groovy_spaces_within_annotation_parentheses = false ij_groovy_spaces_within_array_initializer_braces = false ij_groovy_spaces_within_braces = true ij_groovy_spaces_within_brackets = false ij_groovy_spaces_within_cast_parentheses = false ij_groovy_spaces_within_catch_parentheses = false ij_groovy_spaces_within_for_parentheses = false ij_groovy_spaces_within_gstring_injection_braces = false ij_groovy_spaces_within_if_parentheses = false ij_groovy_spaces_within_list_or_map = false ij_groovy_spaces_within_method_call_parentheses = false ij_groovy_spaces_within_method_parentheses = false ij_groovy_spaces_within_parentheses = false ij_groovy_spaces_within_switch_parentheses = false ij_groovy_spaces_within_synchronized_parentheses = false ij_groovy_spaces_within_try_parentheses = false ij_groovy_spaces_within_tuple_expression = false ij_groovy_spaces_within_while_parentheses = false ij_groovy_special_else_if_treatment = true ij_groovy_ternary_operation_wrap = off ij_groovy_throws_keyword_wrap = off ij_groovy_throws_list_wrap = off ij_groovy_use_flying_geese_braces = false ij_groovy_use_fq_class_names = false ij_groovy_use_fq_class_names_in_javadoc = true ij_groovy_use_relative_indents = false ij_groovy_use_single_class_imports = true ij_groovy_variable_annotation_wrap = off ij_groovy_while_brace_force = never ij_groovy_while_on_new_line = false ij_groovy_wrap_long_lines = false [{*.har,*.jsb2,*.jsb3,*.json,.babelrc,.eslintrc,.stylelintrc,bowerrc,jest.config}] indent_size = 2 ij_json_keep_blank_lines_in_code = 0 ij_json_keep_indents_on_empty_lines = false ij_json_keep_line_breaks = true ij_json_space_after_colon = true ij_json_space_after_comma = true ij_json_space_before_colon = true ij_json_space_before_comma = false ij_json_spaces_within_braces = false ij_json_spaces_within_brackets = false ij_json_wrap_long_lines = false [{*.htm,*.html,*.sht,*.shtm,*.shtml}] ij_html_add_new_line_before_tags = body, div, p, form, h1, h2, h3 ij_html_align_attributes = true ij_html_align_text = false ij_html_attribute_wrap = normal ij_html_block_comment_at_first_column = true ij_html_do_not_align_children_of_min_lines = 0 ij_html_do_not_break_if_inline_tags = title, h1, h2, h3, h4, h5, h6, p ij_html_do_not_indent_children_of_tags = html, body, thead, tbody, tfoot ij_html_enforce_quotes = false ij_html_inline_tags = a, abbr, acronym, b, basefont, bdo, big, br, cite, cite, code, dfn, em, font, i, img, input, kbd, label, q, s, samp, select, small, span, strike, strong, sub, sup, textarea, tt, u, var ij_html_keep_blank_lines = 2 ij_html_keep_indents_on_empty_lines = false ij_html_keep_line_breaks = true ij_html_keep_line_breaks_in_text = true ij_html_keep_whitespaces = false ij_html_keep_whitespaces_inside = span, pre, textarea ij_html_line_comment_at_first_column = true ij_html_new_line_after_last_attribute = never ij_html_new_line_before_first_attribute = never ij_html_quote_style = double ij_html_remove_new_line_before_tags = br ij_html_space_after_tag_name = false ij_html_space_around_equality_in_attribute = false ij_html_space_inside_empty_tag = false ij_html_text_wrap = normal [{*.markdown,*.md,*.mkd}] ij_markdown_wrap_text_if_long = false ij_markdown_wrap_text_inside_blockquotes = false [{*.properties,spring.handlers,spring.schemas}] ij_properties_align_group_field_declarations = false ij_properties_keep_blank_lines = false ij_properties_key_value_delimiter = equals ij_properties_spaces_around_key_value_delimiter = false [{*.yaml,*.yml}] indent_size = 2 ij_yaml_align_values_properties = do_not_align ij_yaml_autoinsert_sequence_marker = true ij_yaml_block_mapping_on_new_line = false ij_yaml_indent_sequence_value = true ij_yaml_keep_indents_on_empty_lines = false ij_yaml_keep_line_breaks = true ij_yaml_sequence_on_new_line = false ij_yaml_space_before_colon = false ij_yaml_spaces_within_braces = true ij_yaml_spaces_within_brackets = true ================================================ FILE: .gitattributes ================================================ * text=auto * eol=lf *.png binary *.jar binary *.jpg binary *.jpeg binary *.webp binary *.bin binary ================================================ FILE: .github/ISSUE_TEMPLATE/bug.yml ================================================ name: Bug 报告 description: 提交一个 bug labels: - "z:question" body: - type: markdown attributes: value: | 感谢你来到这里 在反馈前, 请确认你已经做了下面这些事情 - 阅读过 [「提问的智慧」](https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md) - 阅读过 [「如何有效地报告 Bug」](https://www.chiark.greenend.org.uk/~sgtatham/bugs-cn.html) - 对照过 [Releases](https://github.com/mamoe/mirai/releases),相关问题未在近期更新中解决 - 搜索了已有的 [issues](https://github.com/mamoe/mirai/issues?q=is%3Aissue) 列表中有没相关的信息 - 阅读了 [Mirai 的相关文档](https://github.com/mamoe/mirai/tree/dev/docs) - type: textarea id: issue-description attributes: label: 问题描述 description: 在此详细描述你遇到的问题 validations: required: true - type: textarea id: reproduce attributes: label: 复现 description: 在这里简略说明如何让这个问题再次发生 placeholder: | 在这里简略说明如何让这个问题再次发生 可使用 1. 2. 3. 的列表格式,或其他任意恰当的格式 如果你不确定如何复现, 请尽量描述发生当时的情景 建议提供相关代码 validations: required: true - type: input id: version-mirai-core attributes: label: mirai-core 版本 description: "填写你正在使用的版本号,如 `2.8.2`" placeholder: 2.8.2 validations: required: true - type: dropdown id: bot-protocol attributes: label: bot-protocol options: - ANDROID_PHONE - ANDROID_PAD - ANDROID_WATCH - IPAD - MACOS validations: required: true - type: textarea id: version-others attributes: label: 其他组件版本 description: | 如果你正在通过 mirai-console 或其他间接使用 mirai-core 的中间件(如 mirai-native、mirai-api-http 或者其他第三方 SDK), 请同样提供版本号 如果你正在使用 mirai-console, 请在此处粘贴 `/status` 命令的结果 placeholder: | mirai-api-http: `2.3.3` - type: textarea id: journal-system attributes: label: 系统日志 description: | 请提供全面的相关日志. 请不要截图. 如果日志过大, 可以在 `补充信息` 上传文件. 如果你遇到的问题是 "消息收不到", "收消息报错" 等与协议有关的问题, 请一定提交日志. 若不提交日志, 你的问题可能会被直接关闭. render: 'text' validations: required: false - type: textarea id: journal-network attributes: label: 网络日志 description: | 如果网络日志 (Net xxx) 不包含在系统日志中, 请额外提供网络日志. 若已经包含, 请忽略. 请提供全面的网络日志. 请不要截图. 若使用 Mirai Console 一般网络日志位于 bots/<****>/logs 里 如果日志过大, 可以在 `补充信息` 上传文件. render: text validations: required: false - type: textarea id: additional attributes: label: 补充信息 description: 如有必要,你可以在下文继续添加其他信息 - type: markdown attributes: value: | ---- 在发出 issue 前, 请确认 - 全部信息已经填写完毕, 特别是 「其他组件版本」 - 报告中没有令人反感的语言 - 「复现」的描述是否足够详细准确 ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: Mirai Forum url: https://mirai.mamoe.net about: 论坛 ================================================ FILE: .github/ISSUE_TEMPLATE/feature.md ================================================ --- name: 特性申请 about: 申请 mirai 添加新的特性 title: '' labels: 't:feature' assignees: '' --- ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: paths-ignore: - 'docs/**' - 'mirai-console/docs/**' - '**/*.md' pull_request: paths-ignore: - 'docs/**' - 'mirai-console/docs/**' - '**/*.md' jobs: build: name: "Build (${{ matrix.os }})" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: # You must use macos-12. Only macos-12 get 14GB memory while others have only 7GB. # - windows-2022 - macos-12 env: gradleArgs: --scan isMac: ${{ startsWith(matrix.os, 'macos') }} isWindows: ${{ startsWith(matrix.os, 'windows') }} isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }} isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }} steps: - uses: actions/checkout@v3 with: submodules: 'recursive' - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' - name: Setup Gradle uses: gradle/gradle-build-action@v2 - if: ${{ env.isUnix == 'true' }} run: chmod -R 777 * - if: ${{ env.isWindows == 'true' }} name: Setup Memory Environment on Windows run: > wmic pagefileset where name="D:\\pagefile.sys" set InitialSize=1024,MaximumSize=18432 & net stop mongodb shell: cmd continue-on-error: true - name: Clean and download dependencies run: ./gradlew clean ${{ env.gradleArgs }} - run: > ./gradlew updateSnapshotVersion ${{ env.gradleArgs }} if: github.event.pusher && vars.RUN_MIRAI_SNAPSHOTS == 'true' env: MIRAI_IS_SNAPSHOTS_PUBLISHING: true SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }} SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }} SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }} MIRAI_BUILD_INDEX_AUTH_USERNAME: ${{ secrets.MIRAI_BUILD_INDEX_AUTH_USERNAME }} MIRAI_BUILD_INDEX_AUTH_PASSWORD: ${{ secrets.MIRAI_BUILD_INDEX_AUTH_PASSWORD }} - name: "Assemble" run: ./gradlew assemble ${{ env.gradleArgs }} - name: Publish Local Artifacts if: ${{ env.enableLocalPublishingTest == 'true' }} run: ./gradlew :mirai-deps-test:publishMiraiArtifactsToMavenLocal ${{ env.gradleArgs }} "-Dmirai.build.project.version=2.99.0-deps-test" - name: "Check" run: ./gradlew check ${{ env.gradleArgs }} # Snapshots - if: ${{ env.isMac == 'true' }} name: Ensure KDoc valid run: ./gradlew dokkaHtmlMultiModule ${{ env.gradleArgs }} - name: Release RAM run: node ci-release-helper/scripts/kill-java.js - name: Publish Snapshots if: ${{ github.event.pusher && env.isMac == 'true' && vars.RUN_MIRAI_SNAPSHOTS == 'true' }} run: ./gradlew publishAllPublicationsToMiraiRepoRepository ${{ env.gradleArgs }} env: MIRAI_IS_SNAPSHOTS_PUBLISHING: true SNAPSHOTS_PUBLISHING_USER: ${{ secrets.SNAPSHOTS_PUBLISHING_USER }} SNAPSHOTS_PUBLISHING_KEY: ${{ secrets.SNAPSHOTS_PUBLISHING_KEY }} SNAPSHOTS_PUBLISHING_URL: ${{ secrets.SNAPSHOTS_PUBLISHING_URL }} # Upload - name: Publish MavenLocal run: ./gradlew publishToMavenLocal ${{ env.gradleArgs }} - name: Upload MavenLocal uses: actions/upload-artifact@v3 with: name: maven-cache path: ~/.m2 ================================================ FILE: .github/workflows/check-publishing.yml ================================================ # 当修改构建脚本 (包括修改依赖版本) 时检查配置是否能正常发版 # 发版检查非常慢, 因此不在 build.yml 做 name: Check Publishing on: push: paths: - '**/**.gradle.kts' - '**/gradle.properties' - 'buildSrc/**' pull_request: paths: - '**/**.gradle.kts' - '**/gradle.properties' - 'buildSrc/**' jobs: check-publishing: name: "Check Publishing (${{ matrix.os }})" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: # - windows-2022 # OOM - ubuntu-20.04 - macos-12 include: # - os: windows-2022 # targetName: mingwX64 # parallelCompilation: false - os: ubuntu-20.04 targetName: linuxX64 parallelCompilation: false - os: macos-12 targetName: macosX64 parallelCompilation: true # macOS machine has 14G env: # FIXME there must be two or more targets, or we'll get error on `@OptionalExpectation` # > Declaration annotated with '@OptionalExpectation' can only be used in common module sources gradleArgs: --scan isMac: ${{ startsWith(matrix.os, 'macos') }} isWindows: ${{ startsWith(matrix.os, 'windows') }} isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }} isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }} VCPKG_DEFAULT_BINARY_CACHE: ${{ startsWith(matrix.os, 'windows') && 'C:\vcpkg\binary_cache' || '/usr/local/share/vcpkg/binary_cache' }} steps: - uses: actions/checkout@v3 with: submodules: 'recursive' - uses: actions/setup-java@v3 with: distribution: 'adopt-openj9' java-version: '17' - name: Setup Gradle uses: gradle/gradle-build-action@v2 - name: Cache konan uses: pat-s/always-upload-cache@v3.0.11 with: path: ~/.konan key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} restore-keys: | ${{ runner.os }}-gradle- - name: Prepare to cache vcpkg if: ${{ env.isWindows == 'true' }} run: mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} - name: Cache vcpkg if: ${{ env.isWindows == 'true' }} uses: pat-s/always-upload-cache@v3.0.11 with: path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }} key: ${{ runner.os }}-vcpkg-binary-cache-${{ github.job }} restore-keys: | ${{ runner.os }}-vcpkg-binary-cache- - if: ${{ env.isUnix == 'true' }} run: chmod -R 777 * # Prepare environment for linking on Ubuntu - if: ${{ env.isUbuntu == 'true' }} name: Install OpenSSL on Ubuntu run: sudo apt install libssl-dev -y # Prepare environment for linking on Windows - if: ${{ env.isWindows == 'true' }} name: Setup Memory Environment on Windows run: > wmic pagefileset where name="D:\\pagefile.sys" set InitialSize=1024,MaximumSize=9216 & net stop mongodb shell: cmd continue-on-error: true - if: ${{ env.isWindows == 'true' }} name: Install OpenSSL & cURL on Windows run: | echo "set(VCPKG_BUILD_TYPE release)" | Out-File -FilePath "$env:VCPKG_INSTALLATION_ROOT\triplets\x64-windows.cmake" -Encoding utf8 -Append vcpkg install openssl:x64-windows curl[core,ssl]:x64-windows New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\crypto.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcrypto.lib New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\ssl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libssl.lib New-Item -Path $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\curl.lib -ItemType SymbolicLink -Value $env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\lib\libcurl.lib echo "$env:VCPKG_INSTALLATION_ROOT\installed\x64-windows\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append - name: Clean and download dependencies run: ./gradlew clean ${{ env.gradleArgs }} - name: Prepare deps test build run: ./gradlew :mirai-deps-test:updateProjectVersionForLocalDepsTest ${{ env.gradleArgs }} "-Porg.gradle.parallel=${{ matrix.parallelCompilation }}" - name: Build and Publish Local Artifacts run: ./gradlew :mirai-deps-test:publishMiraiArtifactsToMavenLocal ${{ env.gradleArgs }} "-Porg.gradle.parallel=${{ matrix.parallelCompilation }}" - name: Check Publication run: ./gradlew :mirai-deps-test:check ${{ env.gradleArgs }} "-Dmirai.deps.test.must.run=true" "-Porg.gradle.parallel=${{ matrix.parallelCompilation }}" ================================================ FILE: .github/workflows/doc.yml ================================================ name: mirai-doc Publish on: push: tags: - 'v*' # 正式版本 jobs: mirai-docs: runs-on: macos-12 # 14G memory steps: - name: Checkout repository uses: actions/checkout@v3 - name: Setup JDK 11 uses: actions/setup-java@v3 with: distribution: 'adopt' java-version: '11' - name: chmod -R 777 * run: chmod -R 777 * - name: Prepare environment run: ./gradlew :mirai-dokka:prepare - name: Dokka run: ./gradlew dokkaHtmlMultiModule - name: Update version number run: ./gradlew :mirai-dokka:update-vers - name: Deploy run: ./gradlew :mirai-dokka:deployPages env: gh_token: ${{ secrets.MAMOE_TOKEN }} ================================================ FILE: .github/workflows/release.yml ================================================ name: Release Publish on: push: tags: - 'v*' # 正式版本 paths-ignore: - 'docs/**' - 'mirai-console/docs/**' - '**/*.md' jobs: initialize-sonatype-stage: name: "Initialize sonatype staging repository" runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: submodules: 'recursive' - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' - name: Setup Gradle uses: gradle/gradle-build-action@v2 - run: chmod -R 777 * - name: Create publishing staging repository run: ./gradlew runcihelper --args create-stage-repo --scan "-Pcihelper.cert.username=${{ secrets.SONATYPE_USER }}" "-Pcihelper.cert.password=${{ secrets.SONATYPE_KEY }}" "-Pcihelper.cert.profileid=${{ secrets.SONATYPE_PROFILEID }}" - name: Cache staging repository id uses: actions/upload-artifact@v3 with: name: publish-stage-id path: ci-release-helper/repoid publish-others: name: "Others (${{ matrix.os }})" runs-on: ${{ matrix.os }} needs: [ initialize-sonatype-stage ] strategy: fail-fast: false matrix: os: - macos-12 env: # All targets MUST be enabled. See #2270. gradleArgs: --scan "-Dmirai.target=other" "-Pkotlin.compiler.execution.strategy=in-process" isMac: ${{ startsWith(matrix.os, 'macos') }} isWindows: ${{ startsWith(matrix.os, 'windows') }} isUbuntu: ${{ startsWith(matrix.os, 'ubuntu') }} isUnix: ${{ startsWith(matrix.os, 'macos') || startsWith(matrix.os, 'ubuntu') }} steps: - uses: actions/checkout@v3 with: submodules: 'recursive' - uses: actions/setup-java@v3 with: distribution: 'temurin' java-version: '17' - 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 env: GPG_PRIVATE: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PUBLIC_: ${{ secrets.GPG_PUBLIC_KEY }} - name: Setup Android SDK Ubuntu if: ${{ env.isUbuntu == 'true' }} run: 'touch local.properties && echo sdk.dir=/usr/local/lib/android/sdk >> local.properties' - name: Setup Android SDK macOS if: ${{ env.isMac == 'true' }} run: 'touch local.properties && echo sdk.dir=/Users/runner/Library/Android/sdk >> local.properties' - name: Setup Android SDK Windows if: ${{ env.isWindows == 'true' }} run: 'echo sdk.dir=C:\Android\android-sdk >> local.properties' - name: Setup Gradle uses: gradle/gradle-build-action@v2 - name: Cache konan uses: pat-s/always-upload-cache@v3.0.11 with: path: ~/.konan key: ${{ runner.os }}-gradle-${{ hashFiles('*.gradle.kts') }} restore-keys: | ${{ runner.os }}-gradle- - if: ${{ env.isUnix == 'true' }} run: chmod -R 777 * # Prepare environment for linking for macOS - name: Clean and download dependencies run: ./gradlew clean ${{ env.gradleArgs }} - name: "Assemble" run: ./gradlew assemble ${{ env.gradleArgs }} - name: Publish Local Artifacts if: ${{ env.enableLocalPublishingTest == 'true' }} run: ./gradlew :mirai-deps-test:publishMiraiArtifactsToMavenLocal ${{ env.gradleArgs }} "-Dmirai.build.project.version=2.99.0-deps-test" - name: "Check" run: ./gradlew check ${{ env.gradleArgs }} - if: ${{ env.isMac == 'true' }} name: Ensure KDoc valid run: ./gradlew dokkaHtmlMultiModule ${{ env.gradleArgs }} - name: Initialize Publishing Caching Repository run: ./gradlew runcihelper --args sync-maven-metadata ${{ env.gradleArgs }} - name: Publish if: ${{ env.isMac == 'true' }} run: ./gradlew publishAllPublicationsToMiraiStageRepoRepository ${{ env.gradleArgs }} - name: Restore staging repository id uses: actions/download-artifact@v3 with: name: publish-stage-id path: ci-release-helper/repoid - name: Release RAM run: node ci-release-helper/scripts/kill-java.js - name: Publish to maven central run: ./gradlew runcihelper --args publish-to-maven-central --scan "-Pcihelper.cert.username=${{ secrets.SONATYPE_USER }}" "-Pcihelper.cert.password=${{ secrets.SONATYPE_KEY }}" - name: Publish Gradle plugin run: ./gradlew :mirai-console-gradle:publishPlugins ${{ env.gradleArgs }} -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 }} continue-on-error: true # # close-repository: # runs-on: macos-12 # needs: # - publish-others # - publish-core-native # steps: # - uses: actions/checkout@v3 # with: # submodules: 'recursive' # # - uses: actions/setup-java@v3 # with: # distribution: 'adopt-openj9' # java-version: '17' # # - name: Setup Gradle # uses: gradle/gradle-build-action@v2 # # - name: Close repository # run: ./gradlew :ci-release-helper:closeRepository --scan ================================================ FILE: .gitignore ================================================ # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* target/ build/ .idea/ *.iml mirai.iml /.idea/ .idea/* /.idea/* /test .gradle/ local.properties # Maven publishing credits keys.properties /plugins/ token.txt bintray.user.txt bintray.key.txt # For gpg sign /build-gpg-sign # Name for IDEA direction sorting build-secret-keys/ **/local.* ================================================ FILE: .run/Check Binary Compatibility.run.xml ================================================ true true false ================================================ FILE: .run/Compile everything.run.xml ================================================ true true false ================================================ FILE: .run/Compile mirai-console.run.xml ================================================ true true false ================================================ FILE: .run/Compile mirai-core for JVM.run.xml ================================================ true true false ================================================ FILE: .run/Dump API Changes for mirai-console-frontend-base.run.xml ================================================ true true false ================================================ FILE: .run/Dump API Changes for mirai-console.run.xml ================================================ true true false ================================================ FILE: .run/Dump API Changes for mirai-core-api.run.xml ================================================ true true false ================================================ FILE: .run/Publish deps test artifacts.run.xml ================================================ true true false false ================================================ FILE: .run/Publish local artifacts.run.xml ================================================ true true false false ================================================ FILE: .run/Run IDE.run.xml ================================================ true true false ================================================ FILE: .run/Run core tests.run.xml ================================================ true true false ================================================ FILE: .run/RunMessageDecodingRecorderKt.run.xml ================================================ ================================================ FILE: .run/RunRecorderKt.run.xml ================================================ ================================================ FILE: .run/Test everything.run.xml ================================================ true true false ================================================ FILE: .run/Test mirai-console.run.xml ================================================ true true false ================================================ FILE: .run/Test mirai-core for JVM.run.xml ================================================ true true false ================================================ FILE: CONTRIBUTING.md ================================================ # CONTRIBUTING ----------------------------- 欢迎你来到这里, 请移步 [`docs/contributing`](docs/contributing) ================================================ FILE: LICENSE ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . ================================================ FILE: README-eng.md ================================================ # Mirai Mirai is a high-performance multi-platform library, as well as a framework, providing protocol support for Tencent QQ. Mirai is designed to handle all sorts of messaging works that can be automatically done by bots **in a perfect way**. > Tencent QQ: A modern messaging software used by all Chinese netizens. ## Start **Development document**: [docs](docs) ### Use as a framework Mirai is able to run as a plugin-supported framework. The community, (with `mirai-console`) that allows developers to share their plugins, and for users to install plugins quickly, is building in progress. - JVM languages like `Java` or `Kotlin`: Make Jar plugin for [mirai-console](/mirai-console) directly and share with other developers through the plugin center. - `Kotlin Script`: [mirai-kts](https://github.com/iTXTech/mirai-kts) supports plugins using Kotlin Scripts (`kts`)(**OpenJDK 8+ only,except Android**) - Native languages like `C`, `C++`: [mirai-native](https://github.com/iTXTech/mirai-native) supports plugins from CoolQ **(`Windows JREx86` only / with `Wine`)** - `JavaScript`: [mirai-js](https://github.com/iTXTech/mirai-js) supports plugins using `JavaScript` and inter-operate with **mirai** on JVM directly. - Any language:Use HTTP API from [mirai-api-http](https://github.com/mamoe/mirai-api-http) **Though only Jar plugins are supported officially, language bridges that are maintained by the community can connect with your knowledge.**: - `Python`: [python-mirai](https://github.com/NatriumLab/python-mirai) A Bot framework based on `mirai-api-http`. - `JavaScript`(`Node.js`): [node-mirai](https://github.com/RedBeanN/node-mirai) The Node.js SDK for mirai. - `Go`: [gomirai](https://github.com/Logiase/gomirai) The GoLang SDK for mirai. - `Mozilla Rhino`: [mirai-rhinojs-sdk](https://github.com/StageGuard/mirai-rhinojs-sdk) The Mozilla Rhino (JavaScript) SDK for mirai. - `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) The Lua SDK for mirai-core, supporting Java extensions that act as a bridge between Java and native Lua. - `C++`: [miraiCP](https://github.com/Nambers/MiraiCP) A C++ SDK using the `JNI` technique to connect the Mirai. - `C++`: [mirai-cpp](https://github.com/cyanray/mirai-cpp) A simple C++ SDK using `mirai-api-http` for ALL platforms. - `C++`: [miraipp](https://github.com/Chlorie/miraipp-template) A sophisticated, modern mapping for `mirai-http-api` to C++, providing development documents. - `Rust`: [mirai-rs](https://github.com/HoshinoTented/mirai-rs) The Rust mapping for `mirai-http-api`. - `TypeScript`: [mirai-ts](https://github.com/YunYouJun/mirai-ts) TypeScript SDK comes with a declaration file, has good code hints, and can also be used as a JavaScript SDK. ### Use as a library You can install mirai as a library into your project. #### Import with Gradle Mirai is only published on `jcenter`, therefore please ensure you have the `jcenter()` repository added in your `build.gradle`. ```kotlin repositories{ jcenter() } ``` Then add dependency to `dependencies` block, following: If your project is a multiplatform project, you need to add dependencies for each platform respectively. If your project is not a multiplatform project, add the platform-specific dependency only. Replace `VERSION` with the newest version, say [![Download](https://api.bintray.com/packages/him188moe/mirai/mirai-core/images/download.svg)](https://bintray.com/him188moe/mirai/mirai-core/) **jvm** ```kotlin implementation("net.mamoe:mirai-core:VERSION") ``` **common** ```kotlin implementation("net.mamoe:mirai-core-common:VERSION") ``` **android** ```kotlin implementation("net.mamoe:mirai-core-android:VERSION") ``` #### Import with Maven ```xml jcenter https://jcenter.bintray.com/ ``` ```xml net.mamoe mirai-core-qqandroid 0.23.0 ``` ## Contribution **All kinds of contributions are welcomed.** If you hold an interest in helping us implementing Mirai on JS, iOS or Native platforms, please email us `support@mamoe.net`. If you meet any problem or have any questions, feel free to file an issue. Our goal is to make Mirai easy to use. ## Acknowledgements Thanks to [JetBrains](https://www.jetbrains.com/?from=mirai) for allocating free open-source licences for IDEs such as [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai). [](https://www.jetbrains.com/?from=mirai) ## License Copyright (C) 2019-2021 Mamoe Technologies and mirai contributors This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . ================================================ FILE: README.md ================================================
logo
title ---- [![Gitter](https://badges.gitter.im/mamoe/mirai.svg)](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![MiraiForum](https://img.shields.io/badge/post-on%20MiraiForum-yellow)](https://mirai.mamoe.net) mirai 是一个在全平台下运行,提供 QQ Android 协议支持的高效率机器人库 这个项目的名字来源于

京都动画作品《境界的彼方》栗山未来(Kuriyama mirai)

CRYPTON初音未来为代表的创作与活动(Magical mirai)

图标以及形象由画师DazeCake绘制
## mirai **[English](README-eng.md)** ## 声明 ### 一切开发旨在学习,请勿用于非法用途 - mirai 是完全免费且开放源代码的软件,仅供学习和娱乐用途使用 - mirai 不会通过任何方式强制收取费用,或对使用者提出物质条件 - mirai 由整个开源社区维护,并不是属于某个个体的作品,所有贡献者都享有其作品的著作权。 ### 许可证 Copyright (C) 2019-2023 Mamoe Technologies and contributors. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . `mirai` 采用 `AGPLv3` 协议开源。为了整个社区的良性发展,我们**强烈建议**您做到以下几点: - **间接接触(包括但不限于使用 `Http API` 或 跨进程技术)到 `mirai` 的软件使用 `AGPLv3` 开源** - **不鼓励,不支持一切商业使用** 鉴于项目的特殊性,开发团队可能在任何时间**停止更新**或**删除项目**。 ### 衍生软件需声明引用 - 若引用 mirai 发布的软件包而不修改 mirai,则衍生项目需在描述或应用内的任意部位提及使用 mirai。 - 若修改 mirai 源代码再发布,**或参考 mirai 内部实现发布另一个项目**,则衍生项目必须在**文章首部**或 'mirai' 相关内容**首次出现**的位置**明确声明**来源于本仓库 (`https://github.com/mamoe/mirai`)。不得扭曲或隐藏免费且开源的事实。 ## 协议支持
支持的协议列表 **消息相关** - 文字 - 原生表情 - 商城表情 - 戳一戳 - 图片 (自定义表情) - XML,JSON 等富文本消息 - 长消息(5000 字符 + 50 图片) - 引用回复 - 合并转发 - 撤回 - 提及群员 - 提及全体成员 - 语音 - 闪照 - 撤回群员消息 - 自定义消息 - 音乐分享 - 短视频 **群相关** - 群列表 - 成员列表 - 群员权限 - 禁言 - 全体禁言 - 群公告管理 - 群设置(自动审批、入群公告、成员邀请、匿名聊天) - 处理入群申请 - 移除群员 - 群文件 **好友相关** - 好友列表 - 处理新好友申请 - 删除好友 **其他客户端** - 同步其他客户端的消息 - 向其他客户端发送消息
#### 不会支持的协议 - 金钱相关,如点赞、收付款 - 敏感操作,如主动添加好友、主动加入群、主动邀请好友加群 - 安全相关,获取账号登录凭证(token,cookie等) **一切开发旨在学习,请勿用于非法用途** ## 快速使用 - **用户手册**: [UserManual](docs/UserManual.md) > 如果你希望快速部署一个 Mirai QQ 机器人,安装插件、并投入使用,请看这里 - 论坛: [Mirai Forum](https://mirai.mamoe.net/) > Mirai 只有**唯一一个**官方论坛 Mirai Forum - 在线讨论: [Gitter](https://gitter.im/mamoe/mirai?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) ## 开发相关 - 开发文档: [在 GitHub 阅读](docs/README.md) 或 [在 docs.mirai.mamoe.net 阅读](https://docs.mirai.mamoe.net/) - 参与贡献: [CONTRIBUTING](docs/contributing/README.md) - 更新日志: [release](https://github.com/mamoe/mirai/releases) - 开发计划: [milestones](https://github.com/mamoe/mirai/milestones) - mirai 开发组和官方系列项目: [project-mirai](https://github.com/project-mirai) - mirai 社区相关项目 ( 旧): [awesome-mirai](https://github.com/project-mirai/awsome-mirai/blob/master/README.md) ## 赞助 - 本着与更多 mirai 开发者、用户、支持者共建更好的学习环境为目的,mirai 自 2021 年 3 月 1 日发起官方社区的建设。社区建设可能涉及:[学习论坛](https://mirai.mamoe.net)、[插件中心(在建)](https://github.com/project-mirai/mirai-plugin-center)等。由于社区的运维需要经费,mirai 项目开启 sponsor 功能。 - 请注意,赞助是全自愿的。赞助者不会获得特权,不赞助也可以使用全部的功能。为资金管理方便,赞助后不设退款、折现等选项。最终解释权归社区运营团队所有。 - 全部赞助金额、流向、票据单号等将透明化公示,欢迎任何人随时查看及提出建议。 ## 鸣谢 > [IntelliJ IDEA](https://zh.wikipedia.org/zh-hans/IntelliJ_IDEA) 是一个在各个方面都最大程度地提高开发人员的生产力的 IDE,适用于 JVM 平台语言。 特别感谢 [JetBrains](https://www.jetbrains.com/?from=mirai) 为开源项目提供免费的 [IntelliJ IDEA](https://www.jetbrains.com/idea/?from=mirai) 等 IDE 的授权 [](https://www.jetbrains.com/?from=mirai) ================================================ FILE: build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UnstableApiUsage", "UNUSED_VARIABLE", "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") import org.jetbrains.dokka.base.DokkaBase import org.jetbrains.dokka.base.DokkaBaseConfiguration import shadow.configureMppShadow import java.time.LocalDateTime buildscript { repositories { if (System.getProperty("use.maven.local") == "true") { mavenLocal() } mavenCentral() gradlePluginPortal() google() } dependencies { classpath("com.android.tools.build:gradle:${Versions.androidGradlePlugin}") classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${Versions.atomicFU}") classpath("org.jetbrains.dokka:dokka-base:${Versions.dokka}") } } plugins { kotlin("jvm") apply false // version Versions.kotlinCompiler kotlin("plugin.serialization") version Versions.kotlinCompiler apply false id("com.google.osdetector") id("org.jetbrains.dokka") version Versions.dokka id("me.him188.kotlin-jvm-blocking-bridge") version Versions.blockingBridge id("me.him188.kotlin-dynamic-delegation") version Versions.dynamicDelegation apply false id("me.him188.maven-central-publish") version Versions.mavenCentralPublish apply false id("com.gradle.plugin-publish") version "1.1.0" apply false id("org.jetbrains.kotlinx.binary-compatibility-validator") version Versions.binaryValidator apply false id("com.android.library") apply false id("de.mannodermaus.android-junit5") version "1.8.2.1" apply false } osDetector = osdetector BuildSrcRootProjectHolder.value = rootProject BuildSrcRootProjectHolder.lastUpdateTime = System.currentTimeMillis() analyzes.CompiledCodeVerify.run { registerAllVerifyTasks() } allprojects { group = "net.mamoe" version = Versions.project repositories { if (System.getProperty("use.maven.local") == "true") { mavenLocal() } mavenCentral() gradlePluginPortal() google() } preConfigureJvmTarget() afterEvaluate { configureJvmTarget() configureMppShadow() configureEncoding() configureKotlinTestSettings() configureKotlinOptIns() if (isKotlinJvmProject) { configureFlattenSourceSets() } configureJarManifest() substituteDependenciesUsingExpectedVersion() } } subprojects { afterEvaluate { if (project.path == ":mirai-core-api") configureDokka() if (project.path == ":mirai-console") configureDokka() } } rootProject.configureDokka() tasks.register("cleanExceptIntellij") { group = "build" allprojects.forEach { proj -> if (proj.name != "mirai-console-intellij") { // 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.configureDokka() { val isRoot = this@configureDokka == rootProject if (!isRoot) { apply(plugin = "org.jetbrains.dokka") } tasks.withType().configureEach { pluginConfiguration { this.footerMessage = """Copyright 2019-${ LocalDateTime.now().year } Mamoe Technologies and contributors. Source code: GitHub """.trimIndent() this.customAssets = listOf( rootProject.projectDir.resolve("mirai-dokka/frontend/ext.js"), ) } } tasks.withType().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) } } } } if (isRoot) { tasks.named("dokkaHtmlMultiModule").configure { outputDirectory.set( rootProject.projectDir.resolve("mirai-dokka/pages/snapshot") ) } } } ================================================ FILE: buildSrc/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ plugins { `kotlin-dsl` } repositories { mavenLocal() google() mavenCentral() gradlePluginPortal() } kotlin { sourceSets.all { languageSettings.optIn("kotlin.Experimental") languageSettings.optIn("kotlin.RequiresOptIn") languageSettings.optIn("kotlin.ExperimentalStdlibApi") } } private val versionsText = project.projectDir.resolve("src/main/kotlin/Versions.kt").readText() fun version(name: String): String { return versionsText.lineSequence() .map { it.trim() } .single { it.startsWith("const val $name ") } .substringAfter('"', "") .substringBefore('"', "") .also { check(it.isNotBlank()) logger.debug("$name=$it") } } dependencies { val asmVersion = version("asm") fun asm(module: String) = "org.ow2.asm:asm-$module:$asmVersion" fun kotlinx(id: String, version: String) = "org.jetbrains.kotlinx:kotlinx-$id:$version" fun ktor(id: String, version: String) = "io.ktor:ktor-$id:$version" // compileOnly(kotlin("gradle-plugin-api", "1.3.72")) // Gradle's Kotlin is 1.3.72 // api("com.github.jengelman.gradle.plugins", "shadow", version("shadow")) api("com.github.johnrengelman", "shadow", version("shadow")) api("org.jetbrains.kotlin", "kotlin-gradle-plugin", version("kotlinCompiler")) { exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-stdlib-common") exclude("org.jetbrains.kotlin", "kotlin-reflect") } // api("org.jetbrains.kotlin", "kotlin-compiler-embeddable", version("kotlinCompiler")) // api(ktor("client-okhttp", "1.4.3")) api("com.android.tools.build", "gradle", version("androidGradlePlugin")) api(asm("tree")) api(asm("util")) api(asm("commons")) api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2") { exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-reflect") exclude("org.jetbrains.kotlin", "kotlin-stdlib-common") } // https://mvnrepository.com/artifact/com.android.library/com.android.library.gradle.plugin api("com.android.library:com.android.library.gradle.plugin:${version("androidGradlePlugin")}") api("com.google.code.gson:gson:2.10.1") api("gradle.plugin.com.google.gradle:osdetector-gradle-plugin:1.7.0") api(gradleApi()) } ================================================ FILE: buildSrc/settings.gradle.kts ================================================ /* * Copyright 2019-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: buildSrc/src/main/kotlin/Android.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UNUSED_VARIABLE") import com.android.build.api.dsl.LibraryExtension import org.gradle.api.JavaVersion import org.gradle.api.Project import org.gradle.api.tasks.testing.Test import org.gradle.kotlin.dsl.* import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import java.util.* const val PROP_MIRAI_ENABLE_ANDROID_INSTRUMENTED_TESTS = "mirai.enable.android.instrumented.tests" /** * Use [usingAndroidInstrumentedTests] instead. */ val ENABLE_ANDROID_INSTRUMENTED_TESTS by projectLazy { val name = PROP_MIRAI_ENABLE_ANDROID_INSTRUMENTED_TESTS (System.getProperty(name) ?: System.getenv(name) ?: rootProject.getLocalProperty(name) ?: "true").toBooleanStrict() } val Project.usingAndroidInstrumentedTests get() = ENABLE_ANDROID_INSTRUMENTED_TESTS && isAndroidSdkAvailable fun Project.configureAndroidTarget(androidNamespace: String) { if (ENABLE_ANDROID_INSTRUMENTED_TESTS && !isAndroidSdkAvailable) { if (!ProjectAndroidSdkAvailability.tryFixAndroidSdk(this)) { printAndroidNotInstalled() } } extensions.getByType(KotlinMultiplatformExtension::class.java).apply { if (project.usingAndroidInstrumentedTests) { configureAndroidTargetWithSdk(androidNamespace) } else { configureAndroidTargetWithJvm() } } } private fun Project.configureAndroidTargetWithJvm() { extensions.getByType(KotlinMultiplatformExtension::class.java).apply { jvm("android") { attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.androidJvm) if (IDEA_ACTIVE) { attributes.attribute(MIRAI_PLATFORM_ATTRIBUTE, "android") // workaround for IDE bug } } val jvmBaseMain by sourceSets.getting val jvmBaseTest by sourceSets.getting sourceSets.getByName("androidTest").configureJvmTest("configureAndroidTargetWithJvm") sourceSets.getByName("androidTest").kotlin.srcDir(projectDir.resolve("src/androidUnitTest/kotlin")) sourceSets.getByName("androidTest").dependsOn(jvmBaseTest) sourceSets.getByName("androidMain").apply { dependencies { compileOnly(`android-runtime`) } dependsOn(jvmBaseMain) } tasks.all { if (this.name == "androidTest") { this as Test this.environment(PROP_MIRAI_ANDROID_SDK_KIND, "jdk") } } } } private const val PROP_MIRAI_ANDROID_SDK_KIND = "mirai.android.sdk.kind" @Suppress("UnstableApiUsage") private fun Project.configureAndroidTargetWithSdk(androidNamespace: String) { apply(plugin = "com.android.library") apply(plugin = "de.mannodermaus.android-junit5") extensions.getByType(LibraryExtension::class).apply { namespace = androidNamespace } extensions.getByType(KotlinMultiplatformExtension::class.java).apply { android { publishLibraryVariants("release", "debug") } val jvmBaseMain = sourceSets.maybeCreate("jvmBaseMain") val jvmBaseTest = sourceSets.maybeCreate("jvmBaseTest") val androidMain by sourceSets.getting androidMain.dependsOn(jvmBaseMain) // don't use androidTest, deprecated by Kotlin // this can cause problems on sync // for (s in arrayOf("androidDebug", "androidRelease")) { // sourceSets.all { if (name in s) dependsOn(androidMain) } // } // we should have added a "androidBaseTest" (or "androidTest") for "androidUnitTest" and "androidInstrumentedTest", // but this currently cause bugs in IntelliJ (2023.2) // val androidBaseTest = sourceSets.maybeCreate("androidBaseTest").apply { // dependsOn(jvmBaseTest) // } val androidUnitTest by sourceSets.getting { dependsOn(jvmBaseTest) } // for (s in arrayOf("androidUnitTestDebug", "androidUnitTestRelease")) { // sourceSets.all { if (name in s) dependsOn(androidUnitTest) } // } val androidInstrumentedTest by sourceSets.getting { dependsOn(jvmBaseTest) } // for (s in arrayOf("androidInstrumentedTestDebug")) { // sourceSets.all { if (name in s) dependsOn(androidInstrumentedTest) } // } // afterEvaluate { //// > androidDebug dependsOn commonMain //// androidInstrumentedTest dependsOn jvmBaseTest //// androidInstrumentedTestDebug dependsOn //// androidMain dependsOn commonMain, jvmBaseMain //// androidRelease dependsOn commonMain //// androidUnitTest dependsOn commonTest, jvmBaseTest //// androidUnitTestDebug dependsOn commonTest //// androidUnitTestRelease dependsOn commonTest // error(this@apply.sourceSets.joinToString("\n") { // it.name + " dependsOn " + it.dependsOn.joinToString { it.name } // }) // } configure( listOf( sourceSets.getByName("androidInstrumentedTest"), sourceSets.getByName("androidUnitTest"), ) ) { dependencies { implementation(kotlin("test-annotations-common"))?.because("configureAndroidTargetWithSdk") } } tasks.all { if (this.name == "testDebugUnitTest" || this.name == "testReleaseUnitTest") { this as Test this.environment(PROP_MIRAI_ANDROID_SDK_KIND, "adk") } } } // trick for compiler bug this.sourceSets.apply { removeIf { it.name == "androidAndroidTestRelease" } removeIf { it.name == "androidTestFixtures" } removeIf { it.name == "androidTestFixturesDebug" } removeIf { it.name == "androidTestFixturesRelease" } } extensions.getByType(LibraryExtension::class.java).apply { compileSdk = 33 sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") defaultConfig { minSdk = rootProject.extra["mirai.android.target.api.level"]!!.toString().toInt() targetSdk = 33 } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } buildTypes.getByName("release") { isMinifyEnabled = false isShrinkResources = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), ) } } extensions.getByType(LibraryExtension::class.java).apply { defaultConfig { // 1) Make sure to use the AndroidJUnitRunner, or a subclass of it. This requires a dependency on androidx.test:runner, too! testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" // 2) Connect JUnit 5 to the runner testInstrumentationRunnerArguments["runnerBuilder"] = "de.mannodermaus.junit5.AndroidJUnit5Builder" } } // val sourceSets = arrayOf("androidInstrumentedTest", "androidUnitTest") // .map { kotlin.sourceSets.getByName(it) } // for (sourceSet in sourceSets) { // sourceSet.dependencies { // implementation("androidx.test:runner:1.5.2") // implementation("org.junit.jupiter:junit-jupiter-api:5.9.2") // runtimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2") // // implementation("de.mannodermaus.junit5:android-test-core:1.3.0") // implementation("de.mannodermaus.junit5:android-test-runner:1.3.0") // } // } dependencies { // 4) Jupiter API & Test Runner, if you don't have it already "androidTestImplementation"("androidx.test:runner:1.5.2") "androidTestImplementation"("org.junit.jupiter:junit-jupiter-api:${Versions.junit}") "androidTestRuntimeOnly"("org.junit.jupiter:junit-jupiter-engine:${Versions.junit}") // 5) The instrumentation test companion libraries "androidTestImplementation"("de.mannodermaus.junit5:android-test-core:1.3.0") "androidTestRuntimeOnly"("de.mannodermaus.junit5:android-test-runner:1.3.0") } } private fun Project.printAndroidNotInstalled() { logger.warn( """ 你设置了启用 Android Instrumented Test, 但是未配置 Android SDK. $name 的 Android 目标将会使用桌面 JVM 编译和测试. Android Instrumented Test 将不会进行. 这不会影响 Android 以外的平台的编译和测试. 如果你要给 mirai PR 并且你修改了 Android 部分, 建议解决此警告. 如果你没有修改 Android 部分, 则可以忽略, 或者在项目根目录 local.properties (如果不存在就创建一个) 添加 `$PROP_MIRAI_ENABLE_ANDROID_INSTRUMENTED_TESTS=false`. 在安装 Android SDK 后, 请在项目根目录 local.properties 中添加 `sdk.dir=/path/to/Android/sdk` 指向本机 Android SDK 安装路径. 若要关闭 Android Instrumented Test, 在项目根目录 local.properties 添加 `$PROP_MIRAI_ENABLE_ANDROID_INSTRUMENTED_TESTS=false`. ------- """.trimIndent() ) // logger.warn( // """Android SDK might not be installed. Android target of $name will not be compiled. It does no influence on the compilation of other platforms. // """.trimIndent() // ) } private object ProjectAndroidSdkAvailability { val map: MutableMap by projectLazy { mutableMapOf() } @Synchronized operator fun get(project: Project): Boolean { if (map[project.path] != null) return map[project.path]!! val projectAvailable = project.runCatching { val keyProps = Properties().apply { file("local.properties").takeIf { it.exists() }?.inputStream()?.use { load(it) } } keyProps.getProperty("sdk.dir", "").isNotEmpty() }.getOrElse { false } fun impl(): Boolean { if (project === project.rootProject) return projectAvailable return projectAvailable || get(project.rootProject) } map[project.path] = impl() return map[project.path]!! } fun tryFixAndroidSdk(project: Project): Boolean { val androidHome = System.getenv("ANDROID_HOME") ?: kotlin.run { project.logger.info("tryFixAndroidSdk: environment `ANDROID_HOME` does not exist") return false } val escaped = androidHome .replace(""":""", """\:""") .replace("""\""", """\\""") .trim() project.rootDir.resolve("local.properties") .apply { if (!exists()) createNewFile() } .appendText("sdk.dir=$escaped") project.logger.info("tryFixAndroidSdk: fixed sdk.dir in local.properties: $escaped") map.clear() return get(project) } } private val Project.isAndroidSdkAvailable: Boolean get() = ProjectAndroidSdkAvailability[this] ================================================ FILE: buildSrc/src/main/kotlin/BinaryCompatibilityConfigurator.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import org.gradle.api.Project import org.gradle.configurationcache.extensions.useToRun import java.io.File /* * 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/dev/LICENSE */ object BinaryCompatibilityConfigurator { fun Project.configureBinaryValidators(targetNames: Set) { targetNames.forEach { configureBinaryValidator(it) } } fun Project.configureBinaryValidator(targetName: String?) { val validationDir = projectDir.resolve("compatibility-validation") val dir = validationDir.resolve(targetName ?: "jvm") dir.mkdirs() createValidator(this, dir, targetName) val apiDumpAll = tasks.maybeCreate("apiDumpAll").apply { group = "mirai" } val apiCheckAll = tasks.maybeCreate("apiCheckAll").apply { group = "mirai" } project.afterEvaluate { val validatorProject = findProject(getValidatorDir(dir)) validatorProject?.afterEvaluate { tasks.getByName("apiDump").let { apiDumpAll.dependsOn(it) } } validatorProject?.afterEvaluate { tasks.getByName("apiCheck").let { apiCheckAll.dependsOn(it) } } } } // Also change: settings.gradle.kts:116 private fun Project.getValidatorDir(dir: File) = ":validator" + project.path + "-validator:${dir.name}" private fun File.writeTextIfNeeded(text: String) { if (!this.exists()) return this.writeText(text) if (this.readText() == text) return return this.writeText(text) } /** * @param targetName `null` for JVM projects. */ fun createValidator(project: Project, dir: File, targetName: String?) { dir.resolve("build.gradle.kts").writeTextIfNeeded( applyTemplate( project.path, listOfNotNull( if (targetName == null) "classes/kotlin/main" else "classes/kotlin/$targetName/main", if (targetName?.contains("android") == true && project.usingAndroidInstrumentedTests) "tmp/kotlin-classes/debug" else "" ) ) ) dir.resolve(".gitignore").writeTextIfNeeded( this::class.java.classLoader .getResourceAsStream("binary-compatibility-validator-ignore.txt")!!.readBytes().decodeToString() ) project.afterEvaluate { findProject(getValidatorDir(dir)) ?.afterEvaluate { if (targetName == null) { tasks.findByName("apiBuild")?.dependsOn( *listOfNotNull( project.tasks.getByName("jar"), project.tasks.findByName("compileDebugKotlinAndroid") ).toTypedArray() ) } else { tasks.findByName("apiBuild")?.dependsOn( if (targetName.contains("android") && ENABLE_ANDROID_INSTRUMENTED_TESTS) { project.tasks.getByName("bundleDebugAar") } else { project.tasks.getByName("${targetName}Jar") } ) } } } } fun applyTemplate(projectPath: String, buildDirs: List): String { return this::class.java.classLoader .getResourceAsStream("binary-compatibility-validator-build.txt")!! .useToRun { readBytes() } .decodeToString() .replace("$\$PROJECT_PATH$$", projectPath) .replace("$\$BUILD_DIR$$", buildDirs.joinToString("\n")) .replace("$\$PLUGIN_VERSION$$", Versions.binaryValidator) } } ================================================ FILE: buildSrc/src/main/kotlin/DependencyDumper.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.tasks.TaskProvider import java.io.File import java.util.zip.ZipFile object DependencyDumper { fun registerDumpTask(project: Project, confName: String, out: File): TaskProvider { return regDmpTask(project, confName) { deps -> deps.forEach { println(" `- $it") } out.writeText(deps.joinToString("\n", postfix = "\n")) } } fun registerDumpTaskKtSrc(project: Project, confName: String, out: File, className: String): TaskProvider { val pkgName = className.substringBeforeLast(".") val kname = className.substringAfterLast(".") return regDmpTask(project, confName) { deps -> out.printWriter().use { pr -> pr.println("package $pkgName") pr.println() pr.println("internal object $kname {") pr.println(" val dependencies: List = listOf(") deps.forEach { dependency -> pr.append(" \"").append(dependency).println("\",") } pr.println(" )") pr.println("}") } } } private fun regDmpTask(project: Project, confName: String, action: (List) -> Unit): TaskProvider { val dependenciesDump = project.tasks.maybeCreate("dependenciesDump") dependenciesDump.group = "mirai" return project.tasks.register("dependenciesDump_${confName.capitalize()}") { group = "mirai" doLast { val dependencies = HashSet() fun emit(dep: ResolvedDependency) { if (dependencies.add(dep.moduleGroup + ":" + dep.moduleName)) { dep.children.forEach { emit(it) } } } project.configurations.getByName(confName).resolvedConfiguration.firstLevelModuleDependencies.forEach { dependency -> emit(dependency) } val stdep = dependencies.toMutableList() stdep.sort() action(stdep) } }.also { dependenciesDump.dependsOn(it) } } fun registerDumpClassGraph(project: Project, confName: String, out: String): TaskProvider { val dependenciesDump = project.tasks.maybeCreate("dependenciesDump") dependenciesDump.group = "mirai" return project.tasks.register("dependenciesDumpGraph_${confName.capitalize()}") { group = "mirai" val outFile = temporaryDir.resolve(out) outputs.file(outFile) val conf = project.configurations.getByName(confName) doLast { outFile.parentFile.mkdirs() val classes = HashSet() conf.resolvedConfiguration.files.forEach { file -> if (file.isFile) { ZipFile(file).use { zipFile -> zipFile.entries().asSequence() .filter { it.name.endsWith(".class") } .filterNot { it.name.startsWith("META-INF") } .map { it.name.substringBeforeLast('.').replace('/', '.') } .map { it.removePrefix(".") } .forEach { classes.add(it) } } } else if (file.isDirectory) { file.walk() .filter { it.isFile } .filter { it.name.endsWith(".class") } .map { it.relativeTo(file).path.substringBeforeLast('.') } .map { it.replace('\\', '.').replace('/', '.') } .map { it.removePrefix(".") } .forEach { classes.add(it) } } } outFile.bufferedWriter().use { writer -> classes.sorted().forEach { writer.append(it).append('\n') } } } }.also { dependenciesDump.dependsOn(it) } } } ================================================ FILE: buildSrc/src/main/kotlin/HmppConfigure.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import com.google.gradle.osdetector.OsDetector import org.gradle.api.Project import org.gradle.api.attributes.Attribute import org.gradle.kotlin.dsl.getting import org.gradle.kotlin.dsl.provideDelegate import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType val MIRAI_PLATFORM_ATTRIBUTE: Attribute = Attribute.of( "net.mamoe.mirai.platform", String::class.java ) /** * Flags a target as an HMPP intermediate target */ val MIRAI_PLATFORM_INTERMEDIATE: Attribute = Attribute.of( "net.mamoe.mirai.platform.intermediate", Boolean::class.javaObjectType ) val IDEA_ACTIVE = System.getProperty("idea.active") == "true" && System.getProperty("publication.test") != "true" val OS_NAME = System.getProperty("os.name").lowercase() lateinit var osDetector: OsDetector // aarch = arm val OsDetector.isAarch get() = osDetector.arch.run { contains("aarch", ignoreCase = true) || contains("arm", ignoreCase = true) } @Suppress("ClassName") sealed class HostKind( val targetName: String ) { object LINUX : HostKind("linuxX64") object WINDOWS : HostKind("mingwX64") abstract class MACOS(targetName: String) : HostKind(targetName) object MACOS_X64 : MACOS("macosX64") object MACOS_ARM64 : MACOS("macosArm64") } val HOST_KIND by lazy { when { OS_NAME.contains("windows", true) -> HostKind.WINDOWS OS_NAME.contains("mac", true) -> { if (osDetector.isAarch) { HostKind.MACOS_ARM64 } else { HostKind.MACOS_X64 } } else -> HostKind.LINUX } } /// eg. "!a;!b" means to enable all targets but a or b /// eg. "a;b;!other" means to disable all targets but a or b val ENABLED_TARGETS by projectLazy { val targets = getMiraiTargetFromGradle() // enable all by default targets.split(';').toSet() } fun getMiraiTargetFromGradle() = System.getProperty("mirai.target") ?: System.getenv("mirai.target") ?: rootProject.getLocalProperty("projects.mirai-core.targets") ?: "others" fun isTargetEnabled(name: String): Boolean { return when { name in ENABLED_TARGETS -> true // explicitly enabled "!$name" in ENABLED_TARGETS -> false // explicitly disabled "~$name" in ENABLED_TARGETS -> false // explicitly disabled "!other" in ENABLED_TARGETS -> false // others disabled "~other" in ENABLED_TARGETS -> false // others disabled "!others" in ENABLED_TARGETS -> false // others disabled "~others" in ENABLED_TARGETS -> false // others disabled else -> true } } fun Set.filterTargets() = this.filter { isTargetEnabled(it) }.toSet() const val JVM_TOOLCHAIN_VERSION = 8 val JVM_TOOLCHAIN_ENABLED by projectLazy { rootProject.getLocalProperty("mirai.enable.jvmtoolchain.special", true) } /** * ## Android Test 结构 * * 如果[启用 Android Instrumented Test][ENABLE_ANDROID_INSTRUMENTED_TESTS], 将会配置使用 Android SDK 配置真 Android target, * `androidMain` 将能访问 Android SDK, 也能获得针对 Android 的 IDE 错误检查. */ fun Project.configureJvmTargetsHierarchical(androidNamespace: String) { extensions.getByType(KotlinMultiplatformExtension::class.java).apply { if (JVM_TOOLCHAIN_ENABLED) { jvmToolchain(JVM_TOOLCHAIN_VERSION) } val commonMain by sourceSets.getting val commonTest by sourceSets.getting if (IDEA_ACTIVE) { jvm("jvmBase") { // dummy target for resolution, not published compilations.all { // magic to help IDEA this.compileTaskProvider.configure { enabled = false } } attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.common) // magic // avoid resolution when other modules dependsOn this project attributes.attribute(MIRAI_PLATFORM_ATTRIBUTE, "jvmBase") attributes.attribute(MIRAI_PLATFORM_INTERMEDIATE, true) // no shadow } } else { // if not in IDEA, no need to create intermediate targets. } val jvmBaseMain by lazy { sourceSets.maybeCreate("jvmBaseMain").apply { dependsOn(commonMain) } } val jvmBaseTest by lazy { sourceSets.maybeCreate("jvmBaseTest").apply { dependsOn(commonTest) } } if (isTargetEnabled("android")) { configureAndroidTarget(androidNamespace) } if (isTargetEnabled("jvm")) { jvm("jvm") val jvmMain by sourceSets.getting val jvmTest by sourceSets.getting jvmMain.dependsOn(jvmBaseMain) jvmTest.dependsOn(jvmBaseTest) } } } fun String.titlecase(): String { if (this.isEmpty()) return this val c = get(0) return replaceFirst(c, Character.toTitleCase(c)) } ================================================ FILE: buildSrc/src/main/kotlin/JvmDependencies.kt ================================================ /* * 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("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") import org.gradle.api.artifacts.ExternalModuleDependency import org.gradle.api.artifacts.dsl.DependencyHandler import org.gradle.kotlin.dsl.accessors.runtime.addDependencyTo @Suppress("unused") fun DependencyHandler.compileAndTestRuntime(any: Any) { add("compileOnly", any) add("testImplementation", any) } fun DependencyHandler.smartApi( dependencyNotation: String ): ExternalModuleDependency { return smart("api", dependencyNotation) } fun DependencyHandler.smartImplementation( dependencyNotation: String ): ExternalModuleDependency { return smart("implementation", dependencyNotation) } private fun DependencyHandler.smart( configuration: String, dependencyNotation: String ): ExternalModuleDependency { return addDependencyTo( this, configuration, dependencyNotation ) { fun exclude(group: String, module: String) { exclude( mapOf( "group" to group, "module" to module ) ) } exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8") exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-stdlib-common") exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core-common") exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core") exclude("org.jetbrains.kotlinx", "kotlinx-serialization-common") exclude("org.jetbrains.kotlinx", "kotlinx-serialization-core") } } ================================================ FILE: buildSrc/src/main/kotlin/JvmPublishing.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress( "NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS", "RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS" ) import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import keys.SecretKeys import org.gradle.api.Project import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.getByName import org.gradle.kotlin.dsl.register import java.io.File fun Project.configureRemoteRepos() { tasks.register("ensureMavenCentralAvailable") { doLast { val keys = SecretKeys.getCache(project) if (!keys.loadKey("sonatype").isValid) { error("Maven Central isn't available.") } } } publishing { // sonatype val keys = SecretKeys.getCache(project) repositories { maven { name = "MiraiStageRepo" val stageRepoLoc = getLocalProperty("publishing.stage-repo")?.let(::File) ?.takeIf { it.exists() } ?: rootProject.file("ci-release-helper/stage-repo") url = stageRepoLoc.also { it.mkdirs() }.toURI() } if (System.getenv("MIRAI_IS_SNAPSHOTS_PUBLISHING")?.toBoolean() == true) { maven { name = "MiraiRepo" setUrl(System.getenv("SNAPSHOTS_PUBLISHING_URL")) credentials { username = System.getenv("SNAPSHOTS_PUBLISHING_USER") password = System.getenv("SNAPSHOTS_PUBLISHING_KEY") } } } val sonatype = keys.loadKey("sonatype") if (sonatype.isValid) { maven { name = "MavenCentral" // Maven Central setUrl("https://oss.sonatype.org/service/local/staging/deploy/maven2") credentials { username = sonatype.user password = sonatype.password } } } else { logger.info("Sonatype is not available, Maven Central repository is not configured") } } } } @Suppress("NOTHING_TO_INLINE") inline fun Project.configurePublishing( artifactId: String, vcs: String = "https://github.com/mamoe/mirai", addProjectComponents: Boolean = true, skipPublicationSetup: Boolean = false, addShadowJar: Boolean = true ) { configureRemoteRepos() if (skipPublicationSetup) return val shadowJar = if (!addProjectComponents || !addShadowJar) null else tasks.register("shadowJar") { archiveClassifier.set("all") manifest.inheritFrom(tasks.getByName("jar").manifest) from(project.sourceSets["main"].output) configurations = listOfNotNull(project.configurations.findByName("runtimeClasspath") ?: project.configurations["runtime"]) exclude("META-INF/INDEX.LIST", "META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "module-info.class") } val sourcesJar = if (!addProjectComponents) null else tasks.register("sourcesJar") { archiveClassifier.set("sources") from(sourceSets["main"].allSource) } val stubJavadoc = if (!addProjectComponents) null else tasks.register("javadocJar") { @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") archiveClassifier.set("javadoc") } publishing { publications { register("mavenJava", MavenPublication::class) { if (addProjectComponents) from(components["java"]) groupId = rootProject.group.toString() setArtifactId(artifactId) version = project.version.toString() setupPom( project = project, vcs = vcs ) sourcesJar?.let { artifact(it) } stubJavadoc?.get()?.let { artifact(it) } shadowJar?.get()?.let { artifact(it) } } } } } ================================================ FILE: buildSrc/src/main/kotlin/KotlinMetadataPatcher.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import com.google.gson.Gson import com.google.gson.GsonBuilder import com.google.gson.JsonElement import com.google.gson.JsonPrimitive import org.gradle.api.GradleException import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.publish.tasks.GenerateModuleMetadata import shadow.relocationFilters import java.io.File import java.io.InputStream import java.io.OutputStream import java.security.MessageDigest fun Project.configurePatchKotlinModuleMetadataTask( relocatedPublicationName: String, relocateDependencies: Task, originalPublicationName: String ) { // We will modify Kotlin metadata, so do generate metadata before relocation val generateMetadataTask = tasks.getByName("generateMetadataFileFor${originalPublicationName.titlecase()}Publication") as GenerateModuleMetadata publications.getByName(relocatedPublicationName) { this as MavenPublication this.artifact(generateMetadataTask.outputFile) { classifier = null extension = "module" } } generateMetadataTask.dependsOn(relocateDependencies) val patchMetadataTask = tasks.create("patchMetadataFileFor${relocatedPublicationName.capitalize()}RelocatedPublication") { group = "mirai" generateMetadataTask.finalizedBy(this) dependsOn(generateMetadataTask) dependsOn(relocateDependencies) // remove dependencies in Kotlin module metadata doLast { // mirai-core-jvm-2.13.0.module val file = generateMetadataTask.outputFile.asFile.get() val metadata = Gson().fromJson( file.readText(), JsonElement::class.java ).asJsonObject val metadataVersion = metadata["formatVersion"]?.asString check(metadataVersion == "1.1") { "Unsupported Kotlin metadata version. version=$metadataVersion, file=${file.absolutePath}" } for (variant in metadata["variants"]!!.asJsonArray) { patchKotlinMetadataVariant(variant, relocateDependencies.outputs.files.singleFile) } file.writeText(GsonBuilder().setPrettyPrinting().create().toJson(metadata)) } } // Set "publishKotlinMultiplatformPublicationTo*" and "publish${targetName.capitalize()}PublicationTo*" dependsOn patchMetadataTask if (project.kotlinMpp != null) { tasks.filter { it.name.startsWith("publishKotlinMultiplatformPublicationTo") }.let { publishTasks -> if (publishTasks.isEmpty()) { throw GradleException("[Shadow Relocation] Cannot find publishKotlinMultiplatformPublicationTo for project '${project.path}'.") } publishTasks.forEach { it.dependsOn(patchMetadataTask) } } tasks.filter { it.name.startsWith("publish${relocatedPublicationName.capitalize()}PublicationTo") } .let { publishTasks -> if (publishTasks.isEmpty()) { throw GradleException("[Shadow Relocation] Cannot find publish${relocatedPublicationName.capitalize()}PublicationTo for project '${project.path}'.") } publishTasks.forEach { it.dependsOn(patchMetadataTask) } } } } private fun Project.patchKotlinMetadataVariant(variant: JsonElement, relocatedJar: File) { val dependencies = variant.asJsonObject["dependencies"]!!.asJsonArray dependencies.removeAll { dependency -> val dep = dependency.asJsonObject val groupId = dep["group"]!!.asString val artifactId = dep["module"]!!.asString relocationFilters.any { filter -> filter.matchesDependency( groupId = groupId, artifactId = artifactId ) }.also { println("[Shadow Relocation] Filtering out $groupId:$artifactId from Kotlin module") } } /* "files": [ { "name": "mirai-core-jvm-2.99.0-local.jar", "url": "mirai-core-jvm-2.99.0-local.jar", "size": 14742378, "sha512": "7ab4afc88384a58687467ba13c6aefeda20fa53fd7759dc2bc78b2d46a6285f94ba6ccae426d192e7745f773401b3cb42a853e5445dc23bdcb1b5295e78ff71c", "sha256": "772f593bfb85a80794693d4d9dfe2f77c222cfe9ca7e0d571abaa320e7aa82d3", "sha1": "cb7937269d29b574725d6f28668847fd672de7cf", "md5": "3fca635ba5e55b7dd56c552e4ca01f7e" } ] */ val files = variant.asJsonObject["files"].asJsonArray val filesList = files.toList() files.removeAll { true } for (publishedFile0 in filesList) { val publishedFile = publishedFile0.asJsonObject val name = publishedFile["name"].asJsonPrimitive.asString if (name.endsWith(".jar")) { logPublishing { "Patching Kotlin Metadata: file $name" } for (algorithm in ALGORITHMS) { publishedFile.add(algorithm, JsonPrimitive(relocatedJar.digest(algorithm))) } publishedFile.add("size", JsonPrimitive(relocatedJar.length())) } else { error("Unexpected file '$name' while patching Kotlin metadata") } files.add(publishedFile) } } private val ALGORITHMS = listOf("md5", "sha1", "sha256", "sha512") fun File.digest(algorithm: String): String { val arr = inputStream().buffered().use { it.digest(algorithm) } return arr.toUHexString("").lowercase() } fun InputStream.digest(algorithm: String): ByteArray { val digest = MessageDigest.getInstance(algorithm) digest.reset() use { input -> object : OutputStream() { override fun write(b: Int) { digest.update(b.toByte()) } override fun write(b: ByteArray, off: Int, len: Int) { digest.update(b, off, len) } }.use { output -> input.copyTo(output) } } return digest.digest() } ================================================ FILE: buildSrc/src/main/kotlin/LocalProperties.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ import org.gradle.api.Project import java.util.* /* * Copyright 2019-2022 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/dev/LICENSE */ object BuildSrcRootProjectHolder { lateinit var value: Project var lastUpdateTime: Long = 0 } val rootProject: Project get() = BuildSrcRootProjectHolder.value fun projectLazy(action: () -> T): Lazy { val projLazy = object : Lazy { private lateinit var delegate: Lazy private var holdTime: Long = -1 override val value: T get() { if (holdTime != BuildSrcRootProjectHolder.lastUpdateTime) { synchronized(this) { if (holdTime != BuildSrcRootProjectHolder.lastUpdateTime) { delegate = lazy(action) holdTime = BuildSrcRootProjectHolder.lastUpdateTime } } } return delegate.value } override fun isInitialized(): Boolean { if (!::delegate.isInitialized) return false if (holdTime == BuildSrcRootProjectHolder.lastUpdateTime) { return delegate.isInitialized() } return false } } return projLazy } private lateinit var localProperties: Properties private var localPropertiesEdition: Long = 0 private fun Project.loadLocalPropertiesIfNecessary() { val theFile = rootProject.projectDir.resolve("local.properties") fun isNecessary(): Boolean { if (!::localProperties.isInitialized) return true if (theFile.exists()) { if (localPropertiesEdition != theFile.lastModified()) { return true } } else { if (localPropertiesEdition != 0L) { // deleted return true } } return false } if (!isNecessary()) return localProperties = Properties().apply { localPropertiesEdition = if (theFile.exists()) { theFile.bufferedReader().use { load(it) } theFile.lastModified() } else { 0 } } } fun Project.getLocalProperty(name: String): String? { loadLocalPropertiesIfNecessary() return localProperties.getProperty(name) } fun Project.getLocalProperty(name: String, default: String): String { return getLocalProperty(name) ?: default } fun Project.getLocalProperty(name: String, default: Int): Int { return getLocalProperty(name)?.toInt() ?: default } fun Project.getLocalProperty(name: String, default: Boolean): Boolean { return getLocalProperty(name)?.toBoolean() ?: default } ================================================ FILE: buildSrc/src/main/kotlin/Mpp.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import org.gradle.api.NamedDomainObjectCollection import org.gradle.api.NamedDomainObjectProvider import org.gradle.api.Project import org.gradle.api.artifacts.DependencySubstitutions import org.gradle.api.artifacts.ResolutionStrategy import org.gradle.api.artifacts.component.ComponentSelector import org.gradle.api.plugins.ExtensionAware import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension val NamedDomainObjectCollection.androidMain: NamedDomainObjectProvider get() = named("androidMain") val NamedDomainObjectCollection.jvmMain: NamedDomainObjectProvider get() = named("jvmMain") val NamedDomainObjectCollection.androidTest: NamedDomainObjectProvider get() = named("androidTest") val NamedDomainObjectCollection.jvmTest: NamedDomainObjectProvider get() = named("jvmTest") val NamedDomainObjectCollection.commonMain: NamedDomainObjectProvider get() = named("commonMain") inline fun forMppModules(action: (suffix: String) -> Unit) { arrayOf( "", "-common", "-metadata", "-jvm", "-jdk7", "-jdk8" ).forEach(action) } fun Project.substituteDependenciesUsingExpectedVersion() { configurations.all { resolutionStrategy.substituteDependencies { forMppModules { suffix -> module("org.jetbrains.kotlin:kotlin-stdlib$suffix") using module("org.jetbrains.kotlin:kotlin-stdlib$suffix:${Versions.kotlinStdlib}") module("org.jetbrains.kotlin:kotlin-reflect$suffix") using module("org.jetbrains.kotlin:kotlin-reflect$suffix:${Versions.kotlinStdlib}") module("org.jetbrains.kotlinx:kotlinx-coroutines-core$suffix") using module(kotlinx("coroutines-core$suffix", Versions.coroutines)) module("org.jetbrains.kotlinx:kotlinx-coroutines-debug$suffix") using module(kotlinx("coroutines-debug$suffix", Versions.coroutines)) } } } } class ResolutionStrategyDsl( private val origin: DependencySubstitutions ) : DependencySubstitutions by origin { infix fun ComponentSelector.using(notation: ComponentSelector): DependencySubstitutions.Substitution { return substitute(this).using(notation) } } fun ResolutionStrategy.substituteDependencies(action: ResolutionStrategyDsl.() -> Unit) { dependencySubstitution { action(ResolutionStrategyDsl(this)) } } val Project.kotlinMpp get() = runCatching { (this as ExtensionAware).extensions.getByName("kotlin") as? KotlinMultiplatformExtension }.getOrNull() val Project.kotlinJvm get() = runCatching { (this as ExtensionAware).extensions.getByName("kotlin") as? KotlinJvmProjectExtension }.getOrNull() ================================================ FILE: buildSrc/src/main/kotlin/MppPublishing.kt ================================================ /* * 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 */ import org.gradle.api.Project import org.gradle.api.XmlProvider import org.gradle.api.publish.maven.MavenArtifact import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.TaskProvider import org.gradle.jvm.tasks.Jar import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.register import shadow.RelocationConfig import shadow.relocationFilters inline fun Project.logPublishing(message: () -> String) { logger.debug("[Publishing] Configuring {}", message()) } fun Project.configureMppPublishing() { configureRemoteRepos() // mirai does some magic on MPP targets afterEvaluate { // tasks.findByName("compileKotlinCommon")?.enabled = false // tasks.findByName("compileTestKotlinCommon")?.enabled = false // tasks.findByName("compileCommonMainKotlinMetadata")?.enabled = false // tasks.findByName("compileKotlinMetadata")?.enabled = false // TODO: 2021/1/30 如果添加 JVM 到 root module, 这个 task 会失败因 root module artifacts 有变化 // tasks.findByName("generateMetadataFileForKotlinMultiplatformPublication")?.enabled = false // FIXME: 2021/1/21 } val stubJavadoc = tasks.register("javadocJar", Jar::class) { @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") archiveClassifier.set("javadoc") } afterEvaluate { publishing { logPublishing { "Publications: ${publications.joinToString { it.name }}" } val (nonJvmPublications, jvmPublications) = publications.filterIsInstance() .partition { publication -> tasks.findByName(RelocationConfig.taskNameForRelocateDependencies(publication.name)) == null } for (publication in nonJvmPublications) { configureMultiplatformPublication(publication, stubJavadoc, publication.name) } for (publication in jvmPublications) { // publications.remove(publication) // val newPublication = // publications.register(publication.name + "Shadowed", MavenPublication::class.java) { // val target = kotlinTargets.orEmpty().single { it.targetName == publication.name } // from(target.components.single()) // this.groupId = publication.groupId // this.artifactId = publication.artifactId // this.version = publication.version // artifacts { // publication.artifacts // .filter { !(it.classifier.isNullOrEmpty() && it.extension == "jar") } // not .jar // .forEach { artifact(it) } // copy Kotlin metadata artifacts // } // artifacts.removeAll { it.classifier.isNullOrEmpty() && it.extension == "jar" } // // add relocated jar // tasks.findByName("relocate${publication.name.titlecase()}Dependencies")?.let { relocation -> // artifact(relocation) { // classifier = "" // extension = "jar" // } // } // } configureMultiplatformPublication(publication, stubJavadoc, publication.name) publication.apply { artifacts.filter { it.classifier.isNullOrEmpty() && it.extension == "jar" }.forEach { it.builtBy(tasks.findByName(RelocationConfig.taskNameForRelocateDependencies(publication.name))) } } } } } } private fun Project.configureMultiplatformPublication( publication: MavenPublication, stubJavadoc: TaskProvider, moduleName: String, ) { // Maven Central always require javadoc.jar publication.artifact(stubJavadoc) publication.setupPom(project) logPublishing { publication.name + ": moduleName = $moduleName" } when (moduleName) { "kotlinMultiplatform" -> { publication.artifactId = project.name // publishPlatformArtifactsInRootModule(publications.getByName("jvm") as MavenPublication) // TODO: 2021/1/30 现在添加 JVM 到 root module 会导致 Gradle 依赖无法解决 // https://github.com/mamoe/mirai/issues/932 } "metadata" -> { // TODO: 2021/1/21 seems no use. none `type` is "metadata" publication.artifactId = "${project.name}-metadata" } "jvm" -> { publication.artifactId = "${project.name}-$moduleName" useRelocatedPublication(publication, moduleName) } else -> { // "jvm", "native", "js", "common" publication.artifactId = "${project.name}-$moduleName" } } } /** * Creates a new publication and disables [publication]. */ private fun Project.useRelocatedPublication( publication: MavenPublication, moduleName: String ) { val relocatedPublicationName = RelocationConfig.relocatedPublicationName(publication.name) registerRelocatedPublication(relocatedPublicationName, publication, moduleName) logPublishing { "Registered relocated publication `$relocatedPublicationName` for module $moduleName, for project ${project.path}" } // Add task dependencies addTaskDependenciesForRelocatedPublication(moduleName, relocatedPublicationName) val relocateDependencies = tasks.getByName(RelocationConfig.taskNameForRelocateDependencies(moduleName)) configurePatchKotlinModuleMetadataTask(relocatedPublicationName, relocateDependencies, publication.name) } private fun Project.registerRelocatedPublication( relocatedPublicationName: String, publication: MavenPublication, moduleName: String ) { // copy POM XML, since POM contains transitive dependencies var patched = false lateinit var oldXmlProvider: XmlProvider publication.pom.withXml { oldXmlProvider = this } publications.register(relocatedPublicationName, MavenPublication::class.java) { this.artifactId = publication.artifactId this.groupId = publication.groupId this.version = publication.version this.artifacts.addAll(publication.artifacts.filterNot { it.classifier == null && it.extension == "jar" }) project.tasks.findByName(RelocationConfig.taskNameForRelocateDependencies(moduleName)) ?.let { relocateDependencies -> this.artifact(relocateDependencies) { this.classifier = null this.extension = "jar" } } pom.withXml { val newXml = this for (newChild in newXml.asNode().childrenNodes()) { newXml.asNode().remove(newChild) } // Note: `withXml` is lazy, it is evaluated only when `generatePomFileFor...` for (oldChild in oldXmlProvider.asNode().childrenNodes()) { newXml.asNode().append(oldChild) } removeDependenciesInMavenPom(this) patched = true } } tasks.matching { it.name.startsWith("publish${relocatedPublicationName.titlecase()}PublicationTo") }.all { dependsOn("generatePomFileFor${relocatedPublicationName.titlecase()}Publication") } tasks.matching { it.name == "generatePomFileFor${relocatedPublicationName.titlecase()}Publication" }.all { dependsOn(tasks.getByName("generatePomFileFor${publication.name.titlecase()}Publication")) doLast { check(patched) { "POM is not patched" } } } } private fun Project.addTaskDependenciesForRelocatedPublication(moduleName: String, relocatedPublicationName: String) { val originalTaskNamePrefix = "publish${moduleName.titlecase()}PublicationTo" val relocatedTaskName = "publish${relocatedPublicationName.titlecase()}PublicationTo" tasks.configureEach { if (!name.startsWith(originalTaskNamePrefix)) return@configureEach val originalTask = this this.enabled = false this.description = "${this.description} ([mirai] disabled in favor of $relocatedTaskName)" val relocatedTasks = project.tasks.filter { it.name.startsWith(relocatedTaskName) }.toTypedArray() check(relocatedTasks.isNotEmpty()) { "relocatedTasks is empty" } relocatedTasks.forEach { publishRelocatedPublication -> publishRelocatedPublication.dependsOn(*this.dependsOn.toTypedArray()) logger.info( "[Publishing] $publishRelocatedPublication now dependsOn tasks: " + this.dependsOn.joinToString() ) } project.tasks.filter { it.dependsOn.contains(originalTask) } .forEach { it.dependsOn(*relocatedTasks) } } } // Remove relocated dependencies in Maven pom private fun Project.removeDependenciesInMavenPom(xmlProvider: XmlProvider) { xmlProvider.run { val node = asNode().getSingleChild("dependencies") val dependencies = node.childrenNodes() logger.info("[Shadow Relocation] deps: {}", dependencies) logger.info( "[Shadow Relocation] All filter notations: {}", relocationFilters.flatMap { it.notations.notations() }.joinToString("\n") ) dependencies.forEach { dep -> val groupId = dep.getSingleChild("groupId").value().toString().removeSurrounding("[", "]") val artifactId = dep.getSingleChild("artifactId").value().toString().removeSurrounding("[", "]") logger.info("[Shadow Relocation] Checking $groupId:$artifactId") if ( relocationFilters.any { filter -> filter.matchesDependency(groupId = groupId, artifactId = artifactId) } ) { logger.info("[Shadow Relocation] Filtering out '$groupId:$artifactId' from pom for project '${project.path}'") check(node.remove(dep)) { "Failed to remove dependency node" } } } } } val publishPlatformArtifactsInRootModule: Project.(MavenPublication) -> Unit = { platformPublication -> lateinit var platformPomBuilder: XmlProvider platformPublication.pom.withXml { platformPomBuilder = this } publications.getByName("kotlinMultiplatform").let { it as MavenPublication }.run { this.artifacts.removeIf { it.classifier == null && it.extension == "jar" // mirai-core\build\libs\mirai-core-2.0.0.jar, classifier=null, ext=jar } logPublishing { "Existing artifacts in kotlinMultiplatform: " + this.artifacts.joinToString("\n", prefix = "\n") { it.smartToString() } } platformPublication.artifacts.forEach { logPublishing { "Adding artifact to kotlinMultiplatform: ${it.smartToString()}" } artifact(it) } // replace pom pom.withXml { val pomStringBuilder = asString() pomStringBuilder.setLength(0) platformPomBuilder.toString().lines().forEach { line -> if (!line.contains(" - 修复验证码解决器报错时没有按照预期停止 bot 的错误逻辑 - 修复 `java.lang.ArrayIndexOutOfBoundsException: arraycopy: last destination index 4132 out of bounds for byte[4096]` (#2309) ## mirai-console ### 优化修复 - 修复 console 显示进度条的时候出现死锁 (#2322) - 修复 console 中 Gradle 缓存路径检查不正确的问题 (#2357) - 修复 console 中指定多个远程仓库地址时未按照预期工作的错误 (#2358 by @cssxsh) ================================================ FILE: ci-release-helper/changelogs/2.13.2.md ================================================ ## mirai-core ### 优化和修复 - 修复发送图片时发生 `java.lang.NoSuchMethodError` 的问题 (#2381) > 自 2.13.0 ================================================ FILE: ci-release-helper/changelogs/2.13.3.md ================================================ ## mirai-core ### 优化和修复 - 修复登录时出现 `DecryptionFailedException` 的问题 (#2167, #2419 by @sandtechnology) - 修复使用 `ANDROID_PHONE` 或 `ANDROID_PAD` 登录时出现错误 "版本过低" 的问题 (#2405, #2423 by @sandtechnology) - 为 `friendGroupId` 增加默认值 `0`, 以兼容旧缓存 (#2403 by @cssxsh) ================================================ FILE: ci-release-helper/changelogs/2.13.4.md ================================================ ## mirai-core ### 优化和修复 - 修复无法登录的问题 (#2433 by @sandtechnology) > 此修复也在 2.14.0-RC 包含 ================================================ FILE: ci-release-helper/changelogs/2.14.0-RC.md ================================================ ## mirai-core ### 不兼容变更 - 删除 SwingLoginSolver (#2410) > 它以前是设计给解决图形验证码, 而现在基本不会遇到图形验证码了。现在将默认使用命令行版本 LoginSolver。 > 以前使用 "-Dno-desktop" 可以禁用 SwingLoginSolver,现在这个选项将没有效果,不会报错。 - 现在使用 RemoteFile 将会得到编译错误 > RemoteFile 早在 2.8 就弃用了, 使用时会得到警告. 现在起使用将会得到编译错误. ### 新特性 - 群聊获取历史消息 (`Group` 实现 `RoamingSupported`) (#1866, #2332 by @StageGuard ) > 可使用 `group.roamingMessages` / `group.getRoamingMessages()` - 商城表情: 石头剪刀布 (#2220 by @cssxsh ) > 类型名称为 `RockPaperScissors` - 群打卡事件支持 (#1663, #2217 by @cssxsh ) > 类型名称为 `SignEvent` - `Announcement` 现在可以获取 已确认/未确认 的群成员 (#2255 by @cssxsh ) ### 优化和修复 - 修复无法登录的问题 (#2433 by @sandtechnology ) > 此修复也会在 2.13.4 包含 - 修复群员列表缓存文件不完整时会导致 bot init 失败的错误 (#2399) - 当设备信息等变更的时候清除缓存 (#2346, #2388) > 不清除会导致登录失败等问题 - 修复 iOS 无法查看私聊转发消息的问题 (#1575) - 修复转发消息存在特殊字符时无法加载的问题 (#2241) - 修正 `contentToString()` 注释不严谨处 (#2373, #2374 by @MrXiaoM ) - 修正 PokeMessage.id (#2170) - 修复 QuoteReply 无法引用 bot 发出的 ForwardMessage (#2342) - 修改日志等级的颜色 (#2336) > 现在 WARN 是金黄色,而不是红色 ## mirai-core-mock ### 优化和修复 - 避免 MockAbsoluteFile.md5/sha1 可能为空 (#2436 by @Nambers ) - 修复部分信息撤回逻辑不正确的错误 (#2421 by @Nambers ) - 修复 `MockNormalMember.modiyAdmin()` 逻辑错误 (#2420 by @Nambers ) - 修复 uploadMockImage 上传相同文件时触发 java.nio.file.FileAlreadyExistsException (#2401) - 引入 AvatarGenerator - 修复联系人头像等信息更新同步的问题 ## mirai-console ### 新特性 - JvmPlugin 中现在可以调用 mirai console 所使用的针对 Kotlin object 优化后的 SPI Service Loader (#2247 by @cssxsh) ### 优化和修复 - 修复 `JvmPlugin.onDisable` 被多次执行的错误 (#2015, #2397) - 修复低版本 Android 系统报错 `java.lang.NoSuchMethodError: No virtual method getDeclaredAnnotation(Ljava/lang/Class;)L` (#2354 by @zhaodice) - 在命令的报错中去除不必要的 `InvocationTargetException` 包装 (#2258 by @cssxsh ) - 优化 MiraiLogger (在 mirai-console) 性能 (#2341) - SLF4J 支持 (#2341) ## IDEA - 支持 2022.3 (#2372) > - 新插件版本号为 `223-2.14.0-172-1` > - 新版本只支持 2022.3 + Kotlin 1.7.20 (因为 2022.3 捆绑 1.7.20) --------------- ## 关于 mirai-console SLF4J 支持 (#2341) ### 配置文件修改 > 修改均为默认配置, 先前已经生成的配置不会进行修改 ### `Logger.yml` ```diff # 默认日志输出等级 # 可选值: ALL, VERBOSE, DEBUG, INFO, WARNING, ERROR, NONE defaultPriority: INFO # 特定日志记录器输出等级 loggers: example.logger: NONE console.debug: NONE Bot: ALL + org.eclipse.aether.internal: INFO + org.apache.http.wire: INFO +# 是否启动外部日志框架桥接 +binding: + slf4j: true ``` ### ABI 变更 > 注: 实际上 console 没有直接的 ABI 变更 依赖更新: `org.slf4j:slf4j-api:1.7.32` -> `2.0.3` 此依赖的更新只会影响 `slf4j-api` 的对接, 并不会影响 `slf4j-api` 的单纯使用 > 即不会对插件有任何影响, 只会对部分对 console 进行高度自定义的会有少许影响 ### 其他 API 变更 - mirai-logging-log4j2 现在使用 `org.apache.logging.log4j:log4j-slf4j2-impl` - mirai-logging-slf4j-logback 现在使用 1.3.4 - mirai-core-all 现在携带的是 slf4j 2.0.x ------- 新年快乐! 🎆🎉 ================================================ FILE: ci-release-helper/changelogs/2.14.0.md ================================================ ## mirai-core ### 不兼容变更 - 删除 SwingLoginSolver (#2410) > 它以前是设计给解决图形验证码, 而现在基本不会遇到图形验证码了。现在将默认使用命令行版本 LoginSolver。 > 以前使用 "-Dno-desktop" 可以禁用 SwingLoginSolver,现在这个选项将没有效果,不会报错。 - 现在使用 RemoteFile 将会得到编译错误 > RemoteFile 早在 2.8 就弃用了, 使用时会得到警告. 现在起使用将会得到编译错误. ### 新特性 - 群聊获取历史消息 (`Group` 实现 `RoamingSupported`) (#1866, #2332 by @StageGuard ) > 可使用 `group.roamingMessages` / `group.getRoamingMessages()` - 商城表情: 石头剪刀布 (#2220 by @cssxsh ) > 类型名称为 `RockPaperScissors` - 群打卡事件支持 (#1663, #2217 by @cssxsh ) > 类型名称为 `SignEvent` - `Announcement` 现在可以获取 已确认/未确认 的群成员 (#2255 by @cssxsh ) ### 优化和修复 - 修复无法登录的问题 (#2433 by @sandtechnology ) > 此修复也会在 2.13.4 包含 - 修复群员列表缓存文件不完整时会导致 bot init 失败的错误 (#2399) - 当设备信息等变更的时候清除缓存 (#2346, #2388) > 不清除会导致登录失败等问题 - 修复 iOS 无法查看私聊转发消息的问题 (#1575) - 修复转发消息存在特殊字符时无法加载的问题 (#2241) - 修正 `contentToString()` 注释不严谨处 (#2373, #2374 by @MrXiaoM ) - 修正 PokeMessage.id (#2170) - 修复 QuoteReply 无法引用 bot 发出的 ForwardMessage (#2342) - 修改日志等级的颜色 (#2336) > 现在 WARN 是金黄色,而不是红色 ## mirai-core-mock ### 优化和修复 - 避免 MockAbsoluteFile.md5/sha1 可能为空 (#2436 by @Nambers ) - 修复部分信息撤回逻辑不正确的错误 (#2421 by @Nambers ) - 修复 `MockNormalMember.modiyAdmin()` 逻辑错误 (#2420 by @Nambers ) - 修复 uploadMockImage 上传相同文件时触发 java.nio.file.FileAlreadyExistsException (#2401) - 引入 AvatarGenerator - 修复联系人头像等信息更新同步的问题 ## mirai-console ### 新特性 - JvmPlugin 中现在可以调用 mirai console 所使用的针对 Kotlin object 优化后的 SPI Service Loader (#2247 by @cssxsh) ### 优化和修复 - 修复 `JvmPlugin.onDisable` 被多次执行的错误 (#2015, #2397) - 修复低版本 Android 系统报错 `java.lang.NoSuchMethodError: No virtual method getDeclaredAnnotation(Ljava/lang/Class;)L` (#2354 by @zhaodice) - 在命令的报错中去除不必要的 `InvocationTargetException` 包装 (#2258 by @cssxsh ) - 优化 MiraiLogger (在 mirai-console) 性能 (#2341) - SLF4J 支持 (#2341) - 修复 Plugin.onLoad 抛出错误时没有终止 console 的错误 > 此错误在 2.14.0-RC 引入 ## IDEA - 支持 2022.3 (#2372) > - 新插件版本号为 `223-2.14.0-172-1` > - 新版本只支持 2022.3 + Kotlin 1.7.20 (因为 2022.3 捆绑 1.7.20) --------------- ## 关于 mirai-console SLF4J 支持 (#2341) ### 配置文件修改 > 修改均为默认配置, 先前已经生成的配置不会进行修改 ### `Logger.yml` ```diff # 默认日志输出等级 # 可选值: ALL, VERBOSE, DEBUG, INFO, WARNING, ERROR, NONE defaultPriority: INFO # 特定日志记录器输出等级 loggers: example.logger: NONE console.debug: NONE Bot: ALL + org.eclipse.aether.internal: INFO + org.apache.http.wire: INFO +# 是否启动外部日志框架桥接 +binding: + slf4j: true ``` ### ABI 变更 > 注: 实际上 console 没有直接的 ABI 变更 依赖更新: `org.slf4j:slf4j-api:1.7.32` -> `2.0.3` 此依赖的更新只会影响 `slf4j-api` 的对接, 并不会影响 `slf4j-api` 的单纯使用 > 即不会对插件有任何影响, 只会对部分对 console 进行高度自定义的会有少许影响 ### 其他 API 变更 - mirai-logging-log4j2 现在使用 `org.apache.logging.log4j:log4j-slf4j2-impl` - mirai-logging-slf4j-logback 现在使用 1.3.4 - mirai-core-all 现在携带的是 slf4j 2.0.x ================================================ FILE: ci-release-helper/changelogs/2.15.0-M1.md ================================================ ## mirai-core ### 不兼容变更 - 删除了旧版的为兼容 Java 生成的阻塞式方法桥 > 这只会导致依赖 mirai [2.1.0](https://github.com/mamoe/mirai/releases/tag/2.1.0) (发布于 2 年前) 编译的 Java 代码现在无法使用 mirai 2.15.0-M1 级以上版本运行. 将它们使用 2.15.0-M1 及以上重新编译即可运行. > > 这是因为 KJBB 以前有 bug, 会生成返回值为 `Unit` 的方法桥. mirai 为了兼容, 一直让 KJBB 既生成返回 `Unit` 的, 也生成返回 `void` 的. 但自 Kotiln 编译器 1.8.0 起, 其 IR lowering 会把 `companion object` 中的静态函数 `@JvmStatic` 的返回值由 `Unit` 变更为 `void`, 导致编译器插件 KJBB 不再能做兼容. ### 新特性 - 支持扫码登录 (#2502 with @StageGuard, #1281) 新的登录方法通过 `BotAuthorization` & `BotFactory.newBot(id: Long, authorization: BotAuthorization)` 登录 关于详细的使用方法请参考 `BotAuthorization` 的注释 扫码登录的实现不一定稳定 (因为涉及修改了大量内部登录和维护在线逻辑), 文档也还在正在准备中. **在 2.15.0-RC 可能会修改扫码登录的 API**. > mirai-console **尚未支持 **在命令中指定扫码登录, 但是提供了 `MiraiConsole.addBot(id: Long, authorization: BotAuthorization)` 用于扫码登录 ### 优化和修复 - 更新 Kotlin 到 1.8.10, kotlinx-serialization 到 1.5.0 (#2578) - 修复特殊情况可能无法加载 services 的问题 (#2268, #2511 by @Nambers, #2428 by @cssxsh) > 例如在 Minecraft 插件中 - 增加 TxCaptchaHelper 可用性无法保证的警告 (#2564 by @MrXiaoM) - 修正消息多态序列化, 输出的 JSON 不再包含多余的 "type" 字段 (#2414) - 修正群公告发送失败报错 `no login` (#2069, #2512 by @cssxsh) - 修正使用 `Announcements.get(fid)` 出现 `kotlinx.serialization.MissingFieldException: Field 'msg'` (#2509, #2512 by @cssxsh) - 修正短暂断网时不能成功重连 (#2488, #2504, #2505 by @sandtechnology) - 修复 `OfflineMessageSource` 回复时, 引用回复的 At 变空白的问题 (#2501) - 在无法连接服务器时在报错信息中携带尝试连接的服务器 (#2576 by @cssxsh) - 修正 dumpTlvMap 返回值不正确的问题 (内部) (#2557 by @MrXiaoM) - 修正文档细节 (#2547 by @7aGiven) ## mirai-core-mock - 在 upload 后的 MockImage 中提供 size 属性 (#2515) ## mirai-console ### 新特性 - JvmPlugin 以 `getResource` 方法获取全局资源文件 (#2536 by @ArgonarioD) - 添加新事件 `StartupEvent`, `AutoLoginEvent` (#2446 by @cssxsh) > 分别在 Console 启动完成后, 和自动登录后触发 ### 优化和修复 - 文档修正(#2503 by @7aGiven, #2506 by @7aGiven, #2457 by @char-46, #2577 by @cssxsh, #2491 by @EnchStudio) - 修复在Android系统运行时,被杀后台时抛出的 InterruptedException 导致崩溃 (#2474 by @zhaodice) - 修复使用 Console 扩展时,对于扩展的函数返回非 null 值报错的情况 (#2528 by @NoMathExpectation) ================================================ FILE: ci-release-helper/changelogs/README.md ================================================ # Changelogs 本目录存档历史版本的变更记录 ================================================ FILE: ci-release-helper/scripts/kill-java.js ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ let ostype = require('os').type(); let child_process = require('child_process'); if (ostype.toLowerCase().indexOf('windows') !== -1) { child_process.spawnSync('taskkill', ['/f', '/im', 'java*'], { stdio: "inherit" } ); } else { child_process.spawnSync('pkill', ['-9', 'java'], { stdio: "inherit" } ); } ================================================ FILE: ci-release-helper/src/CiHelper.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("CiHelperKt") package cihelper import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import java.io.File import java.io.OutputStream import java.net.URI import java.net.http.HttpClient import java.net.http.HttpRequest import java.net.http.HttpResponse import java.nio.charset.Charset import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Paths import java.nio.file.attribute.PosixFilePermission import java.nio.file.attribute.PosixFilePermissions import java.security.MessageDigest import java.util.* import java.util.stream.Collectors import kotlin.io.path.* private val hexTemplate: CharArray = "0123456789abcdef".toCharArray() private const val useragent = "Gradle/7.3.1 (Windows 10;10.0;amd64) (Azul Systems, Inc.;18.0.2.1;18.0.2.1+1)" fun ByteArray.hexToString(): String { val sb = StringBuilder(this.size * 2) forEach { sbyte -> sb.append(hexTemplate[sbyte.toInt().shr(4).and(0xF)]) sb.append(hexTemplate[sbyte.toInt().and(0xF)]) } return sb.toString() } private fun getAuth(): String { val cert_username = System.getenv("CERT_USERNAME") ?: System.getProperty("cihelper.cert.username") ?: error("CERT_USERNAME") val cert_password = System.getenv("CERT_PASSWORD") ?: System.getProperty("cihelper.cert.password") ?: error("CERT_PASSWORD") return "Basic " + Base64.getEncoder().encodeToString( ("$cert_username:$cert_password").toByteArray() ) } @Suppress("Since15") fun main(args: Array) { val projVer = System.getenv("PROJ_VERSION") ?: error("Please use `./gradlew runcihelper --args XXXX`") val projArtifacts = System.getenv("PROJ_ARTIFACTS")!!.split("|") val repoLoc = System.getenv("PROJ_MiraiStageRepo")!!.let { Paths.get(URI.create(it)) } if (args.isEmpty()) error("no action") val relatedRepoLoc = repoLoc.resolve("net/mamoe") val httpc = HttpClient.newBuilder().build() when (args[0]) { "sync-maven-metadata" -> { // https://repo1.maven.org/maven2/net/mamoe/mirai-core-all/maven-metadata.xml projArtifacts.forEach { projArtifact -> val savedLoc = relatedRepoLoc.resolve(projArtifact) .createDirectories() .resolve("maven-metadata.xml") println("[metadata.xml] Syncing $projArtifact") val verPath = relatedRepoLoc.resolve(projArtifact).resolve(projVer) val isNotEmpty = if (verPath.exists()) { Files.newDirectoryStream(verPath).use { it.iterator().hasNext() } } else false if (isNotEmpty) { println("[metadata.xml] Skipped $projArtifact because it was published to stage.") return@forEach } val rsp = httpc.send( HttpRequest.newBuilder( URI.create("https://repo1.maven.org/maven2/net/mamoe/$projArtifact/maven-metadata.xml") ).GET().build(), HttpResponse.BodyHandlers.ofFile(savedLoc) ) if (rsp.statusCode() != 200) { if (rsp.statusCode() == 404) { savedLoc.deleteIfExists() return@forEach } error("$rsp -> " + savedLoc.takeIf { it.isRegularFile() }?.readText()) } } } "create-stage-repo" -> { val rsp = httpc.send( HttpRequest.newBuilder( URI.create( "https://oss.sonatype.org/service/local/staging/profiles/${ System.getProperty( "cihelper.cert.profileid" ) }/start" ) ) .header("User-Agent", useragent) .header("Authorization", getAuth()) .header("Content-Type", "application/json;charset=utf-8") .POST(HttpRequest.BodyPublishers.ofString("{\"data\":{\"description\": \"mamoe/mirai release $projVer\"}}")) .build(), HttpResponse.BodyHandlers.ofString() ) if (rsp.statusCode() != 201) { error(rsp.toString()) } val rspx = Json.decodeFromString(JsonObject.serializer(), rsp.body()) val stagedRepositoryId = rspx["data"]!!.jsonObject["stagedRepositoryId"]!!.jsonPrimitive.content File("ci-release-helper").also { it.mkdirs() } .resolve("repoid").writeText(stagedRepositoryId) } "publish-to-maven-central" -> { // https://oss.sonatype.org/service/local/staging/deploy/maven2 relatedRepoLoc.listDirectoryEntries().forEach { subdir -> val verpath = subdir.resolve(projVer) val doDelete = if (!verpath.isDirectory()) { true } else { verpath.listDirectoryEntries().isEmpty() } if (doDelete) { subdir.toFile().deleteRecursively() } } val pendingFiles = Files.walk(relatedRepoLoc) .filter { it.isRegularFile() } .filter { !it.name.endsWith(".md5") && !it.name.endsWith(".sha1") } .filter { !it.name.endsWith(".asc") } .use { stream -> stream.collect(Collectors.toList()) } run `sign artifacts`@{ // build-gpg-sign/keys.gpg // build-gpg-sign/keys.gpg.pub val bgs = Paths.get("build-gpg-sign").toAbsolutePath() if (!bgs.isDirectory()) return@`sign artifacts` val gpgHomeDir = bgs.resolve("homedir") val bgsFile = bgs.toFile() fun execGpg(vararg cmd: String) { println("::group::${cmd.joinToString(" ")}") try { val exitcode = ProcessBuilder("gpg", "--homedir", "homedir", "--batch", "--no-tty", *cmd) .directory(bgsFile) .inheritIO() .start() .waitFor() if (exitcode != 0) { error("Exit code $exitcode != 0") } } finally { println("::endgroup::") } } if (!gpgHomeDir.resolve("pubring.kbx").exists()) { val keys = arrayOf("keys.gpg", "keys.gpg.pub") if (!keys.all { bgs.resolve(it).isRegularFile() }) return@`sign artifacts` val isPosix = FileSystems.getDefault().supportedFileAttributeViews().contains("posix") val dirPermissions = PosixFilePermissions.asFileAttribute( EnumSet.of( PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE, PosixFilePermission.OWNER_EXECUTE ) ) Files.createDirectories( gpgHomeDir, *if (isPosix) arrayOf(dirPermissions) else arrayOf(), ) keys.forEach { execGpg("--import", it) } } println("::group::Signing artifacts") pendingFiles.toList().asSequence().filterNot { it.name == "maven-metadata.xml" } .forEach { pendingFile -> val pt = pendingFile.absolutePathString() val ascFile = pendingFile.resolveSibling(pendingFile.name + ".asc") ascFile.deleteIfExists() execGpg("-a", "--detach-sig", "--sign", pt) pendingFiles.add(ascFile) } println("::endgroup::") } run `calc msg digest`@{ pendingFiles.toList().forEach { pendingFile -> val sha1MD = MessageDigest.getInstance("SHA-1") val md5MD = MessageDigest.getInstance("MD5") pendingFile.inputStream().use { content -> content.copyTo(object : OutputStream() { override fun write(b: Int) { sha1MD.update(b.toByte()) md5MD.update(b.toByte()) } override fun write(b: ByteArray, off: Int, len: Int) { sha1MD.update(b, off, len) md5MD.update(b, off, len) } }) } val sha1 = sha1MD.digest().hexToString() val mg5 = md5MD.digest().hexToString() val pfname = pendingFile.name val sha1File = pendingFile.resolveSibling("$pfname.sha1") val md5File = pendingFile.resolveSibling("$pfname.md5") sha1File.writeText(sha1) md5File.writeText(mg5) pendingFiles.add(sha1File) pendingFiles.add(md5File) } } pendingFiles.sort() println("::group::Publishing to Maven Central") val authorization = getAuth() val errors = mutableListOf() fun resolveSonatypeRepoLoc(): String { val repoIdPath = Paths.get("ci-release-helper/repoid") var repoId = "" if (repoIdPath.isRegularFile()) { repoId = repoIdPath.readText().trim() } else if (repoIdPath.isDirectory()) { val files = repoIdPath.listDirectoryEntries().filter { it.isRegularFile() } if (files.size == 1) { repoId = files.first().readText().trim() } } if (repoId.isNotBlank()) { return "https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$repoId/" } return "https://oss.sonatype.org/service/local/staging/deploy/maven2/" } val repoServerLocation = resolveSonatypeRepoLoc() pendingFiles.forEach { pending -> val netpath = repoLoc.relativize(pending) val uri = repoServerLocation + (netpath.toString().replace("\\", "/")) println("Processing $uri") val rsp = httpc.send( HttpRequest.newBuilder( URI.create(uri) ).PUT(HttpRequest.BodyPublishers.ofFile(pending)) .header("Authorization", authorization) .header("User-Agent", useragent) .build(), HttpResponse.BodyHandlers.ofByteArray() ) if (rsp.statusCode() / 100 != 2) { val errmsg = "$rsp -> " + String(rsp.body(), Charset.defaultCharset()) errors.add(errmsg) println(errmsg) } } println("::endgroup::") if (errors.isNotEmpty()) { error(errors.joinToString("\n\n", prefix = "\n")) } } else -> error("Unknown command: " + args.joinToString(" ")) } } ================================================ FILE: ci-release-helper/src/buildIndex/Index.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:UseSerializers(UuidAsStringSerializer::class) package cihelper.buildIndex import kotlinx.datetime.LocalDateTime import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.UseSerializers import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import java.util.* @Serializable data class NextIndexResp( val moduleId: UUID, val branchId: UUID, val previousIndexId: UUID?, val previousIndexValue: UInt?, val newIndex: Index ) @Serializable data class Index( val id: UUID, val branchId: UUID, val commitRef: String, val value: UInt, val date: LocalDateTime ) { init { require(commitRef.length == 40) { "Invalid commit ref: '$commitRef'" } } } object UuidAsStringSerializer : KSerializer { override val descriptor: SerialDescriptor = String.serializer().descriptor override fun deserialize(decoder: Decoder): UUID { return UUID.fromString(String.serializer().deserialize(decoder)) } override fun serialize(encoder: Encoder, value: UUID) { String.serializer().serialize(encoder, value.toString()) } } ================================================ FILE: ci-release-helper/src/buildIndex/SnapshotVersions.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package cihelper.buildIndex import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.coroutines.runBlocking import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.json.Json object GetNextSnapshotIndex { @JvmStatic fun main(args: Array) { val branch = args.getOrNull(0)?.replace(Regex("""[/\\.,`~!@#$%^&*(){}\[\]|;]"""), "-") ?: error("Missing branch argument") val commitRef = args.getOrNull(1) ?: error("Missing commitRef argument") println("Commit ref is: $commitRef") println("Making request...") HttpClient().use { client -> runBlocking { kotlin.runCatching { client.createBranch(branch = branch) } var index = client.getExistingIndex(branch = branch, commitRef = commitRef) if (index == null) { print("No existing index found. ") index = client.postNextIndex(branch = branch, commitRef = commitRef) println("Got new index: $index") } else { print("Existing index: $index") } println() println("$branch-${index.value}") } } } } suspend fun HttpClient.getExistingIndex( module: String = "mirai-core", branch: String, commitRef: String, ): Index? { // https://build.mirai.mamoe.net/v1/mirai-core/dev/indexes/?commitRef=29121565132bed6e996f3de32faaf49106ae8e39 val resp = get("https://build.mirai.mamoe.net/v1/${module.encodeURLPathPart()}/${branch.encodeURLPathPart()}/indexes/") { basicAuth( System.getenv("mirai.build.index.auth.username"), System.getenv("mirai.build.index.auth.password") ) parameter("commitRef", commitRef) } if (!resp.status.isSuccess()) { val body = runCatching { resp.bodyAsText() }.getOrNull() throw IllegalStateException("Request failed: ${resp.status} $body") } val body = resp.bodyAsText() if (body.isBlank()) return null return Json.decodeFromString(ListSerializer(Index.serializer()), body).lastOrNull() } suspend fun HttpClient.createBranch( module: String = "mirai-core", branch: String, ): Boolean { // https://build.mirai.mamoe.net/v1/mirai-core/dev/indexes/?commitRef=29121565132bed6e996f3de32faaf49106ae8e39 val resp = put("https://build.mirai.mamoe.net/v1/${module.encodeURLPathPart()}/${branch.encodeURLPathPart()}") { basicAuth( System.getenv("mirai.build.index.auth.username"), System.getenv("mirai.build.index.auth.password") ) } return resp.status.isSuccess() } suspend fun HttpClient.postNextIndex( module: String = "mirai-core", branch: String, commitRef: String, ): Index { val resp = post("https://build.mirai.mamoe.net/v1/${module.encodeURLPathPart()}/${branch.encodeURLPathPart()}/indexes/next") { basicAuth( System.getenv("mirai.build.index.auth.username"), System.getenv("mirai.build.index.auth.password") ) parameter("commitRef", commitRef) } if (!resp.status.isSuccess()) { val body = runCatching { resp.bodyAsText() }.getOrNull() throw IllegalStateException("Request failed: ${resp.status} $body") } val body = resp.bodyAsText() return Json.decodeFromString(NextIndexResp.serializer(), body).newIndex } ================================================ FILE: ci-release-helper/src/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package cihelper ================================================ FILE: docs/.conf/nav.js ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ module.exports = { text: "mirai-core", link: "/", items: [ {text: "Index", link: "/"}, {text: "Mirai 生态概览", link: "/mirai-ecology.html"}, {text: "从 1.x 迁移", link: "/MigrationFrom1x.html"}, {text: '用户手册', link: '/UserManual.html'}, {text: '用户手册 - 控制台', link: '/ConsoleTerminal.html'}, {text: 'JVM 环境和开发准备工作', link: '/Preparations.html'}, {text: "配置项目", link: "/ConfiguringProjects.html"}, {text: "常见问题", link: "/Questions.html"}, { text: "CoreAPI", items: [ {text: "CoreAPI", link: "/CoreAPI.html"}, {text: "机器人", link: "/Bots.html"}, {text: "联系人", link: "/Contacts.html"}, {text: "事件", link: "/Events.html"}, {text: "消息", link: "/Messages.html"}, ] }, { text: "Misc", items: [ {text: '主要API', link: '/ConciseAPI.html'}, {text: 'Mirai - Evolution', link: '/Evolution.html'}, {text: 'Kotlin & Java', link: '/KotlinAndJava.html'}, {text: "事件列表", link: "/EventList.html"}, {text: "Debugging Network", link: "/DebuggingNetwork.html"}, {text: "Using Dev Snapshots", link: "/UsingSnapshots.html"}, {text: "mirai 模拟测试框架", link: "/mocking/Mocking.md"}, ] }, ], }; ================================================ FILE: docs/Bots.md ================================================ # Mirai - Bots **目录** - [1. 创建和配置 `Bot`](#1-创建和配置-bot) - [配置 Bot](#配置-bot) - [重要配置](#重要配置) - [切换心跳策略](#切换心跳策略) - [切换登录协议](#切换登录协议) - [覆盖登录解决器](#覆盖登录解决器) - [常用配置](#常用配置) - [修改运行目录](#修改运行目录) - [修改 Bot 缓存目录](#修改-bot-缓存目录) - [设备信息](#设备信息) - [使用其他日志库接管 mirai 日志系统](#使用其他日志库接管-mirai-日志系统) - [重定向日志](#重定向日志) - [启用列表缓存](#启用列表缓存) - [更多配置](#更多配置) - [获取当前所有 `Bot` 实例](#获取当前所有-bot-实例) - [2. 登录](#2-登录) - [处理滑动验证码](#处理滑动验证码) - [常见登录失败原因](#常见登录失败原因) - [附录: 调试网络层](#附录-调试网络层) - [附录: 模拟测试框架](#附录-模拟测试框架) ## 1. 创建和配置 `Bot` 一个机器人被以 `Bot` 对象描述。mirai 的交互入口点是 `Bot`。`Bot` 只可通过 [`BotFactory`](../mirai-core-api/src/commonMain/kotlin/BotFactory.kt#L22-L87) 内的 `newBot` 方法获得: ```kotlin interface BotFactory { fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot fun newBot(qq: Long, password: String): Bot fun newBot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot fun newBot(qq: Long, passwordMd5: ByteArray): Bot // 在 2.15.0 中加入 fun newBot(qq: Long, authorization: BotAuthorization) fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration) companion object : BotFactory by BotFactoryImpl } ``` 通常的调用方法为: ``` // Kotlin val bot = BotFactory.newBot( ) // Java Bot bot = BotFactory.INSTANCE.newBot( ); ``` > Scala 使用者请查看 [#834](https://github.com/mamoe/mirai/issues/834) ### 配置 Bot 可以切换使用的协议、控制日志输出等。 仅能在构造 Bot 时修改其配置: ``` // Kotlin // 使用密码登录 val bot = BotFactory.newBot(qq, password) { // 配置,例如: fileBasedDeviceInfo() } // 在 2.15.0 中加入, 使用二维码登录 val bot = BotFactory.newBot(qq, BotAuthorization.byQRCode()) { protocol = BotConfiguration.MiraiProtocol.ANDROID_WATCH } // Java // 使用密码登录 Bot bot = BotFactory.INSTANCE.newBot(qq, password, new BotConfiguration() {{ // 配置,例如: fileBasedDeviceInfo() }}); Bot bot = BotFactory.INSTANCE.newBot(qq, password, configuration -> {}) // 在 2.15.0 中加入, 使用二维码登录 Bot bot = BotFactory.INSTANCE.newBot(qq, BotAuthorization.byQRCode(), configuration -> { configuration.setProtocol(BotConfiguration.MiraiProtocol.ANDROID_WATCH); }); ``` 下文示例代码都要放入 `// 配置` 中。 > 可在 [BotConfiguration.kt](../mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt#L23) 查看完整配置列表 ### 重要配置 #### 切换心跳策略 心跳策略默认为最佳的 `STAT_HB`,但不适用于一些账号。 如果遇到 Bot **闲置一段时间后**,发消息返回成功但群内收不到的情况,请切换心跳策略,依次尝试 `STAT_HB`、`REGISTER` 和 `NONE`。 ``` // Kotlin heartbeatStrategy = BotConfiguration.HeartbeatStrategy.REGISTER // Java setHeartbeatStrategy(BotConfiguration.HeartbeatStrategy.REGISTER) ``` #### 切换登录协议 Mirai 支持多种登录协议:`ANDROID_PHONE`,`ANDROID_PAD`,`ANDROID_WATCH`,`IPAD`,`MACOS` 默认使用 `ANDROID_PHONE`。 若登录失败,可尝试切换协议。**但注意部分功能在部分协议上不受支持**,详见源码内注释。 要切换协议: ``` // Kotlin protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD // Java setProtocol(MiraiProtocol.ANDROID_PAD) ``` #### 覆盖登录解决器 在登录时可能遇到图形验证码或滑动验证码,Mirai 会使用 `LoginSolver` 解决验证码。 - 在 JVM, Mirai 提供默认的命令行实现 - 在 Android 需要手动实现 `LoginSolver` 若要覆盖默认的 `LoginSolver` (通常不需要): ``` // Kotlin loginSolver = YourLoginSolver // Java setLoginSolver(new YourLoginSolver()) ``` > 要获取更多有关 `LoginSolver` 的信息,查看 [LoginSolver.kt](../mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt#L32) ### 常用配置 #### 修改运行目录 默认为 `File(".")` ``` // Kotlin workingDir = File("C:/mirai") // Java setWorkingDir(new File("C:/mirai")) ``` #### 修改 Bot 缓存目录 缓存目录会相对于 `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(new File("cache")) // 最终为 workingDir 目录中的 cache 目录 setCacheDir(new File("C:/cache")) // 最终为 C:/cache ``` 目前缓存目录会存储列表缓存、登录服务器、资源会话秘钥等。这些数据的存储方式有可能变化,请不要修改缓存目录中的文件。 [FileCacheStrategy]: ../mirai-core-api/src/commonMain/kotlin/utils/FileCacheStrategy.kt#L55 注意,`cacheDir` 仅存储与 Bot 相关的上述信息。其他的如 `InputStream` 的缓存由 [FileCacheStrategy] 管理,默认使用系统临时文件。 #### 设备信息 Bot 默认使用全随机的设备信息。**在更换账号地点时候使用随机设备信息可能会导致无法登录**,当然,**成功登录时使用的设备信息也可以保存后在新的设备使用**。 若要在服务器部署,可以先在本地完成登录,再将设备信息上传到服务器。一个设备信息可以一直使用。 要使用 `device.json` 存储设备信息: ``` fileBasedDeviceInfo() // 存储为 "device.json" // 或 fileBasedDeviceInfo("myDeviceInfo.json") // 存储为 "myDeviceInfo.json" ``` 要自定义设备信息: ``` // Kotlin deviceInfo = { bot -> /* create device info */ } // Java setDeviceInfo(bot -> /* create device info */) ``` 在线生成自定义设备信息的 `device.json`: https://ryoii.github.io/mirai-devicejs-generator/ 更加仿真的设备信息的 `device.json`: https://github.com/cssxsh/mirai-device-generator #### 使用其他日志库接管 mirai 日志系统 *mirai 2.7 起支持* 使用 Log4J, SLF4J 等接管 mirai 日志系统后则可使用它们的过滤等高级功能。参阅 [mirai-logging](../logging/README.md) 以获取更多信息。 #### 重定向日志 Bot 有两个日志类别,`Bot` 或 `Net`。`Bot` 为通常日志,如收到事件。`Net` 为网络日志,包含收到和发出的每一个包和网络层解析时遇到的错误。 重定向日志到文件: ``` redirectBotLogToFile() redirectBotLogToDirectory() redirectNetworkLogToFile() redirectNetworkLogToDirectory() ``` 关闭日志(将会完全禁用日志功能, 无论是否已经通过第三方日志库接管日志系统): ``` noNetworkLog() noBotLog() ``` 手动覆盖日志(不建议[(?)](../logging/README.md)): ``` // Kotlin networkLoggerSupplier = { bot -> /* create logger */ } botLoggerSupplier = { bot -> /* create logger */ } // Java setNetworkLoggerSupplier(bot -> /* create logger */) setBotLoggerSupplier(bot -> /* create logger */) ``` #### 启用列表缓存 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 秒 ``` #### 更多配置 参阅 `BotConfiguration` 源码内注释。 ### 获取当前所有 `Bot` 实例 在登录后 `Bot` 实例会被自动记录。可在 `Bot.instances` 获取到当前**在线**的所有 `Bot` 列表。 ## 2. 登录 创建 `Bot` 后不会自动登录,需要手动调用其 `login()` 方法。只需要调用一次 `login()` 即可,`Bot` 掉线时会自动重连。 ### 处理滑动验证码 [mirai-login-solver-sakura]: https://github.com/KasukuSakura/mirai-login-solver-sakura 服务器正在大力推广滑块验证码。 部分账号可以跳过滑块验证码,Mirai 会自动尝试。 若你的账号无法跳过验证,可尝试使用 [mirai-login-solver-sakura] 处理。 **若遇到滑块验证问题无法解决,可以参考[论坛帮助页面](https://mirai.mamoe.net/topic/223)。** ### 常见登录失败原因 [#993]: https://github.com/mamoe/mirai/discussions/993 | 错误信息 | 可能的原因 | 可能的解决方案 | |:-----------|:---------------------|:--------------------------------------------| | 密码错误 | 密码错误或过长 | 手机协议最大支持 16 位密码 ([#993]). 在官方 PC 客户端登录后修改密码 | | `code=45` | 协议版本过低或设备信息被拉黑 | 删除 device.json, 让其重新生成 | | `code=235` | 协议版本过低或设备信息被拉黑 | 删除 device.json, 让其重新生成 | | `code=237` | 滑块验证处理过慢或者提交ticket有误 | 尝试使用 [mirai-login-solver-sakura] 处理滑块验证 | | `code=238` | 当前协议已禁止密码登录 | 使用扫码登录 | 若以上方案无法解决问题,请尝试 [切换登录协议](#切换登录协议) 和 **[处理滑动验证码](#处理滑动验证码)**。 > 下一步,[Contacts](Contacts.md) > > [回到 Mirai 文档索引](CoreAPI.md) ## 附录: 调试网络层 参阅 [DebuggingNetwork.md](DebuggingNetwork.md) ## 附录: 模拟测试框架 参阅 [Mocking.md](mocking/Mocking.md) > 下一步,[Contacts](Contacts.md) > > [回到 Mirai 文档索引](CoreAPI.md) ================================================ FILE: docs/ConciseAPI.md ================================================ # Mirai - Concise API > 注: > - 本章节展示关于 `mirai-core-api` 比较常用的 API 示例 > - 请配合 `mirai-core-api` 源码查看 > - 本章仅提供 API 粗略介绍 ---------------------- # Bots ## BotFactory `BotFactory` 用于创建一个新的 `Bot`, 详情请看 [Bots.md](Bots.md) ```kotlin val bot = BotFactory.newBot(/*....*/) // Java Bot bot = BotFactory.INSTANCE.newBot(/*....*/); Bot bot = Mirai.getInstance().getBotFactory().newBot(/*....*/); ``` # Misc utils ## Logger Mirai 全部的日志都通过 `MiraiLogger` 输出, 查看 `MiraiLogger` 源码注释获得更多信息 ## ExternalResource `ExternalResource` 代表一个外部文件, 可用于 文件上传, 图片发送, etc. 构造 `ExternalResource` 可以通过以下方法构造 ```kotlin // kotlin File("foo.txt").toExternalResource() // java ExternalResource.create(new File("foo.txt")) ``` `ExternalResource.create()` 内置支持的数据类型有 `java.io.File`, `java.io.RandomAccessFile`, `byte[]`, `java.io.InputStream` > 注: > - `ExternalResource` 和 `java.io.InputStream` 等资源一样, 需要手动关闭 `close()` > - 使用 `java.io.InputStream` 构造 `ExternalResource` 时, 需要关闭 `java.io.InputStream` > - 使用 `java.io.RandomAccessFile` 构造 `ExternalResource` 时, 请不要关闭 `RandomAccessFile`, > 否则会间接关闭 `ExternalResource` ```kotlin // Example // kotlin val inputStream: InputStream = TODO() val resource = inputStream.use { it.toExternalResource() } // java ExternalResource resource; try (InputStream inputStream = TODO()) { resource = ExternalResource.create(inputStream); } catch (IOException exception) { // on Exception catch throw new RuntimeException("Can't create a new external resource", exception); } ``` ### 自行实现 ExternalResource 从 2.9.0 开始,可以很容易实现自定义 `ExternalResource` `@see` `AbstractExternalResource` 源码注释 # Contact & Message ## Send Image ### Origin send image 最原始的发送图片的方法,就是先 `uploadImage` 然后 `sendMessage` Kotlin 可以使用自动补全得到相关方法 > `contact.uploadImage // IDEA 补全` Java 可以使用 `contact.uploadImage(ExternalResource)` 来得到一个图片对象 (~~这也是为啥 ExternalResource 在前面~~) 也可以使用 `Contact` 内的静态方法 ```java Image i = Contact.uploadImage(/*....*/); Image i = ExternalResource.uploadAsImage(/*...*/); ``` ### sendImage `sendImage` 相当于先进行 `uploadImage` 然后再 `sendMessage` Kotlin 可以使用自动补全得到相关方法 > `contact.sendImage // IDEA 补全` 由于 `sendImage` 是 `Contact` 和 `ExternalResource` 内的静态方法, Java 可以使用下述方法调用 ```java Contact.sendImage(/**/); ExternalResource.sendAsImage(/*...*/); ``` ## Send Audio 发送语音与发送图片的区别不大,都是先 `upload` 然后 `send` > - 在 2.7.0 之前,只有群聊 (`Group`) 支持语音(使用 `Voice`), 2.7.0 之后支持私聊语音(使用 `Audio`) > - 每次发送新语音前最好重新 `upload`, 避免复用 `Audio` 对象 > - **只支持 `amr` 和 `silk` 格式** 要得到一个语音对象, 需要先 `uploadAudio` Kotlin 可以使用自动补全得到相关方法 > `contact.uploadAudio // IDEA 补全` Java 可以使用 `contact.uploadAudio(ExternalResource)` 来得到一个语音对象 (~~这也是为啥 ExternalResource 在前面~~) 也可以使用 `ExternalResource` 定义的扩展方法: ```java contact.sendMessage(ExternalResource.uploadAsAudio(/*...*/)); ``` ## Members `Member` 对象分为 `NormalMember`(正常的群成员) 和 `AnonymousMember`(匿名) 对 `Member` 操作时需要具体操作时应该先判断是否为 `NormalMember` 然后强转 ```kotlin // kotlin if (member is NormalMember) { // kotlin smart cast } ``` ```java // java if (member instanceof NormalMember) { NormalMember nMember = (NormalMember) member; } ``` ## Recall Message 撤回信息可以通过 `MessageChain` 或者 `MessageSource` 撤回。 ```kotlin subscribeAlways {// this: MessageEvent this.message.recall() this.message.source.recall() // Java MessageSource.recall(event.getMessage()); MessageSource.recall(event.getMessage().getOrFail(MessageSource.Key)); } ``` # Events 常用事件 | Name | Desc | | :---------------- | :------------ | | MessageEvent | Bot 收到一条新消息 | | NewFriendRequestEvent | 你有一条新的好友申请 | | MemberJoinEvent | 有新群成员加入群 | | MemberLeaveEvent | 群成员离开群聊 | | BotInvitedJoinGroupRequestEvent | Bot 收到了一个加群邀请 | | BotJoinGroupEvent | Bot 加入了一个群聊 | | MemberJoinRequestEvent | 新的入群申请 | ### MessageEvent 当直接监听 `MessageEvent` 时,可以考虑排除 Bot 信息同步事件 `MessageSyncEvent` `MessageSyncEvent` 是 `Bot` 账号在其他客户端发送消息时同步到 mirai 的事件 ```kotlin eventChannel.subscribeAlways { // this: MessageEvent if (this is MessageSyncEvent) return@subscribeAlways } ``` # IMirai `Mirai API` 接口. 是 `Mirai API` 与 Mirai 协议实现对接的接口. `IMirai` 内定义的接口都是较底层的 API, 如果无必要, 尽量避免使用 `IMirai` 相关的方法 最底层的方法位于 `LowLevelApiAccessor` 内, 其方法都使用 `@LowLeveApi` 标注, `IMirai` 接口继承 `LowLevelApiAccessor` 使用 `IMirai` 的标准 API 有稳定性保障, 但是由 `@LowLevelApi` 标注的方法无保障 ================================================ FILE: docs/ConfiguringMultiplatformProjects.md ================================================ # Mirai - Configuring Projects 本文介绍如何在一个 Kotlin 多平台项目中使用 mirai。 ### 选择版本 可参考 [ConfiguringProjects](ConfiguringProjects.md#选择版本) 选择合适的版本。 ## 支持的编译目标平台 mirai 上传到 Maven Central 的预编译模块列表如下表所示。在表中列举的平台即为你的项目可以使用的平台。 如果你使用了一个不受支持的平台,在构建项目时将会得到来自 Gradle 的依赖解决错误。 mirai 曾在 2.13.0 ~ 2.15.0-RC(不包含)支持编译到 macOS、Window、Linux 平台。自 2.15.0-RC 已完全删除对这些平台的支持。 | 发布平台名称 | 描述 | |---------|------------------| | jvm | JVM | | android | Android (Dalvik) | ## 添加依赖 仅需为 `commonMain` 添加依赖,Kotlin 会自动为其他源集配置依赖。 Kotlin 编译器版本必须至少为 1.7.0,Gradle 版本建议使用高于 7.3。 额外添加 `net.mamoe:mirai-core-utils` 是为了临时解决 [#2275](https://github.com/mamoe/mirai/issues/2275)。 ```kotlin plugins { kotlin("multiplatform") version "1.7.20" } kotlin { sourceSets { val commonMain by getting { dependencies { implementation("net.mamoe:mirai-core:2.13.0") implementation("net.mamoe:mirai-core-utils:2.13.0") } } } } ``` ## 解决问题 如果你在使用多平台项目时遇到问题,那应该是正常的。Kotlin 多平台项目在 1.7 仍然是一个测试版功能。欢迎在 issues 提交多平台相关问题。 > 依赖配置完成, > - [回到 Mirai 文档索引](README.md#使用-mirai) ================================================ FILE: docs/ConfiguringProjects.md ================================================ # Mirai - Configuring Projects 本文介绍如何在一个 JVM 项目中使用 mirai。 具体项目可参考 [mirai-hello-world](https://github.com/project-mirai/mirai-hello-world)。 ### 选择版本 有关各类版本的区别,参考 [版本规范](Evolution.md#版本规范)。通常建议选择最新稳定版本。 [Maven Central Version]: https://img.shields.io/maven-central/v/net.mamoe/mirai-core-api.svg?label=Maven%20Central [Maven Central]: https://search.maven.org/search?q=net.mamoe%20mirai [GitHub Releases]: https://github.com/mamoe/mirai/releases/latest [GR all]: https://github.com/mamoe/mirai/releases/ | 版本类型 | 版本号链接 | |:------:|:-----------------------------------:| | 稳定 | [GitHub Releases] | | 预览 | [GitHub Releases][GR all] | | 开发 | [UsingSnapshots](UsingSnapshots.md) | ### 配置项目 本文提供如下三种配置方法。推荐使用 Gradle 构建。 **注意,下文版本号可能过旧,请自行参照上述表格更新版本号** - [A. 使用 Gradle](#a-使用-gradle) - [B. 使用 Maven](#b-使用-maven) - [C. 下载 JAR 包](#c-下载-jar-包) ## A. 使用 Gradle ### Gradle Kotlin DSL 在 `build.gradle.kts` 添加: ```kotlin plugins { kotlin("jvm") version "1.5.30" // 确保添加 Kotlin } dependencies { api("net.mamoe", "mirai-core", "2.9.1") } ``` **注意,必须添加 Kotlin 插件才能正确获取 mirai 软件包。** > 依赖配置完成,请选择: > - [分离 API 和实现(可选)](#分离-api-和实现可选) > - [回到 Mirai 文档索引](README.md#使用-mirai) ### Gradle Groovy DSL 在 `build.gradle` 添加 ```groovy plugins { id 'org.jetbrains.kotlin.jvm' version '1.5.30' // 确保添加 Kotlin } dependencies { implementation 'net.mamoe:mirai-core:2.9.1' } ``` > 依赖配置完成,请选择: > - [分离 API 和实现(可选)](#分离-api-和实现可选) > - [回到 Mirai 文档索引](README.md#使用-mirai) ### 分离 API 和实现(可选) Mirai 在开发时需要 `net.mamoe:mirai-core-api`, 在运行时需要 `net.mamoe:mirai-core`。可以在开发和编译时只依赖 `mirai-core-api`,会减轻对 IDE 的负担。 在 2.8.0 起 Mirai 提供 `mirai-bom` 用于自动协调 Mirai 不同组件的版本信息,这是引用 Mirai 平台的首选方式。 使用 `mirai-bom` 也会对 Dependabot 等自动化依赖管理程序更加友好。 ```kotlin dependencies { api(platform("net.mamoe:mirai-bom:2.9.1")) api("net.mamoe:mirai-core-api") // 编译代码使用 runtimeOnly("net.mamoe:mirai-core") // 运行时使用 } ``` 也可以继续使用如下传统方式,但务必保证 `mirai-core-api` 和 `mirai-core` 的版本号相一致,以避免潜在的异常。 尤其注意 Dependabot 等依赖管理程序可能会导致模块版本不同。 ```kotlin dependencies { val miraiVersion = "2.9.1" api("net.mamoe", "mirai-core-api", miraiVersion) // 编译代码使用 runtimeOnly("net.mamoe", "mirai-core", miraiVersion) // 运行时使用 } ``` ## B. 使用 Maven > 推荐使用 gradle, 使用 maven 您可能会遇到各种奇怪的依赖错乱问题 在 `pom.xml` 中添加 mirai 依赖: ```xml net.mamoe mirai-core-jvm 2.9.1 ``` > 注意在 Maven,artifactId 要使用带 `-jvm` 后缀的 通常 mirai 可以直接使用。但 mirai 使用的 Kotlin 1.5 可能与你的项目使用的其他库依赖的 Kotlin 版本冲突,Maven 有时候无法正确处理这种冲突。此时请手动添加 Kotlin 标准库依赖。 ```xml 1.5.10 ``` ```xml org.jetbrains.kotlin kotlin-stdlib-jdk8 ${kotlin.version} ``` > 可以在 [Kotlin 官方文档](https://www.kotlincn.net/docs/reference/using-maven.html) 获取更多有关配置 Kotlin 的信息。 > 依赖配置完成,[回到 Mirai 文档索引](README.md#使用-mirai) ## C. 下载 JAR 包 非常不推荐这种方法,请尽可能使用构建工具。 在 [Maven Central](https://repo.maven.apache.org/maven2/net/mamoe/mirai-core-all/) 或 [阿里云代理仓库](https://maven.aliyun.com/repository/central/net/mamoe/mirai-core-all/) 下载指定版本的 `-all.jar` 文件,即包含 `mirai-core`,`mirai-core-api`,`mirai-core-utils` 和其他依赖。 > [回到 Mirai 文档索引](README.md#使用-mirai) ================================================ FILE: docs/ConsoleTerminal.md ================================================ # Mirai - Console Terminal 本文介绍如何使用 Mirai Console 的纯控制台前端,即 `mirai-console-terminal` 。本文部分内容实际上对所有前端都通用。 本文面向用户,若要开发 Mirai Console,可参考 [Console 开发文档](../mirai-console/docs/README.md)。 本文假设你使用 Windows 操作系统。但 Mirai Console 并不仅限于 Windows 平台使用,在其他操作系统上的使用方法应当是类似的。 **重要**:关闭 Mirai Console 需要通过 `stop` 命令关闭。 直接关闭窗口会导致数据损坏、数据丢失、系统崩溃等错误。 安装 ---- 可以使用 [脚本](https://mirai.mamoe.net/assets/uploads/files/1618372079496-install-20210412.cmd) 自动安装 32 位带 HTTP 插件的版本,也可以使用安装器个性化安装: [iTXTech/mcl-installer]: https://github.com/iTXTech/mcl-installer/releases 1. 访问 [iTXTech/mcl-installer]; 2. 下载适合你的系统的可执行文件; 3. 在一个新文件夹存放这个文件,运行它; 4. 通常可以一路回车使用默认设置完成安装,安装完成后程序自动退出; 5. 运行 `mcl.cmd` 启动,成功后会看到绿色的 `mirai-console started successfully`。 了解运行环境 ---- 安装时自动下载了 Mirai Console 启动器(简称 [MCL](https://github.com/iTXTech/mirai-console-loader))。 启动器会帮你准备运行环境,下载和更新 Mirai 核心。你也可以使用启动器下载一些插件(见下文)。 第一次运行 `mcl.cmd` 时会初始化运行环境。下表说明了各个文件夹的用途。 MCL 只是启动器,没有机器人功能。MCL 支持从远程仓库下载插件,并启动 Mirai Console 终端版(`mirai-console-terminal`)。 如果遇到启动器问题,请提交至 [iTXTech/mirai-console-loader](https://github.com/iTXTech/mirai-console-loader) 。 | 文件夹名称 | 用途 | |:-------------------------:|:---------------------| | `data` | 存放插件的数据,一般不需要在意它们 | | `config` | 存放插件的配置,可以打开并修改配置 | | `logs` | 存放运行时的日志,日志默认保留 7 天 | | `libs` | 存放 mirai-core 等核心库文件 | | `plugins` | 存放插件 | | `plugin-libraries` | 存放插件的库缓存 | | `plugin-shared-libraries` | 存放插件的公共库 | | `modules` | 存放启动器的拓展模块 | > 可以在[这里](https://github.com/iTXTech/mirai-console-loader)查看 MCL 详细用法 了解插件 ---- Mirai Console 原生支持 JAR 文件插件。一般插件的后缀为 `.mirai2.jar`(新版本)或 `.mirai.jar` (旧版本)。 将插件 JAR 放在 `plugins` 目录中,重启 Mirai Console 就会自动扫描并加载。 Mirai Console 下载和安装插件 ---- 刚刚装好的 Mirai Console 没有功能,功能将由插件提供。 ### 使用 MCL 自动安装插件 ### 如何安装官方插件 Mirai 官方提供两个插件: - [chat-command](https://github.com/project-mirai/chat-command): 允许在聊天环境通过以 "/" 起始的消息执行指令(也可以配置前缀) - [mirai-api-http](https://github.com/project-mirai/mirai-api-http):提供 HTTP 支持,允许使用其他编程语言的插件 打开命令行 (Windows 系统在文件夹按住 Shift 单击鼠标右键,点击 "在此处打开 PowerShell"), 可以使用 MCL 自动安装这些插件,例如: 安装 mirai-api-http 的 2.x 版本: ```powershell ./mcl --update-package net.mamoe:mirai-api-http --type plugin --channel maven-stable ``` 安装 chat-command: ```powershell ./mcl --update-package net.mamoe:chat-command --type plugin --channel maven-stable ``` 注意:插件有多个频道,`--channel maven-stable` 表示使用从 `maven` 更新的 `stable`(稳定)的频道。不同的插件可能会设置不同的频道, 具体需要使用哪个频道可参考特定插件的说明 (很多插件会单独说明要如何安装它们, 因此不必过多考虑)。 详细文档:[MCL 命令行参数](https://github.com/iTXTech/mirai-console-loader/blob/master/cli.md) ### 在哪找社区插件 - Mirai 官方论坛 [Mirai Forum](https://mirai.mamoe.net/category/11/%E6%8F%92%E4%BB%B6%E5%8F%91%E5%B8%83) > *我们还正在建设插件中心,完成后将会简化寻找插件的工作* ### 如何安装社区插件 如果是 JAR 文件的插件,放入 `plugins` 即可。其他插件一般都有特殊说明如何使用,请参考它们的说明。 注意,mirai 在 2.11 时修改了加载策略。请尽量使用 `mirai2.jar` 后缀版本的插件 ### 常用的插件 - [chat-command](https://github.com/project-mirai/chat-command): 不安装此插件不能在聊天环境中执行命令 - [mirai-api-http](https://github.com/project-mirai/mirai-api-http): 提供 HTTP 支持,允许使用其他编程语言的插件 - [mirai-silk-converter](https://github.com/project-mirai/mirai-silk-converter): 可以自动将 `wav`, `mp3` 等格式转换为语音所需格式 `silk` - [LuckPerms-Mirai](https://github.com/Karlatemp/LuckPerms-Mirai): 高级权限组插件,适合权限分配模型比较复杂的情况,并且可以提供网页UI的权限编辑器 (指令 `lp editor`) - [mirai-login-solver-sakura](https://github.com/KasukuSakura/mirai-login-solver-sakura): 验证处理工具,主要是为了优化和方便处理各种验证码 使用控制台指令 ----- 启动 `mcl.cmd` 就会看到控制台。在控制台可以输入指令,按回车执行这条指令。 Mirai Console 内置一些指令,输入 `?` 并回车可以查看指令列表。 一些常用指令介绍在[这里](/mirai-console/docs/BuiltInCommands.md#mirai-console---builtin-commands) 。 ### 在聊天框中使用命令 (权限授予) 要允许从 QQ 聊天环境中使用各种命令, 你 **必须** 完成以下的配置: 1. 安装 [chat-command](https://github.com/project-mirai/chat-command) 2. 完成命令执行权限授予 > 关于不同的权限系统,授予权限的方式或者授予权限的命令格式可能有所不一样。 > > 当使用非内置权限系统时,具体的权限管理相关命令以相关的权限系统的文档为准 > > 如 `LuckPerms-Mirai` 插件的权限管理命令为 `/lp` 而不是 `/permission` 要完成权限授予,你必须通过在控制台执行 [`/permission permit [target] [permission]`](/mirai-console/docs/BuiltInCommands.md#permissioncommand) 来授予其他人执行相关命令的权限,需要执行的权限一般情况在插件的介绍页都会给明。 详见 [`PermissionCommand`](/mirai-console/docs/BuiltInCommands.md#permissioncommand) 。 ### 指令参数智能解析 Console 会自动根据语境推断指令参数的含义。 假设一条禁言指令: ``` /mute # 为 target 设置 duration 秒的禁言 ``` `` 为目标群成员,`` 为禁言时间秒数。 若在群内发送消息执行指令 `/mute 123 60`,`123` 将会被首先看作是群员 QQ 号码寻找群员。 若未找到,则会将 `123` 看作是群员的名片重新寻找。 群名片和昵称搜索是模糊匹配的,优先取用不会产生歧义的最相似的匹配。 例如 `hello` 能匹配 `hella`,匹配率 80%;`hello` 匹配 `hel` 匹配率 60%。由于这两个匹配率都超过 60%,它们都会成为候选;由于它们之间相差超过 10%,Mirai 会认为这没有歧义,选用 `hella`。 在一个群内也可以引用另一个群内的成员,使用 `群号码.群员号码` 格式。若没有 `群号码` 则会使用指令执行人所在的群。 在有多个机器人账号登录的情况下,可以使用 `机器人号码.群员号码` 的格式来指定某个机器人的群员。如果只有一个机器人则只需要使用 `群员号码`。 在控制台执行指令时,需要提供群号码才能引用到群员。 下面将列举智能解析支持的格式列表。 #### 好友 | 格式 | 示例 | 说明 | |------------|-----------------|----------------------| | 机器人号码.好友号码 | `123456.987654` | 一个机器人的一个好友 | | 好友号码 | `987654` | 当前唯一在线机器人的一个好友 | | `~` | `~` | 仅聊天环境下,指代指令调用人自己作为好友 | #### 群员 | 格式 | 示例 | 说明 | |----------------|------------------------|----------------------| | 机器人号码.群号码.群员号码 | `123456.123456.987654` | 一个机器人的一个群中的一个群员 | | 机器人号码.群号码.群员名片 | `123456.123456.Alice` | 一个机器人的一个群中的一个群员 | | 提及群员 | `@Cinnamon` | 一个机器人的一个群中的一个群员 | | 机器人号码.群号码.$ | `123456.123456.$` | 一个机器人的一个群中的随机群员 | | 群号码.群员号码 | `123456.987654` | 当前唯一在线机器人的一个群的一个群员 | | 群号码.群员名片 | `123456.Alice` | 当前唯一在线机器人的一个群的一个群员 | | 群号码.$ | `123456.$` | 当前唯一在线机器人的一个群的随机群员 | | 群员号码 | `987654` | 仅聊天环境下,当前群的一个群成员 | | 群员名片 | `Alice` | 仅聊天环境下,当前群的一个群成员 | | `$` | `$` | 仅聊天环境下,当前群的随机群成员 | | `~` | `~` | 仅聊天环境下,指代指令调用人自己作为群员 | #### 群 | 格式 | 示例 | 说明 | |-----------|-----------------|---------------| | 机器人号码.群号码 | `123456.987654` | 一个机器人的一个群 | | 群号码 | `987654` | 当前唯一在线机器人的一个群 | | `~` | `~` | 仅聊天环境下,指代当前群聊 | 配置 ---- Mirai Console 支持一些自定义配置。各项配置可以在 `config` 目录中找到。 ### 自动登录 修改 `AutoLogin.yml` 可配置自动登录。也可以使用 `/autologin` 指令。 ### 指令前缀 可以在 `Command.yml` 配置指令前缀,默认为 `/`。注意,部分指令可不需要前缀也能使用,这取决于插件开发者的选择。 ### 管理日志 Mirai Console 会记录运行时的日志并保存到 `logs` 目录中。 可以参考[日志文档](../mirai-console/docs/Logging.md)了解如何配置日志的详略程度。 若要向插件开发者提交问题,建议将日志等级调整为 `ALL` 并复现问题后将当天日志一并提交。 ### 配置权限 `PermissionService.yml` 包含权限授予信息。通常建议使用指令 `/perm` 来修改权限,而不建议直接修改配置。 ### 远程仓库 `PluginDependencies.yml` 包含对远程仓库的配置,通常不需要修改,除非某个插件要求。 帮助改善文档 ----- 如果你认为本文档还需要覆盖一些内容,请在 [issues](https://github.com/mamoe/mirai/issues/new/choose) 提交建议。 ================================================ FILE: docs/Contacts.md ================================================ # Mirai - Contacts [![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbiAgICArYXZhdGFyVXJsOiBTdHJpbmdcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK3F1ZXJ5UHJvZmlsZSgpIFVzZXJQcm9maWxlXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5jbGFzcyBPdGhlckNsaWVudCB7XG4gICAgK2luZm9cbn1cblxuQ29udGFjdE9yQm90PHwtLUNvbnRhY3RcbkNvbnRhY3RPckJvdDx8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PHwtLUJvdFxuVXNlck9yQm90PHwtLVVzZXJcblxuQ29udGFjdDx8LS1Vc2VyXG5Db250YWN0PHwtLUdyb3VwXG5Db250YWN0PHwtLU90aGVyQ2xpZW50XG5cblVzZXI8fC0tTWVtYmVyXG5Vc2VyPHwtLUZyaWVuZFxuXG5NZW1iZXI8fC0tTm9ybWFsTWVtYmVyXG5NZW1iZXI8fC0tQW5vbnltb3VzTWVtYmVyIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIEJvdCB7XG4gICAgK2ZyaWVuZHM6IENvbnRhY3RMaXN0XG4gICAgK2dyb3VwczogQ29udGFjdExpc3RcbiAgICArZ2V0RnJpZW5kKExvbmcpIEZyaWVuZD9cbiAgICArZ2V0RnJpZW5kT3JOdWxsKExvbmcpIEZyaWVuZFxuICAgICtnZXRHcm91cChMb25nKSBHcm91cD9cbiAgICArZ2V0R3JvdXBPckZhaWwoTG9uZykgR3JvdXBcbiAgICArbG9naW4oKVxuICAgICtjbG9zZSgpXG59XG5cbmNsYXNzIENvbnRhY3RPckJvdCB7XG4gICAgK2lkOiBJbnRcbiAgICArYXZhdGFyVXJsOiBTdHJpbmdcbn1cblxuY2xhc3MgVXNlck9yQm90IHtcbiAgICArbnVkZ2UoKSBOdWRnZVxufVxuXG5jbGFzcyBDb250YWN0IHtcbiAgICArYm90OiBCb3RcbiAgICArc2VuZE1lc3NhZ2UoTWVzc2FnZSkgTWVzc2FnZVJlY2VpcHRcbiAgICArc2VuZE1lc3NhZ2UoU3RyaW5nKSBNZXNzYWdlUmVjZWlwdFxuICAgICt1cGxvYWRJbWFnZShFeHRlcm5hbEltYWdlKSBJbWFnZVxufVxuXG5jbGFzcyBVc2VyIHtcbiAgICArbmljazogU3RyaW5nXG4gICAgK3JlbWFyazogU3RyaW5nXG4gICAgK3F1ZXJ5UHJvZmlsZSgpIFVzZXJQcm9maWxlXG59XG5cbmNsYXNzIEdyb3VwIHtcbiAgICArbWVtYmVyczogQ29udGFjdExpc3RcbiAgICArbmFtZTogU3RyaW5nXG4gICAgK3NldHRpbmdzOiBHcm91cFNldHRpbmdzXG4gICAgK293bmVyOiBOb3JtYWxNZW1iZXJcbiAgICArYm90TXV0ZVJlbWFpbmluZzogTG9uZ1xuICAgICtib3RQZXJtaXNzaW9uOiBNZW1iZXJQZXJtaXNzaW9uXG4gICAgK3F1aXQoKSBCb29sZWFuXG4gICAgK3VwbG9hZFZvaWNlKCkgVm9pY2Vcbn1cblxuY2xhc3MgTm9ybWFsTWVtYmVyIHtcbiAgICArbXV0ZSgpXG4gICAgK2tpY2soKVxufVxuXG5jbGFzcyBBbm9ueW1vdXNNZW1iZXIge1xuICAgICthbm9ueW1vdXNJZDogU3RyaW5nXG59XG5cbmNsYXNzIE1lbWJlciB7XG4gICAgK2dyb3VwOiBHcm91cFxufVxuXG5jbGFzcyBPdGhlckNsaWVudCB7XG4gICAgK2luZm9cbn1cblxuQ29udGFjdE9yQm90PHwtLUNvbnRhY3RcbkNvbnRhY3RPckJvdDx8LS1Vc2VyT3JCb3RcblxuVXNlck9yQm90PHwtLUJvdFxuVXNlck9yQm90PHwtLVVzZXJcblxuQ29udGFjdDx8LS1Vc2VyXG5Db250YWN0PHwtLUdyb3VwXG5Db250YWN0PHwtLU90aGVyQ2xpZW50XG5cblVzZXI8fC0tTWVtYmVyXG5Vc2VyPHwtLUZyaWVuZFxuXG5NZW1iZXI8fC0tTm9ybWFsTWVtYmVyXG5NZW1iZXI8fC0tQW5vbnltb3VzTWVtYmVyIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0) | 类型 | 描述 | 最低支持的版本 | |:-----------------:|:-------------------------------------------------|:-----------:| | `ContactOrBot` | `Contact` 和 `Bot` 的公共接口 | 2.0 | | `OtherClient` | Bot 的*其他客户端*,如 "我的 iPad","我的电脑" | 2.0 | | `Bot` | 机器人对象 | 2.0 | | `Contact` | 联系人对象,即所有的群,好友,陌生人,群成员等 | 2.0 | | `Group` | 群对象 | 2.0 | | `User` | 用户对象,即 "个人". 包含好友,陌生人,群成员,临时会话用户 | 2.0 | | `Friend` | 好友对象 | 2.0 | | `Stranger` | 陌生人对象 | 2.0 | | `Member` | 群成员对象,属于一个 `Group`. | 2.0 | | `NormalMember` | 普通群成员对象. | 2.0 | | `AnonymousMember` | 匿名群成员对象. | 2.0 | 我们称 `Contact` 为 _联系人_,它表示一个 `Bot` 可以联系的对象。如上图所示,`Contact` 的子类不仅有好友和群成员,还包括群和其他客户端。因此请不要因“联系人”中的“人”就认为它只代表一个用户。 ## 获取联系人对象 `Bot.getFriends()`,`Bot.getGroups()` 等方法可以获取到对象列表。 可通过 `Bot.getFriend`, `Bot.getGroup`,`Bot.getStranger` 以 QQ 号或群号主动获取某个对象。 可以通过事件被动获取 *(后文介绍)*。 ## 联系人对象唯一且属于 Bot 每个联系人对象都属于一个 `Bot`。可以通过 `Contact.bot` 获取到它们所属的 `Bot`。 对于同一个 `Bot`,不会有 `id` 相同的两个 `Group` 对象。通过 `Bot.getGroup` 得到的和在群消息事件得到的相同 `id` 的 `Group` 对象是同一个。 若应用同时登录多个 `Bot`,不同 `Bot` 的 `id` 相同的 `Group` 也是互相独立的。 ## 常用功能 基于面向对象的设计,可直接获取 `Contact` 的属性如 `nick`,`permission`。请在实践时在接口源码内查看更清晰的说明。 ### 接收消息 消息通过事件被动接收。事件将在下一章节 [Events](Events.md) 讲解。 ### 主动发送消息 获取到目标对象并调用 `Contact.sendMessage(message)`。如要向某个群发送消息,则需要对应的 `Group` 对象,并调用 `Group.sendMessage(message)`。 注意,由于对象代表意义不同,发送的消息类型也可能不同。`NormalMember` 是普通群成员,`NormalMember.sendMessage` 是对群成员发送临时会话消息,而 `Friend.sendMessage` 是发送好友消息。(备注:在实际情况下,如果机器人与群成员有好友关系,`NormalMember.sendMessage` 也会自动转换为发送好友消息,以保证消息送达) ### 联系其他客户端 一个 QQ 账号可以在多个客户端登录,mirai 支持向其他客户端收发消息. 其他客户端被抽象为 `OtherClient`。 可以通过 `Bot.getOtherClients()` 获取到所有在线的其他客户端列表,并使用 `OtherClient.sendMessage` 来发送消息,这与操作普通好友类似。 ### 使用戳一戳 戳一戳的被动接收与消息的接收相同,也是以事件的形式(`NudgeEvent`)。 要发起戳一戳,使用 `Contact.nudge()` 创建一个戳一戳动作(`Nudge`)然后将其发送到目标用户或群(`Nudge.sendTo`)。 ### 操作群成员禁言和移除 使用 `Member.mute`, `Member.unmute`, `Member.kick`。 ### 提及群成员 提及(`@`)群成员在 mirai 属于 _消息_ 的范畴。将在后面章节介绍。 ### 其他功能 其他功能与上述类似,都作为成员方法位于目标对象中。未在成员方法或类注释中出现的其他功能即为目前还没有支持的功能。 ---- > 下一步,[Events](Events.md) > > [回到 Mirai 文档索引](CoreAPI.md) ================================================ FILE: docs/CoreAPI.md ================================================ # Mirai - Core API > 本文档适用于 mirai 2.x 版本 > 要由 mirai 1.x 迁移到 2.x,请阅读 [MigrationFrom1x.md](MigrationFrom1x.md) > 在 GitHub 和 `docs.mirai.mamoe.net` 默认看到的是最新版本的文档,可能不适用于旧版本。 > 可以将 URL `https://github.com/mamoe/mirai/blob/2.4.2/docs/README.md` 中的 `2.4.2` 替换为其他版本来查看对应文档。 - 机器人 [Bots](Bots.md) - 联系人 [Contacts](Contacts.md) - 事件 [Events](Events.md) - 消息 [Messages](Messages.md) *附录* - 常用 API [ConciseAPI](ConciseAPI.md) > 希望改进文档? 请在 [#848](https://github.com/mamoe/mirai/discussions/848) 提出建议 ================================================ FILE: docs/DebuggingNetwork.md ================================================ # Mirai - DebuggingNetwork 本章节介绍调试网络层的方法。 ## 环境变量 可通过 JVM 环境变量改变网络层的行为。一般用户通常不需要手动变更这些功能。 [launch-undispatched]: https://github.com/mamoe/mirai/blob/6eff4bdf40815598a2d987e08d89df6b97663967/mirai-core-api/src/commonMain/kotlin/internal/event/InternalEventListeners.kt#L141 [#1715]: https://github.com/mamoe/mirai/issues/1715 | 环境变量名称 | 可选值 | 解释 | |:----------------------------------------------------|:---------------------------------|:---------------------------------------------------------------------------------------| | `mirai.network.handler.selector.max.attempts` | `[1, 2147483647]` | 最大重连尝试次数 | | `mirai.network.reconnect.delay` | `[0, 9223372036854775807]` | 两次重连尝试的间隔毫秒数 | | `mirai.network.handler.selector.logging` | `true`/`false` | 启用执行重连时的详细日志 | | `mirai.network.handler.cancellation.trace` | `true`/`false` | 让网络层的异常时包含详细原因 | | `mirai.network.state.observer.logging` | `true`/`on`/`false`/`off`/`full` | 启用网络层状态变更的日志 | | `mirai.network.auth.logging` | `true`/`false` | 启用进行登录验证时的内部日志 | | `mirai.event.launch.undispatched` | `true`/`false` | 详见 [源码内注释][launch-undispatched] | | `mirai.resource.creation.stack.enabled` | `true`/`false` | 启用 `ExternalResource` 创建时的 stacktrace 记录 (影响性能), 在资源泄露时展示 | | `mirai.unknown.image.type.logging` | `true`/`false` | 启用遇到未知图片格式时的日志 | | `mirai.network.show.all.components` | `true`/`false` | 在网络层异常中附加当前所有组件 (components) 内容 | | `mirai.network.show.components.creation.stacktrace` | `true`/`false` | 在网络层异常中附加当前组件容器创建时的 stacktrace | | `mirai.network.packet.logger` | `true`/`false` | 启用数据包日志 (将为展示所有接收的数据包的 id, sequenceId, extraData 以及内容 hex) | | `mirai.network.show.verbose.packets` | `true`/`false` | 在日志记录数据包时包含冗长的数据包 (如 `MessageSvc.PbGetMsg`, `OnlinePush.ReqPush`, `StatSvc.SimpleGet`) | | `mirai.network.show.packet.details` | `true`/`false` | 在日志记录数据包时包含 mirai 解析结果 | | `mirai.event.show.verbose.events` | `true`/`false` | 在日志记录事件时包含冗长的事件 (如 `GroupMessagePreSendEvent`, `GroupMessagePostSendEvent`) | | `mirai.event.trace` | `true`/`false` | 在日志记录事件监听器创建及使用的信息 | | `mirai.message.allow.sending.file.message` | `true`/`false` | 允许发送 `FileMessage`, 用于兼容旧代码 ([#1715]) | | `mirai.jce.deserializer.debug` | `true`/`false` | 启用数据包解析错误的详细信息显示 | 修改示例: 在启动 JVM 时添加参数 `-Dmirai.network.handler.selector.logging=true` 则启用执行重连时的详细日志 ================================================ FILE: docs/EventList.md ================================================ # 事件 ## 事件列表一览 提示: - 在 IntelliJ 平台双击 shift 可输入类名进行全局搜索 - 在 IntelliJ 平台, 按 alt + 7 可打开文件的结构, [效果图](/.github/EZSLAB`K@YFFOW47{090W8B.png) ### Bot - Bot 登录完成: BotOnlineEvent - Bot 离线: BotOfflineEvent - 主动: Active - 被挤下线: Force - 被服务器断开或因网络问题而掉线: Dropped - 服务器主动要求更换另一个服务器: RequireReconnect - Bot 重新登录: BotReloginEvent - Bot 头像改变: BotAvatarChangedEvent - Bot 昵称改变: BotNickChangedEvent - Bot 被戳: NudgeEvent ### 消息 - 被动收到消息:MessageEvent - 群消息:GroupMessageEvent - 好友消息:FriendMessageEvent - 群临时会话消息:GroupTempMessageEvent - 陌生人消息:StrangerMessageEvent - 其他客户端消息:OtherClientMessageEvent - 主动发送消息前: MessagePreSendEvent - 群消息: GroupMessagePreSendEvent - 好友消息: FriendMessagePreSendEvent - 群临时会话消息: GroupTempMessagePreSendEvent - 陌生人消息:StrangerMessagePreSendEvent - 从其他客户端同步消息 MessageSyncEvent - 群消息: GroupMessageSyncEvent - 好友消息: FriendMessageSyncEvent - 群临时会话消息: GroupTempMessageSyncEvent - 陌生人消息: StrangerMessageSyncEvent - 主动发送消息后: MessagePostSendEvent - 群消息: GroupMessagePostSendEvent - 好友消息: FriendMessagePostSendEvent - 群临时会话消息: GroupTempMessagePostSendEvent - 陌生人消息:StrangerMessagePostSendEvent - 其他客户端消息:OtherClientMessagePostSendEvent - 消息撤回: MessageRecallEvent - 好友撤回: FriendRecall - 群撤回: GroupRecall - 群临时会话撤回: TempRecall - 图片上传前: BeforeImageUploadEvent - 图片上传完成: ImageUploadEvent - 图片上传成功: Succeed - 图片上传失败: Failed - 戳一戳: NudgeEvent ### 群 - 机器人被踢出群或在其他客户端主动退出一个群: BotLeaveEvent - 机器人主动退出一个群: Active - 机器人被管理员或群主踢出群: Kick - 机器人在群里的权限被改变: BotGroupPermissionChangeEvent - 机器人被禁言: BotMuteEvent - 机器人被取消禁言: BotUnmuteEvent - 机器人成功加入了一个新群: BotJoinGroupEvent #### 群设置 - 群设置改变: GroupSettingChangeEvent - 群名改变: GroupNameChangeEvent - 入群公告改变: GroupEntranceAnnouncementChangeEvent - 全员禁言状态改变: GroupMuteAllEvent - 匿名聊天状态改变: GroupAllowAnonymousChatEvent - 允许群员邀请好友加群状态改变: GroupAllowMemberInviteEvent #### 群成员 ##### 成员列表变更 - 成员已经加入群: MemberJoinEvent - 成员被邀请加入群: Invite - 成员主动加入群: Active - 成员已经离开群: MemberLeaveEvent - 成员被踢出群: Kick - 成员主动离开群: Quit - 一个账号请求加入群: MemberJoinRequestEvent - 机器人被邀请加入群: BotInvitedJoinGroupRequestEvent ##### 名片和头衔 - 成员群名片改动: MemberCardChangeEvent - 成员群特殊头衔改动: MemberSpecialTitleChangeEvent - 成员群荣誉改变: MemberHonorChangeEvent ##### 成员权限 - 成员权限改变: MemberPermissionChangeEvent ##### 动作 - 群成员被禁言: MemberMuteEvent - 群成员被取消禁言: MemberUnmuteEvent ### 好友 - 好友昵称改变: FriendRemarkChangeEvent - 成功添加了一个新好友: FriendAddEvent - 好友已被删除: FriendDeleteEvent - 一个账号请求添加机器人为好友: NewFriendRequestEvent - 好友头像改变: FriendAvatarChangedEvent - 好友昵称改变: FriendNickChangedEvent - 好友输入状态改变: FriendInputStatusChangedEvent ### 控制台 - 自动登录执行后: AutoLoginEvent [2.15.0, +∞) - 控制台启动完成: StartupEvent [2.15.0, +∞) ================================================ FILE: docs/Events.md ================================================ # Mirai - Events ## 目录 - [事件系统](#事件系统) - [快速指导](#快速指导) - [事件通道](#事件通道) - [通道操作](#通道操作) - [过滤](#过滤) - [添加 `CoroutineContext`](#添加-coroutinecontext) - [限制作用域](#限制作用域) - [链式调用](#链式调用) - [在 `EventChannel` 监听事件](#在-eventchannel-监听事件) - [监听事件的其他方法](#监听事件的其他方法) - [使用 `ListenerHost` 监听事件](#使用-eventhandler-注解标注的方法监听事件) - [在 Kotlin 使用 DSL 监听事件](#在-kotlin-使用-dsl-监听事件) - [实现事件](#实现事件) * [新建事件](#新建事件) * [广播自定义事件](#广播自定义事件) * [监听自定义事件](#监听自定义事件) * [在 Console 中自定义事件](../mirai-console/docs/plugin/JVMPlugin-DataExchange.md) - [工具函数(Kotlin)](#工具函数kotlin) - [线性同步(`syncFromEvent`)](#线性同步syncfromevent) - [线性同步(`nextEvent`)](#线性同步nextevent) - [条件选择(`selectMessages`)](#条件选择selectmessages) - [循环条件选择(`whileSelectMessages`)](#循环条件选择whileselectmessages) ## 事件系统 Mirai 许多功能都依赖事件。 [`Event`]: ../mirai-core-api/src/commonMain/kotlin/event/Event.kt#L21-L62 每个事件都实现接口 [`Event`],且继承 `AbstractEvent`。 实现 `CancellableEvent` 的事件可以被取消(`CancellableEvent.cancel`)。 **[事件列表](EventList.md)** > 回到 [目录](#目录) ## 快速指导 如果你了解事件且不希望详细阅读,可以立即仿照下面示例创建事件监听并跳过本章节。 注意,**`GlobalEventChannel` 会监听到来自所有 `Bot` 的事件,如果只希望监听某一个 `Bot` 的事件,请使用 `bot.eventChannel`。** 有关消息 `Message`、`MessageChain` 将会在后文 _消息系统_ 章节解释。 ### Kotlin ```kotlin // 事件监听器是协程任务。如果你有 CoroutineScope,可从 scope 继承生命周期管理和 coroutineContext GlobalEventChannel.parentScope(coroutineScope).subscribeAlways { event -> // this: GroupMessageEvent // event: GroupMessageEvent // `event.message` 是接收到的消息内容, 可自行处理. 由于 `this` 也是 `GroupMessageEvent`, 可以通过 `message` 直接获取. 详细查阅 `GroupMessageEvent`. subject.sendMessage("Hello!") } // `GlobalEventChannel.parentScope(coroutineScope)` 也可以替换为使用扩展 `coroutineScope.globalEventChannel()`, 根据个人习惯选择 // 如果不想限制生命周期,可获取 listener 处理 val listener: CompletableJob = GlobalEventChannel.subscribeAlways { event -> } listener.complete() // 停止监听 ``` 异常默认会被相关 `Bot` 日志记录。可以在 `subscribeAlways` 之前添加如下内容来处理异常。 ``` .exceptionHandler { e -> e.printStackTrace() } ``` **注意**:如果要在 Mirai Console 插件中监听事件,请不要使用使用无作用域控制的 `GlobalEventChannel` ,如 `GlobalEventChannel.subscribeAlways` 。请使用插件主类的扩展函数 `globalEventChannel()` 或者 `GlobalEventChannel.parentScope(scope)` 等方式控制监听器协程作用域。 ### Java ```java // 创建监听 Listener listener=GlobalEventChannel.INSTANCE.parentScope(scope).subscribeAlways(GroupMessageEvent.class,event->{ MessageChain chain=event.getMessage(); // 可获取到消息内容等, 详细查阅 `GroupMessageEvent` event.getSubject().sendMessage("Hello!"); // 回复消息 }) listener.complete(); // 停止监听 ``` 异常默认会被相关 `Bot` 日志记录。可以在 `subscribeAlways` 之前添加如下内容来处理异常。 ```java .exceptionHandler(e->e.printStackTrace()) ``` **注意**:如果要在 Mirai Console 插件中监听事件,请不要使用使用无作用域控制的 `GlobalEventChannel` ,如 `GlobalEventChannel.subscribeAlways` 。请使用 `GlobalEventChannel.parentScope(PluginMain.INSTANCE)` 等方式控制监听器协程作用域。 > 你已经了解了基本事件操作。现在你可以继续阅读通道处理和扩展等内容,或: > > - 跳到下一章 [Messages](Messages.md) > - [查看事件列表](EventList.md) > - [回到事件文档目录](#目录) > - [回到 Mirai 文档索引](CoreAPI.md) ## 事件通道 [`EventChannel`]: ../mirai-core-api/src/commonMain/kotlin/event/EventChannel.kt [事件通道][`EventChannel`]是监听事件的入口。 **在不同的事件通道中可以监听到不同类型的事件**。 ### 获取事件通道 [`GlobalEventChannel`]: ../mirai-core-api/src/commonMain/kotlin/event/GlobalEventChannel.kt [`GlobalEventChannel`] 是最大的通道:所有的事件都可以在 [`GlobalEventChannel`] 监听到。**因此,[`GlobalEventChannel`] 会包含来自所有 `Bot` 实例的事件。** 通常不会直接使用 [`GlobalEventChannel`],而是使用经过 [通道操作](#通道操作) 操作的子通道。 > 回到 [目录](#目录) ## 通道操作 `EventChannel` 可以通过一些操作转换。 **一个通道的属性都是*不变的*:每个转换操作都会创建一个新的通道而不会修改原通道。** ### 过滤 `GlobalEventChannel` 包含任何 `Event`,可以通过 `EventChannel.filter` 过滤得到一个只包含期望的事件的 `EventChannel`。 ```kotlin var channel = GlobalEventChannel.filter { it is BotEvent && it.bot.id == 123456L } // 筛选来自某一个 Bot 的事件 ``` ```java EventChannel channel = GlobalEventChannel.INSTANCE.filter(ev -> ev instanceof BotEvent && ((BotEvent) ev).bot.id == 123456); // 筛选来自某一个 Bot 的事件 ``` > 回到 [通道操作](#通道操作) > 你可以选择跳过下文介绍的协程属性和作用域,直接阅读 [在 `EventChannel` 监听事件](#在-eventchannel-监听事件) ### 添加 `CoroutineContext` 一个通道持有属性 `defaultCoroutineContext`,将会自动添加给每个事件监听器(见后文)。 可以为通道添加一些 `CoroutineContext`,如 `CoroutineExceptionHandler`(用于处理监听时产生的异常)。 ```kotlin channel.exceptionHandler { exception -> logger.error(exception) } ``` ```java channel.exceptionHandler(exception -> logger.error(exception); ); ``` 这本质上是添加了一个 `CoroutineExceptionHandler`。之后当事件监听器出现异常,异常就会被传递到这个 `CoroutineExceptionHandler` 处理。 > 回到 [通道操作](#通道操作) ### 限制作用域 在监听时会创建一些*事件监听器*。*事件监听器*本质上是一个协程 *`Job`*,因此可以有父 `Job`。 要指定父 `Job`,请使用 `parentJob` 或 `parentScope` 操作。 ```kotlin // parentScope channel = channel.parentScope(MyApplicationScope) // parentJob val job = SupervisorJob() channel = channel.parentJob(job) ``` 在 Kotlin,可以使用如下扩展快速在 GlobalEventChannel 创建一个指定协程作用域下的事件通道。 > ```kotlin > fun CoroutineScope.globalEventChannel(coroutineContext: CoroutineContext = EmptyCoroutineContext): EventChannel = GlobalEventChannel.parentScope(this, coroutineContext) > ``` ```kotlin val channel = MyApplicationScope.globalEventChannel() ``` 作用域限制对于应用生命周期管理会很有用。请看如下 Mirai Console 插件示例。 ```kotlin object MyPluginMain : KotlinPlugin() { // KotlinPlugin 实现了 CoroutineScope // 插件被启用时调用 override fun onEnable() { // `this` 是插件的协程作用域 // 在插件协程作用域里创建事件监听。当插件被停用时,插件的协程作用域也会被关闭,事件监听就会被同步停止。 this.globalEventChannel().subscribeAlways { event -> // 处理事件 } } } ``` > 有关限制作用域的实现细节,可在使用时阅读源码内文档。 ### 链式调用 对通道的操作都会返回 `this`,因此可以链式调用。 ```kotlin val channel = GlobalEventChannel .filterIsInstance() .filter { it.bot.id == 123456L } .filter { /* some other conditions */ } .parentScope(MyApplicationScope) .exceptionHandler { exception -> exception.printStacktrace() } ``` > 回到 [通道操作](#通道操作) > 回到 [目录](#目录) ## 在 `EventChannel` 监听事件 使用: - `EventChannel.subscribe`:监听事件并自行决定何时停止 - `EventChannel.subscribeAlways`:一直监听事件 - `EventChannel.subscribeOnce`:只监听一次事件 ```kotlin bot.eventChannel.subscribeAlways { event -> // this: GroupMessageEvent // event: GroupMessageEvent subject.sendMessage("Hello from mirai!") } ``` ```java bot.eventChannel.subscribeAlways(GroupMessageEvent.class, event -> { event.getSubject().sendMessage("Hello from mirai!"); }) ``` > 实现细节可查看源码内注释。 > 回到 [目录](#目录) ## 监听事件的其他方法 监听都需要在*事件通道*中进行。如下几种方法都本质上会调用上述 `EventChannel.subscribe` 等方法。 - [使用 `@EventHandler` 注解标注的方法监听事件](#使用-eventhandler-注解标注的方法监听事件) - [在 Kotlin 使用 DSL 监听事件](#在-kotlin-使用-dsl-监听事件) ### 使用 `@EventHandler` 注解标注的方法监听事件 标注一个函数(方法)为事件监听器。mirai 通过反射获取他们并为之注册事件。 - [Kotlin 函数](#kotlin-函数) - [Java 方法](#java-方法) #### Kotlin 函数 Kotlin 函数要求: - 接收者和函数参数: 所标注的 Kotlin 函数必须至少拥有一个接收者或一个函数参数, 或二者都具有. 接收者和函数参数的类型必须相同 (如果二者都存在) 接收者或函数参数的类型都必须为 `Event` 或其子类. 所有 Kotlin 非 `suspend` 的函数都将会在 `Dispatchers.IO` 中调用 支持的函数类型: ```kotlin // 所有函数参数, 函数返回值都不允许标记为可空 (带有 '?' 符号) // T 表示任何 Event 类型. suspend fun T.onEvent(T) suspend fun T.onEvent(T): ListeningStatus suspend fun T.onEvent(T): Nothing suspend fun onEvent(T) suspend fun onEvent(T): ListeningStatus suspend fun onEvent(T): Nothing suspend fun T.onEvent() suspend fun T.onEvent(): ListeningStatus suspend fun T.onEvent(): Nothing fun T.onEvent(T) fun T.onEvent(T): ListeningStatus fun T.onEvent(T): Nothing fun onEvent(T) fun onEvent(T): ListeningStatus fun onEvent(T): Nothing fun T.onEvent() fun T.onEvent(): ListeningStatus fun T.onEvent(): Nothing ``` Kotlin 使用示例: - 独立 `CoroutineScope` 和 `ListenerHost` ```kotlin object MyEvents : ListenerHost { override val coroutineContext = SupervisorJob() // 可以抛出任何异常, 将在 this.coroutineContext 或 registerEvents 时提供的 CoroutineScope.coroutineContext 中的 CoroutineExceptionHandler 处理. @EventHandler suspend fun MessageEvent.onMessage() { reply("received") } } eventChannel.registerListenerHost(MyEvents) ``` `onMessage` 抛出的异常将会交给 `myCoroutineScope` 处理 - 合并 `CoroutineScope` 和 `ListenerHost`: 使用 `SimpleListenerHost` ```kotlin object MyEvents : SimpleListenerHost( /* override coroutineContext here */ ) { override fun handleException(context: CoroutineContext, exception: Throwable) { // 处理 onMessage 中未捕获的异常 } @EventHandler suspend fun MessageEvent.onMessage() { // 可以抛出任何异常, 将在 handleException 处理 reply("received") // 无返回值 (或者返回 Unit), 表示一直监听事件. } @EventHandler suspend fun MessageEvent.onMessage(): ListeningStatus { // 可以抛出任何异常, 将在 handleException 处理 reply("received") return ListeningStatus.LISTENING // 表示继续监听事件 // return ListeningStatus.STOPPED // 表示停止监听事件 } } eventChannel.registerListenerHost(MyEvents) ``` #### Java 方法 所有 Java 方法都会在 `Dispatchers.IO` 中调用,因此在 Java 也可以调用阻塞方法。 支持的方法类型: ``` // T 表示任何 Event 类型. void onEvent(T) Void onEvent(T) ListeningStatus onEvent(T) // 禁止返回 null ``` Java 使用示例: ```java public class MyEventHandlers extends SimpleListenerHost { @Override public void handleException(@NotNull CoroutineContext context, @NotNull Throwable exception){ // 处理事件处理时抛出的异常 } @EventHandler public void onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理 event.getSubject().sendMessage("received"); // 无返回值, 表示一直监听事件. } @NotNull @EventHandler public ListeningStatus onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理 event.getSubject().sendMessage("received"); return ListeningStatus.LISTENING; // 表示继续监听事件 // return ListeningStatus.STOPPED; // 表示停止监听事件 } } // 注册: // eventChannel.registerListenerHost(new MyEventHandlers()) ``` > 回到 [监听事件的其他方法](#监听事件的其他方法) ### 在 Kotlin 使用 DSL 监听事件 > **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题** [subscribeMessages](../mirai-core-api/src/commonMain/kotlin/event/subscribeMessages.kt#L37-L64) 示例: ```kotlin eventChannel.subscribeMessages { "test" { // 当消息内容为 "test" 时执行 // this: MessageEvent reply("test!") } "Hello" reply "Hi" // 当消息内容为 "Hello" 时回复 "Hi" "quote me" quoteReply "ok" // 当消息内容为 "quote me" 时引用该消息并回复 "ok" "quote me2" quoteReply { // lambda 也是允许的: // 返回值接受 Any? // 为 Unit 时不发送任何内容; // 为 Message 时直接发送; // 为 String 时发送为 PlainText; // 否则 toString 并发送为 PlainText "ok" } case("iGNorECase", ignoreCase=true) reply "OK" // 忽略大小写 startsWith("-") reply { cmd -> // 当消息内容以 "-" 开头时执行 // cmd 为消息去除开头 "-" 的内容 } val listener: Listener = "1" reply "2" // 每个语句都会被注册为事件监听器,可以这样获取监听器 listener.complete() // 停止 "1" reply "2" 这个事件监听 } ``` > 回到 [目录](#目录) ## 实现事件 相信你在使用 mirai 自带的事件时已经感到受益匪浅了,这种机制也可以作用在你的程序上,让其他人的程序也能像监听 mirai 自带的事件一样,对你程序的行为作出反应。 ### 新建事件 新建一个类,让类实现接口 `Event` 并继承 `AbstractEvent` 即可。根据内部实现,事件必须继承 `AbstractEvent`,如下示例。 ```kotlin // kotlin: class ExampleEvent( var action: String ) : AbstractEvent() ``` ```java // java: public class ExampleEvent extends AbstractEvent { String action; public ExampleEvent(String action) { this.action = action; } public String getAction() { return action; } public void setAction(String action) { this.action = action; } } ``` ### 广播自定义事件 事件需要被广播,才会被监听器接收到,使已监听事件的程序作出响应。以上文的 `ExampleEvent` 为例, kotlin:`event.broadcast()` java:`EventKt.broadcast(Event)` > 注: 在 kotlin 中进行事件的广播需要在协程上下文执行 ```kotlin // kotlin: var event = ExampleEvent("some action") var finalAction = event.broadcast().action println("action = $finalAction") ``` ```java // java: ExampleEvent event = new ExampleEvent("some action"); String finalAction = EventKt.broadcast(event).getAction(); System.out.println("action = " + finalAction); ``` ### 监听自定义事件 同上文监听事件的方式几乎一样。不过需要注意的是,从 bot 获取的消息通道 (`bot.eventChannel`),只能监听 `BotEvent`,如果你的事件类没有实现 `BotEvent`,将无法通过这个通道来监听此事件。因此你可能需要使用 `GlobalEventChannel` 来代替 `bot.eventChannel`。 以下的示例是 监听事件以影响上一个部分 `广播自定义事件` 中的变量 `action` 的值。 ```kotlin // kotlin: GlobalEventChannel.subscribeAlways { event -> // 处理事件 if (event.action == "some action") { event.action = "another action" } } ``` ```java // java: GlobalEventChannel.INSTANCE.subscribeAlways(ExampleEvent.class, event -> { // 处理事件 if (event.getAction().equals("some action")) { event.setAction("another action"); } }); ``` ### 在 Console 中自定义事件 请参考 [Console - JVMPlugin - Data Exchange](../mirai-console/docs/plugin/JVMPlugin-DataExchange.md) > 回到 [目录](#目录) ## 工具函数(Kotlin) > *可能需要较好的 Kotlin 技能才能理解以下内容。* > **可以[跳过本节](# )** 基于 Kotlin 协程特性,mirai 提供 ` ### 线性同步(`syncFromEvent`) [syncFromEvent.kt](../mirai-core-api/src/commonMain/kotlin/event/syncFromEvent.kt) 挂起协程并获取下一个戳 Bot 的对象: ```kotlin val target: UserOrBot = syncFromEvent { sender } ``` 带超时版本: ```kotlin val target: UserOrBot = syncFromEvent(5000) { sender } // 5000ms ``` 异步 `async` 版本: ```kotlin val target: Deferred = coroutineScope.asyncFromEvent { sender } ``` ### 线性同步(`nextEvent`) [nextEvent.kt](../mirai-core-api/src/commonMain/kotlin/event/nextEvent.kt) 挂起协程并获取下一个指定事件: ```kotlin val event: BotNudgedEvent = nextEvent() ``` 带超时和过滤器版本: ```kotlin val event: BotNudgedEvent = nextEvent(5000) { it.bot.id == 123456L } ``` ### 条件选择(`selectMessages`) > **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题** [select.kt](../mirai-core-api/src/commonMain/kotlin/event/select.kt) 类似于 Kotlin 协程 `select`,mirai 也提供类似的功能。 `selectMessages`:挂起当前协程,等待任意一个事件监听器触发后返回其返回值。 ```kotlin MyCoroutineScope.subscribeAlways { if (message.contentEquals("ocr")) { subject.sendMessage("请发送你要进行 OCR 的图片或图片链接") val image: InputStream = selectMessages { has { URL(it.queryUrl()).openStream() } has { URL(it.content).openStream() } defaultReply { "请发送图片或图片链接" } timeout(30_000) { event.quoteReply("请在 30 秒内发送图片或图片链接"); null } } ?: return@subscribeAlways val result = ocr(image) subject.sendMessage(message.quote() + result) } } ``` 这种语法就相当于(伪代码): ``` val image = when (下一条消息) { 包含图片 { 查询图片链接() } 包含纯文本URL { 下载图片() } 其他情况 { 引用回复() } 超时 { 引用回复() } } ``` ### 循环条件选择(`whileSelectMessages`) > **警告:此节内容需要坚实的 Kotlin 技能,盲目使用会导致问题** [select.kt](../mirai-core-api/src/commonMain/kotlin/event/select.kt) 类似于 Kotlin 协程 `whileSelect`,mirai 也提供类似的功能。 `whileSelectMessages`:挂起当前协程,等待任意一个事件监听器返回 `false` 后返回。 ```kotlin subject.sendMessage("开启复读模式") whileSelectMessages { "stop" { subject.sendMessage("已关闭复读") false // 停止循环 } // 也可以使用 startsWith("") { ... } 等 DSL default { subject.sendMessage(message) true // 继续循环 } timeout(3000) { // on true } } // 等待直到 `false` subject.sendMessage("复读模式结束") ``` > 回到 [目录](#目录) ######   ---- > 下一步,[Messages](Messages.md) > > [回到 Mirai 文档索引](CoreAPI.md) ================================================ FILE: docs/Evolution.md ================================================ # Mirai - Evolution ### Mirai 演进 Mirai 是不断前进的库,目标是提供稳定且高效的 API。 维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。 ### 版本规范 Mirai 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。 在日常开发中, Mirai 会以 `-dev-1`,`-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。 在大版本开发过程中,Mirai 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。 这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。 在大版本即将发布前,Mirai 会以 `-RC` 版本后缀发布最终的预览版本。 `RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。 ### 版本选择 **稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。 | 目的 | 推荐至少更新到版本 | |:------------------:|:--------------:| | 生产环境 | `x.y.z` | | 希望尽早体验稳定新特性 | `-RC` | | 无论如何都想体验新特性 | `-M` | | 为 Mirai 提交 PR | `-dev` | ## 更新兼容性 对于 `x.y.z` 版本号: - 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。 - 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。 - 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。 ## 弃用周期 一个计划被删除的 API,将会在下一个次版本开始经历弃用周期。 如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。 在 `1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误); 在 `1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。 `HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。 ---- > [回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发) ================================================ FILE: docs/KotlinAndJava.md ================================================ # Mirai - Kotlin And Java 本章介绍部分 Kotlin 定义对应的 Java 定义,以帮助 Java 使用者理解 Mirai 的源代码。 每部分第一个代码块为 Kotlin 代码,第二个代码块为 Java 代码。 预计阅读时间:5 分钟 #### 通用 - Kotlin 的定义都默认是 `public` 和 `final` - Kotlin 不需要句末分号,通常以换行作为一个语句的结束 #### `class` ```kotlin class A ``` ```java public final class A { } ``` #### 构造器定义 以下几种 Kotlin 定义是等价的。 ```kotlin class A { private val value: String constructor(value: String) { this.value = value } constructor(integer: Int) { this.value = integer.toString() } } ``` ```kotlin class A(val value: String) { // 类定义后面的括号表示主构造器 constructor(integer: Int) : this(integer.toString()) } ``` 对应的 Java 定义为: ```java public final class A { private final String value; public A(String value) { this.value = value; } public A(int integer) { this.value = String.valueOf(integer); } } ``` 通常 Kotlin class 都会有一个主构造器。 #### 构造器调用 Kotlin 不需要 `new` 关键字。 ```kotlin val a = A("test") ``` ```java A a = new A("test"); ``` #### 函数 ```kotlin class A { fun test(string: String): Int = 1 } ``` ```java public final class A { public int test(String string) { return 1; } } ``` #### 属性 `val` - Kotlin 的 `val` 是不可变的,只能被赋值一次 - 编译器为 `val` 创建 `getter` ```kotlin class A { val value: String = "test" } ``` ```java public final class A { private final String value = "test"; public final String getValue() { return value; } } ``` #### 属性 `var` - Kotlin 的 `var` 相较于 `val` 是可变的,可以被多次赋值。 - 编译器为 `var` 创建 `getter` 和 `setter` ```kotlin class A { var value: String = "test" } ``` ```java public final class A { private String value = "test"; public final String getValue() { return value; } public final String setValue(String value) { this.value = value; } } ``` #### 顶层定义和 `const` - Kotlin 的定义不一定需要在 `class` 中,允许直接存在于文件中的「顶层函数」和「顶层属性」 - `XXX.kt` 中的顶层定义会被编译为名为 `XXXKt` 的 `class` - 顶层定义会被编译为 `static` - `const` 可以修饰一个属性,编译器会把它编译为 Java 静态字段。 ```kotlin // Test.kt val x: String = "xx" const val CONST_VALUE: String = "cc" fun foo() { } ``` ```java // TestKt.java public final class TestKt { public static final String CONST_VALUE = "cc"; // const val 没有 getter private static final String x = "xx"; public static String getX(){ return x; } public static void foo() { } } ``` #### 单例对象 - Kotlin `object` 定义一个单例对象 ```kotlin object Test ``` ```java public final class Test { public static final Test INSTANCE = new Test(); private Test() {} } ``` #### 静态 ```kotlin object Test { val x = "x" // public String getX() @JvmField val y = "y" // public static final String y; @JvmStatic val z = "z" // public static String getZ() } ``` ```java public final class Test { public static final Test INSTANCE = new Test(); private Test() {} private final String x = "x"; // val public String getX() { return x; } public static final String y = "y"; // @JvmField val private final String z = "z"; // @JvmStatic val public static String getZ() { return z; } } ``` #### 静态 - Kotlin 没有 `static` 关键字,但可以通过 `@JvmStatic` 将一个函数编译为 `static` ```kotlin object Test { fun a() { } @JvmStatic fun b() { } } ``` ```java public final class Test { public static final Test INSTANCE = new Test(); private Test() {} public void a() { } public static void b() { } } ``` #### 伴生对象 - `class` 可以拥有 `companion object` - 伴生对象内的 `@JvmField` 定义将会被编译到外部 `class` - 伴生对象内的 `@JvmStatic` 函数以成员方法编译到伴生对象,然后以静态方法编译到外部 `class` ```kotlin class Test { companion object { @JvmField val CONST: String = "" fun a() { } @JvmStatic fun b() { } } } ``` ```java public final class Test { public static final Companion Companion = new Companion(); public static final String CONST = ""; public static void b() { Companion.b(); } public static final class Companion { public void a() { } public void b() { } } } ``` #### 协程 Kotlin 协程是语言级特性,`suspend` 修饰的函数会在编译期被处理。 ```kotlin class A { suspend fun getValue(): String { /* ... */ } } ``` ```java public final class A { public Object getValue(Continuation<? super String> $completion) { // 由 Kotlin 编译器生成非常复杂的方法体 } } ``` `$completion` 参数类似于一个回调。需要熟悉 Kotlin 协程原理才能实现。为帮助 Java 用户,mirai 使用编译器插件处理 `suspend` 函数。 ```kotlin class A { @JvmBlockingBridge suspend fun getValue(): String { /* ... */ } } ``` ```java public final class A { public Object getValue(Continuation<? super String> $completion) { // 由 Kotlin 编译器生成非常复杂的方法体 } // 通过 @JvmBlockingBridge 生成的方法 public String getValue() { // 由 @JvmBlockingBridge 的编译器生成方法体,调用 getValue(Continuation) } } ``` Java 使用者可以认为 `@JvmBlockingBridge suspend fun getValue(): String` 相当于 `fun getValue(): String`。 ---- > [回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发) ================================================ FILE: docs/Messages.md ================================================ # Mirai - Messages ## 目录 - [消息系统](#消息系统) - [消息类型](#消息类型) - [消息元素](#消息元素) - [消息链](#消息链) - [发送消息](#发送消息) - [构造消息链](#构造消息链) - [元素唯一性](#元素唯一性) - [获取消息链中的消息元素](#获取消息链中的消息元素) - [序列化](#序列化) - [消息的其他常用功能](#消息的其他常用功能) - [Mirai 码](#mirai-码) - [转义规则](#转义规则) - [消息链的 mirai 码](#消息链的-mirai-码) - [由 `CodableMessage` 取得 mirai 码字符串](#由-codablemessage-取得-mirai-码字符串) - [由 mirai 码字符串取得 `MessageChain` 实例](#由-mirai-码字符串取得-messagechain-实例) - [`serializeToMiraiCode` 与 `toString` 的区别](#serializetomiraicode-与-tostring-的区别) --- ## 消息系统 在[Contacts 章节](Contacts.md)提到,要发送消息,使用 `Contact.sendMessage(Message)`。`Message` 架构如下图所示。 [![](https://mermaid.ink/img/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiY2xhc3NEaWFncmFtXG5cbmNsYXNzIE1lc3NhZ2VDaGFpblxuTWVzc2FnZUNoYWluIDogTGlzdH5TaW5nbGVNZXNzYWdlflxuXG5NZXNzYWdlPHwtLU1lc3NhZ2VDaGFpblxuTWVzc2FnZTx8LS1TaW5nbGVNZXNzYWdlXG5cbk1lc3NhZ2VDaGFpbiBvLS0gU2luZ2xlTWVzc2FnZVxuXG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VDb250ZW50XG5TaW5nbGVNZXNzYWdlPHwtLU1lc3NhZ2VNZXRhZGF0YVxuXG4iLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ) `SingleMessage` 表示单个消息元素。 `MessageChain`(消息链) 是 `List<SingleMessage>`。主动发送的消息和从服务器接收消息都是 `MessageChain`。 > 回到 [目录](#目录) ## 消息类型 Mirai 支持富文本消息。 *单个消息元素(`SingleMessage`)* 分为 *内容(`MessageContent`)* 和 *元数据(`MessageMetadata`)*。 实践中,消息内容和消息元数据会混合存在于消息链中。 ### 内容 *内容(`MessageContent`)* 即为 *纯文本*、*提及某人*、*图片*、*语音* 和 *音乐分享* 等**有内容**的数据,一条消息中必须包含内容才能发送。 ### 元数据 *元数据(`MessageMetadata`)* 包含 *来源*、*引用回复* 和 *秀图标识* 等。 - *消息来源*(`MessageSource`)存在于每条消息中,包含唯一识别信息,用于撤回和引用回复的定位。 - *引用回复*(`QuoteReply`)若存在,则会在客户端中解析为本条消息引用了另一条消息。 - *秀图标识*(`ShowImageFlag`)若存在,则表明这条消息中的图片是以秀图发送(QQ 的一个功能)。 元数据与内容的区分就在于,一条消息没有元数据也能显示,但一条消息不能没有内容。**元数据是消息的属性**。 后文会介绍如何获取元数据。 > 回到 [目录](#目录) --- ## 消息元素 Mirai 支持多种消息类型。 消息拥有三种转换到字符串的表示方式。 | 方法 | 解释 | |:-------------------------|:---------------------------------------------------------------------------------------------| | `serializeToMiraiCode()` | 对应的 Mirai 码. 消息的一种序列化方式,格式为 `[mirai:TYPE:PROP]`,其中 `TYPE` 为消息类型, `PROP` 为属性 | | `contentToString()` | QQ 对话框中以纯文本方式会显示的消息内容。无法用纯文字表示的消息会丢失信息,如任何图片都是 `[图片]` | | `toString()` | Java 对象的 `toString()`,会尽可能包含多的信息用于调试作用,**行为可能不确定** | 各类型消息元素及其 `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 [`Face`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Face.kt [`PokeMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt [`VipFace`]: ../mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt [`Image`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Image.kt [`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 [`FileMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/FileMessage.kt [`RockPaperScissors`]: ../mirai-core-api/src/commonMain/kotlin/message/data/RockPaperScissors.kt [`MessageSource`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt [`QuoteReply`]: ../mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt [`LightApp`]: ../mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt [`SimpleServiceMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt [`Voice`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt [`Audio`]: ../mirai-core-api/src/commonMain/kotlin/message/data/Audio.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/MessageOrigin.kt [`MessageOrigin`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageOrigin.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 *<sup>(3)</sup>* | | [`MarketFace`] | 商城表情 | `[表情对应的中文名]` | 2.0 | | [`ForwardMessage`] | 合并转发 | `[转发消息]` | 2.0 *<sup>(1)</sup>* | | [`SimpleServiceMessage`] | (不稳定)服务消息 | `$content` | 2.0 | | [`MusicShare`] | 音乐分享 | `[分享]曲名` | 2.1 | | [`Dice`] | 魔法表情骰子 | `[骰子:$value]` | 2.5 | | [`RockPaperScissors`] | 魔法表情猜拳 | `[石头]`/`[剪刀]`/`[布]` | 2.14 | | [`FileMessage`] | 文件消息 | `[文件]文件名称` | 2.5 | | [`Audio`] | 语音 | `[语音消息]` | 2.7 | | [`MessageMetadata`] 类型 | 解释 | 最低支持的版本 | |:-----------------------:|:------------|:-------------------:| | [`MessageSource`] | 消息来源元数据 | 2.0 | | [`QuoteReply`] | 引用回复 | 2.0 | | [`ShowImageFlag`] | 秀图标识 | 2.2 | | [`RichMessageOrigin`] | 富文本消息源 | 2.3 *<sup>(2)</sup>* | | [`MessageOrigin`] | 富文本消息源 | 2.6 | > *(1):* [`ForwardMessage`] 在 2.0 支持发送, 在 2.3 支持接收 > *(2):* [`RichMessageOrigin`] 在 2.3 增加, 在 2.6 弃用并以 [`MessageOrigin`] 替换 > *(3):* [`Voice`] 在 2.0 增加, 在 2.7 弃用并以 [`Audio`] 替换 ### 用法 只需要得到各种类型 `Message` 的实例就可以使用,可以直接发送(`Contact.sendMessage`)也可以连接到消息链中(`Message.plus`)。 可在上文表格中找到需要的类型并在源码内文档获取更多实践上的帮助。 > 回到 [目录](#目录) ## 消息链 [`MessageChain`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt [`SingleMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt [`CodableMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt 前文已经介绍消息链,这里简略介绍消息链的使用。详细的使用请查看源码内注释。 ### 发送消息 在 [Contacts 章节](Contacts.md) 提到,要发送消息使用 `Contact.sendMessage`。`Contact.sendMessage` 的定义是: ```kotlin suspend fun sendMessage(message: Message): MessageReceipt<Contact> ``` 要发送字符串消息,使用:(第一部分是 Kotlin,随后是 Java,下同) ```kotlin contact.sendMessage("Hello!") ``` ```java contact.sendMessage("Hello!"); ``` 发送字符串实际上是在发送纯文本消息。上面的代码相当于: ```kotlin contact.sendMessage(PlainText("Hello!")) ``` ```java contact.sendMessage(new PlainText("Hello!")); ``` 要发送多元素消息,可将消息使用 `plus` 操作连接: ```kotlin contact.sendMessage(PlainText("你要的图片是") + Image("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png")) // 一个纯文本加一个图片 ``` ```java contact.sendMessage(new PlainText("你要的图片是:").plus(Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png"))); // 一个纯文本加一个图片 ``` 注当需要拼接的消息元素较多时,建议[使用构建器](#构造消息链)。 在 mirai 2.12 起,`plus` 在不同时涉及消息链以及元数据时,速度与构建器相当。而在这以前 `plus` 在很多情况下都比使用构建器慢。(备注:只要不是要连接数百个或更多元素,这个性能差距实际上也可以忽略) ### 构造消息链 多个消息元素可以作为消息链组合。 #### 在 Kotlin 构造消息链 | 定义 | |:--------------------------------------------------------| | `fun Iterable<Messaged>.toMessageChain(): MessageChain` | | `fun Sequence<Messaged>.toMessageChain(): MessageChain` | | `fun Array<Message>.toMessageChain(): MessageChain` | | `fun Message.toMessageChain(): MessageChain` | | `fun messageChainOf(vararg Message): MessageChain` | | `fun Message.plus(tail: Message): MessageChain` | 可以使用如上表格所示的方法构造,或使用 DSL 构建器。构建器的简单定义如下: ```kotlin class MessageChainBuilder : MutableList<SingleMessage>, Appendable { operator fun Message.unaryPlus() operator fun String.unaryPlus() fun add(vararg messages: Message) } ``` 在 Kotlin 的使用示例: ```kotlin val chain = buildMessageChain { +PlainText("a") +AtAll +Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") add(At(123456)) // `+` 和 `add` 作用相同 } // chain 结果是包含 PlainText, AtAll, Image, At 的 MessageChain ``` 该示例中 `+` 是位于 `MessageChainBuilder` 的 `Message.unaryPlus` 扩展。使用 `+` 和使用 `add` 是相等的。 #### 在 Java 构造消息链 | 定义 | |:---------------------------------------------------------------------| | `public static MessageChain newChain(Iterable<Message> iterable)` | | `public static MessageChain newChain(Message iterable...)` | | `public static MessageChain newChain(Iterator<Message> iterable...)` | 方法都位于 `net.mamoe.mirai.message.data.MessageUtils`。 使用 `newChain`: ```java MessageChain chain = MessageUtils.newChain(new PlainText("Hello"), Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png")); ``` 使用 `MessageChainBuilder`: ```java MessageChain chain = new MessageChainBuilder() .append(new PlainText("string")) .append("string") // 会被构造成 PlainText 再添加, 相当于上一行 .append(AtAll.INSTANCE) .append(Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png")) .build(); ``` ### 作为字符串处理消息 通常要把消息作为字符串处理,可在 Kotlin 使用 `message.content` 或在 Java 使用 `message.contentToString()`。 获取到的字符串表示只包含各 [`MessageContent`] 以官方风格显示的消息内容。如 `"你本次测试的成绩是[图片]"`、`[语音]`、`[微笑]`。 使用 `toString()` 对某些元素可以获得与 `contentToString()` 相似的结果,但 **`toString` 是不稳定**的,可能在未来版本变更。 `contentToString()` 实际上也**没有**对所有元素都作出稳定性承诺,在开发时应尽可能避免基于复杂消息类型的字符串表示处理。 ### 处理富文本消息 Mirai 不内置富文本消息的处理工具类。`MessageChain` 实现接口 `List<SingleMessage>`,一个思路是遍历 list 并判断类型处理: ```java for (element : messageChain) { if (element instanceof Image) { // 处理一个 Image } } ``` 也可以像数组一样按下标随机访问: ```java SingleMessage element = messageChain.get(0); if (element instanceof Image) { // 处理一个 Image } ``` ### 元素唯一性 [`MessageKey`]: ../mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt [`ConstrainSingle`]: ../mirai-core-api/src/commonMain/kotlin/message/data/ConstrainSingle.kt [`HummerMessage`]: ../mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt 部分元素只能单一存在于消息链中。这样的元素实现接口 [`ConstrainSingle`]。 唯一的元素例如 *消息元数据* [`MessageSource`],在连接时,新的(右侧)元素会替换旧的(左侧)元素。如: ```kotlin val source1: MessageSource val source2: MessageSource val chain: MessageChain = source1 + source2 // 结果 chain 只包含一个元素,即右侧的 source2。 ``` 元素唯一性的识别基于 [`MessageKey`]。有些 [`MessageKey`] 拥有多态机制。例如 [`HummerMessage`] 的继承关系 ``` MessageContent ↑ HummerMessage ↑ +------------+-------------+------------+ | | | | PokeMessage VipFace FlashImage ... ``` 当连接一个 [`VipFace`] 到一个 [`MessageChain`] 时,由于 [`VipFace`] 最远父类为 `MessageContent`,消息链中第一个 `MessageContent` 会被(保留顺序地)替换为 [`VipFace`],其他所有 `MessageContent` 都会被删除。 ```kotlin // Kotlin val face = VipFace(VipFace.AiXin, 1) // VipFace 是 ConstrainSingle val chain = messageChainOf(plainText, quoteReply, at, atAll) // quoteReply 是 MessageMetadata, 其他三个都是 MessageContent val result = chain + face // 右侧的 VipFace 替换掉所有的 MessageContent. 它会存在于第一个 MessageContent 位置. // 结果为 [VipFace, QuoteReply] ``` ```java // Java VipFace face = new VipFace(VipFace.AiXin, 1); // VipFace 是 ConstrainSingle MessageChain chain = MessageChain.newChain(plainText, quoteReply, at, atAll); // quoteReply 是 MessageMetadata, 其他三个都是 MessageContent MessageChain result = chain.plus(face); // 右侧的 VipFace 替换掉所有的 MessageContent. 它会存在于第一个 MessageContent 位置. // 结果为 [VipFace, QuoteReply] ``` 简单来说,这符合现实的逻辑:一条消息如果包含了语音,就不能同时包含群文件、提及全体成员、文字内容等元素。进行 `chain.plus(audio)` 时,如果消息内容冲突则会发生替换,且总是右侧元素替换左侧元素。 而如果消息可以同时存在,比如一个纯文本和一个或多个图片相连 `plainText.plus(image1).plus(image2)` 时没有冲突,不会发生替换。结果将会是 `[PlainText, Image, Image]` 的 `MessageChain`。 ### 获取消息链中的消息元素 #### A. 筛选 List [`MessageChain`] 继承接口 `List<SingleMessage>`。 ```kotlin val image: Image? = chain.findIsInstance<Image>() ``` ```java Image image = (Image) chain.stream().filter(Image.class::isInstance).findFirst().orElse(null); ``` 在 Kotlin 要获取第一个指定类型实例还可以使用快捷扩展。 ```kotlin val image: Image? = chain.findIsInstance<Image>() val image: Image = chain.firstIsInstance<Image>() // 不存在时 NoSuchElementException ``` #### B. 获取唯一元素 如果要获取 `ConstrainSingle` 的消息元素,可以快速通过键获得。 ```kotlin val quote: QuoteReply? = chain[QuoteReply] // 类似 Map.get val quote: QuoteReply = chain.getOrFail(QuoteReply) // 不存在时 NoSuchElementException ``` ```java QuoteReply quote = chain.get(QuoteReply.Key); ``` 一些元数据就可以通过这个方法获得。如上述示例就是在获取引用回复(`QuoteReply`)。如果获取不为 `null` 则表明这条消息包含对其他某条消息的引用。 > 这是因为 `MessageKey` 一般都以消息元素的 `companion object` 实现 #### C. 使用属性委托 可在 Kotlin 使用属性委托。这样的方法与上述方法在性能上等价。 ```kotlin val image: Image by chain // 不存在时 NoSuchElementException val image: Image? by chain.orNull() val image: Image? by chain.orElse { /* 返回一个 Image */ } ``` #### D. 遍历 List ```java for (SingleMessage message : messageChain) { // ... } ``` 也可以使用 `messageChain.iterator()`、 `messageChain.stream()` 等。 ### 序列化 > 简单地讲,序列化指的是将内存中的对象转换为其他易于存储等的表示方式。如对象变 JSON 字符串、对象变 MiraiCode 字符串。 消息可以序列化为 JSON 字符串,使用 `MessageChain.serializeToJsonString` 和 `MessageChain.deserializeFromJsonString`。 ```java String json = MessageChain.serializeToJsonString(message); MessageChain chain = MessageChain.deserializeFromJsonString(message); ``` #### 使用 kotlinx.serialization 若要将消息类型使用在其他类型中,如 ```kotlin data class Foo( val image: Image ) ``` 则需要在序列化时为 format 添加 `serializersModule`: ```kotlin val json = Json { serializersModule = MessageSerializers.serializersModule } ``` 如果遇到 [`Encountered unknown key 'type'.`](https://github.com/mamoe/mirai/issues/1273#issuecomment-850997979) 等序列化错误,请添加: ```kotlin Json { serializersModule = MessageSerializers.serializersModule ignoreUnknownKeys = true // 添加这一行 } ``` #### 元素类型不一定被保留 如 _消息源 `MessageSource`_ 有 _在线消息源 `OnlineMessageSource`_ 和 _离线消息源 `OfflineMessageSource`_ 之分。`OnlineMessageSource` 在序列化并反序列化后会变为 `OfflineMessageSource`,因为在线消息源只能从消息回执和消息事件中的 `MessageChain` 得到。 但在 API 使用上不会有区别(共有属性获取到的值不会变化)。只是可能需要注意 `equals` 等方法的使用。 所有 `Message` 反序列化的结果都是 `MessageChain`,即使原消息可能是 `SingleMessage`。如果原消息是 `SingleMessage`,可在反序列化后通过 `chian.get(0)` 下标访问并强转类型(或者其他更好的方法)。 #### 序列化稳定性 在旧版本产生的 JSON 字符串在绝大多数情况下可以由新版本 Mirai 读取并反序列化成原 `MessageChain`。如果更新时放弃了对某个旧版本消息元素的支持,将会在更新日志中说明。 但这个规则不适用于 _实验性特性_(带有 `@MiraiExperimentalApi` 注解)。任何使用了实验性特性的消息元素都随时可变。 ### 消息的其他常用功能 #### 撤回自己或群员的消息 撤回的核心操作是 `MessageSource.recall`。 如果操作目标 `MessageSource` 指代的是自己的消息,那么就撤回该消息,操作群员消息同理。 来自 `GroupMessageEvent` 等消息事件的属性 `message` 的 `MessageChain` 都包含 `MessageSource` 元素,指代这条消息本身。那么就可以用来撤回这条消息。 ```kotlin // Kotlin messageSource.recall() messageChain.recall() // 获取其中 MessageSource 元素并操作 recall messageSource.recallIn(3000) // 启动协程, 3 秒后撤回消息. 返回的 AsyncRecallResult 可以获取结果 messageChain.recallIn(3000) ``` ```java // Java MessageSource.recall(messageSource); MessageSource.recall(messageChain); // 获取其中 MessageSource 元素并操作 recall MessageSource.recallIn(messageSource, 3000) // 启动异步任务, 3 秒后撤回消息. 返回的 AsyncRecallResult 可以获取结果 MessageSource.recallIn(messageChain, 3000) ``` #### 发送带有引用回复的消息 引用回复 `QuoteReply` 是一个元数据 `MessageMetadata`。将 `QuoteReply` 实例连接到消息链中,发送后的消息就会引用一条其他消息。 例如监听事件并回复一条群消息: ```kotlin // Kotlin bot.eventChannel.subscribeAlways<GroupMessageEvent> { // this: GroupMessageEvent subject.sendMessage(message.quote() + "Hi!") // 引用收到的消息并回复 "Hi!", 也可以添加图片等更多元素. } ``` ```java // Java bot.getEventChannel().subscribeAlways<GroupMessageEvent>(event -> { MessageChain chain = new MessageChainBuilder() // 引用收到的消息并回复 "Hi!", 也可以添加图片等更多元素. .append(new QuoteReply(event.getMessage())) .append("Hi!") .build(); event.getSubject().sendMessage(chain); }); ``` #### 更多消息类型 在 [消息元素](#消息元素) 表格中找到你需要的消息元素,然后到源码内注释查看相应的用法说明。 > 回到 [目录](#目录) --- ## Mirai 码 实现了接口 `CodableMessage` 的消息类型支持 mirai 码表示。可以在[下文](#由-codablemessage-取得-mirai-码字符串)找到这些消息类型的列表。 ### 转义规则 mirai 码内的属性字符串会被转义。 | 原字符 | 转义结果字符 | |:----------:|:---------:| | `[` | `\[` | | `]` | `\]` | | `:` | `\:` | | `,` | `\,` | | `\` | `\\` | | *换行符 \n* | `\n` | | *换行符 \r* | `\r` | ### 组成约定 一个有效的 mirai 码 (如 `[mirai:atall]` (无参数), `[mirai:at:123]` (有参数)) 可分为以下几个组成部分 - `[mirai:` 固定开头 - 消息类型, 如 `at` - 消息参数 - `:` 固定分隔符 - 参数内容 **(需要进行转义)** - `]` 固定结尾 #### 为何需要进行转义 为了 mirai 码的正确解析, 不转义无法正确解析原本意义 假如有以下参数 ``` {"msg": [1, 2, 3]} ``` 如果不进行转义直接进行 mirai 码拼接 (如: `[mirai:msg:{"msg": [1, 2, 3]}]`), 那么 mirai 码会被错误解析 > 解析结果如下: > > - mirai 码 `[mirai:msg:{"msg": [1, 2, 3]` > - 纯文本 `}]` ### 消息链的 mirai 码 消息链 [`MessageChain`] 是多个 [`SingleMessage`] 的集合。[`MessageChain`] 也实现 [`CodableMessage`]。在转换为 mirai 码时所有 [`CodableMessage`] 直接相连: ``` val chain = messageChainOf(PlainText("plain"), At(123), AtAll) chain.serializeToMiraiCode() // "plain[mirai:at:123][mirai:atall]" ``` ### 由 `CodableMessage` 取得 mirai 码字符串 通过 `CodableMessage.serializeToMiraiCode()`。 ``` val at = At(123) at.serializeToMiraiCode() // 结果为 `[mirai:at:123]` ``` | 消息类型 | `serializeToMiraiCode()` | |:------------------------:|:-------------------------------------------------| | [`PlainText`] | `$content` | | [`Image`] | `[mirai:image:$imageId]` | | [`At`] | `[mirai:at:$target]` | | [`AtAll`] | `[mirai:atall]` | | [`Face`] | `[mirai:face:id]` | | [`FlashImage`] | `[mirai:flash:${image.imageId}]` | | [`PokeMessage`] | `[mirai:poke:$name,$pokeType,$id]` | | [`VipFace`] | `[mirai:vipface:${kind.id},${kind.name},$count]` | | [`LightApp`] | `[mirai:app:$content]` | | [`SimpleServiceMessage`] | `[mirai:service:$serviceId,$content]` | | [`Dice`] | `[mirai:dice:$value]` | | [`MusicShare`] | `[mirai:musicshare:$args]` | | [`FileMessage`] | `[mirai:file:$id,$internalId,$name,$size]` | | [`RockPaperScissors`] | `[mirai:rps:$name]` | ### 由 mirai 码字符串取得 `MessageChain` 实例 ```kotlin val chain = "[mirai:atall]".deserializeMiraiCode() ``` ```java MessageChain chain = MiraiCode.deserializeFromMiraiCode("[mirai:atall]"); ``` ### 转义字符串 若要执行转义(一般没有必要手动这么做): ```kotlin PlainText("[mirai:atall]").serializeToMiraiCode() // \[mirai\:atall\] ``` ```java MiraiCode.serializeToMiraiCode(new PlainText("[mirai:atall]")) // \[mirai\:atall\] ``` ### `serializeToMiraiCode` 与 `toString` 的区别 - 如 [消息元素](#消息元素) 所示, `toString()` 会尽可能包含多的信息用于调试作用,**行为可能不确定** - `toString()` 偏人类可读, `serializeToMiraiCode()` 偏机器可读 - `toString()` **没有转义**, `serializeToMiraiCode()` 有正确转义 - `serializeToMiraiCode()` 会跳过 `不支持 mirai 码处理` 的元素 - `toString()` 基本不可用于 `deserializeMiraiCode`/`MiraiCode.deserializeFromMiraiCode` --------- 到这里,你已经完成了 Mirai 所有文档的阅读。现在你已经熟悉了 Mirai,并可以开始使用了。 你可以首先构造 Bot,登录,然后从监听事件起开始创建你的机器人,或从 Bot 获取到指定群主动发送消息。在使用中遇到问题可以参考 Mirai 源码内注释,该注释会包含更多实践上的帮助。 如果你仍然对 Mirai 架构有不明确的地方,欢迎在 [#848](https://github.com/mamoe/mirai/discussions/848) 提出建议,或者直接在 PR 提交你的修改。 > 回到 [目录](#目录) > > [回到 Mirai 文档索引](CoreAPI.md) ================================================ FILE: docs/MigrationFrom1x.md ================================================ # Mirai - Migration From 1.x 本文介绍如何从 1.x 升级到 2.x。 根据你的语言选择:[Kotlin](#使用-kotlin) | [Java](#使用-java) ## 使用 Kotlin ### 如何自动完成 Kotlin 的错误替换 仅 IntelliJ IDEA 和 Android Studio 支持这个功能。 把光标放在一个错误中间(或者按 F2 自动跳转到错误),在 Windows 使用 `Alt + Enter` 快捷键,macOS 使用 `Option + Enter`,会得到弹窗如下图。 ![YBP47V_Z640J_YU5WZ_JVPW.png](https://i.loli.net/2020/12/18/CiX9qApu5BnVPch.png) 第一项为仅替换这一个错误,第二项为替换项目中的所有这个错误。一般推荐选择第二项并回车即可。 **Mirai 的修改都尽可能地提供了这样的替换,请依次按如下步骤更新以下几个版本,才能使用这些替换。** 部分无法提供自动替换的修改会在下文说明。 ### `1.x` -> `2.0-M1-1` 替换依赖(可以直接全局搜索替换): - `net.mamoe:mirai-core` -> `net.mamoe:mirai-core-api` - `net.mamoe:mirai-core-qqandroid` -> `net.mamoe:mirai-core` **Kotlin**: 1. 将 `MessageChain[Image]` 等 IDE 会提示错误的调用调整为: - `.findIsInstance<Image>()` (`Image` 不存在时返回 `null`) - `.firstIsInstance<Image>()` (`Image` 不存在时抛出异常) **提示**: 如果你是想获取消息的内容,可以使用 `messageChain.content` 扩展,而不需要使用 `MessageChain[PlainText]` 2. `Bot.getFriend` 等函数以前在找不到好友时会抛出异常,现在它们会返回 `null`。 请替换 `Bot.getFriend` 为 `Bot.getFriendOrFail`。 只要能通过编译就可以适配下一个版本。 ### `2.0-M1` -> `2.0-M2` 修改都可以自动替换完成。**但请不要跳过 `2.0-M2` 这一步骤。** ### `2.0-M2` -> `2.0-RC` 1. 戳一戳事件由以前的多个事件变为了统一的单个 `NudgeEvent`。若有使用请直接参考 `NudgeEvent` 源码修改。 2. `Listener.ConcurrencyKind` 和 `Listener.EventPriority` 由嵌套类移动到顶层,请执行全局替换: - `Listener.ConcurrencyKind` -> `ConcurrencyKind` - `Listener.EventPriority` -> `EventPriority` 3. `IMirai` 低级 API 函数名现在不再带有 `_lowLevel` 前缀, 直接删除前缀即可。 其他修改都可以自动替换完成。 ### `2.0-RC` -> `2.0.0` 直接把版本号更改为 `2.0.0`。 至此你已经成功升级到了 mirai 2.0。[回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发) ---- ## 使用 Java **请依次按如下步骤更新以下几个版本** ### `1.x` -> `2.0-M1` - 消息事件包名有调整, 请根据 IDE 提示自动导入引用失效的包. - Bot 构造方法调整, 将原 `BotFactoryKt.newBot(...)` 替换为 `BotFactory.INSTANCE.newBot(...)` - 如果有调用 `Utils.getDefaultLogger().invoke(...)`,替换为 `MiraiLogger.create(...)` - `Bot.getFriend` 等方法以前在找不到好友时会抛出异常,现在它们会返回 `null`。 请替换为 `Bot.getFriendOrFail`。 ### `2.0-M1` -> `2.0-M2` 图片和语音上传的 API 有更改。 新增了资源 API,可以统一缓存文件。 ``` ExternalResource.create(file); ExternalResource.create(inputStream); ``` 上传一个资源为图片或语音: ``` contact.uploadImage(resource); contact.uploadVoice(resource); ``` 或者使用工具方法直接发送一个 `File` 或 `InputStream` 为图片: ``` Contact.sendImage(contact, inputStream); // 返回 MessageReceipt Contact.sendImage(contact, file); // 返回 MessageReceipt Contact.uploadImage(contact, inputStream); // 返回 Image 消息 Contact.uploadImage(contact, file); // 返回 Image 消息 ``` ### `2.0-M2` -> `2.0-RC` 1. 戳一戳事件由以前的多个事件变为了统一的单个 `NudgeEvent`。若有使用请直接参考 `NudgeEvent` 源码修改。 2. `Listener.ConcurrencyKind` 和 `Listener.EventPriority` 由嵌套类移动到顶层,请执行全局替换: - `Listener.ConcurrencyKind` -> `ConcurrencyKind` - `Listener.EventPriority` -> `EventPriority` 3. `IMirai` 低级 API 方法名现在不再带有 `_lowLevel` 前缀, 直接删除前缀即可。 ### `2.0-RC` -> `2.0.0` 直接把版本号更改为 `2.0.0`。 至此你已经成功升级到了 mirai 2.0。[回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发) ================================================ FILE: docs/Preparations.md ================================================ # Mirai - Preparations 本章节介绍 Mirai 的 JVM 环境和开发准备工作。 ## JVM 环境要求 - 桌面 JVM:最低 Java 8,但推荐 Java 17(要使用一键启动器,需要 11 及以上) - Android: - mirai 2.15.0 起: API 等级 21 (Android 5.0,LOLLIPOP) - mirai 2.15.0 前: API 等级 26 (Android 8.0,O) 目前主要使用的自动启动器,[Mirai Console Loader](https://github.com/iTXTech/mirai-console-loader) ,(MCL) 默认安装 JRE 17。 **但注意不要使用 Oracle JDK** ([原因](https://github.com/mamoe/mirai/discussions/779)),可以使用其他任何 JDK。 > 要下载 JDK: > - 手动下载安装如 [AdoptOpenJDK](https://adoptopenjdk.net/) > - 自动在 IntelliJ IDEA `Project Structure`(`Ctrl+Shift+Alt+S`) -> `SDKs` -> `+` -> `Download JDK` 下载安装 ## 开发的准备工作 ### 安装 IDE 插件 [Mirai Console IntelliJ]: /mirai-console/tools/intellij-plugin [Mirai Console IntelliJ-JB]: https://plugins.jetbrains.com/plugin/15094-mirai-console [Mirai Console IntelliJ-OK]: https://plugins.jetbrains.com/embeddable/install/15094 <!--[Kotlin Jvm Blocking Bridge]: https://github.com/mamoe/kotlin-jvm-blocking-bridge--> <!--[Kotlin Jvm Blocking Bridge-JB]: https://plugins.jetbrains.com/plugin/14816-kotlin-jvm-blocking-bridge--> <!--[Kotlin Jvm Blocking Bridge-OK]: https://plugins.jetbrains.com/embeddable/install/14816--> 推荐使用 [IntelliJ IDEA](https://www.jetbrains.com/idea/) 或 [Android Studio](https://developer.android.com/studio)。Mirai 提供 IDE 插件来提升开发体验。 | 插件名 | 描述 | 一键安装 | JetBrains 插件仓库 | |:------------------------:|:------------------------------------------:|:---------------------------------:|:--------------------------------:| | [Mirai Console IntelliJ] | 提供 mirai-core 的错误检查和 mirai-console 的插件开发辅助 | [一键安装][Mirai Console IntelliJ-OK] | [说明页][Mirai Console IntelliJ-JB] | <!--| [Kotlin Jvm Blocking Bridge] | 帮助 Java 用户调用 Kotlin suspend 函数 | [Kotlin Jvm Blocking Bridge-OK] | [Kotlin Jvm Blocking Bridge-JB] |--> 使用 Kotlin 建议安装 Mirai Console IntelliJ。同时请确保 Kotlin 插件是最新版本(在 `Settings -> Plugins` 启用并更新 Kotlin 到最新)。 ### Kotlin [Kotlin](https://kotl.in) 是让开发人员更快乐的一门现代编程语言,由 [IntelliJ IDEA](https://www.jetbrains.com/idea/) 的开发公司 [JetBrains](https://www.jetbrains.com/) 维护,被 Google 推举为 Android 首选编程语言。 使用 Mirai 是一个不错的学习 Kotlin 机会,使用者有兴趣可以在 [官方中文文档](https://www.kotlincn.net/docs/reference/) 学习 Kotlin。 Java 开发者如果只希望使用 Mirai 而不学习 Kotlin,也请阅读 [Kotlin 定义对应的 Java 定义](KotlinAndJava.md)(5 分钟)。 ---- > [回到 Mirai 文档索引](README.md#jvm-平台-mirai-开发) ================================================ FILE: docs/Questions.md ================================================ # Mirai - Questions ## 用户常见问题 > 'java' 不是内部或外部命令,也不是可运行的程序 没有安装 Java。 > `Failed to fetch announcement for ...` [MCL](https://github.com/iTXTech/mirai-console-loader) 查询更新信息失败,可以尝试编辑 `config.json`, 更换 [mirai repo](https://github.com/project-mirai/mirai-repo-mirror#%E4%BB%93%E5%BA%93%E9%95%9C%E5%83%8F)。 > Login failed: Error(bot=..., code=..., title=.... 这些是服务器返回的信息,它表示你的账号被登录风控了。 风控没有 100% 稳定的解决方法,你可以关注 [论坛公告帖 - 无法登录的临时处理方案](https://mirai.mamoe.net/topic/223)。 密码登录中是否出现滑块验证或短信验证都是不可控的,这取决于腾讯的服务器要求你完成什么验证,没有办法自由选择。 > 登录协议如何修改 对于手动密码登录,第三个参数就是协议, 例如 `login 12345 114514 MACOS` 对于自动密码登录,可以使用指令修改,例如 `autoLogin setConfig 12345 protocol MACOS`, 也可以在 Mirai Console 关闭的情况下, 编辑 `config/Console/AutoLogin.yml` 文件。 注意,文件中有一个账号为 `12345` 的示例,请注意确认修改的配置对应的账号,不要修改错了示例。 > 聊天框无法使用指令(使用指令后没效果) 1. 确认机器人收到消息 日志里会有消息记录,没有就是没收到,如果是群聊消息,注意是否已 `收入群助手`,这可能会导致收不到消息 2. 确认是否已经安装 [chat-command](https://github.com/project-mirai/chat-command/releases/latest) 如果插件的指令是对接的 `Mirai Console` 的指令接口,那它就需要 `chat-command` 3. 确认是否已经授权给目标用户 默认情况下所有用户都是没有权限的,聊天框下无法使用指令 备注:如果使用了 LuckPerms-Mirai,可通过在控制台执行 /lp verbose on 查看权限检测情况 4. 确认日志中没有相关报错 如果插件指令执行出错,也有可能无法提供回复,请联系插件作者 > 如何确认 `Mirai 版本` 或 `插件版本` 等信息 可以启动 Mirai Console 的情况下: 使用指令 `/status` 。 无法启动的情况下: Mirai Console 的组件在 `libs` 文件夹下, 文件名包含 `版本信息`。 > 找不到 `http api` 的相关配置文件 可能需要安装插件 <https://github.com/project-mirai/mirai-api-http>。 > 如何添加 jvm 参数,例如 `-Dmirai.no-desktop=true` 编辑启动脚本 `mcl.cmd`,在 `-jar` 前面加上 `-D...`,例如 `-Dmirai.no-desktop=true -jar mcl...`。 Linux 和 macOS 的启动脚本是 `mcl` (没有后缀的那个文件)。 > MCL 不能更新到最新版 Mirai 编辑 `config.json` 修改 `maven_repo` 为 `https://repo.huaweicloud.com/repository/maven` (默认是阿里云,更新很不及时) 将 `packages` 中 `net.mamoe:` 开头的项的 `channel` 改为 `maven` (最新版) 或 `maven-stable` (最新稳定版) ## 开发者常见问题 > 如何自定义登录验证处理 扫码登陆,滑块处理等 [覆盖登录解决器](Bots.md#覆盖登录解决器) > IDEA 下 `import` 爆红,mirai 相关依赖全部无法解析 IDEA 版本过于老旧,无法分析新版本的 Kotlin 依赖,请尝试升级 IDEA 后重试。 > 有些事件收到不到 `SignEvent` 或者 `NudgeEvent`: 总的来说 `MACOS` 和 `ANDROID_WATCH` 相对其他协议会缺少一些事件的接收。 对于这些协议来说是不会收到的,因为这在他们对应官方客户端版本里本来就没有这些功能。 `GroupMessageEvent`: 群事件收不到可能是因为你将群设置为 `屏蔽群` 或者 `收入了群助手`。 另外如果消息是 `转发消息` 或 `卡片消息` 等特殊消息, 也有可能因为风控无法接收和发出。 > 发送语音之后播放没有声音 你可能需要安装插件或引入依赖 <https://github.com/project-mirai/mirai-silk-converter>。 > Mirai Console 插件找不到类 1. 使用了私有的第三方库 你需要使用 `shadowLink` 参照 [打包依赖](../mirai-console/tools/gradle-plugin/README.md#打包依赖) 2. 使用 数据库框架/反射框架 但找不到 驱动类/实体类 你需要将**上下文**切换到插件中,或者指定 `ClassLoader`, 相关内容请参考 [JVM Plugins - Debug](../mirai-console/docs/plugin/JVMPlugin-Debug.md) 下面只列出部分例子,请根据你所使用的框架的文档进行修整。 切换上下文: ```java import java.util.ServiceLoader; public class SQL { void load() { Thread current = Thread.currentThread(); ClassLoader context = current.getContextClassLoader(); ClassLoader plugin = this.getClass().getClassLoader(); try { current.setContextClassLoader(plugin); ServiceLoader.load(java.sql.Driver.class).reload(); // database load ... } finally { current.setContextClassLoader(context); } } } ``` 指定 `ClassLoader` ```java import jakarta.persistence.Embeddable; import jakarta.persistence.Entity; import jakarta.persistence.MappedSuperclass; import org.reflections.Reflections; import org.reflections.util.ConfigurationBuilder; import java.util.Set; public class SQL { Set<Class<?>> load() { ClassLoader plugin = this.getClass().getClassLoader(); ConfigurationBuilder builder = new ConfigurationBuilder() .forPackage("org.example", plugin) .addClassLoaders(plugin); // 指定从插件的类加载器中检索类 Reflections reflections = new Reflections(builder); Set<Class<?>> query = org.reflections.scanners.Scanners.TypesAnnotated .of(Entity.class, Embeddable.class, MappedSuperclass.class) .asClass(plugin) // 指定从插件的类加载器中提取类 .apply(reflections.getStore()); return query; } } ``` ================================================ FILE: docs/README.md ================================================ # Mirai 欢迎来到 mirai 开发文档。 本文面向要进行开发的用户。对于只使用现成插件的用户,请阅读 [用户手册](UserManual.md)。 [Mirai 生态概览](mirai-ecology.md) [Mirai VuePress 文档](https://docs.mirai.mamoe.net/) ## 社区 SDK **mirai 官方提供 [Kotlin/Java 等 JVM 平台语言开发支持](#使用-mirai) 。如果不熟悉这些语言,请使用以下社区 SDK:** 要使用这些社区 SDK 需要先配置 Mirai Console,可以使用 [一键安装](https://mirai.mamoe.net/assets/uploads/files/1618372079496-install-20210412.cmd) (32位,带 HTTP 插件),也可以阅读 [用户手册](UserManual.md) 进行个性化安装。 你可以使用一个或多个语言来开发插件,而且在自己开发的同时也可以[使用下载的插件](UserManual.md#下载和安装插件)。 [`mirai-console`]: ../mirai-console [mamoe/mirai-api-http]: https://github.com/mamoe/mirai-api-http [iTXTech/mirai-native]: https://github.com/iTXTech/mirai-native [iTXTech/mirai-js]: https://github.com/iTXTech/mirai-js [iTXTech/mirai-kts]: https://github.com/iTXTech/mirai-kts [AliceBot]: https://github.com/AliceBotProject/alicebot [GraiaProject/Ariadne]: https://github.com/GraiaProject/Ariadne [GraiaProject/Avilla]: https://github.com/GraiaProject/Avilla [Elaina]: https://github.com/wyapx/Elaina [ArcletProject/Edoves]: https://github.com/ArcletProject/Edoves [NoneBot]: https://github.com/nonebot/nonebot2 [RedBeanN/node-mirai]: https://github.com/RedBeanN/node-mirai [Logiase/gomirai]: https://github.com/Logiase/gomirai [cyanray/mirai-cpp]: https://github.com/cyanray/mirai-cpp [Chlorie/miraipp]: https://github.com/Chlorie/miraipp-template [Numendacil/cpp-mirai-client]: https://github.com/Numendacil/cpp-mirai-client [Executor-Cheng/mirai-CSharp]: https://github.com/Executor-Cheng/mirai-CSharp [HoshinoTented/mirai-rs]: https://github.com/HoshinoTented/mirai-rs [YunYouJun/mirai-ts]: https://github.com/YunYouJun/mirai-ts [nepsyn/miraipie]: https://github.com/nepsyn/miraipie [only52607/e-mirai]: https://github.com/only52607/e-mirai [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 [Coloryr/ColorMirai]: https://github.com/Coloryr/ColorMirai [AHpxChina/Mirai.Net]: https://github.com/AHpxChina/Mirai.Net [Cyl18/Chaldene]: https://github.com/Cyl18/Chaldene [Miyakowww/CocoaFramework2]: https://github.com/Miyakowww/CocoaFramework2 [Shimogawa/rubirai]: https://github.com/Shimogawa/rubirai [Excaive/miraicle]: https://github.com/Excaive/miraicle [nkxingxh/miraiez]: https://github.com/nkxingxh/miraiez [Xwdit/RainyBot-Core]: https://github.com/Xwdit/RainyBot-Core [OneBot]: https://github.com/howmanybots/onebot [Mirai HTTP]: https://github.com/project-mirai/mirai-api-http [jerrita/saaya]: https://github.com/jerrita/saaya [YiriMirai]: https://github.com/YiriMiraiProject/YiriMirai [MiraiBots.jl]: https://github.com/melonedo/MiraiBots.jl [Novices666/mirai-epl]:https://github.com/Novices666/mirai-epl [easyMirai]:https://github.com/easyMirais/easyMirai [MR-XieXuan/MiraiTravel]:https://github.com/MR-XieXuan/MiraiTravel [yuansicloud/Abp.Mirai]:https://github.com/yuansicloud/Abp.Mirai ### 原生接口 这些接口直接在 JVM 上实现,不需要中间件,拥有更佳的性能。 | 技术 | 维护者及项目地址 | |:-------------------|:-----------------------| | `Kotlin Scripting` | [iTXTech/mirai-kts] | | `C++` | [Nambers/MiraiCP] | | `JavaScript` | [iTXTech/mirai-js] | | *酷 Q DLL 插件* | [iTXTech/mirai-native] | ### HTTP 接口 目前有两个 HTTP 协议插件。使用 HTTP 协议插件可以支持更多编程语言和技术。 - [***Mirai HTTP***][Mirai HTTP] 由 Mirai 开发团队提供第一级支持,目前多数 SDK 都基于它; - [OneBot] 标准则兼容酷Q协议,可以让基于酷Q HTTP 插件的项目在 Mirai 平台运行。 | 名称 | 实现 | 维护者及项目地址 | |:-----------------|:------------|:-------------------------------| | ***Mirai Http*** | Mirai 标准 | [mamoe/mirai-api-http] | | *OneBot Http* | [OneBot] 标准 | [yyuueexxiinngg/onebot-kotlin] | 下表列举基于 Mirai HTTP 插件实现对一些编程语言支持的项目列表。要使用它们,你需要[在 Mirai Console 安装 `mirai-api-http`](https://github.com/project-mirai/mirai-api-http#%E5%AE%89%E8%A3%85mirai-api-http)(如果使用上面的一键安装则不需要额外操作)。 | 语言和技术 | 维护者及项目地址 | |:--------------------------|:-------------------------------------| | `C#` | [Executor-Cheng/mirai-CSharp] | | `C#` | [Hyperai][theGravityLab/ProjHyperai] | | `C#` | [Coloryr/ColorMirai] | | `C#` | [AhpxChina/Mirai.Net] | | `C#` | [Cyl18/Chaldene] | | `C#` | [Miyakowww/CocoaFramework2] | | `C#` | [yuansicloud/Abp.Mirai] | | `C++` | [cyanray/mirai-cpp] | | `C++` | [Chlorie/miraipp] | | `C++` | [Numendacil/cpp-mirai-client] | | `GDScript` | [Xwdit/RainyBot-Core] | | `Go` | [Logiase/gomirai] | | `JavaScript` / Node.js | [RedBeanN/node-mirai] | | `JavaScript` / Node.js | [drinkal/Mirai-js] | | `JavaScript` / TypeScript | [YunYouJun/mirai-ts] | | `JavaScript` / TypeScript | [nepsyn/miraipie] | | `Julia` | [MiraiBots.jl] | | `PHP` | [MiraiEz][nkxingxh/MiraiEz] | | `PHP` | [MR-XieXuan/MiraiTravel] | | `Python` | [AliceBot] | | `Python` | [Ariadne][GraiaProject/Ariadne] | | `Python` | [Avilla][GraiaProject/Avilla] | | `Python` | [easyMirai] | | `Python` | [Edoves][ArcletProject/Edoves] | | `Python` | [Elaina] | | `Python` | [NoneBot] | | `Python` | [jerrita/saaya] | | `Python` | [YiriMirai] | | `Python` | [Excaive/miraicle] | | `Ruby` | [Shimogawa/rubirai] | | `Rust` | [HoshinoTented/mirai-rs] | | `易语言` | [only52607/e-mirai] | | `易语言` | [Novices666/mirai-epl] | > 按字母顺序排序,排序不代表排名 > * 想在这里添加你的项目?欢迎[提交 PR](https://github.com/mamoe/mirai/edit/dev/docs/README.md) 。* 特别地,有一些 SDK 直接基于 mirai-core 开发,不需要 [`mirai-console`]: - `Lua`: [lua-mirai](https://github.com/only52607/lua-mirai) ## 使用 Mirai Mirai 原生支持 Java、Kotlin 等 JVM 平台编程语言。 要使用 Mirai,可以使用 mirai-core 作为一个依赖库获得机器人功能,也可以为 mirai-console 开发插件。 生态详情可阅读:[Mirai 生态概览](mirai-ecology.md)。 ### JVM 平台 mirai-core 开发 本节介绍使用 Java、Kotlin 等 JVM 平台编程语言使用 mirai-core 作为一个依赖库获得机器人功能。 这通常适用于你在开发一个其他应用程序而需要使用机器人功能的情况。 1. [JVM 环境和开发准备工作](Preparations.md#mirai---preparations) 2. [配置 mirai-core 项目依赖](ConfiguringProjects.md) 3. [阅读 mirai-core 文档](CoreAPI.md) > 如果你希望先体验 mirai > 的机器人功能,可克隆 [mirai-hello-world](https://github.com/project-mirai/mirai-hello-world) > 并在 IDE 内运行其中 Kotlin 或 Java 入口点 `main`。 ### JVM 平台 mirai-console 插件开发 本节介绍使用 Java、Kotlin 等 JVM 平台编程语言基于 mirai-core,开发可于 mirai-console 加载的插件来提供机器人功能。 这通常适用于你为了开发一个机器人程序的情况。开发 mirai-console 插件既可以[单独使用](../mirai-console/docs/Run.md),也可以使用来自社区的其他插件。 1. [JVM 环境和开发准备工作](Preparations.md#mirai---preparations) 2. [配置 mirai-console 插件项目](../mirai-console/docs/ConfiguringProjects.md) 3. [阅读 mirai-core 文档](CoreAPI.md) mirai-core 文档可让你了解如何使用 Bot 功能。 4. [阅读 mirai-console 文档](../mirai-console/docs/README.md) mirai-console 文档可让你了解 mirai-console 的一些系统。 ### 多平台 mirai-core 开发 [Kotlin 多平台]: https://kotlinlang.org/docs/multiplatform.html 本节介绍使用 Kotlin 使用 mirai-core 开发 [Kotlin 多平台] 应用程序。 1. [JVM 环境和开发准备工作](Preparations.md#mirai---preparations) 2. [配置 mirai-core 多平台项目依赖](ConfiguringMultiplatformProjects.md) 3. [阅读 mirai-core 文档](CoreAPI.md) ## 发布项目 欢迎各类基于 mirai 开发的开源项目在论坛发布。 - [在论坛发布](https://mirai.mamoe.net/category/6/%E9%A1%B9%E7%9B%AE%E5%8F%91%E5%B8%83) ## 文档 mirai 在 GitHub 托管的文档可让你简要了解各个系统。 mirai 的源码内注释十分详细,包含各种实践示例。 ### mirai-core 文档 请在 [CoreAPI.md](CoreAPI.md) 阅读 JVM 平台的 mirai-core 开发文档。 ### mirai-core API KDoc 可在 <https://kdoc.mirai.mamoe.net/> 查看基于源码内注释生成的 KDoc(类似 JavaDoc)。 但更建议使用 IntelliJ IDEA 等 IDE 在开发时查询源码内注释。 ### mirai-console 文档 请在 [mirai-console/docs](../mirai-console/docs/README.md) 阅读 mirai-console 开发文档。 ================================================ FILE: docs/UserManual.md ================================================ # Mirai - UserManual 这里是 Mirai 用户手册。本文面向对开发并不熟悉,但希望使用 Mirai 提供的 QQ 机器人服务支持的用户。 如果你要开发 Mirai 插件或参与贡献 Mirai 项目,请先阅读 [开发文档](README.md)。 ## 启动 Mirai 想要部署并使用 Mirai QQ机器人框架,只需要启动 Mirai 控制台(即 Mirai Console), 它自带一些基础功能,也可以加载社区提供的插件。 Mirai 控制台现在有两个版本,Mirai 插件在这两个版本的 Mirai Console 上都可以运行: [MCLI-1.png]: .UserManual_images/MCLI-1.png [MCPS-1.png]: .UserManual_images/MCPS-1.png | 类型 | 长啥样? | 好用吗? | 怎么装? | |:-----|:-------------|:----------|:----------------------| | 纯控制台 | [MCLI-1.png] | 稳定,也适合服务器 | [使用纯控制台版本](#使用纯控制台版本) | | 图形界面 | [MCPS-1.png] | 测试版,不推荐使用 | [使用图形界面版本](#使用图形界面版本) | ## 使用纯控制台版本 详细教程请查看 [ConsoleTerminal.md](ConsoleTerminal.md)。 以 Windows 系统为例,以下为简要安装步骤: 1. 前往 [iTXTech/mcl-installer](https://github.com/iTXTech/mcl-installer/releases) 下载适合您系统的最新版本的 MCL 安装器 2. 创建好文件夹之后,将 MCL 安装器移动到其中 3. 双击 `mcl-installer.exe` 过程中只需要按几次回车键,即可安装完毕 4. 运行 `mcl.cmd` 即可启动 MCL 控制台 安装插件只需要将下载好的插件置于 plugins 目录,然后重启 MCL 控制台即可。 ## ~~使用图形界面版本~~ 开发者已停止更新,且有许多历史问题,故不推荐使用 前往 [sonder-joker/mirai-compose](https://github.com/sonder-joker/mirai-compose/releases) 下载适合你的系统的压缩包, > MAC 系统下载 .dmg 后缀的文件 > Windows 系统下载 .msi 后缀的文件 > Linux 系统下载 .deb 后缀的文件 以 Windows 系统为例,以下为简要安装步骤: 1. 下载 `mirai-compose-<版本>.msi` 2. 双击运行安装程序,选择一个合适的文件夹,然后点击安装 3. 安装完毕后打开刚才指定的文件夹 4. 双击启动其中的 `mirai-compose.exe` 即可开始运行 5. 运行后点击左上角可以添加 QQ bot 账号 安装插件只需要将下载好的插件置于 plugins 目录,安装完毕后重启 mirai-compose 以生效。 ## 解决问题 请先阅读 [常见问题](Questions.md) 。 如果遇到使用问题或想提建议,可以在 [issues](https://github.com/mamoe/mirai/issues) 发表。也可以在[论坛](https://mirai.mamoe.net/)交流想法。 ================================================ FILE: docs/UsingSnapshots.md ================================================ # Mirai - Using Snapshots 每个 commit 在构建成功后都会发布一个开发测试版本到 mirai 仓库。如有需要,可添加仓库并使用。开发测试版本非常不稳定,仅用于测试某 commit 对一个问题的修复情况,而不建议在生产或开发环境使用。 每个开发测试版本只保留一个月。 - [在 Maven 使用](#在-maven-使用) - [在 Gradle 使用](#在-gradle-使用) ## 在 Maven 使用 ### 1. 添加 Maven 仓库 ```xml <repositories> <repository> <id>miraisnapshots</id> <name>mirai snapshots</name> <url>https://repo.mirai.mamoe.net/snapshots</url> </repository> </repositories> ``` ### 2. 修改依赖版本 1. 选择需要测试的 commit, 在 GitHub 查看其构建状态, 如图所示: ![](images/snapshots-find-actions.png) 2. 点击 "Build / JVM" 右侧的 "Details": ![](images/snapshots-build-jvm.png) 3. 在打开的页面中点击 "Summary", 然后在 "Annotations" 栏目中找到类似 "本 commit 的预览版本号: 2.15.0-build-index-1" 的提示, 得到开发测试版本号 `2.15.0-build-index-1`. 其中, `build-index` 为此 commit 所属分支名, `2.15.0` 意为当前分支的主版本号, `1` 为此分支下的第 1 次成功构建. 通常在 `dev` 分支构建的预览版本号类似为 `2.15.0-dev-102` ```xml <dependencies> <dependency> <groupId>net.mamoe</groupId> <artifactId>mirai-core-jvm</artifactId> <version>2.15.0-build-index-1</version> </dependency> </dependencies> ``` ## 在 Gradle 使用 ### 1. 添加 Maven 仓库 build.gradle(.kts) ``` repositories { maven("https://repo.mirai.mamoe.net/snapshots") } ``` ### 2. 修改依赖版本 1. 选择需要测试的 commit, 在 GitHub 查看其构建状态, 如图所示: ![](images/snapshots-find-actions.png) 2. 点击 "Build / JVM" 右侧的 "Details": ![](images/snapshots-build-jvm.png) 3. 在打开的页面中点击 "Summary", 然后在 "Annotations" 栏目中找到类似 "本 commit 的预览版本号: 2.15.0-build-index-1" 的提示, 得到开发测试版本号 `2.15.0-build-index-1`. 其中, `build-index` 为此 commit 所属分支名, `2.15.0` 意为当前分支的主版本号, `1` 为此分支下的第 1 次成功构建. 通常在 `dev` 分支构建的预览版本号类似为 `2.15.0-dev-102` build.gradle(.kts) ``` dependencies { implementation("net.mamoe:mirai-core:2.15.0-build-index-1") } ``` ## 使用测试版本 Mirai Console Gradle 插件 settings.gradle(.kts) ``` pluginManagement { repositories { gradlePluginPortal() maven("https://repo.mirai.mamoe.net/snapshots") } } ``` plugin.gradle(.kts) ``` plugins { // ... id("net.mamoe.mirai-console") version "2.15.0-build-index-1" } ``` ## 附录 ### 获取 dev 分支的最新版本号 1. 访问 Mirai Build Index API: <https://build.mirai.mamoe.net/v1/mirai-core/dev/indexes/latest> 获取 "value" 的值, 例如 `127` 2. 访问 <https://github.com/mamoe/mirai/blob/dev/buildSrc/src/main/kotlin/Versions.kt> 获取由 `/*PROJECT_VERSION_START*/` 和 `/*PROJECT_VERSION_END*/` 包围的主分支版本号, 例如 `2.16.0` 3. 组合得到版本号 `2.16.0-dev-127` ================================================ FILE: docs/contributing/ImplementingProtocol.md ================================================ # 实现协议 本文将说明实现一些协议的通常步骤,并以示例逐步演示。本文还将介绍 mirai 的消息处理流程。 通常请于 `commonMain` 编写代码。`commonMain` 的 Kotlin 代码将可以用于全平台,即同时(自动)支持 JVM 和 Native 目标。 ## 添加一个消息类型 ### 1. 抽象接口 在 `mirai-core-api` 的 `message.data` 中添加新类型接口(消息元素),如 `Audio` ,`FileMessage`。 在设计消息元素时应使其继承 `MessageMetadata`(元数据) 或 `MessageContent` (内容)。若该消息在官方客户端中只能单独出现,那么还应该让它继承 `ConstrainSingle` 来维持这一性质。 消息元素应是不可变的,即不允许出现 `var` 或可变类型。 作为示例,假设现在要支持视频消息,在 `mirai-core-api` 定义接口 `Video`: ```kotlin public interface Video : MessageContent, ConstrainSingle { override val key: Key get() = Key public companion object Key : AbstractPolymorphicMessageKey<MessageContent, Video>( MessageContent, { it.safeCast() }) /** * 文件名 */ val filename: String /** * 长度, 单位为秒. */ val length: Long } ``` 由于视频不可与文字等其他消息内容元素同时出现,`Video` 需要实现 `ConstrainSingle` ,并提供 `companion object Key`。 为了实现最好的协议支持,我们将 `Video` 分为 `OnlineVideo` 和 `OfflineVideo`。`OnlineVideo` 表示从服务器接收的,而 `OfflineVideo` 本地构造的。之后在实现时 `OnlineVideo` 可以存储来自服务器的原数据,以实现快速转发等优化。 ```kotlin public sealed interface Video : MessageContent, ConstrainSingle { override val key: Key get() = Key public companion object Key : AbstractPolymorphicMessageKey<MessageContent, Video>( MessageContent, { it.safeCast() }) /** * 文件名 */ val filename: String /** * 长度, 单位为秒. */ val length: Long } @NotStableForInheritance // 表示此 API 不可由用户自行实现 public interface OnlineVideo : Video { override val key: Key get() = Key /** * 获取下载链接 URL */ val urlForDownload: String // 需要覆盖 Key public companion object Key : AbstractPolymorphicMessageKey<Video, OnlineVideo>( Video, { it.safeCast() } ) { // SERIAL_NAME为之后支持序列化做准备 public const val SERIAL_NAME: String = "OnlineVideo" } } @NotStableForInheritance // 表示此 API 不可由用户自行实现 public interface OfflineVideo : Video { override val key: Key get() = Key // 需要覆盖 Key public companion object Key : AbstractPolymorphicMessageKey<Video, OfflineVideo>( Video, { it.safeCast() } ) { // SERIAL_NAME为之后支持序列化做准备 public const val SERIAL_NAME: String = "OfflineVideo" } } ``` 这样设计接口的好处包括未来很容易修改。例如 mirai 2.0 设计的 `Image` 接口一直维护到现在(2.13)也没有遇到问题,其内部结构也变更了多次,但用户的使用方法没有改变。 ### 2. 提供工厂或构建器 考虑添加相应的 `Factory`(抽象工厂)或 `Builder`(生成器)来提供构造消息实例的方式,而避免直接提供构造器。 直接提供公开构造器是一种沉重的承诺,将增加维护难度(现在已有的部分公开构造器是历史遗留)。 在 `Video` 示例中,由于 `OnlineVideo` 是来自服务器的,我们无序提供构造方式。可为 `OfflineVideo` 增加构建工厂: ```kotlin // ... public interface OfflineVideo : Video { // ... public interface Factory { /** 构建一个 [OfflineVideo] 实例 */ public fun create(filename: String, length: Long): OfflineVideo companion object INSTANCE : Factory by loadService() // `loadService` 是 mirai 实现的全平台服务加载方式,类似于 JDK `ServiceLoader`。 } } ``` 用户在 Kotlin 可通过 `OfflineVideo.Factory.create(filename, length)`,或在 Java 通过 `OfflineVideo.Factory.INSTANCE.create(filename, length)` 取得 `OfflineVideo` 实例。 注意,示例中 `OnlineVideo` 用的属性并不多(只有两个),使用工厂会比使用生成器(`Builder`)方便。但对于 `Image` 等拥有众多属性的类型,建议提供 `Builder`。一种实现方式可参见 `Image.Builder`。 ### 3. 实现接口 接口和工厂准备就绪,现在在 `mirai-core` 的 `net.mamoe.mirai.internal.message.data.` 包下实现接口。 请一般使用 `...Impl` 的命名方式,并为抽象类(`abstract class`)增加 `Abstract` 前缀。 如本示例中我们将实现: - `abstract class AbstractVideo : Video` — 实现基本的 `Video` 类 - `class OnlineVideoImpl : OnlineVideo` — 实现 `OnlineVideo` 的特殊内容 - `class OfflineVideoImpl : OfflineVideo` — 实现 `OfflineVideo` 的特殊内容 ```kotlin internal sealed class AbstractVideo( override val filename: String, override val length: Long, ) : Video { private val toStringTemp by lazy { "[mirai:video:$filename, length=$length]" } // 这不是标准 mirai code,只是为了提供易读的表示方式 override fun toString(): String = toStringTemp override fun contentToString(): String = "[视频]" override fun equals(other: Any?): Boolean { if (other == null) return false // isSameType 是适用于全平台的类型判断方式 if (!isSameType(this, other)) return false if (this.filename != other.filename) return false if (this.length != other.length) return false return true } override fun hashCode(): Int { var result = filename.hashCode() result = 31 * result + length.hashCode() return result } abstract fun toVideoFile(): ImMsgBody.VideoFile } internal class OnlineVideoImpl( override val filename: String, override val length: Long, private val original: ImMsgBody.VideoFile? // 协议数据结构 ) : OnlineVideo, AbstractVideo() { override fun toVideoFile(): ImMsgBody.VideoFile = original } internal class OfflineVideoImpl( override val filename: String, override val length: Long, ) : OfflineVideo, AbstractVideo() { override fun toVideoFile(): ImMsgBody.VideoFile = /* ... */ } ``` ### 4. 实现工厂 因为 mirai 在 JVM 使用 `ServiceLoader`,需要定义工厂实现类为 `class` 而不是 `object`。 对于 `Video` 示例: ```kotlin internal class OfflineVideoFactoryImpl : OfflineVideo.Factory { override fun create(filename: String, length: Long): OfflineVideo = OfflineVideoImpl(filename, length) } ``` 实现后需要同时为 JVM 和 Native 注册工厂: - JVM:在 jvmBaseMain/resources/META-INF/services/ 新建一个文件名为 `net.mamoe.mirai.message.data.OfflineVideo$Factory` ,文件内容为 `net.mamoe.mirai.internal.message.data.OfflineVideoFactoryImpl` ; - Native: 在 `net.mamoe.mirai.internal.utils.MiraiCoreServices.registerAll` 末尾增加。如对于 `Video` 示例: ```kotlin Services.register( "net.mamoe.mirai.message.data.OfflineVideo.Factory", "net.mamoe.mirai.internal.message.data.OfflineVideoFactoryImpl" ) { net.mamoe.mirai.internal.message.data.OfflineVideoFactoryImpl() } ``` ### 5. 测试接口和实现 请在 `commonTest/kotlin/message/data` 添加新增消息类型的测试。 需要测试的内容可能包含: - `equals`、`hashCode` - `toString`、`contentToString` - 检验参数有效性 语音消息的测试 `net.mamoe.mirai.internal.message.data.AudioTest` 可供参考。 ### 6. 实现收发协议 #### 多流水线系统 mirai 的多套系统都使用了"流水线"(pipeline)设计模式。 服务器消息实际上是数种通知中的一种(mirai 称之为 `notice`)。 加密的通知经过*网络层*解密处理后成为二进制数据包 `IncomingPacket`,网络层会分类该 `IncomingPacket`给合适的* 数据包工厂*(`PacketFactory`)处理。 数据包工厂会解析二进制数据包,产生结构数据类型。 结构数据类型将被发送至*通知流水线*(`NoticeProcessorPipeline`),经由负责的*流水线处理员* (`NoticeProcessor`)处理后最终成为一个 mirai 事件。而负责处理消息事件的处理员会将消息原数据发送至消息流水线处理得到 `MessageChain`。 #### 群消息处理流程 一条群消息的完整处理流程如下图所示: [![](https://mermaid.ink/img/pako:eNpVkctKw0AUQH9lmFWEth8QqNCmqbgoFnSZzZjcJoPJTEkmgjQFK_hYaNGFIiK6kOLOVvABivg1mehfOElTxdUdzj1zuY8BtrkDWMduSPoe2mhZDKGGlk4P5MWhPJ_Jk4f0eH8pp03NwqvM5gFlbpfYWyA0eTn-r-HCNJTZidw15lMG3Tjyat3NPCimMS6oDQuzpcyVkMf9DkQRccHcBibKnDmvYvAgqKlY0nZO57LhEcpK3EDV6nKSfZxl79fp497X_Z0aIUHNovEi99tl-jpJx7khb04TZBQNF8b37lV2O5HPIzl7yt4-E7NeV99ejuRo-gfbykRqS7iCAwgDQh21vEFexMLCgwAsrKunAz0S-2oUiw2VGvcdIsB0qOAh1nvEj6CCSSz4-g6zsS7CGBZSixJ1i6C0hj_Pm6rs)](https://mermaid-js.github.io/mermaid-live-editor/edit#pako:eNpVkctKw0AUQH9lmFWEth8QqNCmqbgoFnSZzZjcJoPJTEkmgjQFK_hYaNGFIiK6kOLOVvABivg1mehfOElTxdUdzj1zuY8BtrkDWMduSPoe2mhZDKGGlk4P5MWhPJ_Jk4f0eH8pp03NwqvM5gFlbpfYWyA0eTn-r-HCNJTZidw15lMG3Tjyat3NPCimMS6oDQuzpcyVkMf9DkQRccHcBibKnDmvYvAgqKlY0nZO57LhEcpK3EDV6nKSfZxl79fp497X_Z0aIUHNovEi99tl-jpJx7khb04TZBQNF8b37lV2O5HPIzl7yt4-E7NeV99ejuRo-gfbykRqS7iCAwgDQh21vEFexMLCgwAsrKunAz0S-2oUiw2VGvcdIsB0qOAh1nvEj6CCSSz4-g6zsS7CGBZSixJ1i6C0hj_Pm6rs) #### 消息流水线 **要增加新的消息类型支持,我们需要扩展 *消息流水线*(`MessagePipeline`)**。 事实上为了支持复杂的富文本消息以及多样的消息类型,消息流水线也分有多个子部门。 - `MessageEncoderPipeline`:包含一系列消息编码器(`MessageEncoder`),将 mirai-core-api 的结构编码为协议数据; - `MessageDecoderPipeline`:包含一系列消息解码器(`MessageDecoder`),将协议数据解码为 mirai-core-api 的结构; - `OutgoingMessagePipeline`:包含一系列消息预处理器(`OutgoingMessagePreprocessor` )、转换器(`OutgoingMessageTransformer`)、发送器(`OutgoingMessageSender` )和后处理器(`OutgoingMessagePostprocessor` )。预处理器可校验消息长度等、转换器可根据情况把普通消息转为长消息或分片消息、发送器则处理非常规类型的消息(如音乐分享)。 通常来说,一个协议上就是"消息"的消息元素,如 mirai `PlainText` 和 `At` 对应协议 `Elem.Text` 、mirai `Face` 对应协议 `Elem.Face`。要支持这样的消息元素,只需要增加一个 `MessageEncoder` 用于编码和一个 `MessageDecoder` 用于解码即可。 [//]: # (> 实际上 `Dice` 的处理还涉及*消息细化*(Message Refinement)机制,该机制将在之后解释。 ) 而对于协议上有独立通道,而 mirai 为了易用性将他们设计为消息的消息元素,需要实现 `MessageSender` 并以高于一般消息发送器 `GeneralMessageSender` 的优先级注册。 如(`MusicShare` )实际上通过独立的音乐分享通道发送;又如文件消息(`FileMessage`)需要通过 `FileManagement.Feed` 通道发送。 #### `MessageProtocol` 无论是协议上就是消息的消息元素,还是协议上有独立通道的消息元素,它们的实现的方式都是类似的 —— 通过 `MessageProtocol.collectProcessors`。 `MessageProtocol` 是对*协议实现*的抽象。一个 `MessageProtocol` 包含了它负责的消息类型的所有处理器。 继续使用 `Video` 示例,我们假设 `Video` 功能在协议上就是"消息",因此只需要增加 `MessageEncoder` 与 `MessageDecoder`: ```kotlin package net.mamoe.mirai.internal.message.protocol.impl internal class VideoProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) // 添加 Encoder 到消息流水线 add(Decoder()) } private class Encoder : MessageEncoder<Video> { override suspend fun MessageEncoderContext.process(data: Video) { markAsConsumed() // 表示本 `MessageEncoder` 将会完成对输入的 `Video` 的全部处理 if (data is AbstractVideo) { // `collect` 收集一个协议结构 collect(ImMsgBody.Elem(videoFile = data.toVideoFile())) } } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.videoFile == null) return markAsConsumed() // 表示本 `MessageDecoder` 将完成对输入的 `ImMsgBody.Elem` 的全部处理 collect( OnlineVideoImpl( data.videoFile.fileName, data.videoFile.fileTime.toLong(), data.videoFile ) ) } } } ``` 假如 `Video` 功能在协议上不是"消息",就需要实现 `MessageSender`。`MessageSender` 将直接涉及发送合适的数据包,可参考 `net.mamoe.mirai.internal.message.protocol.impl.MusicShareProtocol.Sender` 实现。 最后需要注册 `VideoProtocol`,这与之前注册 `Factory` 的方式一样,只是需要基于 `net.mamoe.mirai.internal.message.protocol.MessageProtocol` : - JVM:在 jvmBaseMain/resources/META-INF/services/ 新建一个文件名为 `net.mamoe.mirai.internal.message.protocol.MessageProtocol` ,文件内容为 `net.mamoe.mirai.internal.message.protocol.impl.VideoProtocol` - Native: 在 `net.mamoe.mirai.internal.utils.MiraiCoreServices.registerAll` 末尾增加: ```kotlin Services.register( "net.mamoe.mirai.internal.message.protocol.MessageProtocol", "net.mamoe.mirai.internal.message.protocol.impl.VideoProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.VideoProtocol() } ``` ### 7. 添加收发消息测试 mirai 拥有对消息流水线的测试框架,位于 `commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest` 。 请为你的 `...Protocol` 在 `commonTest/kotlin/message/protocol/impl` 增加一个 `...ProtocolTest`。可以参考 `TextProtocolTest` (协议上的消息)和 `MusicShareProtocolTest`(独立通道)。 在单元测试通过之后,也请登录测试账号在本地环境进行收发消息测试。 提示:要运行 `main` 函数,可在 `jvmTest/kotlin/local` 中编写。该目录已被忽略 Git 跟踪。 ### 8. 实现序列化 mirai 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 。 mirai 提供 `MessageSerializers` 来支持消息元素的多态序列化。 序列化器(`KSerializer`)在 `MessageProtocol` 中收集。 对于 `Video` 示例,我们首先为 `OnlineVideoImpl` 和 `OfflineVideoImpl` 实现序列化。 `OfflineVideoImpl` 的序列化很简单,只需要添加两个注解: ```kotlin @SerialName(OfflineVideo.SERIAL_NAME) @Serializable // 编译器将会自动生成序列化器 internal class OfflineVideoImpl( override val filename: String, override val length: Long, ) : OfflineVideo, AbstractVideo() { // ... } internal class VideoProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { // ... // `superclassesScope` 的参数为 `OfflineVideo` 的父类。 // 这么做是因为反射在 Native 平台不可用。 MessageSerializer.superclassesScope( MessageContent::class, SingleMessage::class ) { add( MessageSerializer( OfflineVideoImpl::class, OfflineVideoImpl.serializer() ) ) // 绑定序列化器 } } } ``` 由于 `OnlineVideoImpl` 拥有 `ImMsgBody.VideoFile?` 属性, ```kotlin @SerialName(OnlineVideo.SERIAL_NAME) @Serializable // 编译器将会自动生成序列化器 internal class OnlineVideoImpl( override val filename: String, override val length: Long, private val original: @Serializable(VideoFileAsByteArraySerializer::class) ImMsgBody.VideoFile? // 协议数据结构 ) : OnlineVideo, AbstractVideo() { override fun toVideoFile(): ImMsgBody.VideoFile = original } // 将 `VideoFile` 作为十六进制字符串序列化,这样输出的 JSON 字符串更易读。 internal object VideoFileAsHexSerializer : KSerializer<ImMsgBody.VideoFile> by String.serializer() .map( // 使用 String.serializer() 作为代理序列化器 resultantDescriptor = ImMsgBody.VideoFile.serializer().descriptor, deserialize = { // 反序列化:即字符串到 ImMsgBody.VideoFile it.hexToBytes() // 转换为字符串 .loadAs(ImMsgBody.VideoFile.serializer()) // 解析 ByteArray 到结构 }, serialize = { // 序列化:即 ImMsgBody.VideoFile 到字符串 it.toByteArray(ImMsgBody.VideoFile.serializer()) // 导出结构数据为 ByteArray .toUHexString() // 转换为十六进制字符串表示 } ) internal class VideoProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { // ... MessageSerializer.superclassesScope( MessageContent::class, SingleMessage::class ) { // ... add( MessageSerializer( OnlineVideoImpl::class, OnlineVideoImpl.serializer() ) ) // 绑定序列化器 } } } ``` ### 9. 添加序列化测试 请在第 7 步实现的你的 `MessageProtocolTest` 中添加 添加序列化测试。 你可以仿照已有的测试如 `TextProtocolTest` 等编写你的序列化测试。通常只需要添加一个类似于 `TextProtocolTest` 的 `test serialization for AtAll` 的函数即可。 ### 10. 提交 PR 恭喜,到这一步你已经实现了高质量的新消息类型的支持。请在 `https://github.com/mamoe/mirai/compare` 提交 PR,让你的修改能进入 mirai 主分支。感谢你对 mirai 的贡献。 ================================================ FILE: docs/contributing/README.md ================================================ # 贡献 本文是关于向 mirai 贡献的指南。mirai 欢迎并感谢一切形式的贡献。 [mirai-core-api]: ../../mirai-core-api [mirai-core-utils]: ../../mirai-core-utils [mirai-core]: ../../mirai-core [mirai-console]: ../../mirai-console/backend/mirai-console [mirai-console-integration-test]: ../../mirai-console/backend/integration-test [mirai-console-codegen]: ../../mirai-console/backend/codegen [mirai-console-terminal]: ../../mirai-console/frontend/mirai-console-terminal [mirai-conosle-compiler-annotations]: ../../mirai-console/tools/compiler-annotations [mirai-conosle-compiler-common]: ../../mirai-console/tools/compiler-common [mirai-conosle-intellij]: ../../mirai-console/tools/intellij-plugin [mirai-conosle-gradle]: ../../mirai-console/tools/gradle-plugin [mirai-bom]: ../../mirai-bom [mirai-dokka]: ../../mirai-dokka [mirai-core-all]: ../../mirai-core-all [mirai-logging]: ../../logging [mirai-logging-log4j2]: ../../logging/mirai-logging-log4j2 [mirai-logging-slf4j]: ../../logging/mirai-logging-slf4j [mirai-logging-slf4j-simple]: ../../logging/mirai-logging-slf4j-simple [mirai-logging-slf4j-logback]: ../../logging/mirai-logging-slf4j-logback 当前仓库 mamoe/mirai 包含 mirai 核心模块: | 名称 | 描述 | |------------------------|---------------------| | mirai-core-utils | 一些工具类,供其他模块使用 | | mirai-core-api | mirai 机器人核心 API | | mirai-core | mirai 机器人核心实现 | | mirai-core-all | 上述三个模块的集合,用于启动器 | | mirai-console | 插件模式机器人框架后端 | | mirai-console-terminal | mirai-console 的终端前端 | | mirai-console-intellij | IntelliJ IDEA 插件 | | mirai-console-gradle | Gradle 插件 | | mirai-bom | Maven BOM | | mirai-logging | 常用日志库转接器 | 若你想以非提交代码方式帮助 mirai,可以为 [mirai-console](/mirai-console) 编写插件并发布到[论坛](https://mirai.mamoe.net/),或在论坛帮助新入门的朋友使用 mirai 等。 若希望提交代码,请继续阅读。 ## Git 分支 - `1.x`:1.x 版本的开发 (已停止); - `dev`:2.x 版本的开发(主分支); - `-release` 后缀:某个版本的小更新分支。如 `2.10-release` 会包含 `2.10.x` 小版本的更新; - 其他分支:正在开发中或留档的开发进度。 通常请基于 `dev` 分支进行修改。 基于[版本规范](../Evolution.md#版本规范) ,若一个修改适合发布为小版本更新,mirai 会从 `dev` 中提取该修复到目标 `-release` 分支。 ## 安装 JDK 需要安装 JDK 才能编译 mirai。mirai 主分支最新提交在如下环境测试可以编译: | 操作系统 | JDK | 架构 | |--------------|--------------------|---------| | macOS 12.0.1 | AdoptOpenJDK 17 | aarch64 | | macOS 12.0.1 | Amazon Corretto 11 | amd64 | | Windows 10 | OpenJDK 17 | amd64 | | Ubuntu 20.04 | AdoptOpenJDK 17 | amd64 | 若在其他环境下无法正常编译, 请尝试选择上述一个环境配置。 ## 我不熟悉 Gradle 或 Kotlin / 我赶时间 在 [SimpleInstructions](SimpleInstructions.md) 查看你可能想做的事情的简单命令。 ## `mirai-core` 术语 根据语境,mirai-core 有时候可能指 `mirai-core` 这个模块,有时候可能指 `mirai-core-utils` 、`mirai-core-api`、 `mirai-core` 这三个模块的整体。 本文中,`mirai-core` 将特指 `mirai-core` 模块,而用 'core' 或者 'mirai core' 指相关三个模块的整体。 ## core 多平台架构 [HMPP]: https://kotlinlang.org/docs/multiplatform-discover-project.html core 三个模块都使用 Kotlin [HMPP] 功能,同时支持 JVM 和 Android 两种平台。你可以在 [Kotlin 官方文档][HMPP] 了解 HMPP 模式。 core 的编译目标层级结构如图所示: ``` common / \ jvm android ``` | 发布平台名称 | 描述 | |---------|------------------| | jvm | JVM | | android | Android (Dalvik) | 备注: - common 包含全平台通用代码,绝大部分代码都位于 common; - jvmBase 包含针对 JVM 平台的通用代码; ## 开发提示 建议使用 IntelliJ IDEA 或 Android Studio,并安装最新的 Kotlin 插件。 建议设置 IntelliJ 的内存为至少 6GB,否则 IDE 可能会频繁冻结编辑器收集垃圾。(可在 `Help -> Edit Custom VM Options` 中添加 `-Xmx6000m`) ### 关闭部分项目以提升速度 你可以在项目根目录创建 `local.properties`,中按照如下配置,关闭部分项目来提升开发速度。在关闭后,请终止所有 Gradle 后台进程,以保证更改正确应用。 ```properties # 关闭 IntelliJ IDEA 插件模块,这可以避免下载 1~2GB 的依赖 projects.mirai-console-intellij.enabled=false # 关闭 Gradle 插件模块 projects.mirai-console-gradle.enabled=false # 关闭 mirai 依赖测试模块 projects.mirai-deps-test.enabled=false # 也可以用其他模块的路径替换 module-path,可关闭该模块 projects.module-path.enabled=false # 特殊配置,关闭 mirai-console 后端,这同时也会关闭全部 console 相关的项目 projects.mirai-console.enabled=false # 特殊配置,关闭 mirai-logging,这会关闭所有日志转接模块 projects.mirai-logging.enabled=false # 特殊配置,是否取消指定 jvmToolchain,在本地 jvmTest 中需要访问 JDK 9+ 的内容时需要携带此配置 mirai.enable.jvmtoolchain.special=false ``` 通常关闭 IDEA 插件和 Gradle 插件可以显著提高初始化速度(IDEA 插件项目在初始化时需要下载 1G 左右编译依赖)。 ### 关闭 core 的部分构建目标 可以在上述 `local.properties` 中,配置 `projects.mirai-core.targets=` 使用以下配置语法关闭部分构建目标。关闭后可以减轻 IDE 负担,也可以避免下载工具链而加快初始化速度。 所有目标默认都启用。 **注意**,在关闭一个目标后,将无法编辑该目标的相关源集的源码。 因此若非主机性能太差或在 CI 机器运行,**不建议**关闭目标。 [//]: # (备注: 如果要发版, 必须开启全部目标, 否则会导致 metadata 中的平台不全) - `xxx`:显式启用 `xxx` 目标 - `!xxx`:显式禁用 `xxx` 目标 - `others`:显式启用其他所有所有目标 - `!others`:禁用没有显式启用的所有目标 其中 xxx 表示构建目标名称。可用的目标名称有(区分大小写):`jvm`、`android` ``` # 只启用 `jvm` 目标,禁用其他所有目标 (Android) projects.mirai-core.targets=jvm;!others # 启用 `jvm` 和 `android` 目标 projects.mirai-core.targets=jvm;android ``` ### 直接启动 mirai-core 本地测试 一般情况下, 只要 JVM 平台测试通过其他平台也能测试通过。 在 JVM 平台直接启动 mirai-core, 见 [mirai-core/jvmTest](/mirai-core/src/jvmTest/README.md) ## 构建 mirai 项目 JAR 查看 [Building](building/README.md) ## 部署 mirai 到本地仓库 [bignum]: https://github.com/ionspin/kotlin-multiplatform-bignum 要部署 mirai 项目到本地 Maven 仓库(Maven Local),只需使用如下命令,其中 `2.99.0-local` 可为任意想在本地仓库发布的版本号。 ```shell ./gradlew publishMiraiArtifactsToMavenLocal "-Dmirai.build.project.version=2.99.0-local" ``` 注意,因为构建默认启用多线程,此操作可能会占用约 32GB 内存。如果主机条件不足,或希望减少内存占用,可以如下方式禁用多线程加速: ```shell ./gradlew publishMiraiArtifactsToMavenLocal "-Dmirai.build.project.version=2.99.0-local" "-Porg.gradle.parallel=false" ``` 随后可通过 `implementation("net.mamoe:mirai-core:2.99.0-local")` 引入项目。 由于 mirai 项目结构复杂,构建可能由于各种原因失败,mirai 提供依赖可用性测试。 部署一份版本为 `2.99.0-deps-test` 的 mirai 到本地仓库,执行 `./gradlew :mirai-deps-test:test` 即可运行相关测试。若测试通过,则代表部署的项目可通过各种方式正常引入到其他项目。 ## 通过 Gradle Composite Build 引入 mirai 若在 Gradle 通过 Composite Build 引入 mirai,则在编译时可能遇到依赖冲突问题。 mirai 使用特定版本的 Ktor 2、[kt-bignum][bignum] 等库,会将它们的包名增加前缀 `net.mamoe.mirai.internal.deps.` 来避免产生冲突。 但这个操作仅对部署的版本有效,在 Gradle 中构建时,仍然可能会有依赖冲突。但若在测试中没遇到问题,一般不用担心。 要详细了解这个过程以及实现原理,请查看 [源码](../../buildSrc/src/main/kotlin/shadow/Relocation.kt)(含注释)。 ## 寻找待解决的问题 可以在 [issues](https://github.com/mamoe/mirai/issues) 查看 mirai 遇到的所有问题,或在里程碑查看版本计划. [里程碑](https://github.com/mamoe/mirai/milestones) 为各版本的开发计划. 在完成所有任务后就会发布该版本. `Backlog` 为没有设定目标版本的计划. 如果有相关 PR, 这些计划就可能会被确定到一个最近的版本. ## 开发文档 本节列举为了帮助开发某些模块的文档。 - [添加新协议支持](ImplementingProtocol.md)。 ## 提交高质量的 commit 以及 PR mirai 由社区驱动,审核者在业余时间审核 PR。这些规范性建议将帮助你提交高质量的 commit 和 PR,同时节约你和审核者的时间,最大程度地帮助 mirai,也能帮助你在其他项目提交同样高质量的 PR。 如果你不太保证自己能达到这些要求也没关系,mirai 感谢你花费的每一分钟,维护者会审核 PR 并尽可能帮助你。 ### 代码规范 - 请在 Kotlin 模块使用纯 Kotlin 实现。 - 尽量不要引用新的库 - 遵守 Kotlin 官方代码规范(提交前使用 IDE 格式化代码即可 (commit 时勾选 'Reformat code')) ### 为 Java 做兼容 mirai 使用 Kotlin 编写,大量使用 Kotlin 协程等语言级特性。为了能兼容 Java,mirai 使用 Kotlin 编译器插件 [KJBB](https://github.com/him188/kotlin-jvm-blocking-bridge) 。KJBB 会为 Kotlin 挂起函数生成一个辅助方法来允许 Java 调用。在开发时只需要: - 安装 IDE 插件 [kotlin-jvm-blocking-bridge](https://github.com/Him188/kotlin-jvm-blocking-bridge/blob/master/README-chs.md#%E5%AE%89%E8%A3%85-intellij-idea-%E6%88%96-android-studio-%E6%8F%92%E4%BB%B6) - 若要添加一个 suspend 函数, 为它添加 `@JvmBlockingBridge` 注解即可允许 Java 调用 ### 确保二进制兼容性 请在提交前进行 [ABI 验证](VerifyingABI.md)。 ### 代码注释语言 内部实现的注释可以使用英文或中文(无变体要求)。公开 API 的注释(KDoc)请只使用简体中文,且无需提供翻译。 ### 编写高质量的文档 mirai 在乎质量,这也包括文档质量。根据 mirai 的历史 PR,许多贡献者容易忽视下列问题: - 汉字与英文或数字之间需要有空格 - KDoc 语法衍生于 Markdown 语法,没有空行的换行会被压缩为一行,因此需要有正确的标点符号结束一句话 可以阅读 [中文技术文档的写作规范](https://github.com/ruanyf/document-style-guide/blob/master/docs/title.md) 了解如何编写高质量的中文文档。为了方便,在 mirai 代码文档和注释中可以使用英文标点符号。但在编写 docs 目录中等的 `.md` 正式文档时,请遵守写作规范。 ### PR 标题 编写 PR 标题时,可以使用英文或中文。正确描述 PR 的修改,避免 "修复了一个 bug" 这类模糊标题即可。 ### 会如何合并 PR 由于仓库庞大,mirai 主分支(`dev`)维护线性提交历史来确保历史的可读性。这意味着主分支不允许有 merge 操作,只允许基于分支最新提交的提交。 一般 PR 会以 squash 方式合并,即 PR 的所有 commit 都会被合并为一个 commit,并由审核者拟定标题。这个标题通常会直接采用 PR 标题(如果符合规范)。 若提交的 PR 需要以多个步骤完成,且 PR 的提交概述符合约定,审核者通常会以 rebase 方式合并 PR,即 PR 的 commit 会被重置到基于最新 `dev` 分支并按顺序并入。 ### commit message 由于很多 PR 会以 squash 方式合并,可以不需要遵守本节的提交概述(commit message)的约定。 编写提交概述以及 PR 的标题时,可以使用英文或中文。 由于代码量庞大,请在提交概述包含涉及模块名称。模块名称可以是 `[core]` 、`[console]`、`[idea]`、`[gradle]` 或 `[build]` 。如有必要,还可以添加子模块名称如 `[core/native]` 表示 mirai-core 中的 native 部分。 mirai 不要求将 commit 为了区分新功能或修复 bug 而*特地*使用 `fix:` 或 `feat:` 等前缀(允许你认为的合理的使用,或是你更喜欢这样区分)。只需要用最合适的语言描述修改。如 `Optimize gradle properties` 。 请以标准英文句子语法编写提交概述,可省略末尾标点符号。如 `Configure shadow relocation, and add checks for multiplatform publishing.` 或 `Optimize gradle properties`。 ### 确保 PR 只解决一类问题 一个 PR 只能解决一个(类)问题,比如 "支持视频消息"、" 修复无法发送图片的问题"、"修复一些消息类型无法序列化的问题"。 等。当解决一类问题时,请规范 commit 来区分解决这类问题的步骤(如果有必要)。 ### 确保 commit 的可阅读性 一个 PR 只能包含能通过 commit message 描述的对一个部分的修改。比如 "更新 console 文档" 、"Add logger for NetworkHandler" 等。 commit 必须不带有或指明必要的副作用。例如名称为 "修复无法发送图片的问题" 的 commit 却修改了对消息长度的限制是不好的。此时一个恰当的概述为 " 调整消息长度限制, 修复无法发送图片的问题" (修改内容 + 修改带来的影响)。 若要修复某一个 issue,请不要仅提交 "fix #123"。请附带具体修复内容,把修复 issue 作为"修改带来的影响"。例如 "调整消息长度限制, 修复无法发送图片的问题, fix #123"。("fix #123" 会触发 GitHub 自动链接 issue 到 PR) ### PR 在有审核后避免 force push PR 在有人审核(review)后,请不要进行 force push(`git push -f` ),否则将可能会导致审核人需要重新审核你的全部代码——这会浪费大量时间。请在收到 approve 的 review 并且 PR 被标记 `ready-to-merge` 之后再进行 force push 优化提交历史。当然,即使不优化提交历史,PR 也会在合适的时机使用 squash 方式合并。 在有审核之前可任意 force push。 [//]: # (### 加入开发组) [//]: # () [//]: # (你可以随时提交 PR 解决任何问题。而若有兴趣,我们也欢迎你加入开发组,请联系 support@mamoe.net) [//]: # () [//]: # ([mirai-compose]: https://github.com/sonder-joker/mirai-compose) [//]: # () [//]: # ([plugin-center 服务端]: https://github.com/project-mirai/mirai-plugin-center) [//]: # () [//]: # ([mirai-api-http]: https://github.com/project-mirai/mirai-api-http) [//]: # () [//]: # ([project-mirai/docs]: https://github.com/project-mirai/docs) [//]: # () [//]: # ([docs.mirai.mamoe.net]: https://docs.mirai.mamoe.net) [//]: # () [//]: # (| 名称 | 描述 |) [//]: # (|:-----------------------:|:----------------------------------------------------------------------------:|) [//]: # (| core 和 console 日常更新 | 在 milestone 安排的日常更新。我们目前版本速度是一个月到两个月发布一个次版本(2.x&#41;。需要日常的开发。 |) [//]: # (| console 后端 | 架构稳定,现在格外需要在易用性上的提升,首先需要一个优化方案,再实现它们。 |) [//]: # (| console 文档 | 根据用户反馈,现在文档十分缺少。需要以用户的身份体验过 console 的人编写用户文档。 |) [//]: # (| 图形前端 [mirai-compose] | 各功能都缺目前尤其缺少对接 console PluginConfig 的图形化配置的实现。 |) [//]: # (| [plugin-center 服务端] | 插件中心正在建设中。后端 Spring,前端 Vuetify。由于开发人员学业繁忙,暂搁置。 |) [//]: # (| plugin-center 社区 | 插件中心计划支持所有语言的插件,因此需要与社区 SDK 作者沟通并帮助它们接入 Console 的 PluginLoader API 和插件中心的要求。 |) [//]: # (| plugin-center console 端 | 需要评估现在 console 架构是否足够支持插件中心及所有语言插件的管理,实现与插件中心的对接。 |) [//]: # (| plugin-center gradle | 对接插件中心,实现通过 Task 上传插件。还没有开始做。 |) [//]: # (| mirai-console-loader | console 启动器。对接插件中心的 API,支持下载和更新插件等。不确定之后是否会有人实现。 |) [//]: # (| IDE 插件 | IntelliJ IDEA 的插件的工作。可以为 mirai 框架添加检查等功能。这个部分目前基本满足需求。 |) [//]: # (| [mirai-api-http] v2 | 日常维护。 |) [//]: # (| [project-mirai/docs] | 用户友好文档自动部署,使用 VuePress , 部署于 [docs.mirai.mamoe.net],目前还有部分超链接错误的问题。 |) ================================================ FILE: docs/contributing/SimpleInstructions.md ================================================ # 简单命令 以下为你可能想做的事情的示例命令: 1. clone 项目 2. 在项目根目录创建 local.properties,并加入如下内容: ```properties projects.mirai-console-intellij.enabled=false projects.mirai-deps-test.enabled=false ``` 3. 执行以下命令: - 我只是 JDK/Java/Kotlin JVM 用户(或者我不知道这什么什么意思): - 编译并打包 mirai-core-all JAR,成品将存放在 `mirai-core-all/build/libs/`: ```shell ./gradlew :mirai-core-all:shadowJar "-Dprojects.mirai-core.targets=jvm;!others" ``` - 编译并打包 mirai-console JAR,成品将存放在 `mirai-console/build/libs/`: ```shell ./gradlew :mirai-console:shadowJar "-Dprojects.mirai-core.targets=jvm;!others" ``` - 将 mirai 发布到 mavenLocal 以便本地引入,发布后的版本为 `2.99.0-local`: ```shell ./gradlew publishMiraiArtifactsToMavenLocal "-Dprojects.mirai-core.targets=jvm;!others" "-Dmirai.build.project.version=2.99.0-local" ``` - 我是 Android 用户: - 将 mirai 发布到 mavenLocal 以便本地引入,发布后的版本为 `2.99.0-local`: ```shell ./gradlew publishMiraiArtifactsToMavenLocal "-Dprojects.mirai-core.targets=jvm;android;!others" ``` 若上述命令不工作,尝试在 Android Studio 中打开项目并在 Studio 的终端中执行命令。 ================================================ FILE: docs/contributing/VerifyingABI.md ================================================ # 进行 ABI 验证 mirai 通过 [binary-compatibility-validator](https://github.com/Kotlin/binary-compatibility-validator)) 维护 [ABI](https://zh.wikipedia.org/zh-cn/%E5%BA%94%E7%94%A8%E4%BA%8C%E8%BF%9B%E5%88%B6%E6%8E%A5%E5%8F%A3) 稳定性。 若要修改 mirai-core-api,可执行 Gradle 任务 `apiCheckAll` 来检验 ABI 兼容性,也可以在 IDEA 双击 Control 键运行 `Check Binary Compatiblity`。 若正在添加一个新功能,可以执行 Gradle 任务 `apiDumpAll` 或在 IDEA 双击 Control 键运行 `Dump API Changes for ...` 来更新记录。这将会生成 `*.api` 文件,文件的变化反映了你的修改情况。请人工审核该文件以确保向下兼容。 ================================================ FILE: docs/contributing/building/BuildingCoreAndroid.md ================================================ # 构建 mirai-core Android 目标 mirai 项目支持两种方式构建 Android 目标。 若主机在 `local.properties` 中配置了 `sdk.dir` 为 Android SDK 路径(就像普通 Android 项目一样), 并且 mirai 的 Android 目标为启用状态(见 [关闭部分项目以提升速度](../README.md#关闭部分项目以提升速度)),则会使用 Android SDK 方式构建 android 目标,否则使用 JDK 方式。 ## Android 源集结构 以 "ADK" 指代 "Android SDK",下表展示 mirai core 项目中与 Android 相关的 Kotlin 源集、其依赖的源集列表、以及可用性。 | sourceSet | dependsOn | 可用性 | |-------------------------|-------------|-----------| | androidMain | jvmBaseMain | ADK 和 JDK | | androidInstrumentedTest | jvmBaseTest | ADK | | androidUnitTest | jvmBaseTest | ADK 和 JDK | ## Android SDK 构建方式 就像一个普通的 Android 库一样,mirai 可使用 Android SDK 编译,并拥有在 JVM 的单元测试和在 Dalvik 上运行的 instrumented tests。 这是最推荐的构建方式,能保证 mirai 在真实 Android 环境通过测试,且能获得针对 Android 的 IDEA 代码检查。 注意,`androidInstrumentedTest` 将会使用 Android 模拟器运行。 ## JDK 构建方式 若 `sdk.dir` 未配置,则不会配置使用 Android SDK,而会使用桌面 JDK。`androidInstrumentedTest` 将会被禁用。 ================================================ FILE: docs/contributing/building/README.md ================================================ # 构建 本文介绍如何构建 mirai 的各模块。 ## 构建 JVM 目标 要构建只有 JVM 目标的项目(如 `mirai-console`,只需在项目根目录使用如下命令执行 Gradle 任务: ```shell ./gradlew :mirai-console:assemble # 编译 ./gradlew :mirai-console:check # 测试 ./gradlew :mirai-console:build # 编译和测试 ``` 其中 `:mirai-console` 是目标项目的路径(path)。 你也可以在 IDEA 等有 Gradle 支持的 IDE 中在通过侧边栏等方式选择项目的 `assemble` 等任务: ![](images/run-gradle-tasks-in-idea.png) 类似,但需要使用 `:mirai-core:compileKotlinJvm` 和 `:mirai-core:jvmTest` 分别用于编译和测试。提示:直接执行测试时也会自动先完成编译。 在 IDEA 开发中无需特殊考虑,一般直接通过点击单元测试行号处的运行按钮即可选择 JVM 平台运行。 要批量运行测试,可使用 `./gradlew :mirai-core:check` 运行 mirai-core 模块的所有目标的所有测试。 不建议在日常使用 `./gradlew check` 运行所有项目的测试,因为这可能会消耗时间和主机运行资源。但也值得在即将提交 PR 或喝咖啡休息时这么做。 ### 构建 core 的 Android 目标 查看 [BuildingCoreAndroid](BuildingCoreAndroid.md)。 ## 构建 IntelliJ 插件 可通过如下命令构建 IntelliJ 平台 IDE 的插件。构建成功的插件将可以在 `mirai-console/tools/intellij-plugin/build/distribution` 中找到。 ```shell ./graldew :mirai-console-intellij:buidlPlugin ``` ## 获得 mirai-console JAR 在项目根目录执行如下命令可以获得包含依赖的 mirai-console JAR。其他模块也类似。 ```shell ./gradlew :mirai-console:shadowJar ``` ================================================ FILE: docs/contributing/mock/SpaceAllocation.md ================================================ # TmpFsServer > 此文件是关于 `mirai-core-mock` 是如何分配文件资源路径的具体说明 > > 所有的临时资源的路径 (包括: 图片, 语音, 群文件, etc.) > > 注: > - 此文档说明只适用于默认实现 ## 通用资源 TmpFsServer 会为每一个上传的文件分配一个 ResourceID, 规则如下 ```text ${resource.size}-${resource.sha1.hex()}-${resource.md5.hex()} ``` 数据资源会直接拷贝至 `/storage/$resourceId` ## 图片资源 图片资源除了 ResourceID 外, 还拥有一个 `ImageID`, mirai-core-api 中 ImageID 的格式为 `{XXXXXX}.yyy`, 而 TmpFsServer 仅截取其中 `XXXX` 部分作为 `ImageID` 在完成资源上传后, TmpFsServer 会额外将 `/images/$imgId` 链接到 `/storage/$resourceId` # ServerFileDisk / ServerFileSystem (群文件管理系统) 每一个群都有一个自己的 `ServerFileSystem`, 每一个 `ServerFileSystem` 都会分配一块区域存储自己的数据, 路径为 `/fs-disk/${UUID.randomUUID()}` (下文简记为 `/sfs`) 由于群文件的特殊性, 群文件采用随机 UUID 作为文件名 > 特殊性: 群文件允许同名文件的存在 群文件数据结构如下 ```text /sfs/details/root/.... /sfs/details/fileN/.... /sfs/details/dirN/.... /sfs/root/fileN.... /sfs/dirN/fileN.... /sfs/fileN..... ``` ## 普通文件 对于普通文件, ServerFileSystem 会随机分配一个 ID, 并将数据拷贝至 `/sfs/$id`, 然后在此文件所在的文件夹创建一个同名空文件, 并生成相关的 details 信息 > 如: 如果是在根目录, 则会创建 `/sfs/root/$id`, > > 如果是在目录 `testdir`(实际是随机 UUID), 则会创建 `/sfs/testdir/$id` ## 目录 对于目录, 相关的行为与普通文件一样。 唯一的区别是, `/sfs/$id` 是一个目录而不是一个文件 ## details 信息 `/sfs/details/$id/....` 存有每一个文件的详细信息 (对于根目录, `/sfs/details/root/...`) 其中的文件意义为: - `~/parent` 此文件所在的文件夹 - `~/name` 此 文件/文件夹 的名字, 对于根目录永远为 `""` - `~/creator` 此 文件/文集夹 的创建者, 编码为 二进制 big-endian int64 - `~/createTime` 此 文件/文件夹 的创建时间, 编码为 二进制 big-endian int64 ================================================ FILE: docs/files/install-20210412.cmd ================================================ @echo off powershell (new-object System.Net.WebClient).DownloadFile( 'https://github.com/iTXTech/mcl-installer/releases/download/2827601/mcl-installer-2827601-windows-amd64.exe','mcl-installer.exe') del input.txt REM װ Java echo Y >> input.txt REM ʹ Java 11 echo 11 >> input.txt REM ʹ JRE echo 1 >> input.txt REM ʹ 32 λ JRE ļϵͳ, ҲҪ mirai-native echo x32 >> input.txt echo Y >> input.txt echo Y >> input.txt echo Y >> input.txt echo Y >> input.txt echo Y >> input.txt echo Y >> input.txt mcl-installer.exe < input.txt del input.txt del mcl-installer.exe cmd /c mcl.cmd --update-package net.mamoe:mirai-api-http --channel stable --type plugin echo echo echo װɹ, Ժִ mcl.cmd Mirai Console echo Installation succeed. Run mcl.cmd to start Mirai Console. pause ================================================ FILE: docs/mirai-ecology.md ================================================ # A brief introduction of Mirai Ecosystem Mirai是一个免费开源的QQ机器人框架,由于其开源和易拓展的优势,现在已经有很多基于Mirai的官方和非官方衍生框架和应用(下文统一称为**项目**),其关系错综复杂。这里将简单为你介绍整个 **Mirai 生态中各个框架和应用的关系**。 > 强烈建议你从头并按照**文章顺序和思路**阅读,即使你对 Mirai 生态有一定的了解。 ## 目录: 1. [Mirai](#mirai-使用-kotlin-编写的高效率-qq-机器人框架) 2. [mirai-console](#mirai-console-mirai-官方的可拓展式-qq-机器人管理控制台) - [chat-command](#chat-command-mirai-console-的官方的聊天命令插件) - [mirai-api-http](#mirai-api-http-mirai-console-的官方-http-api-插件) - [mirai-native](#mirai-native-与-coolq-对接的一个通道) 3. [mirai-console-loader](#mirai-console-loader-mirai-console-的官方一键启动器) ## [Mirai](https://github.com/mamoe/mirai): 使用 [Kotlin](https://www.kotlincn.net/) 编写的高效率 QQ 机器人框架 Mirai,正如你所见,这个名字不带任何的前缀或后缀,它是**整个生态的中心**,在这个生态中,所有的项目都直接或间接与 Mirai 有密不可分的关系,Mirai为用户提供了最<u>基础</u>且<u>核心</u>的功能:**接收消息**与**发送消息**。 你需要明确了解的是,Mirai 的定位是<u> QQ 机器人**框架**</u>,或者说它是一个**库(Library)**。<u>如果你觉得理解起来有点困难,你可以把它类比为**建筑的骨架**,一栋只有建筑骨架的建筑是不能住人的,你需要根据这个骨架来盖好这栋楼,它才能住人。同样地,Mirai 是不能直接启动的,需要使用这个框架并按照你的想法来编写QQ机器人程序</u>。 通常来说,Mirai 就是一个 <u>QQ 机器人框架</u>,但是它的内部并不是一个单一的结构: - [mirai-core-api](https://github.com/mamoe/mirai/tree/dev/mirai-core-api):提供了使用 Mirai 的接口。它抽象了核心协议的接口,如**收发消息**,**群操作**等,但不承担具体工作。 - [mirai-core](https://github.com/mamoe/mirai/tree/dev/mirai-core):是 Mirai 对 QQ 的具体协议实现,它承担具体且核心的工作。 它们的关系如下: [![](https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0) 可以看到,协议内容只在 Mirai 内部使用,只有 mirai-core-api 对外提供了功能。 Mirai 运行在 JVM,你需要使用 Kotlin 语言或 Java 语言来编写你的 QQ 机器人程序。在运行时需要有 mirai-core,在开发时只需要有 mirai-core-api。 > 有关 JVM 平台项目配置可以在[之后](ConfiguringProjects.md#mirai---configuring-projects)了解到。 ## [mirai-console](/mirai-console): Mirai 官方的可拓展式 QQ 机器人管理控制台 前面我们提到,你可以使用 Mirai 框架提供的对外开放接口来编写个性化QQ机器人程序,而 mirai-console 就是 Mirai 官方开发组编写的 QQ 机器人程序,它**在 Mirai 框架提供的基础功能的基础上进行了封装**并**进一步提供了更方便的开放接口**。 比如,console 提供了**配置自动登录**,**自动配置/数据存储**功能,这样你就不需要自己考虑写配置登录或者数据存储了,减少了代码工作量。<u>承接上面的例子,mirai-console 就相当于为你在建筑骨架上砌好了墙,分好了房间,这样你就能更容易地盖好这栋楼了</u>。 **重要的是,mirai-console 有命令行界面,可以直接在终端运行它,并实时看到你的机器人状态、插件的运行情况,或者通过 console 提供的命令管理指令权限或者执行插件提供的指令等。** 你可以使用 Kotlin 语言或 Java 语言来编写基于 mirai-console 的 QQ机器人程序,在 mirai-console 中,你编写的程序被称作**插件(Plugin)**,它将被打包为 jar 文件,这样可以很方便发布你的机器人功能程序给其他人使用,把插件 mirai-console 指定的文件夹下,启动 mirai-console 就可以加载你的程序了。 > 你一定玩过 Minecraft, 这里的插件就像 Minecraft 的 mod 一样! 当然,在编写 console 插件时,你**不仅能使用 console 提供的简便接口**,你**还可以同时使用 mirai-core-api 提供的基础接口**。 mirai-console 的内部也不是单一的结构,它分为**前端**和**后端**具体如下: - [后端](/mirai-console/backend):后端就是做了上述工作,即提供**指令**,**权限**,**自动数据** 等开放接口。 - [前端](/mirai-console/frontend):前端就是启动 mirai-console 并监控后端运行状态的端口,上述命令行界面是前端的一种,即 [** mirai-console-terminal**](/mirai-console/frontend/mirai-console-terminal) ,在终端启动的前端。另外还有其他可用的前端如运行在 Android 的 **[MiraiAndroid](https://github.com/mzdluo123/MiraiAndroid)**。 后端就像是“**电脑主机**”,执行着核心工作,前端就像是“**显示器**”、“**鼠标**”和“**键盘**”,可以控制后端。 将 mirai-console 放入上述关系图: [![](https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85bCB6KOF5Z-656GA5Yqf6IO9fG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBzdWJncmFwaCBtaXJhaWNvbnNvbGUgW1wiTWlyYWkgQ29uc29sZVwiXVxuICAgICAgICBtaXJhaWNvbnNvbGViYWNrZW5kKFwiQmFja0VuZFwiKTo6OmhpZ2hsaWdodFxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC10ZXJtaW5hbChcIkZyb250RW5kOiB0ZXJtaW5hbFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC1hbmRyb2lkKFwiRnJvbnRFbmQ6IEFuZHJvaWRcIilcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbnNvbGVwbHVnaW5zIFtcIk1pcmFpIENvbnNvbGUg5o-S5Lu2XCJdXG4gICAgICAgIHlvdXJtaXJhaXBsdWdpbihcIuS9oOe8luWGmeeahCBDb25zb2xlIOaPkuS7tlwiKVxuICAgIGVuZFxuICAgIHlvdXJtaXJhaXBsdWdpbiAtLT4gbWlyYWljb25zb2xlYmFja2VuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85bCB6KOF5Z-656GA5Yqf6IO9fG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBzdWJncmFwaCBtaXJhaWNvbnNvbGUgW1wiTWlyYWkgQ29uc29sZVwiXVxuICAgICAgICBtaXJhaWNvbnNvbGViYWNrZW5kKFwiQmFja0VuZFwiKTo6OmhpZ2hsaWdodFxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC10ZXJtaW5hbChcIkZyb250RW5kOiB0ZXJtaW5hbFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC1hbmRyb2lkKFwiRnJvbnRFbmQ6IEFuZHJvaWRcIilcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbnNvbGVwbHVnaW5zIFtcIk1pcmFpIENvbnNvbGUg5o-S5Lu2XCJdXG4gICAgICAgIHlvdXJtaXJhaXBsdWdpbihcIuS9oOe8luWGmeeahCBDb25zb2xlIOaPkuS7tlwiKVxuICAgIGVuZFxuICAgIHlvdXJtaXJhaXBsdWdpbiAtLT4gbWlyYWljb25zb2xlYmFja2VuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) > 要获取有关 mirai-console 的更多信息,请前往 [mirai-console](https://github.com/mamoe/mirai-console) ## [chat-command](https://github.com/project-mirai/chat-command): Mirai Console 的官方的聊天命令插件 在 mirai-console 的前端中,我们提到可以在前端执行 console 内置和插件提供的**命令**,如果我们想在聊天环境(好友聊天/群聊天/临时会话)执行 console 命令,我们就需要官方提供的 chat-command,并且,console 中的权限管理正是管理**聊天环境中用户执行权限**的。 将 chat-command 放入上述关系图: [![](https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85bCB6KOF5Z-656GA5Yqf6IO9fG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBzdWJncmFwaCBtaXJhaWNvbnNvbGUgW1wiTWlyYWkgQ29uc29sZVwiXVxuICAgICAgICBtaXJhaWNvbnNvbGViYWNrZW5kKFwiQmFja0VuZFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC10ZXJtaW5hbChcIkZyb250RW5kOiB0ZXJtaW5hbFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC1hbmRyb2lkKFwiRnJvbnRFbmQ6IEFuZHJvaWRcIilcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbnNvbGVwbHVnaW5zIFtcIk1pcmFpIENvbnNvbGUg5o-S5Lu2XCJdXG4gICAgICAgIHlvdXJtaXJhaXBsdWdpbihcIuS9oOe8luWGmeeahCBDb25zb2xlIOaPkuS7tlwiKVxuICAgICAgICBjaGF0Y29tbWFuZChcImNoYXQtY29tbWFuZCDmj5Lku7ZcIik6OjpoaWdobGlnaHRcbiAgICBlbmRcbiAgICB5b3VybWlyYWlwbHVnaW4gLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBjaGF0Y29tbWFuZCAtLT4gbWlyYWljb25zb2xlYmFja2VuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85bCB6KOF5Z-656GA5Yqf6IO9fG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBzdWJncmFwaCBtaXJhaWNvbnNvbGUgW1wiTWlyYWkgQ29uc29sZVwiXVxuICAgICAgICBtaXJhaWNvbnNvbGViYWNrZW5kKFwiQmFja0VuZFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC10ZXJtaW5hbChcIkZyb250RW5kOiB0ZXJtaW5hbFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC1hbmRyb2lkKFwiRnJvbnRFbmQ6IEFuZHJvaWRcIilcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbnNvbGVwbHVnaW5zIFtcIk1pcmFpIENvbnNvbGUg5o-S5Lu2XCJdXG4gICAgICAgIHlvdXJtaXJhaXBsdWdpbihcIuS9oOe8luWGmeeahCBDb25zb2xlIOaPkuS7tlwiKVxuICAgICAgICBjaGF0Y29tbWFuZChcImNoYXQtY29tbWFuZCDmj5Lku7ZcIik6OjpoaWdobGlnaHRcbiAgICBlbmRcbiAgICB5b3VybWlyYWlwbHVnaW4gLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBjaGF0Y29tbWFuZCAtLT4gbWlyYWljb25zb2xlYmFja2VuZCIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) ## [mirai-api-http](https://github.com/mamoe/mirai-api-http): Mirai Console 的官方 HTTP API 插件 在 console 中提到,插件也可以使用 mirai-core-api 提供的基础接口, 而 mirai-api-http 插件就是一个将 mirai-core-api 的所有功能封装为 http 服务的插件,同时也提供了 WebSocket 服务。 这样即使你不懂 Java 或 Kotlin,使用 mirai-api-http 插件,你也可以与 Mirai 交互,极大地增加了语言拓展性,也因此衍生出了许多基于 mirai-api-http 的社区 SDK。 这些社区 SDK 封装了 mirai-api-http 提供的接口,且使用了其他语言的良好特性,使编写QQ机器人程序变得更加容易,高效。 将 mirai-api-http 和部分 [社区 SDK](README.md#社区-sdk) 放入上述关系图: [![](https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85bCB6KOF5Z-656GA5Yqf6IO9fG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBzdWJncmFwaCBtaXJhaWNvbnNvbGUgW1wiTWlyYWkgQ29uc29sZVwiXVxuICAgICAgICBtaXJhaWNvbnNvbGViYWNrZW5kKFwiQmFja0VuZFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC10ZXJtaW5hbChcIkZyb250RW5kOiB0ZXJtaW5hbFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC1hbmRyb2lkKFwiRnJvbnRFbmQ6IEFuZHJvaWRcIilcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbnNvbGVwbHVnaW5zIFtcIk1pcmFpIENvbnNvbGUg5o-S5Lu2XCJdXG4gICAgICAgIHlvdXJtaXJhaXBsdWdpbihcIuS9oOe8luWGmeeahCBDb25zb2xlIOaPkuS7tlwiKSAtLT4gbWlyYWljb25zb2xlYmFja2VuZFxuICAgICAgICBjaGF0Y29tbWFuZChcImNoYXQtY29tbWFuZCDmj5Lku7ZcIikgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgbWlyYWlhcGlodHRwKFwibWlyYWktYXBpLWh0dHAg5o-S5Lu2XCIpOjo6aGlnaGxpZ2h0ICAtLT4gbWlyYWljb25zb2xlYmFja2VuZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbW11bml0eXNkayBbXCLnpL7ljLogU0RLXCJdXG4gICAgICAgIG1vcmUoXCIuLi5cIik6OjpoaWdobGlnaHQgLS0-IG1pcmFpYXBpaHR0cFxuICAgICAgICBqcyhcIm1pcmFpLWpzXCIpOjo6aGlnaGxpZ2h0IC0tPiBtaXJhaWFwaWh0dHBcbiAgICAgICAga3RzKFwibWlyYWkta3RzXCIpOjo6aGlnaGxpZ2h0IC0tPiBtaXJhaWFwaWh0dHBcbiAgICAgICAgcnMoXCJtaXJhaS1yc1wiKTo6OmhpZ2hsaWdodCAtLT4gbWlyYWlhcGlodHRwXG4gICAgZW5kIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKG1pcmFpLWNvcmUtYXBpKTo6OmNvcmVoaWdobGlnaHRcbiAgICAgICAgbWlyYWljb3JlcXFhbmRyb2lkKFwibWlyYWktY29yZTxici8-KFFRQW5kcm9pZCDljY_orq4pXCIpXG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZCAtLT4gfOaPkOS-m-WNj-iurnxtaXJhaWNvcmVhcGlcbiAgICBlbmRcbiAgICBtaXJhaWludGVyZmFjZShcIuS9oOe8luWGmeeahDxici8-5py65Zmo5Lq656iL5bqPXCIpXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85o-Q5L6b5Z-656GA5Yqf6IO9fG1pcmFpaW50ZXJmYWNlXG4gICAgbWlyYWljb3JlYXBpIC0tPiB85bCB6KOF5Z-656GA5Yqf6IO9fG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICBzdWJncmFwaCBtaXJhaWNvbnNvbGUgW1wiTWlyYWkgQ29uc29sZVwiXVxuICAgICAgICBtaXJhaWNvbnNvbGViYWNrZW5kKFwiQmFja0VuZFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC10ZXJtaW5hbChcIkZyb250RW5kOiB0ZXJtaW5hbFwiKVxuICAgICAgICBtaXJhaWNvbnNvbGVmcm9udGVuZC1hbmRyb2lkKFwiRnJvbnRFbmQ6IEFuZHJvaWRcIilcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZCAtLT4gbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbnNvbGVwbHVnaW5zIFtcIk1pcmFpIENvbnNvbGUg5o-S5Lu2XCJdXG4gICAgICAgIHlvdXJtaXJhaXBsdWdpbihcIuS9oOe8luWGmeeahCBDb25zb2xlIOaPkuS7tlwiKSAtLT4gbWlyYWljb25zb2xlYmFja2VuZFxuICAgICAgICBjaGF0Y29tbWFuZChcImNoYXQtY29tbWFuZCDmj5Lku7ZcIikgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgbWlyYWlhcGlodHRwKFwibWlyYWktYXBpLWh0dHAg5o-S5Lu2XCIpOjo6aGlnaGxpZ2h0ICAtLT4gbWlyYWljb25zb2xlYmFja2VuZFxuICAgIGVuZFxuICAgIHN1YmdyYXBoIGNvbW11bml0eXNkayBbXCLnpL7ljLogU0RLXCJdXG4gICAgICAgIG1vcmUoXCIuLi5cIik6OjpoaWdobGlnaHQgLS0-IG1pcmFpYXBpaHR0cFxuICAgICAgICBqcyhcIm1pcmFpLWpzXCIpOjo6aGlnaGxpZ2h0IC0tPiBtaXJhaWFwaWh0dHBcbiAgICAgICAga3RzKFwibWlyYWkta3RzXCIpOjo6aGlnaGxpZ2h0IC0tPiBtaXJhaWFwaWh0dHBcbiAgICAgICAgcnMoXCJtaXJhaS1yc1wiKTo6OmhpZ2hsaWdodCAtLT4gbWlyYWlhcGlodHRwXG4gICAgZW5kIiwibWVybWFpZCI6eyJ0aGVtZSI6ImRlZmF1bHQifSwidXBkYXRlRWRpdG9yIjpmYWxzZX0) ## [mirai-native](https://github.com/iTXTech/mirai-native): 与 CoolQ 对接的一个通道 如果你是 酷Q 用户,想在 Mirai 中使用 酷Q 插件,你可以使用 mirai-native 插件,它可以加载 CQP.dll 并兼容**大部分**酷Q 插件,但**不支持** CPK 和解包的 DLL。 不过 酷Q 已经停止提供服务,我们也不再建议使用 酷Q 的插件。 将 mirai-native 放入上述关系图: [![](https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKFwibWlyYWktY29yZS1hcGlcIik6Ojpjb3JlaGlnaGxpZ2h0XG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZChcIm1pcmFpLWNvcmU8YnIvPihRUUFuZHJvaWQg5Y2P6K6uKVwiKVxuICAgICAgICBtaXJhaWNvcmVxcWFuZHJvaWQgLS0-IHzmj5DkvpvljY_orq58bWlyYWljb3JlYXBpXG4gICAgZW5kXG4gICAgbWlyYWlpbnRlcmZhY2UoXCLkvaDnvJblhpnnmoQ8YnIvPuacuuWZqOS6uueoi-W6j1wiKVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOaPkOS-m-WfuuehgOWKn-iDvXxtaXJhaWludGVyZmFjZVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOWwgeijheWfuuehgOWKn-iDvXxtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgc3ViZ3JhcGggbWlyYWljb25zb2xlIFtcIk1pcmFpIENvbnNvbGVcIl1cbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZChcIkJhY2tFbmRcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWwoXCJGcm9udEVuZDogdGVybWluYWxcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZChcIkZyb250RW5kOiBBbmRyb2lkXCIpXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLXRlcm1pbmFsXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLWFuZHJvaWRcbiAgICBlbmRcbiAgICBzdWJncmFwaCBjb25zb2xlcGx1Z2lucyBbXCJNaXJhaSBDb25zb2xlIOaPkuS7tlwiXVxuICAgICAgICB5b3VybWlyYWljb25zb2xlcGx1Z2luKFwi5L2g57yW5YaZ55qEIENvbnNvbGUg5o-S5Lu2XCIpIC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgICAgIG1pcmFpYXBpaHR0cChcIm1pcmFpLWFwaS1odHRwIOaPkuS7tlwiKSAgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgb3RoZXJzbWlyYWlwbHVnaW4oXCLlhbbku5YgQ29uc29sZSDmj5Lku7ZcIikgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgbWlyYWluYXRpdmUoXCJtaXJhaS1uYXRpdmUg5o-S5Lu2XCIpOjo6aGlnaGxpZ2h0IC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgZW5kXG4gICAgc3ViZ3JhcGggY29tbXVuaXR5c2RrIFtcIuekvuWMuiBTREtcIl1cbiAgICAgICAgY29tbXVuaXR5c2RrYmFzZWVkb25taXJhaWFwaWh0dHAoXCLln7rkuo4gbWlyYWktYXBpLWh0dHAg55qE56S-5Yy6IFNES1wiKSAtLT4gbWlyYWlhcGlodHRwXG4gICAgZW5kXG4gICAgY29vbHFwbHVnaW5zKFwi6YW3UeaPkuS7tlwiKTo6OmhpZ2hsaWdodCAtLT4gbWlyYWluYXRpdmUiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKFwibWlyYWktY29yZS1hcGlcIik6Ojpjb3JlaGlnaGxpZ2h0XG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZChcIm1pcmFpLWNvcmU8YnIvPihRUUFuZHJvaWQg5Y2P6K6uKVwiKVxuICAgICAgICBtaXJhaWNvcmVxcWFuZHJvaWQgLS0-IHzmj5DkvpvljY_orq58bWlyYWljb3JlYXBpXG4gICAgZW5kXG4gICAgbWlyYWlpbnRlcmZhY2UoXCLkvaDnvJblhpnnmoQ8YnIvPuacuuWZqOS6uueoi-W6j1wiKVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOaPkOS-m-WfuuehgOWKn-iDvXxtaXJhaWludGVyZmFjZVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOWwgeijheWfuuehgOWKn-iDvXxtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgc3ViZ3JhcGggbWlyYWljb25zb2xlIFtcIk1pcmFpIENvbnNvbGVcIl1cbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZChcIkJhY2tFbmRcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWwoXCJGcm9udEVuZDogdGVybWluYWxcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZChcIkZyb250RW5kOiBBbmRyb2lkXCIpXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLXRlcm1pbmFsXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLWFuZHJvaWRcbiAgICBlbmRcbiAgICBzdWJncmFwaCBjb25zb2xlcGx1Z2lucyBbXCJNaXJhaSBDb25zb2xlIOaPkuS7tlwiXVxuICAgICAgICB5b3VybWlyYWljb25zb2xlcGx1Z2luKFwi5L2g57yW5YaZ55qEIENvbnNvbGUg5o-S5Lu2XCIpIC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgICAgIG1pcmFpYXBpaHR0cChcIm1pcmFpLWFwaS1odHRwIOaPkuS7tlwiKSAgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgb3RoZXJzbWlyYWlwbHVnaW4oXCLlhbbku5YgQ29uc29sZSDmj5Lku7ZcIikgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgbWlyYWluYXRpdmUoXCJtaXJhaS1uYXRpdmUg5o-S5Lu2XCIpOjo6aGlnaGxpZ2h0IC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgZW5kXG4gICAgc3ViZ3JhcGggY29tbXVuaXR5c2RrIFtcIuekvuWMuiBTREtcIl1cbiAgICAgICAgY29tbXVuaXR5c2RrYmFzZWVkb25taXJhaWFwaWh0dHAoXCLln7rkuo4gbWlyYWktYXBpLWh0dHAg55qE56S-5Yy6IFNES1wiKSAtLT4gbWlyYWlhcGlodHRwXG4gICAgZW5kXG4gICAgY29vbHFwbHVnaW5zKFwi6YW3UeaPkuS7tlwiKTo6OmhpZ2hsaWdodCAtLT4gbWlyYWluYXRpdmUiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGVmYXVsdCJ9LCJ1cGRhdGVFZGl0b3IiOmZhbHNlfQ) ## [**mirai-console-loader**](https://github.com/iTXTech/mirai-console-loader): Mirai Console 的官方一键启动器 前面说道我们可以直接在终端运行 mirai-console 的前端 mirai-console-terminal,然而想运行起来 mirai-console-terminal 却不是很容易的事情:你需要下载好 Mirai 官方发布的 mirai-core,mirai-console 和 mirai-console-terminal 的 jar 文件,你还需要知道 terminal 前端的入口是 **net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader**,然后你还需要通过一大串指令启动它。 ```bash java -cp "上述jar文件路径" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader ``` 这种复杂的启动方式劝退了许多新手,尤其是未曾听说过 JVM 的新手。 mirai-console-loader 应运而生,它的工作就是简化 console 启动流程,一键帮你下载 jar 文件,自动更新,文件损坏检查...... 你能在手动启动时担心的问题 mirai-console-loader 都帮你想到了!你只需要: ```bash ./mcl ``` 即可快速启动 mirai-console 的 terminal 前端。同时 mirai-console-loader 还有一些拓展功能,可以自定义你的启动流程。 将 mirai-console-loader 放入上述关系图: [![](https://mermaid.ink/img/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKFwibWlyYWktY29yZS1hcGlcIik6Ojpjb3JlaGlnaGxpZ2h0XG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZChcIm1pcmFpLWNvcmU8YnIvPihRUUFuZHJvaWQg5Y2P6K6uKVwiKVxuICAgICAgICBtaXJhaWNvcmVxcWFuZHJvaWQgLS0-IHzmj5DkvpvljY_orq58bWlyYWljb3JlYXBpXG4gICAgZW5kXG4gICAgbWlyYWlpbnRlcmZhY2UoXCLkvaDnvJblhpnnmoQ8YnIvPuacuuWZqOS6uueoi-W6j1wiKVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOaPkOS-m-WfuuehgOWKn-iDvXxtaXJhaWludGVyZmFjZVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOWwgeijheWfuuehgOWKn-iDvXxtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgc3ViZ3JhcGggbWlyYWljb25zb2xlIFtcIk1pcmFpIENvbnNvbGVcIl1cbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZChcIkJhY2tFbmRcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWwoXCJGcm9udEVuZDogdGVybWluYWxcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZChcIkZyb250RW5kOiBBbmRyb2lkXCIpXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLXRlcm1pbmFsXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLWFuZHJvaWRcbiAgICBlbmRcbiAgICBzdWJncmFwaCBjb25zb2xlcGx1Z2lucyBbXCJNaXJhaSBDb25zb2xlIOaPkuS7tlwiXVxuICAgICAgICB5b3VybWlyYWljb25zb2xlcGx1Z2luKFwi5L2g57yW5YaZ55qEIENvbnNvbGUg5o-S5Lu2XCIpIC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgICAgIG1pcmFpYXBpaHR0cChcIm1pcmFpLWFwaS1odHRwIOaPkuS7tlwiKSAgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgb3RoZXJzbWlyYWlwbHVnaW4oXCLlhbbku5YgQ29uc29sZSDmj5Lku7ZcIikgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgbWlyYWluYXRpdmUoXCJtaXJhaS1uYXRpdmUg5o-S5Lu2XCIpIC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgZW5kXG4gICAgc3ViZ3JhcGggY29tbXVuaXR5c2RrIFtcIuekvuWMuiBTREtcIl1cbiAgICAgICAgY29tbXVuaXR5c2RrYmFzZWVkb25taXJhaWFwaWh0dHAoXCLln7rkuo4gbWlyYWktYXBpLWh0dHAg55qE56S-5Yy6IFNES1wiKSAtLT4gbWlyYWlhcGlodHRwXG4gICAgZW5kXG4gICAgY29vbHFwbHVnaW5zKFwi6YW3UeaPkuS7tlwiKSAtLT4gbWlyYWluYXRpdmVcbiAgICBtaXJhaWNvbnNvbGVsb2FkZXIoXCJtaXJhaS1jb25zb2xlLWxvYWRlclwiKTo6OmhpZ2hsaWdodCAtLS0-IHzlkK_liqh8bWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9)](https://mermaid-js.github.io/mermaid-live-editor/#/edit/eyJjb2RlIjoiZmxvd2NoYXJ0IExSXG4gICAgY2xhc3NEZWYgY29yZWhpZ2hsaWdodCBmaWxsOiNmOTYsc3Ryb2tlOiMzMzMsc3Ryb2tlLXdpZHRoOjNweDtcbiAgICBjbGFzc0RlZiBoaWdobGlnaHQgZmlsbDojZjg4LHN0cm9rZTojMzMzLHN0cm9rZS13aWR0aDozcHhcbiAgICBzdWJncmFwaCBtaXJhaSBbXCJNaXJhaSDmoYbmnrZcIl1cbiAgICAgICAgbWlyYWljb3JlYXBpKFwibWlyYWktY29yZS1hcGlcIik6Ojpjb3JlaGlnaGxpZ2h0XG4gICAgICAgIG1pcmFpY29yZXFxYW5kcm9pZChcIm1pcmFpLWNvcmU8YnIvPihRUUFuZHJvaWQg5Y2P6K6uKVwiKVxuICAgICAgICBtaXJhaWNvcmVxcWFuZHJvaWQgLS0-IHzmj5DkvpvljY_orq58bWlyYWljb3JlYXBpXG4gICAgZW5kXG4gICAgbWlyYWlpbnRlcmZhY2UoXCLkvaDnvJblhpnnmoQ8YnIvPuacuuWZqOS6uueoi-W6j1wiKVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOaPkOS-m-WfuuehgOWKn-iDvXxtaXJhaWludGVyZmFjZVxuICAgIG1pcmFpY29yZWFwaSAtLT4gfOWwgeijheWfuuehgOWKn-iDvXxtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgc3ViZ3JhcGggbWlyYWljb25zb2xlIFtcIk1pcmFpIENvbnNvbGVcIl1cbiAgICAgICAgbWlyYWljb25zb2xlYmFja2VuZChcIkJhY2tFbmRcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWwoXCJGcm9udEVuZDogdGVybWluYWxcIilcbiAgICAgICAgbWlyYWljb25zb2xlZnJvbnRlbmQtYW5kcm9pZChcIkZyb250RW5kOiBBbmRyb2lkXCIpXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLXRlcm1pbmFsXG4gICAgICAgIG1pcmFpY29uc29sZWJhY2tlbmQgLS0-IG1pcmFpY29uc29sZWZyb250ZW5kLWFuZHJvaWRcbiAgICBlbmRcbiAgICBzdWJncmFwaCBjb25zb2xlcGx1Z2lucyBbXCJNaXJhaSBDb25zb2xlIOaPkuS7tlwiXVxuICAgICAgICB5b3VybWlyYWljb25zb2xlcGx1Z2luKFwi5L2g57yW5YaZ55qEIENvbnNvbGUg5o-S5Lu2XCIpIC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgICAgIG1pcmFpYXBpaHR0cChcIm1pcmFpLWFwaS1odHRwIOaPkuS7tlwiKSAgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgb3RoZXJzbWlyYWlwbHVnaW4oXCLlhbbku5YgQ29uc29sZSDmj5Lku7ZcIikgLS0-IG1pcmFpY29uc29sZWJhY2tlbmRcbiAgICAgICAgbWlyYWluYXRpdmUoXCJtaXJhaS1uYXRpdmUg5o-S5Lu2XCIpIC0tPiBtaXJhaWNvbnNvbGViYWNrZW5kXG4gICAgZW5kXG4gICAgc3ViZ3JhcGggY29tbXVuaXR5c2RrIFtcIuekvuWMuiBTREtcIl1cbiAgICAgICAgY29tbXVuaXR5c2RrYmFzZWVkb25taXJhaWFwaWh0dHAoXCLln7rkuo4gbWlyYWktYXBpLWh0dHAg55qE56S-5Yy6IFNES1wiKSAtLT4gbWlyYWlhcGlodHRwXG4gICAgZW5kXG4gICAgY29vbHFwbHVnaW5zKFwi6YW3UeaPkuS7tlwiKSAtLT4gbWlyYWluYXRpdmVcbiAgICBtaXJhaWNvbnNvbGVsb2FkZXIoXCJtaXJhaS1jb25zb2xlLWxvYWRlclwiKTo6OmhpZ2hsaWdodCAtLS0-IHzlkK_liqh8bWlyYWljb25zb2xlZnJvbnRlbmQtdGVybWluYWxcbiIsIm1lcm1haWQiOnsidGhlbWUiOiJkZWZhdWx0In0sInVwZGF0ZUVkaXRvciI6ZmFsc2V9) ------ 本文作者 [@StageGuard](https://github.com/StageGuard)。如有疏漏或错误,欢迎[提出 Issue](https://github.com/mamoe/mirai/issues/new) 修正。 实体关系图采用 [Mermaid](https://github.com/mermaid-js/mermaid) 绘制。 > [回到 Mirai 文档索引](README.md) ### 变更记录 当前版本:`1.2.0` (2021/1/24) - `1.0.0`: 2020/12/13 - [PR 初始版本](https://github.com/mamoe/mirai/pull/710) - `1.1.0`: 2021/1/13 - 增加了作者信息 - `1.2.0`: 2021/1/24 - 进行了语言精简, 简化关系图 ================================================ FILE: docs/mocking/Mocking.md ================================================ # Mirai - Mocking 本章节介绍 mirai 模拟环境 > mirai 模拟环境从 `2.13` 开始支持 > > 注: > - **不支持**同时运行模拟环境和真实环境 > - **不支持**从模拟环境切换回真实环境 ----------------------------------- # 在非 console 中进行模拟 ## 环境准备 要使用 mirai 模拟环境测试框架, 首先需要额外添加一项依赖 ```kotlin dependencies { testImplementation("net.mamoe:mirai-core-mock:$VERSION") } ``` 并在本地的测试入口添加以下的代码 ```kotlin internal fun main() { MockBotFactory.initialize() // ..... } ``` ## 创建 Bot 对于创建 `MockBot`, 更好的方法是使用 `MockBotFactory.newMockBotBuilder()` 也可以使用原始的 `BotFactory` 来创建一个新的 `MockBot`, 系统会使用默认值填充相关的信息 ## 使用 关于 `MockBot` 可以在 [这里](https://github.com/mamoe/mirai/tree/dev/mirai-core-mock/test/mock) 找到 mirai-core-mock 的相关用法 可以在 [DslTest.kt](https://github.com/mamoe/mirai/blob/dev/mirai-core-mock/test/DslTest.kt) 中找到关于 mirai-core-mock DSL 的用法 ---------------- # 在 console 中进行模拟 Work In Progress... ================================================ FILE: docs/src/Contacts.mermaid.md ================================================ ```mermaid classDiagram class Bot { +friends: ContactList +groups: ContactList +getFriend(Long) Friend? +getFriendOrNull(Long) Friend +getGroup(Long) Group? +getGroupOrFail(Long) Group +login() +close() } class ContactOrBot { +id: Int +avatarUrl: String } class UserOrBot { +nudge() Nudge } class Contact { +bot: Bot +sendMessage(Message) MessageReceipt +sendMessage(String) MessageReceipt +uploadImage(ExternalImage) Image } class User { +nick: String +remark: String +queryProfile() UserProfile } class Group { +members: ContactList +name: String +settings: GroupSettings +owner: NormalMember +botMuteRemaining: Long +botPermission: MemberPermission +quit() Boolean +uploadVoice() Voice } class NormalMember { +mute() +kick() } class AnonymousMember { +anonymousId: String } class Member { +group: Group } class OtherClient { +info } ContactOrBot<|--Contact ContactOrBot<|--UserOrBot UserOrBot<|--Bot UserOrBot<|--User Contact<|--User Contact<|--Group Contact<|--OtherClient User<|--Member User<|--Friend Member<|--NormalMember Member<|--AnonymousMember ``` ================================================ FILE: docs/src/Messages.mermaid.md ================================================ ```mermaid classDiagram class MessageChain MessageChain : List~SingleMessage~ Message<|--MessageChain Message<|--SingleMessage MessageChain o-- SingleMessage SingleMessage<|--MessageContent SingleMessage<|--MessageMetadata %%% MessageMetadata<|--QuoteReply MessageMetadata<|--MessageSource %% MessageSource<|--OnlineMessageSource MessageSource<|--OfflineMessageSource MessageContent<|--PlainText MessageContent<|--Image MessageContent<|--At MessageContent<|--AtAll MessageContent<|--Face MessageContent<|--ForwardMessage MessageContent<|--HummerMessage HummerMessage<|--PokeMessage HummerMessage<|--VipFace HummerMessage<|--FlashImage MessageContent<|--RichMessage RichMessage<|--ServiceMessage RichMessage<|--LightApp MessageContent<|--PttMessage PttMessage<|--Voice ``` ================================================ FILE: gradle/wrapper/gradle-wrapper.properties ================================================ # # Copyright 2019-2023 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/dev/LICENSE # distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: gradle.properties ================================================ # # Copyright 2019-2023 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/dev/LICENSE # kotlin.code.style=official # config kotlin.incremental.multiplatform=true org.gradle.jvmargs=-Xmx7000m -Dfile.encoding=UTF-8 --illegal-access=permit -Dkotlin.daemon.jvm.options=--illegal-access=permit --add-opens java.base/java.util=ALL-UNNAMED org.gradle.parallel=true org.gradle.vfs.watch=true #kotlin.mpp.enableCompatibilityMetadataVariant=true systemProp.org.gradle.internal.publish.checksums.insecure=true gnsp.disableApplyOnlyOnRootProjectEnforcement=true mirai.android.target.api.level=21 # Enable if you want to use mavenLocal for both Gradle plugin and project dependencies resolutions. systemProp.use.maven.local=false org.gradle.caching=true kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.stability.nowarn=true kotlin.mpp.androidSourceSetLayoutVersion=2 android.disableAutomaticComponentCreation=true android.useAndroidX=true ================================================ FILE: gradlew ================================================ #!/bin/sh # # Copyright 2019-2023 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/dev/LICENSE # ############################################################################## # # Gradle start up script for POSIX generated by Gradle. # # Important for running: # # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is # noncompliant, but you have some other compliant shell such as ksh or # bash, then to run this script, type that shell name before the whole # command line, like: # # ksh Gradle # # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», # «${var#prefix}», «${var%suffix}», and «$( cmd )»; # * compound commands having a testable exit status, especially «case»; # * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # # (2) This script targets any POSIX shell, so it avoids extensions provided # by Bash, Ksh, etc; in particular arrays are avoided. # # The "traditional" practice of packing multiple parameters into a # space-separated string is a well documented source of bugs and security # problems, so this is (mostly) avoided, by progressively accumulating # options in "$@", and eventually passing that to Java. # # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; # see the in-line comments for details. # # There are tweaks for specific operating systems such as AIX, CygWin, # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. # ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link app_path=$0 # Need this for daisy-chained symlinks. while APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path [ -h "$app_path" ] do ls=$( ls -ld "$app_path" ) link=${ls#*' -> '} case $link in #( /*) app_path=$link ;; #( *) app_path=$APP_HOME$link ;; esac done APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum warn () { echo "$*" } >&2 die () { echo echo "$*" echo exit 1 } >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "$( uname )" in #( CYGWIN* ) cygwin=true ;; #( Darwin* ) darwin=true ;; #( MSYS* | MINGW* ) msys=true ;; #( NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD=$JAVA_HOME/jre/sh/java else JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac case $MAX_FD in #( '' | soft) :;; #( *) ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac fi # Collect all arguments for the java command, stacking in reverse order: # * args from the command line # * the main class name # * -classpath # * -D...appname settings # * --module-path (only if needed) # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) # Now convert the arguments - kludge to limit ourselves to /bin/sh for arg do if case $arg in #( -*) false ;; # don't mess with options #( /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath [ -e "$t" ] ;; #( *) false ;; esac then arg=$( cygpath --path --ignore --mixed "$arg" ) fi # Roll the args list around exactly as many times as the number of # args, so each arg winds up back in the position where it started, but # possibly modified. # # NB: a `for` loop captures its iteration list before it begins, so # changing the positional parameters here affects neither the number of # iterations, nor the values presented in `arg`. shift # remove old arg set -- "$@" "$arg" # push replacement arg done fi # Collect all arguments for the java command; # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # shell script including quotes and variable substitutions, so put them in # double quotes to make sure that they get re-expanded; and # * put everything else in single quotes, so that it's not re-expanded. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ org.gradle.wrapper.GradleWrapperMain \ "$@" # Use "xargs" to parse quoted args. # # With -n1 it outputs one arg per line, with the quotes and backslashes removed. # # In Bash we could simply go: # # readarray ARGS < <( xargs -n1 <<<"$var" ) && # set -- "${ARGS[@]}" "$@" # # but POSIX shell has neither arrays nor command substitution, so instead we # post-process each arg (as a line of input to sed) to backslash-escape any # character that might be a shell metacharacter, then use eval to reverse # that process (while maintaining the separation between arguments), and wrap # the whole thing up as a single "set" statement. # # This will of course break if any of these variables contains a newline or # an unmatched quote. # eval "set -- $( printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | xargs -n1 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | tr '\n' ' ' )" '"$@"' exec "$JAVACMD" "$@" ================================================ FILE: gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: install.sh ================================================ # # Copyright 2019-2022 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/dev/LICENSE # cargo install --force cbindgen cargo install bindgen ================================================ FILE: logging/README.md ================================================ # mirai-logging Mirai 日志转接模块,用于使用各大主流日志库接管 Mirai 日志系统以实现统一管理。 请注意,该接管不会影响通过 `redirectBotLogToFile()` 将 Bot 日志重定向到文件以及其他依赖于 mirai 内建日志系统的功能。在接管日志后,需要在目标日志库以配置等方式实现重定向等功能。 仅在 mirai 2.7 起提供。 ## 模块列表 [Log4j2]: https://logging.apache.org/log4j/ [SLF4J]: http://www.slf4j.org/ [Logback]: http://logback.qos.ch/ 若使用 SLF4J 的转接器,则会同时将对 Log4J2 的调用转向 SLF4J。同样地,若使用 Log4J2 的转接器,同时将对 SLF4J 的调用转向 Log4J2。因此可使用单一日志库。 | groupId | artifactId | 对接日志库 | 备注 | 最低 mirai 版本 | |:-----------:|:-----------------------------:|:---------:|:-------------------------------|:--------------:| | `net.mamoe` | `mirai-logging-log4j2` | [Log4J2] | 使用 log4j2-core. | 2.7.0 | | `net.mamoe` | `mirai-logging-slf4j-logback` | [Logback] | 使用 logback-classic. | 2.7.0 | | `net.mamoe` | `mirai-logging-slf4j-simple` | [SLF4J] | 使用 slf4j-simple. | 2.7.0 | | `net.mamoe` | `mirai-logging-slf4j` | [SLF4J] | 需要自定义添加 SLF4J 的任意实现模块. | 2.7.0 | ## 使用方法 选择上述模块中的其中一个,在运行时 classpath 包含即可。 若使用构建工具,可将其作为类似 mirai-core 的依赖添加,版本号与 mirai-core 相同。 ### Gradle Kotlin DSL 在 `build.gradle.kts` 添加: ```kotlin dependencies { api("net.mamoe", "mirai-core", "2.7.0") api("net.mamoe", "mirai-logging-log4j2", "2.7.0") // 在依赖 mirai-core 或 mirai-core-api 的前提下额外添加日志转接模块. 版本号相同 } ``` ### Gradle Groovy DSL 在 `build.gradle.kts` 添加: ```groovy dependencies { api 'net.mamoe:mirai-core:2.7.0' api 'net.mamoe:mirai-logging-log4j2:2.7.0' // 在依赖 mirai-core 或 mirai-core-api 的前提下额外添加日志转接模块. 版本号相同 } ``` ### Maven 在 `pom.xml` 添加: ```xml <dependencies> <dependency> <groupId>net.mamoe</groupId> <artifactId>mirai-core-jvm</artifactId> <version>2.7.0</version> </dependency> <!--在依赖 mirai-core 或 mirai-core-api 的前提下额外添加日志转接模块. 版本号相同--> <dependency> <groupId>net.mamoe</groupId> <artifactId>mirai-logging-log4j2</artifactId> <version>2.7.0</version> </dependency> </dependencies> ``` ## 自行实现日志转接 Mirai 通过 Java `ServiceLoader` 加载 `MiraiLogger.Factory`。只需要实现该类型并以标准 service 方式提供即可(如 `resources` 中 `META-INF/services`)。 但**更推荐**使用上述转接模块先转接到 Log4J2 或 SLF4J,然后再基于 log4j-api 或 slf4j-api 转接。这样更稳定,也可以获取到 `Marker` 的支持。 ================================================ FILE: logging/mirai-logging-log4j2/build.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java") `maven-publish` } version = Versions.core description = "Mirai Log4J Adapter" kotlin { explicitApi() } dependencies { api(project(":mirai-core-api")) api(`log4j-api`) api(`log4j-core`) api(`log4j-slf4j2-impl`) testImplementation(`slf4j-api`) testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-log4j2") ================================================ FILE: logging/mirai-logging-log4j2/resources/META-INF/services/net.mamoe.mirai.utils.MiraiLogger$Factory ================================================ # # 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/dev/LICENSE # net.mamoe.mirai.utils.logging.MiraiLog4JFactory ================================================ FILE: logging/mirai-logging-log4j2/src/MiraiLog4JFactory.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils.logging import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.MiraiLogger import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.MarkerManager /** * 使用 Log4J 接管 mirai 日志系统. */ @MiraiInternalApi public class MiraiLog4JFactory : MiraiLogger.Factory { override fun create(requester: Class<*>, identity: String?): MiraiLogger { val logger = LogManager.getLogger(requester) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return net.mamoe.mirai.internal.utils.Log4jLoggerAdapter( logger, MarkerManager.getMarker(identity ?: logger.name).addParents(net.mamoe.mirai.internal.utils.MARKER_MIRAI) ) } } ================================================ FILE: logging/mirai-logging-log4j2/test/MiraiLog4JAdapterTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.utils.logging import io.ktor.client.* import io.ktor.client.engine.okhttp.* import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.loadService import java.io.ByteArrayOutputStream import java.io.PrintStream import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertIs internal class MiraiLog4JAdapterTest { @Test fun `using log4j`() { assertIs<MiraiLog4JFactory>(loadService(MiraiLogger.Factory::class)) val logger = MiraiLogger.Factory.create(this::class) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") assertIs<net.mamoe.mirai.internal.utils.Log4jLoggerAdapter>(logger) } @Test fun `print test`() { val out = ByteArrayOutputStream() System.setOut(PrintStream(out, true)) System.setErr(PrintStream(out, true)) HttpClient(OkHttp) val logger = MiraiLogger.Factory.create(this::class) logger.error("Hi") /* SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console... */ out.flush() println(out.toString()) assertFalse { out.toString().contains("slf4j", ignoreCase = true) } assertFalse { out.toString().contains("Log4j2 could not find a logging implementation", ignoreCase = true) } } } ================================================ FILE: logging/mirai-logging-slf4j/build.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java") `maven-publish` } version = Versions.core description = "Mirai SLF4J Adapter" kotlin { explicitApi() } dependencies { api(project(":mirai-core-api")) implementation(project(":mirai-logging-log4j2")) { exclude("org.apache.logging.log4j", "log4j-slf4j2-impl") } // mirai -> log4j2 implementation(`log4j-to-slf4j`) // log4j2 -> slf4j api(`slf4j-api`) testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-slf4j") ================================================ FILE: logging/mirai-logging-slf4j/test/MiraiSlf4JAdapterTest.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils.logging import io.ktor.client.* import io.ktor.client.engine.okhttp.* import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.loadService import org.junit.jupiter.api.Order import org.slf4j.LoggerFactory import org.slf4j.helpers.NOPLogger import java.io.ByteArrayOutputStream import java.io.PrintStream import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertIs internal class MiraiSlf4JAdapterTest { @Order(1) @Test fun `using log4j`() { assertIs<MiraiLog4JFactory>(loadService(MiraiLogger.Factory::class)) val logger = MiraiLogger.Factory.create(this::class) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") assertIs<net.mamoe.mirai.internal.utils.Log4jLoggerAdapter>(logger) } @Order(0) @Test fun `print test`() { val out = ByteArrayOutputStream() System.setOut(PrintStream(out, true)) System.setErr(PrintStream(out, true)) assertIs<NOPLogger>(LoggerFactory.getLogger("s")) HttpClient(OkHttp) val logger = MiraiLogger.Factory.create(this::class) logger.error("Hi") /* SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console... */ out.flush() println(out.toString()) assertFalse { out.toString().contains("Log4j2 could not find a logging implementation", ignoreCase = true) } // assertTrue { // out.toString().contains("SLF4J: Defaulting to no-operation (NOP) logger implementation", ignoreCase = true) // } } } ================================================ FILE: logging/mirai-logging-slf4j-logback/build.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java") `maven-publish` } version = Versions.core description = "Mirai SLF4J Adapter with logback-classic" kotlin { explicitApi() } dependencies { api(project(":mirai-core-api")) api(project(":mirai-logging-slf4j")) // mirai -> slf4j implementation(project(":mirai-logging-log4j2")) { exclude("org.apache.logging.log4j", "log4j-slf4j2-impl") } // mirai -> log4j2 api(`slf4j-api`) api(`logback-classic`) testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-slf4j-logback") ================================================ FILE: logging/mirai-logging-slf4j-logback/test/MiraiSlf4JLogbackAdapterTest.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils.logging import io.ktor.client.* import io.ktor.client.engine.okhttp.* import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.loadService import org.junit.jupiter.api.Order import org.slf4j.LoggerFactory import org.slf4j.helpers.NOPLogger import java.io.ByteArrayOutputStream import java.io.PrintStream import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertIs import kotlin.test.assertIsNot internal class MiraiSlf4JLogbackAdapterTest { @Order(1) @Test fun `using log4j`() { assertIs<MiraiLog4JFactory>(loadService(MiraiLogger.Factory::class)) val logger = MiraiLogger.Factory.create(this::class) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") assertIs<net.mamoe.mirai.internal.utils.Log4jLoggerAdapter>(logger) } @Order(0) @Test fun `print test`() { val out = ByteArrayOutputStream() System.setOut(PrintStream(out, true)) System.setErr(PrintStream(out, true)) assertIsNot<NOPLogger>(LoggerFactory.getLogger("s")) HttpClient(OkHttp) val logger = MiraiLogger.Factory.create(this::class) logger.error("Hi") /* SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console... */ out.flush() println(out.toString()) assertFalse { out.toString().contains("Log4j2 could not find a logging implementation", ignoreCase = true) } assertFalse { out.toString().contains("SLF4J: Defaulting to no-operation (NOP) logger implementation", ignoreCase = true) } } } ================================================ FILE: logging/mirai-logging-slf4j-simple/build.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java") `maven-publish` } version = Versions.core description = "Mirai SLF4J Adapter with slf4j-simple" kotlin { explicitApi() } dependencies { api(project(":mirai-core-api")) api(project(":mirai-logging-slf4j")) // mirai -> slf4j implementation(project(":mirai-logging-log4j2")) { exclude("org.apache.logging.log4j", "log4j-slf4j-impl") } // mirai -> log4j2 api(`slf4j-api`) api(`slf4j-simple`) testImplementation(project(":mirai-core")) testImplementation(project(":mirai-core-utils")) testImplementation(`ktor-client-okhttp`) } configurePublishing("mirai-logging-slf4j-simple") ================================================ FILE: logging/mirai-logging-slf4j-simple/test/MiraiSlf4JSimpleAdapterTest.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils.logging import io.ktor.client.* import io.ktor.client.engine.okhttp.* import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.loadService import org.junit.jupiter.api.Order import org.slf4j.LoggerFactory import org.slf4j.helpers.NOPLogger import java.io.ByteArrayOutputStream import java.io.PrintStream import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertIs import kotlin.test.assertIsNot internal class MiraiSlf4JSimpleAdapterTest { @Order(1) @Test fun `using log4j`() { assertIs<MiraiLog4JFactory>(loadService(MiraiLogger.Factory::class)) val logger = MiraiLogger.Factory.create(this::class) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") assertIs<net.mamoe.mirai.internal.utils.Log4jLoggerAdapter>(logger) } @Order(0) @Test fun `print test`() { val out = ByteArrayOutputStream() System.setOut(PrintStream(out, true)) System.setErr(PrintStream(out, true)) assertIsNot<NOPLogger>(LoggerFactory.getLogger("s")) HttpClient(OkHttp) val logger = MiraiLogger.Factory.create(this::class) logger.error("Hi") /* SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. ERROR StatusLogger Log4j2 could not find a logging implementation. Please add log4j-core to the classpath. Using SimpleLogger to log to the console... */ out.flush() println(out.toString()) assertFalse { out.toString().contains("Log4j2 could not find a logging implementation", ignoreCase = true) } assertFalse { out.toString().contains("SLF4J: Defaulting to no-operation (NOP) logger implementation", ignoreCase = true) } } } ================================================ FILE: mirai-bom/build.gradle.kts ================================================ /* * 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/dev/LICENSE */ plugins { `java-platform` `maven-publish` } description = "Mirai BOM" rootProject.subprojects .filter { it.path != project.path } .forEach { project.evaluationDependsOn(it.path) } dependencies { constraints { rootProject.subprojects .filter { it.path != project.path } .filter { it.extensions.findByName("publishing") != null } .forEach { subProject -> subProject.publishing.publications .withType<MavenPublication>() .forEach { this@constraints.api("${it.groupId}:${it.artifactId}:${it.version}") } } } } configurePublishing( "mirai-bom", addProjectComponents = false, ) publishing.publications.getByName<MavenPublication>("mavenJava") { from(components["javaPlatform"]) } ================================================ FILE: mirai-console/.gitignore ================================================ # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* target/ build/ .idea/ *.iml /.idea/ .idea/* /.idea/* /test .gradle/ local.properties # Maven publishing credits keys.properties /plugins/ bintray.user.txt bintray.key.txt token.txt /*/token.txt ================================================ FILE: mirai-console/.gitmodules ================================================ [submodule "frontend/mirai-android"] path = frontend/mirai-android url = https://github.com/mzdluo123/MiraiAndroid ================================================ FILE: mirai-console/README.md ================================================ # mirai-console 高效率 QQ 机器人框架 ## 使用 - **[启动 Console](docs/Run.md)** ### 安装 JAR 插件 将 `jar` 文件放入 `plugins` 并重启 Mirai Console。 ### 执行指令 在控制台输入 `?` 查看可用指令列表。** 注意,请先为用户授予执行某些指令的权限,详见 [示例](docs/BuiltInCommands.md#授予一个用户执行所有指令的权限)**。 ### 内置指令 [BuiltInCommands](docs/BuiltInCommands.md#mirai-console---builtin-commands) ## 开发 [开发文档](docs/README.md#mirai-console) ## 实用链接 - [社区 SDK](https://github.com/mamoe/mirai#%E4%BD%BF%E7%94%A8-mirai-console-%E6%9C%8D%E5%8A%A1%E7%AB%AF%E4%B8%BA-mirai-console-%E5%BC%80%E5%8F%91%E6%8F%92%E4%BB%B6) - [论坛](https://mirai.mamoe.net) - [Mirai 项目组](https://github.com/project-mirai) - [在 Android 平台使用](https://github.com/mzdluo123/MiraiAndroid) - Mirai 官方维护的插件: - [chat-command](https://github.com/project-mirai/chat-command) - [mirai-api-http](https://github.com/project-mirai/mirai-api-http) ================================================ FILE: mirai-console/backend/codegen/README.md ================================================ # Mirai Console - Backend.codegen 后端代码生成模块,用于最小化重复代码的人工成本。 - `MessageScope` 代码生成: [MessageScopeCodegen.kt: Line 33](src/MessageScopeCodegen.kt#L33) - `Value` 和 `PluginData` 相关代码生成: [ValueSettingCodegen.kt: Line 18](src/ValuePluginDataCodegen.kt#L18) ================================================ FILE: mirai-console/backend/codegen/build.gradle.kts ================================================ /* * 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 */ plugins { kotlin("jvm") kotlin("plugin.serialization") id("java") } kotlin { sourceSets { all { languageSettings.optIn("kotlin.Experimental") languageSettings.optIn("kotlin.RequiresOptIn") languageSettings.progressiveMode = true languageSettings.optIn("net.mamoe.mirai.utils.MiraiInternalAPI") languageSettings.optIn("kotlin.ExperimentalUnsignedTypes") languageSettings.optIn("kotlin.experimental.ExperimentalTypeInference") languageSettings.optIn("kotlin.contracts.ExperimentalContracts") } } } dependencies { api(kotlin("stdlib-jdk8")) implementation(kotlin("reflect")) api(project(":mirai-core-utils")) } ================================================ FILE: mirai-console/backend/codegen/src/Codegen.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("FunctionName", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "PRE_RELEASE_CLASS", "unused") package net.mamoe.mirai.console.codegen import org.intellij.lang.annotations.Language import kotlin.reflect.KClass import kotlin.reflect.full.functions import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.isSubclassOf inline fun <reified T> runCodegenInObject() = runCodegenInObject(T::class) fun runCodegenInObject(clazz: KClass<*>) { clazz.nestedClasses .filter { it.isSubclassOf(RegionCodegen::class) } .associateWith { kClass -> kClass.functions.find { it.name == "main" && it.hasAnnotation<JvmStatic>() } } .filter { it.value != null } .forEach { (kClass, entryPoint) -> println("---------------------------------------------") println("Running Codegen: ${kClass.simpleName}") entryPoint!!.call(kClass.objectInstance, arrayOf<String>()) println("---------------------------------------------") } } abstract class Replacer(private val name: String) : (String) -> String { override fun toString(): String { return name } } fun Codegen.Replacer(block: (String) -> String): Replacer { return object : Replacer(this@Replacer::class.simpleName ?: "<unnamed>") { override fun invoke(p1: String): String = block(p1) } } class CodegenScope : MutableList<Replacer> by mutableListOf() { fun applyTo(fileContent: String): String { return this.fold(fileContent) { acc, replacer -> replacer(acc) } } @CodegenDsl operator fun Codegen.invoke(vararg ktTypes: KtType) { if (ktTypes.isEmpty() && this is DefaultInvoke) { invoke(defaultInvokeArgs) } invoke(ktTypes.toList()) } @CodegenDsl operator fun Codegen.invoke(ktTypes: Collection<KtType>) { add(Replacer { str -> str + buildString { ktTypes.forEach { ktType -> applyTo(this, ktType) } } }) } @RegionCodegenDsl operator fun RegionCodegen.invoke(vararg ktTypes: KtType) = invoke(ktTypes.toList()) @RegionCodegenDsl operator fun RegionCodegen.invoke(ktTypes: Collection<KtType>) { add(Replacer { content -> content.replace(Regex("""//// region $regionName CODEGEN ////([\s\S]*?)( *)//// endregion $regionName CODEGEN ////""")) { result -> val indent = result.groups[2]!!.value val indentedCode = CodegenScope() .apply { (this@invoke as Codegen).invoke(*ktTypes.toTypedArray()) } // add codegen task .applyTo("") // perform codegen .lines().dropLastWhile(String::isBlank).joinToString("\n") // remove blank following lines .mapLine { "${indent}$it" } // indent """ |//// region $regionName CODEGEN //// | |${indentedCode} | |${indent}//// endregion $regionName CODEGEN //// """.trimMargin() } }) } @DslMarker annotation class CodegenDsl } internal fun String.mapLine(mapper: (String) -> CharSequence) = this.lines().joinToString("\n", transform = mapper) @DslMarker annotation class RegionCodegenDsl interface DefaultInvoke { val defaultInvokeArgs: List<KtType> } abstract class Codegen { fun applyTo(stringBuilder: StringBuilder, ktType: KtType) = this.run { stringBuilder.apply(ktType) } protected abstract fun StringBuilder.apply(ktType: KtType) } abstract class RegionCodegen(private val targetFile: String, regionName: String? = null) : Codegen() { val regionName: String by lazy { regionName ?: this::class.simpleName!!.substringBefore("Codegen") } fun startIndependently() { codegen(targetFile) { this@RegionCodegen.invoke() } } } abstract class PrimitiveCodegen : Codegen() { protected abstract fun StringBuilder.apply(ktType: KtPrimitive) fun StringBuilder.apply(ktType: List<KtPrimitive>) = ktType.forEach { apply(it) } } fun StringBuilder.appendKCode(@Language("kt") ktCode: String): StringBuilder = append(kCode(ktCode)).appendLine() ================================================ FILE: mirai-console/backend/codegen/src/MessageScopeCodegen.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.codegen import net.mamoe.mirai.utils.capitalize internal val TypeCandidatesForMessageScope = arrayOf( KtType("Contact"), KtType("CommandSender"), ) internal val KtMessageScope = KtType("MessageScope") internal fun <A> Array<A>.arrangements(): List<Pair<A, A>> { val result = mutableListOf<Pair<A, A>>() for (a in this) { for (b in this) { result.add(a to b) } } return result } internal object MessageScopeCodegen { object IterableMessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = listOf(KtString) // invoke once @Suppress( "RedundantVisibilityModifier", "ClassName", "KDocUnresolvedReference", "RedundantSuspendModifier", "SpellCheckingInspection" ) override fun StringBuilder.apply(ktType: KtType) { for (collectionName in arrayOf("Iterable", "Sequence", "Array")) { for (candidate in (TypeCandidatesForMessageScope + KtMessageScope)) { appendKCode( """ @JvmName("toMessageScope${candidate.standardName.capitalize()}${collectionName.capitalize()}") public fun $collectionName<$candidate?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope(acc, messageScope.asMessageScopeOrNoop()) } } """ ) appendLine() } } for (candidate in (TypeCandidatesForMessageScope + KtMessageScope)) { appendKCode( """ @JvmSynthetic @JvmName("toMessageScope${candidate.standardName.capitalize()}Flow") public suspend fun Flow<$candidate>.toMessageScope(): MessageScope { // Flow<Any?>.firstOrNull isn't yet supported return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope(acc, messageScope.asMessageScope()) } } """ ) appendLine() } } } object MessageScopeBuildersCodegen : RegionCodegen("MessageScope.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = listOf(KtString) // invoke once @Suppress("RedundantVisibilityModifier", "ClassName", "KDocUnresolvedReference", "unused") override fun StringBuilder.apply(ktType: KtType) { for (candidate in TypeCandidatesForMessageScope) { appendKCode( """ public fun ${candidate}.asMessageScope(): MessageScope = createScopeDelegate(this) """ ) appendLine() } // // for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) { // appendKCode( // """ // @LowPriorityInOverloadResolution // public fun ${a}.scopeWith(vararg others: ${b}): MessageScope { // return others.fold(this.asMessageScope()) { acc, other -> CombinedScope(acc, other.asMessageScope()) } // } // """ // ) // appendLine() // } for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) { appendKCode( """ @LowPriorityInOverloadResolution public fun ${a}?.scopeWith(vararg others: ${b}?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } """ ) appendLine() } // // for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) { // appendKCode( // """ // public fun ${a}.scopeWith(other: ${b}): MessageScope { // return CombinedScope(asMessageScope(), other.asMessageScope()) // } // """ // ) // appendLine() // } for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) { appendKCode( """ public fun ${a}?.scopeWith(other: ${b}?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } """ ) appendLine() } // // for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) { // appendKCode( // """ // public inline fun <R> ${a}.scopeWith(vararg others: ${b}, action: MessageScope.() -> R): R { // return scopeWith(*others).invoke(action) // } // """ // ) // appendLine() // } for ((a, b) in (TypeCandidatesForMessageScope + KtMessageScope).arrangements()) { appendKCode( """ public inline fun <R> ${a}?.scopeWith(vararg others: ${b}?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } """ ) appendLine() } for (a in (TypeCandidatesForMessageScope + KtMessageScope)) { appendKCode( """ @Deprecated( "Senseless scopeWith. Use asMessageScope.", ReplaceWith("this.asMessageScope()", "net.mamoe.mirai.console.util.asMessageScope") ) // diagnostic deprecation public inline fun ${a}.scopeWith(): MessageScope = asMessageScope() """ ) appendLine() } for (a in (TypeCandidatesForMessageScope + KtMessageScope)) { appendKCode( """ @Deprecated( "Senseless scopeWith. Use .asMessageScope().invoke.", ReplaceWith( "this.asMessageScope()(action)", "net.mamoe.mirai.console.util.asMessageScope", "net.mamoe.mirai.console.util.invoke", ) ) // diagnostic deprecation public inline fun <R> ${a}.scopeWith(action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return asMessageScope()(action) } """ ) appendLine() } } } /** * 运行本 object 中所有嵌套 object Codegen */ @OptIn(ExperimentalStdlibApi::class) @JvmStatic fun main(args: Array<String>) { runCodegenInObject(this::class) } } ================================================ FILE: mirai-console/backend/codegen/src/ValuePluginDataCodegen.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("PRE_RELEASE_CLASS", "ClassName", "RedundantVisibilityModifier", "KDocUnresolvedReference") package net.mamoe.mirai.console.codegen import kotlin.reflect.full.functions import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.isSubclassOf internal object ValuePluginDataCodegen { /** * The interface */ object PrimitiveValuesCodegen : RegionCodegen("Value.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { @Suppress("ClassName") appendKCode( """ /** * 表示一个不可空 [$ktType] [Value]. */ public interface ${ktType}Value : PrimitiveValue<$ktType> """ ) } } object BuiltInSerializerConstantsPrimitivesCodegen : RegionCodegen("_PluginData.value.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { appendLine( kCode( """ @JvmStatic internal val ${ktType.standardName}SerializerDescriptor = ${ktType.standardName}.serializer().descriptor """ ).lines().joinToString("\n") { " $it" } ) } } object PrimitiveValuesImplCodegen : RegionCodegen("_PrimitiveValueDeclarations.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { appendKCode( """ internal abstract class ${ktType.standardName}ValueImpl : ${ktType.standardName}Value, SerializerAwareValue<${ktType.standardName}>, KSerializer<Unit>, AbstractValueImpl<${ktType.standardName}> { constructor() constructor(default: ${ktType.standardName}) { _value = default } private var _value: ${ktType.standardName}? = null final override var value: ${ktType.standardName} get() = _value ?: error("${ktType.standardName}Value.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.${ktType.standardName}SerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = ${ktType.standardName}.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(${ktType.standardName}.serializer().deserialize(decoder)) override fun toString(): String = _value${if (ktType != KtString) "?.toString()" else ""} ?: "${ktType.standardName}Value.value not yet initialized." override fun equals(other: Any?): Boolean = other is ${ktType.standardName}ValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } """ ) } } object PluginData_value_PrimitivesImplCodegen : RegionCodegen("_PluginData.value.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { appendKCode( """ internal fun PluginData.valueImpl(default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> { return object : ${ktType.standardName}ValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.${ktType.lowerCaseName}ValueImpl(): SerializerAwareValue<${ktType.standardName}> { return object : ${ktType.standardName}ValueImpl() { override fun onChanged() = this@${ktType.lowerCaseName}ValueImpl.onValueChanged(this) } } """ ) } } object PluginData_valueImplPrimitiveCodegen : RegionCodegen("_PluginData.value.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { appendKCode( """ ${ktType.standardName}::class -> ${ktType.lowerCaseName}ValueImpl() """.trimIndent() ) } } object PluginData_value_primitivesCodegen : RegionCodegen("PluginData.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { @Suppress("unused") appendKCode( """ /** * 创建一个 [${ktType.standardName}] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> = valueImpl(default) """ ) appendLine() } } object JPluginData_value_primitivesCodegen : RegionCodegen("JAutoSavePluginData.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { @Suppress("unused") appendKCode( """ /** * 创建一个 [${ktType.standardName}] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> = valueImpl(default) """ ) appendLine() } } /** * @since 2.11 */ object JavaAutoSavePluginData_value_primitivesCodegen : RegionCodegen("JavaAutoSavePluginData.kt"), DefaultInvoke { @JvmStatic fun main(args: Array<String>) = super.startIndependently() override val defaultInvokeArgs: List<KtType> = KtPrimitives + KtString override fun StringBuilder.apply(ktType: KtType) { @Suppress("unused") appendKCode( """ /** * 创建一个名称为 [name], 类型为 [${ktType.standardName}] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: ${ktType.standardName}): SerializerAwareValue<${ktType.standardName}> = valueImpl(default).apply { track(this, name, emptyList()) } """ ) appendLine() } } /** * 运行本 object 中所有嵌套 object Codegen */ @OptIn(ExperimentalStdlibApi::class) @JvmStatic fun main(args: Array<String>) { ValuePluginDataCodegen::class.nestedClasses .filter { it.isSubclassOf(RegionCodegen::class) } .associateWith { kClass -> kClass.functions.find { it.name == "main" && it.hasAnnotation<JvmStatic>() } } .filter { it.value != null } .forEach { (kClass, entryPoint) -> println("---------------------------------------------") println("Running Codegen: ${kClass.simpleName}") entryPoint!!.call(kClass.objectInstance, arrayOf<String>()) println("---------------------------------------------") } } } ================================================ FILE: mirai-console/backend/codegen/src/old/JSettingCodegen.kt ================================================ /* * 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("PRE_RELEASE_CLASS") package net.mamoe.mirai.console.codegen.old /** * used to generate Java PluginData */ open class JClazz(val primitiveName: String, val packageName: String) { open val funName: String = "value" } class JListClazz(item: JClazz) : JClazz("List<${item.packageName}>", "List<${item.packageName}>") { override val funName = item.primitiveName.lowercase() + "List" } class JArrayClazz(item: JClazz) : JClazz(item.primitiveName + "[]", item.primitiveName + "[]") class JMapClazz(key: JClazz, value: JClazz) : JClazz("Map<${key.packageName},${value.packageName}>", "Map<${key.packageName},${value.packageName}>") internal val J_NUMBERS = listOf( JClazz("int", "Integer"), JClazz("short", "Short"), JClazz("byte", "Byte"), JClazz("long", "Long"), JClazz("float", "Float"), JClazz("double", "Double") ) internal val J_EXTRA = listOf( JClazz("String", "String"), JClazz("boolean", "Boolean"), JClazz("char", "Char") ) fun JClazz.getTemplate(): String = """ @NotNull default Value<${this.packageName}> $funName(${this.primitiveName} defaultValue){ return _PluginDataKt.value(this,defaultValue); } """ fun main() { println(buildString { appendLine(COPYRIGHT) appendLine() appendLine(FILE_SUPPRESS) appendLine() appendLine( "/**\n" + " * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.JPluginDataCodegen.kt\n" + " * !!! DO NOT MODIFY THIS FILE MANUALLY\n" + " */\n" + "\"\"\"" ) appendLine() appendLine() //do simplest (J_EXTRA + J_NUMBERS).forEach { appendLine(it.getTemplate()) } (J_EXTRA + J_NUMBERS).forEach { appendLine(JListClazz(it).getTemplate()) } (J_EXTRA + J_NUMBERS).forEach { appendLine(JArrayClazz(it).getTemplate()) } (J_EXTRA + J_NUMBERS).forEach { key -> (J_EXTRA + J_NUMBERS).forEach { value -> appendLine(JMapClazz(key, value).getTemplate()) } } }) } ================================================ FILE: mirai-console/backend/codegen/src/old/SettingValueUseSiteCodegen.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("PRE_RELEASE_CLASS") package net.mamoe.mirai.console.codegen.old import org.intellij.lang.annotations.Language import java.io.File fun main() { println(File("").absolutePath) // default project base dir File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/_PluginData.kt").apply { createNewFile() }.writeText(buildString { appendLine(COPYRIGHT) appendLine() appendLine(FILE_SUPPRESS) appendLine() appendLine(PACKAGE) appendLine() appendLine(IMPORTS) appendLine() appendLine() appendLine(DO_NOT_MODIFY) appendLine() appendLine() appendLine(genAllValueUseSite()) }) } private val DO_NOT_MODIFY = """ /** * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.PluginDataValueUseSiteCodegen.kt * !!! DO NOT MODIFY THIS FILE MANUALLY */ """.trimIndent() private val PACKAGE = """ package net.mamoe.mirai.console.data """.trimIndent() internal val FILE_SUPPRESS = """ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused") """.trimIndent() private val IMPORTS = """ import net.mamoe.mirai.console.data.internal.valueImpl import kotlin.internal.LowPriorityInOverloadResolution """.trimIndent() fun genAllValueUseSite(): String = buildString { @Suppress("SpellCheckingInspection") fun appendln(@Language("kt") code: String) { this.appendLine(code.trimIndent()) } // PRIMITIVE for (number in NUMBERS + OTHER_PRIMITIVES) { appendln(genValueUseSite(number, number)) } // PRIMITIVE ARRAYS for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) { appendln( genValueUseSite( "${number}Array", "${number}Array" ) ) } // TYPED ARRAYS for (number in NUMBERS + OTHER_PRIMITIVES) { appendln( genValueUseSite( "Array<${number}>", "Typed${number}Array" ) ) } // PRIMITIVE LISTS / PRIMITIVE SETS for (collectionName in listOf("List", "Set")) { for (number in NUMBERS + OTHER_PRIMITIVES) { appendln( genValueUseSite( "${collectionName}<${number}>", "${number}${collectionName}" ) ) } } // MUTABLE LIST / MUTABLE SET for (collectionName in listOf("List", "Set")) { for (number in NUMBERS + OTHER_PRIMITIVES) { appendLine() appendln( """ @JvmName("valueMutable") fun PluginData.value(default: Mutable${collectionName}<${number}>): Mutable${number}${collectionName}Value = valueImpl(default) """.trimIndent() ) } } // SPECIAL appendLine() @Suppress("unused", "SpellCheckingInspection", "KDocUnresolvedReference") appendln( """ fun <T : PluginData> PluginData.value(default: T): Value<T> { require(this::class != default::class) { "Recursive nesting is prohibited" } return valueImpl(default).also { if (default is PluginData.NestedPluginData) { default.attachedValue = it } } } inline fun <T : PluginData> PluginData.value(default: T, crossinline initializer: T.() -> Unit): Value<T> = value(default).also { it.value.apply(initializer) } inline fun <reified T : PluginData> PluginData.value(default: List<T>): PluginDataListValue<T> = valueImpl(default) @JvmName("valueMutable") inline fun <reified T : PluginData> PluginData.value(default: MutableList<T>): MutablePluginDataListValue<T> = valueImpl(default) inline fun <reified T : PluginData> PluginData.value(default: Set<T>): PluginDataSetValue<T> = valueImpl(default) @JvmName("valueMutable") inline fun <reified T : PluginData> PluginData.value(default: MutableSet<T>): MutablePluginDataSetValue<T> = valueImpl(default) /** * 创建一个只引用对象而不跟踪其属性的值. * * @param T 类型. 必须拥有 [kotlinx.serialization.Serializable] 注解 (因此编译器会自动生成序列化器) */ @DangerousReferenceOnlyValue @JvmName("valueDynamic") @LowPriorityInOverloadResolution inline fun <reified T : Any> PluginData.value(default: T): Value<T> = valueImpl(default) @RequiresOptIn( ""${'"'} 这种只保存引用的 Value 可能会导致意料之外的结果, 在使用时须保持谨慎. 对值的改变不会触发自动保存, 也不会同步到 UI 中. 在 UI 中只能编辑序列化之后的值. ""${'"'}, level = RequiresOptIn.Level.WARNING, ) @Retention(AnnotationRetention.BINARY) @Target(AnnotationTarget.FUNCTION) annotation class DangerousReferenceOnlyValue """ ) } fun genValueUseSite(kotlinTypeName: String, miraiValueName: String): String = """ fun PluginData.value(default: $kotlinTypeName): ${miraiValueName}Value = valueImpl(default) """.trimIndent() ================================================ FILE: mirai-console/backend/codegen/src/old/ValueImplCodegen.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("PRE_RELEASE_CLASS") package net.mamoe.mirai.console.codegen.old import org.intellij.lang.annotations.Language import java.io.File fun main() { println(File("").absolutePath) // default project base dir File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/internal/_ValueImpl.kt").apply { createNewFile() }.writeText(buildString { appendLine(COPYRIGHT) appendLine() appendLine(PACKAGE) appendLine() appendLine(IMPORTS) appendLine() appendLine() appendLine(DO_NOT_MODIFY) appendLine() appendLine() appendLine(genAllValueImpl()) }) } private val DO_NOT_MODIFY = """ /** * !!! This file is auto-generated by backend/codegen/src/kotlin/net.mamoe.mirai.console.codegen.ValueImplCodegen.kt * !!! DO NOT MODIFY THIS FILE MANUALLY */ """.trimIndent() private val PACKAGE = """ package net.mamoe.mirai.console.data.internal """.trimIndent() private val IMPORTS = """ import kotlinx.serialization.* import kotlinx.serialization.builtins.* import net.mamoe.mirai.console.data.* """.trimIndent() @Suppress("SpellCheckingInspection") fun genAllValueImpl(): String = buildString { fun appendln(@Language("kt") code: String) { this.appendLine(code.trimIndent()) } // PRIMITIVE for (number in NUMBERS + OTHER_PRIMITIVES) { appendln( genPrimitiveValueImpl( number, number, "$number.serializer()", false ) ) appendLine() } // PRIMITIVE ARRAYS for (number in NUMBERS + OTHER_PRIMITIVES.filterNot { it == "String" }) { appendln( genPrimitiveValueImpl( "${number}Array", "${number}Array", "${number}ArraySerializer()", true ) ) appendLine() } // TYPED ARRAYS for (number in NUMBERS + OTHER_PRIMITIVES) { appendln( genPrimitiveValueImpl( "Array<${number}>", "Typed${number}Array", "ArraySerializer(${number}.serializer())", true ) ) appendLine() } // PRIMITIVE LISTS / SETS for (collectionName in listOf("List", "Set")) { for (number in NUMBERS + OTHER_PRIMITIVES) { appendln( genCollectionValueImpl( collectionName, "${collectionName}<${number}>", "${number}${collectionName}", "${collectionName}Serializer(${number}.serializer())", false ) ) appendLine() } } appendLine() // MUTABLE LIST / MUTABLE SET for (collectionName in listOf("List", "Set")) { for (number in NUMBERS + OTHER_PRIMITIVES) { @Suppress("unused") appendln( """ @JvmName("valueImplMutable${number}${collectionName}") internal fun PluginData.valueImpl( default: Mutable${collectionName}<${number}> ): Mutable${number}${collectionName}Value { var internalValue: Mutable${collectionName}<${number}> = default val delegt = dynamicMutable${collectionName} { internalValue } return object : Mutable${number}${collectionName}Value(), Mutable${collectionName}<${number}> by delegt { override var value: Mutable${collectionName}<${number}> get() = internalValue set(new) { if (new != internalValue) { internalValue = new onElementChanged(this) } } private val outerThis get() = this override val serializer: KSerializer<Mutable${collectionName}<${number}>> = object : KSerializer<Mutable${collectionName}<${number}>> { private val delegate = ${collectionName}Serializer(${number}.serializer()) override val descriptor: SerialDescriptor get() = delegate.descriptor override fun deserialize(decoder: Decoder): Mutable${collectionName}<${number}> { return delegate.deserialize(decoder).toMutable${collectionName}().observable { onElementChanged(outerThis) } } override fun serialize(encoder: Encoder, value: Mutable${collectionName}<${number}>) { delegate.serialize(encoder, value) } } } } """ ) appendLine() } } appendLine() @Suppress("unused") appendln( """ internal fun <T : PluginData> PluginData.valueImpl(default: T): Value<T> { return object : PluginDataValue<T>() { private var internalValue: T = default override var value: T get() = internalValue set(new) { if (new != internalValue) { internalValue = new onElementChanged(this) } } override val serializer = object : KSerializer<T>{ override val descriptor: SerialDescriptor get() = internalValue.updaterSerializer.descriptor override fun deserialize(decoder: Decoder): T { internalValue.updaterSerializer.deserialize(decoder) return internalValue } override fun serialize(encoder: Encoder, value: T) { internalValue.updaterSerializer.serialize(encoder, PluginDataSerializerMark) } } } } """ ) } fun genPrimitiveValueImpl( kotlinTypeName: String, miraiValueName: String, serializer: String, isArray: Boolean ): String = """ internal fun PluginData.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value { return object : ${miraiValueName}Value() { private var internalValue: $kotlinTypeName = default override var value: $kotlinTypeName get() = internalValue set(new) { ${ if (isArray) """ if (!new.contentEquals(internalValue)) { internalValue = new onElementChanged(this) } """.trim() else """ if (new != internalValue) { internalValue = new onElementChanged(this) } """.trim() } } override val serializer get() = $serializer } } """.trimIndent() + "\n" @Suppress("SpellCheckingInspection") fun genCollectionValueImpl( collectionName: String, kotlinTypeName: String, miraiValueName: String, serializer: String, isArray: Boolean ): String = """ internal fun PluginData.valueImpl(default: ${kotlinTypeName}): ${miraiValueName}Value { var internalValue: $kotlinTypeName = default val delegt = dynamic$collectionName { internalValue } return object : ${miraiValueName}Value(), $kotlinTypeName by delegt { override var value: $kotlinTypeName get() = internalValue set(new) { ${ if (isArray) """ if (!new.contentEquals(internalValue)) { internalValue = new onElementChanged(this) } """.trim() else """ if (new != internalValue) { internalValue = new onElementChanged(this) } """.trim() } } override val serializer get() = $serializer } } """.trimIndent() + "\n" ================================================ FILE: mirai-console/backend/codegen/src/old/ValuesCodegen.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("ClassName", "unused", "PRE_RELEASE_CLASS") package net.mamoe.mirai.console.codegen.old import org.intellij.lang.annotations.Language import java.io.File fun main() { println(File("").absolutePath) // default project base dir File("backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/data/_Value.kt").apply { createNewFile() }.writeText(genPublicApi()) } internal val COPYRIGHT = """ /* * 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 */ """.trim() internal val NUMBERS = listOf( "Int", "Short", "Byte", "Long", "Float", "Double" ) internal val UNSIGNED_NUMBERS = listOf( "UInt", "UShort", "UByte", "ULong" ) internal val OTHER_PRIMITIVES = listOf( "Boolean", "Char", "String" ) fun genPublicApi() = buildString { fun appendln(@Language("kt") code: String) { this.appendLine(code.trimIndent()) } appendln(COPYRIGHT.trim()) appendLine() appendln( """ package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty """ ) appendLine() appendln( """ /** * !!! This file is auto-generated by backend/codegen/src/main/kotlin/net.mamoe.mirai.console.codegen.ValuesCodegen.kt * !!! for better performance * !!! DO NOT MODIFY THIS FILE MANUALLY */ """ ) appendLine() appendln( """ sealed class Value<T : Any> : ReadWriteProperty<PluginData, T> { abstract var value: T /** * 用于更新 [value] 的序列化器 */ abstract val serializer: KSerializer<T> override fun getValue(thisRef: PluginData, property: KProperty<*>): T = value override fun setValue(thisRef: PluginData, property: KProperty<*>, value: T) { this.value = value } override fun equals(other: Any?): Boolean { if (other==null)return false if (other::class != this::class) return false other as Value<*> return other.value == this.value } override fun hashCode(): Int = value.hashCode() } """ ) appendLine() // PRIMITIVES appendln( """ sealed class PrimitiveValue<T : Any> : Value<T>() sealed class NumberValue<T : Number> : Value<T>() """ ) for (number in NUMBERS) { val template = """ abstract class ${number}Value internal constructor() : NumberValue<${number}>() """ appendln(template) } appendLine() for (number in OTHER_PRIMITIVES) { val template = """ abstract class ${number}Value internal constructor() : PrimitiveValue<${number}>() """ appendln(template) } appendLine() // ARRAYS appendln( """ // T can be primitive array or typed Array sealed class ArrayValue<T : Any> : Value<T>() """ ) // PRIMITIVE ARRAYS appendln( """ sealed class PrimitiveArrayValue<T : Any> : ArrayValue<T>() """ ) appendLine() for (number in (NUMBERS + OTHER_PRIMITIVES).filterNot { it == "String" }) { appendln( """ abstract class ${number}ArrayValue internal constructor() : PrimitiveArrayValue<${number}Array>(), Iterable<${number}> { override fun iterator(): Iterator<${number}> = this.value.iterator() } """ ) appendLine() } appendLine() // TYPED ARRAYS appendln( """ sealed class TypedPrimitiveArrayValue<E> : ArrayValue<Array<E>>() , Iterable<E>{ override fun iterator() = this.value.iterator() } """ ) appendLine() for (number in (NUMBERS + OTHER_PRIMITIVES)) { appendln( """ abstract class Typed${number}ArrayValue internal constructor() : TypedPrimitiveArrayValue<${number}>() """ ) } appendLine() // TYPED LISTS / SETS for (collectionName in listOf("List", "Set")) { appendln( """ sealed class ${collectionName}Value<E> : Value<${collectionName}<E>>(), ${collectionName}<E> """ ) for (number in (NUMBERS + OTHER_PRIMITIVES)) { val template = """ abstract class ${number}${collectionName}Value internal constructor() : ${collectionName}Value<${number}>() """ appendln(template) } appendLine() // SETTING appendln( """ abstract class PluginData${collectionName}Value<T: PluginData> internal constructor() : Value<${collectionName}<T>>(), ${collectionName}<T> """ ) appendLine() } // SETTING VALUE appendln( """ abstract class PluginDataValue<T : PluginData> internal constructor() : Value<T>() """ ) appendLine() // MUTABLE LIST / MUTABLE SET for (collectionName in listOf("List", "Set")) { appendln( """ abstract class Mutable${collectionName}Value<T : Any> internal constructor() : Value<Mutable${collectionName}<Value<T>>>(), Mutable${collectionName}<T> """ ) appendLine() for (number in (NUMBERS + OTHER_PRIMITIVES)) { appendln( """ abstract class Mutable${number}${collectionName}Value internal constructor() : Value<Mutable${collectionName}<${number}>>(), Mutable${collectionName}<${number}> """ ) } appendLine() // SETTING appendln( """ abstract class MutablePluginData${collectionName}Value<T: PluginData> internal constructor() : Value<Mutable${collectionName}<T>>(), Mutable${collectionName}<T> """ ) appendLine() } appendLine() // DYNAMIC appendln( """ /** * 只引用这个对象, 而不跟踪其成员. * 仅适用于基础类型, 用于 mutable list/map 等情况; 或标注了 [Serializable] 的类. */ abstract class DynamicReferenceValue<T : Any> : Value<T>() """ ) } ================================================ FILE: mirai-console/backend/codegen/src/util.kt ================================================ /* * 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", "MemberVisibilityCanBePrivate", "unused", "PRE_RELEASE_CLASS") package net.mamoe.mirai.console.codegen import org.intellij.lang.annotations.Language import java.io.File typealias KtByte = KtType.KtPrimitive.KtByte typealias KtShort = KtType.KtPrimitive.KtShort typealias KtInt = KtType.KtPrimitive.KtInt typealias KtLong = KtType.KtPrimitive.KtLong typealias KtFloat = KtType.KtPrimitive.KtFloat typealias KtDouble = KtType.KtPrimitive.KtDouble typealias KtChar = KtType.KtPrimitive.KtChar typealias KtBoolean = KtType.KtPrimitive.KtBoolean typealias KtString = KtType.KtString typealias KtCollection = KtType.KtCollection typealias KtMap = KtType.KtMap typealias KtPrimitive = KtType.KtPrimitive sealed class KtType { /** * Its classname in standard library */ abstract val standardName: String override fun toString(): String = standardName /** * Not Including [String] */ sealed class KtPrimitive( override val standardName: String, val jPrimitiveName: String = standardName.lowercase(), val jObjectName: String = standardName ) : KtType() { object KtByte : KtPrimitive("Byte") object KtShort : KtPrimitive("Short") object KtInt : KtPrimitive("Int", jObjectName = "Integer") object KtLong : KtPrimitive("Long") object KtFloat : KtPrimitive("Float") object KtDouble : KtPrimitive("Double") object KtChar : KtPrimitive("Char", jObjectName = "Character") object KtBoolean : KtPrimitive("Boolean") } object KtString : KtType() { override val standardName: String get() = "String" } /** * [List], [Set] */ data class KtCollection(override val standardName: String) : KtType() object KtMap : KtType() { override val standardName: String get() = "Map" } data class Custom(override val standardName: String) : KtType() { override fun toString(): String { return standardName } } companion object { operator fun invoke(standardName: String): KtType = Custom(standardName) } } val KtPrimitiveIntegers = listOf(KtByte, KtShort, KtInt, KtLong) val KtPrimitiveFloatings = listOf(KtFloat, KtDouble) val KtPrimitiveNumbers = KtPrimitiveIntegers + KtPrimitiveFloatings val KtPrimitiveNonNumbers = listOf(KtChar, KtBoolean) val KtPrimitives = KtPrimitiveNumbers + KtPrimitiveNonNumbers operator fun KtType.plus(type: KtType): List<KtType> { return listOf(this, type) } val KtType.lowerCaseName: String get() = this.standardName.lowercase() inline fun kCode(@Language("kt") source: String) = source.trimIndent() fun codegen(targetFile: String, block: CodegenScope.() -> Unit) { //// region PrimitiveValue CODEGEN //// //// region PrimitiveValue CODEGEN //// targetFile.findFileSmart().also { println("Codegen target: ${it.absolutePath}") }.apply { writeText( CodegenScope().apply(block).onEach { println("Applying replacement: $it") }.applyTo(readText()) ) } } fun String.findFileSmart(): File = kotlin.run { if (contains("/")) { // absolute File(this) } else { val list = File(".").walk().filter { it.name == this }.toList() if (list.isNotEmpty()) return list.single() File(".").walk().filter { it.name.contains(this) }.single() } }.also { require(it.exists()) { "file doesn't exist" } } ================================================ FILE: mirai-console/backend/integration-test/README.md ================================================ # Console - Integration Test Mirai Console 一体化测试单元 (目前仅内部测试) --- ## 使用 Integration Test Framework TODO ### 添加一个新测试 #### 创建 Integration Test 测试点 创建一个新的子测试单元并继承 `AbstractTestPoint` - 在其 `beforeConsoleStartup()` 准备测试环境 (如写入配置文件, etc) - 在其 `onConsoleStartSuccessfully()` 检查插件相关行为是否正确 然后在 `MiraiConsoleIntegrationTestLauncher.points` 添加新单元的完整类路径 ---- ## Mirai Console Internal Testing ### 添加一个新测试 (CONSOLE 内部测试) 在 `test/testpoints` 添加新测试点, 然后在 [`MiraiConsoleIntegrationTestBootstrap.kt`](test/MiraiConsoleIntegrationTestBootstrap.kt) 添加相关单元 ### 创建配套子插件 在 `testers` 创建新的文件夹, 刷新 Gradle 即可获得生成的 build.gradle.kts. 即可创建新的配套插件, 可用于测试插件依赖, etc 只有在修改 build.gradle.kts 后才需要将其添加 git, 其他情况下会自动生成. ================================================ FILE: mirai-console/backend/integration-test/build.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("UnusedImport") import java.util.* plugins { kotlin("jvm") kotlin("plugin.serialization") id("java") } version = Versions.console description = "Mirai Console Backend Real-Time Testing Unit" kotlin { explicitApiWarning() } dependencies { api(project(":mirai-core-api")) api(project(":mirai-core-utils")) testRuntimeOnly(project(":mirai-core")) api(project(":mirai-console-compiler-annotations")) api(project(":mirai-console")) api(project(":mirai-console-frontend-base")) api(project(":mirai-console-terminal")) api(`kotlin-stdlib-jdk8`) api(`kotlinx-atomicfu`) api(`kotlinx-coroutines-core`) api(`kotlinx-serialization-core`) api(`kotlinx-serialization-json`) api(`kotlin-reflect`) api(`kotlin-test-junit5`) api(`yamlkt`) api(`jetbrains-annotations`) api(`caller-finder`) api(`kotlinx-coroutines-jdk8`) val asmVersion = Versions.asm fun asm(module: String) = "org.ow2.asm:asm-$module:$asmVersion" api(asm("tree")) api(asm("util")) api(asm("commons")) } // requires manual run val deleteSandbox = tasks.register("deleteSandbox", Delete::class.java) { group = "mirai" delete("build/IntegrationTest") } //tasks.getByName("clean").dependsOn(deleteSandbox) val subplugins = mutableListOf<TaskProvider<Jar>>() val mcit_test = tasks.named<Test>("test") mcit_test.configure { val test0 = this doFirst { // For IDEA Debugging @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") val extArgs = test0.jvmArgs!!.asSequence().map { extArg -> Base64.getEncoder().encodeToString(extArg.toByteArray()) }.joinToString(",") test0.jvmArgs = mutableListOf() test0.environment("IT_ARGS", extArgs) // For plugins coping val jars = subplugins.asSequence() .map { it.get() } .flatMap { it.outputs.files.files.asSequence() } .toList() test0.environment("IT_PLUGINS", jars.size) jars.forEachIndexed { index, jar -> test0.environment("IT_PLUGINS_$index", jar.absolutePath) } } } val crtProject = project allprojects { if (project != crtProject) { if (project.file(".module-group.txt").exists()) return@allprojects project.afterEvaluate { runCatching { val tk = tasks.named<Jar>("jar") subplugins.add(tk) mcit_test.configure { dependsOn(tk) inputs.files(tk) } } } } } ================================================ FILE: mirai-console/backend/integration-test/src/AbstractTestPoint.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.console.integrationtest /** * IntegrationTest 测试单元 * * 每个被注册的单元都会在 console 启动的各个阶段调用相关的函数, 可以在相关函数执行测试代码 * * ## 注册单元 * * 每个单元都需要被注册, 即被添加进 [MiraiConsoleIntegrationTestLauncher.points] * * @see MiraiConsoleIntegrationTestLauncher * @see AbstractTestPointAsPlugin */ public abstract class AbstractTestPoint { /** * 本函数会在 console 启动前调用, 可以在此处进行环境配置 */ protected open fun beforeConsoleStartup() {} /** * 本函数会在 console 启动成功后立即调用, 可进行环境检查, 命令执行测试, 或更多 */ protected open fun onConsoleStartSuccessfully() {} // access internal companion object { internal fun AbstractTestPoint.internalOSS() { onConsoleStartSuccessfully() } internal fun AbstractTestPoint.internalBCS() { beforeConsoleStartup() } } } ================================================ FILE: mirai-console/backend/integration-test/src/AbstractTestPointAsPlugin.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.createInstanceOrNull /** * IntegrationTest 测试单元 (Plugin mode) * * 该单元除了拥有 [AbstractTestPoint] 具有的功能之外, 还可以直接模拟一个插件的行为. * * 在此单元里, 可以像写正常的 console 插件一样在此写测试时插件 */ public abstract class AbstractTestPointAsPlugin : AbstractTestPoint() { protected abstract fun newPluginDescription(): JvmPluginDescription protected open fun KotlinPlugin.onInit() {} protected open fun KotlinPlugin.onLoad0(storage: PluginComponentStorage) {} protected open fun KotlinPlugin.onEnable0() {} protected open fun KotlinPlugin.onDisable0() {} protected open fun exceptionHandler(exception: Throwable, step: JvmPluginExecutionStep, instance: KotlinPlugin) { IntegrationTestBootstrapContext.failures.add(this.javaClass) } private fun callEH(exception: Throwable, step: JvmPluginExecutionStep, instance: KotlinPlugin) { try { exceptionHandler(exception, step, instance) } catch (e: Throwable) { forceFail(cause = e) } } protected enum class JvmPluginExecutionStep { OnEnable, OnDisable, OnLoad } @Suppress("unused") @PublishedApi internal abstract class TestPointPluginImpl( private val impl: AbstractTestPointAsPlugin ) : KotlinPlugin(impl.newPluginDescription()) { init { impl.apply { onInit() } } @PublishedApi internal constructor( impl: Class<out AbstractTestPointAsPlugin> ) : this( impl.kotlin.createInstanceOrNull() ?: impl.getConstructor().newInstance() ) override fun onDisable() { try { impl.apply { onDisable0() } } catch (e: Throwable) { impl.callEH(e, JvmPluginExecutionStep.OnDisable, this) throw e } } override fun onEnable() { try { impl.apply { onEnable0() } } catch (e: Throwable) { impl.callEH(e, JvmPluginExecutionStep.OnEnable, this) throw e } } override fun PluginComponentStorage.onLoad() { try { impl.apply { onLoad0(this@onLoad) } } catch (e: Throwable) { impl.callEH(e, JvmPluginExecutionStep.OnLoad, this@TestPointPluginImpl) throw e } } } } ================================================ FILE: mirai-console/backend/integration-test/src/IntegrationTestBootstrap.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmName("IntegrationTestBootstrap") @file:OptIn(ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class, ConsoleInternalApi::class) package net.mamoe.console.integrationtest import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.runBlocking import net.mamoe.console.integrationtest.AbstractTestPoint.Companion.internalBCS import net.mamoe.console.integrationtest.AbstractTestPoint.Companion.internalOSS import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.terminal.ConsoleTerminalExperimentalApi import net.mamoe.mirai.console.terminal.ConsoleTerminalSettings import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.sha1 import net.mamoe.mirai.utils.toUHexString import org.objectweb.asm.* import java.io.File import java.io.FileDescriptor import java.io.FileOutputStream import java.io.PrintStream import java.util.concurrent.ConcurrentLinkedDeque import java.util.zip.ZipEntry import java.util.zip.ZipFile import java.util.zip.ZipOutputStream import kotlin.system.exitProcess internal object IntegrationTestBootstrapContext { val failures = ConcurrentLinkedDeque<Class<*>>() } /** * 入口点为 /test/MiraiConsoleIntegrationTestBootstrap.kt 并非此函数(文件), * 不要直接执行此函数 */ @OptIn(ConsoleTerminalExperimentalApi::class) @PublishedApi internal fun main() { // PRE CHECK run { if (!System.getenv("MIRAI_CONSOLE_INTEGRATION_TEST").orEmpty().toBoolean()) { error("Don't launch IntegrationTestBootstrap directly. See /test/MiraiConsoleIntegrationTestBootstrap.kt") } } System.setProperty("mirai.console.skip-end-user-readme", "") // @context: env.testunit = true // @context: env.inJUnitProcess = false // @context: env.exitProcessSafety = true // @context: process.type = sandbox // @context: process.cwd = /mirai-console/backend/build/rttu // @context: process.timeout = 5min ConsoleTerminalSettings.setupAnsi = false ConsoleTerminalSettings.noConsole = true ConsoleTerminalSettings.launchOptions.crashWhenPluginLoadFailed = true val testUnits: List<AbstractTestPoint> = readStringListFromEnv("IT_POINTS").asSequence() .onEach { println("[MCIT] Loading test point: $it") } .map { Class.forName(it) } .map { @Suppress("DEPRECATION") it.kotlin.objectInstance ?: it.newInstance() } .map { it.cast<AbstractTestPoint>() } .toList() File("plugins").mkdirs() File("modules").mkdirs() prepareConsole() testUnits.forEach { (it as? AbstractTestPointAsPlugin)?.generatePluginJar() } testUnits.forEach { it.internalBCS() } Thread.sleep(2000L) try { MiraiConsoleTerminalLoader.startAsDaemon() } catch (e: Throwable) { val ps = PrintStream(FileOutputStream(FileDescriptor.out)) e.printStackTrace(ps) ps.flush() exitProcess(1) } if (!MiraiConsole.isActive) { error("Failed to start console") } if (IntegrationTestBootstrapContext.failures.isNotEmpty()) { val logger = MiraiConsole.mainLogger logger.error("Failed tests: ") IntegrationTestBootstrapContext.failures.toSet().forEach { logger.error(" `- $it") } error("Failed tests: ${IntegrationTestBootstrapContext.failures.toSet()}") } // I/main: mirai-console started successfully. testUnits.forEach { it.internalOSS() } runBlocking { MiraiConsole.job.cancelAndJoin() } exitProcess(0) } private fun File.mkparents(): File = apply { parentFile?.mkdirs() } private fun prepareConsole() { File("config/Console/Logger.yml").mkparents().writeText( """ defaultPriority: ALL loggers: Bot: ALL org.eclipse.aether.internal: INFO org.apache.http.wire: INFO """ ) fun saveSha1(t: File) { val sfile = File(t.path + ".sha1") sfile.writeText(t.readBytes().sha1().toUHexString(separator = "").lowercase()) } readStringListFromEnv("IT_PLUGINS").forEach plLoop@{ path -> val jarFile = File(path) ZipFile(jarFile).use { zipFile -> zipFile.getEntry("mvn.txt")?.let { zipFile.getInputStream(it) }?.bufferedReader()?.useLines { lines -> val libName = lines.filterNot { it.isBlank() }.filterNot { it[0] == '#' }.first() // net.mamoe:test:1.0.0 val (gid, art, ver) = libName.split(':') val targetDir = File("plugin-libraries").resolve(gid.replace('.', '/')) .resolve(art).resolve(ver).also { it.mkdirs() } val fname = "$art-$ver" val pom = """ <?xml version="1.0" encoding="UTF-8"?> <project> <modelVersion>4.0.0</modelVersion> <groupId>$gid</groupId> <artifactId>$art</artifactId> <version>$ver</version> </project> """.trimIndent() targetDir.resolve("$fname.pom").let { it.writeText(pom); saveSha1(it) } targetDir.resolve("$fname.jar").let { jarFile.copyTo(it, overwrite = true) saveSha1(it) } targetDir.resolve("_remote.repositories").writeText( """ $fname.pom>= $fname.jar>= """.trimIndent() ) println("[MCIT] Copied module: $libName $jarFile -> $targetDir") return@plLoop } if (zipFile.getEntry("module.txt") == null) { if (zipFile.entries().asSequence().any { it.name.contains("services/net.mamoe.mirai.console.plugin.jvm") } ) { var target = File("plugins/${jarFile.name}").mkparents() var counter = 0 while (target.exists()) { target = File("plugins/${jarFile.nameWithoutExtension}${counter}.${jarFile.extension}") counter++ } jarFile.copyTo(target, overwrite = true) println("[MCIT] Copied external plugin: $jarFile -> $target") return@plLoop } } // DYN MODULE val target = File("modules/${jarFile.name}").mkparents() jarFile.copyTo(target, overwrite = true) println("[MCIT] Copied module: $jarFile") } } } private fun AbstractTestPointAsPlugin.generatePluginJar() { val simpleName = this.javaClass.simpleName val point = this val jarFile = File("plugins").resolve("$simpleName.jar") // PluginMainPoint: net.mamoe.console.integrationtestAbstractTestPointAsPlugin$TestPointPluginImpl jarFile.mkparents() ZipOutputStream( FileOutputStream(jarFile).buffered() ).use { zipOutputStream -> // META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin zipOutputStream.putNextEntry( ZipEntry( "META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin" ) ) val delegateClassName = "net.mamoe.console.integrationtest.tpd.$simpleName" zipOutputStream.write(delegateClassName.toByteArray()) // MainClass val internalClassName = delegateClassName.replace('.', '/') zipOutputStream.putNextEntry(ZipEntry("$internalClassName.class")) val classWriter = ClassWriter(ClassWriter.COMPUTE_MAXS) val superName = "net/mamoe/console/integrationtest/AbstractTestPointAsPlugin\$TestPointPluginImpl" classWriter.visit( Opcodes.V1_8, Opcodes.ACC_PUBLIC, internalClassName, null, superName, null ) // region Copy class annotations this.javaClass.getResourceAsStream(javaClass.simpleName + ".class")!!.use { ClassReader(it) }.accept(object : ClassVisitor(Opcodes.ASM9) { override fun visitAnnotation(descriptor: String, visible: Boolean): AnnotationVisitor? { if ("kotlin/Metadata" in descriptor) return null return classWriter.visitAnnotation(descriptor, visible) } override fun visitTypeAnnotation( typeRef: Int, typePath: TypePath, descriptor: String, visible: Boolean ): AnnotationVisitor? { if ("kotlin/Metadata" in descriptor) return null return classWriter.visitTypeAnnotation(typeRef, typePath, descriptor, visible) } }, ClassReader.SKIP_CODE) // endregion classWriter.visitMethod( Opcodes.ACC_PUBLIC, "<init>", "()V", null, null )!!.let { initMethod -> initMethod.visitVarInsn(Opcodes.ALOAD, 0) initMethod.visitLdcInsn(Type.getType(point.javaClass)) initMethod.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, "<init>", "(Ljava/lang/Class;)V", false) initMethod.visitInsn(Opcodes.RETURN) initMethod.visitMaxs(0, 0) initMethod.visitEnd() } zipOutputStream.write(classWriter.toByteArray()) } } ================================================ FILE: mirai-console/backend/integration-test/src/MiraiConsoleIntegrationTestLauncher.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest import net.mamoe.mirai.utils.lateinitMutableProperty import java.io.File import java.io.OutputStream import java.io.PrintStream import java.lang.management.ManagementFactory import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean import kotlin.concurrent.thread // TODO: 不完整, 还无法完全公开, 目前仅允许 console 内部使用 /** * MiraiConsoleIntegrationTest 启动器 */ public class MiraiConsoleIntegrationTestLauncher { /** java.exe 路径 */ public var javaexec: String by lateinitMutableProperty { findJavaExec() } /** * 测试环境运行目录, **每次启动前会直接删除该文件夹的内容**(IMPORTANT) */ public var workingDir: File = File("mirai-console-integration-test") /** 额外 JVM 参数 */ public var vmoptions: MutableList<String> = mutableListOf() /** 额外环境变量 */ public var extraEnvironment: MutableMap<String, String> = mutableMapOf() /** 类路径, 需要包含 MiraiConsoleIntegrationTest Framework */ public var classpath: String by lateinitMutableProperty { ManagementFactory.getRuntimeMXBean().classPath } /** 标准输出重定向位置 */ public var output: OutputStream = System.out /** 标准错误重定向位置 */ public var error: OutputStream = System.err /** [MiraiConsoleIntegrationTestLauncher] 启动日志的输出 */ public var log: PrintStream = System.out /** 测试单元完整类名, 需要可以在 [classpath] 中找到 */ public var points: MutableCollection<String> = mutableListOf() /** 测试环境的额外插件, 为文件路径, 相对于 [workingDir] */ public var plugins: MutableCollection<String> = mutableListOf() public fun launch() { workingDir.listFiles()?.filterNot { it.name.contains("libraries") } ?.forEach { it.deleteRecursively() } // do not download repeatedly workingDir.mkdirs() val isDebugging = vmoptions.any { it.startsWith("-agentlib:") } val builder = ProcessBuilder( javaexec, *vmoptions.toTypedArray(), "-cp", classpath, "net.mamoe.console.integrationtest.IntegrationTestBootstrap", ) .directory(workingDir) // .inheritIO() // No output in idea val env = builder.environment() env.putAll(extraEnvironment) env["MIRAI_CONSOLE_INTEGRATION_TEST"] = "true" saveStringListToEnv("IT_PLUGINS", plugins, env) saveStringListToEnv("IT_POINTS", points, env) log.println("[MCIT] Launching IntegrationTest") log.println("[MCIT] `- Arguments: ${builder.command().joinToString(" ")}") log.println("[MCIT] `- Directory: ${builder.directory().absoluteFile}") log.println("[MCIT] `- Debugging: $isDebugging") if (isDebugging) { log.println("[MCIT] Running in debug mode. Watchdog thread will not start") } val process = builder.start() val timedOut = AtomicBoolean(false) val watchdog = thread { if (isDebugging) return@thread try { Thread.sleep(TimeUnit.MINUTES.toMillis(5)) timedOut.set(true) process.destroyForcibly() } catch (ignored: InterruptedException) { } } thread { process.inputStream.copyTo(output) } thread { process.errorStream.copyTo(error) } val rsp = process.waitFor() if (timedOut.get()) { error("Mirai console daemon timed out") } watchdog.interrupt() if (rsp != 0) error("Rsp $rsp") } } private fun findJavaExec(): String { findJavaExec0()?.let { return it.absolutePath } System.err.println("[MCIT] WARNING: Unable to determine the current runtime executable path.") System.err.println("[MCIT] WARNING: Using default executable to launch test unit") return "java" } private fun findJavaExec0(): File? { val ext = if ("windows" in System.getProperty("os.name").lowercase()) { ".exe" } else "" val javaHome = File(System.getProperty("java.home")) javaHome.resolve("bin/java$ext").takeIf { it.exists() }?.let { return it } javaHome.resolve("java$ext").takeIf { it.exists() }?.let { return it } javaHome.resolve("jre/bin/java$ext").takeIf { it.exists() }?.let { return it } javaHome.resolve("jre/java$ext").takeIf { it.exists() }?.let { return it } return null } ================================================ FILE: mirai-console/backend/integration-test/src/utils.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.console.integrationtest import net.mamoe.mirai.console.internal.plugin.ConsoleJvmPluginTestFailedError import net.mamoe.mirai.utils.MiraiInternalApi import org.junit.jupiter.api.fail import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes import org.objectweb.asm.tree.ClassNode import java.io.File import java.util.* internal fun readStringListFromEnv(key: String): MutableList<String> { val size = System.getenv(key)?.toInt() ?: 0 val rsp = mutableListOf<String>() for (i in 0 until size) { rsp.add(System.getenv("${key}_$i")!!) } return rsp } internal fun saveStringListToEnv(key: String, value: Collection<String>, env: MutableMap<String, String>) { env[key] = value.size.toString() value.forEachIndexed { index, v -> env["${key}_$index"] = v } } // region assertion kits public fun File.assertNotExists() { if (exists()) { fail { "Except ${this.absolutePath} not exists but this file exists in disk" } } } public fun assertClassSame(expected: Class<*>?, actually: Class<*>?) { fun vt(c: Class<*>?): String { if (c == null) return "<null>" return "$c from ${c.classLoader}" } if (expected === actually) return fail { "Class not same:\n" + "Class excepted: ${vt(expected)}\n" + "Class actually: ${vt(actually)}" } } @OptIn(MiraiInternalApi::class) public fun forceFail( msg: String? = null, cause: Throwable? = null, ): Nothing { throw ConsoleJvmPluginTestFailedError(msg, cause) } // endregion // region JVM Utils public val vmClassfileVersion: Int = runCatching { val obj = ClassReader("java.lang.Object") val classobj = ClassNode().also { obj.accept(it, ClassReader.SKIP_CODE) } classobj.version }.recoverCatching { val ccl = object : ClassLoader(null) { fun canLoad(ver: Int): Boolean { val klass = ClassWriter(ClassWriter.COMPUTE_MAXS) val cname = "net/mamoe/console/integrationtest/vtest/C${ver}_${System.currentTimeMillis()}_${UUID.randomUUID()}" .replace('-', '_') klass.visit( ver, Opcodes.ACC_PUBLIC or Opcodes.ACC_FINAL, cname, null, "java/lang/Object", null ) klass.visitMethod(Opcodes.ACC_PRIVATE, "<init>", "()V", null, null)!!.also { cinit -> cinit.visitVarInsn(Opcodes.ALOAD, 0) cinit.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false) cinit.visitInsn(Opcodes.RETURN) cinit.visitMaxs(0, 0) } val code = klass.toByteArray() return kotlin.runCatching { val k = defineClass(null, code, 0, code.size) Class.forName(k.name, true, this) }.isSuccess } } if (ccl.canLoad(Opcodes.V17)) return@recoverCatching Opcodes.V17 if (ccl.canLoad(Opcodes.V16)) return@recoverCatching Opcodes.V16 if (ccl.canLoad(Opcodes.V15)) return@recoverCatching Opcodes.V15 if (ccl.canLoad(Opcodes.V14)) return@recoverCatching Opcodes.V14 if (ccl.canLoad(Opcodes.V13)) return@recoverCatching Opcodes.V13 if (ccl.canLoad(Opcodes.V12)) return@recoverCatching Opcodes.V12 if (ccl.canLoad(Opcodes.V11)) return@recoverCatching Opcodes.V11 if (ccl.canLoad(Opcodes.V10)) return@recoverCatching Opcodes.V10 if (ccl.canLoad(Opcodes.V9)) return@recoverCatching Opcodes.V9 Opcodes.V1_8 }.getOrElse { Opcodes.V1_8 } // Fallback public fun canVmLoad(opversion: Int): Boolean = opversion <= vmClassfileVersion // endregion ================================================ FILE: mirai-console/backend/integration-test/test/MiraiConsoleIntegrationTestBootstrap.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest import net.mamoe.console.integrationtest.testpoints.MCITBSelfAssertions import org.objectweb.asm.ClassReader import java.io.File import java.lang.management.ManagementFactory import java.nio.file.Files import java.nio.file.Paths import java.util.* import java.util.stream.Collectors import kotlin.io.path.inputStream import kotlin.io.path.isDirectory import kotlin.io.path.name import kotlin.reflect.KClass import kotlin.test.Test import kotlin.test.assertTrue class MiraiConsoleIntegrationTestBootstrap { @Test fun bootstrap() { /* implementation note: 不使用 @TempDir 是为了保存最后一次失败快照, 便于 debug */ val workingDir = File("build/IntegrationTest") // mirai-console/backend/integration-test/build/IntegrationTest // 注意: 如果更改 workingDir, 还要更改 mirai-console/backend/integration-test/build.gradle.kts:60 (clean task 的依赖) val launcher = MiraiConsoleIntegrationTestLauncher() launcher.workingDir = workingDir launcher.plugins = readStringListFromEnv("IT_PLUGINS") launcher.points = resolveTestPoints().also { points -> // Avoid error in resolving points assertTrue { points.contains("net.mamoe.console.integrationtest.testpoints.MCITBSelfAssertions") } assertTrue { points.contains("net.mamoe.console.integrationtest.testpoints.DoNothingPoint") } assertTrue { points.contains("net.mamoe.console.integrationtest.testpoints.plugin.PluginDataRenameToIdTest") } }.asSequence().map { v -> when (v) { is Class<*> -> v.name is KClass<*> -> v.java.name is String -> v else -> v.javaClass.name } }.map { it.replace('/', '.') }.toMutableList() launcher.vmoptions = mutableListOf( *ManagementFactory.getRuntimeMXBean().inputArguments.filterNot { it.startsWith("-Djava.security.manager=") }.filterNot { it.startsWith("-Xmx") }.toTypedArray(), *System.getenv("IT_ARGS")!!.splitToSequence(",").map { Base64.getDecoder().decode(it).decodeToString() }.filter { it.isNotEmpty() }.toList().toTypedArray() ) launcher.launch() } private fun resolveTestPoints(): Collection<Any> { val ptloc = MCITBSelfAssertions.javaClass.getResource(MCITBSelfAssertions.javaClass.simpleName + ".class") ptloc ?: error("Failed to resolve test points") val path = Paths.get(ptloc.toURI()).parent return Files.walk(path) .filter { !it.isDirectory() } .filter { it.name.endsWith(".class") } .map { pt -> pt.inputStream().use { ClassReader(it).className } } .map { it.replace('/', '.') } .filter { AbstractTestPoint::class.java.isAssignableFrom(Class.forName(it)) } .use { it.collect(Collectors.toList()) } } } ================================================ FILE: mirai-console/backend/integration-test/test/testpoints/DoNothingPoint.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.console.integrationtest.testpoints import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info /* DoNothingPoint: Example */ internal object DoNothingPoint : AbstractTestPointAsPlugin() { var enableCalled = false override fun newPluginDescription(): JvmPluginDescription { return JvmPluginDescription( id = "net.mamoe.testpoint.do-nothing", version = "1.1.0", name = "DoNothing", ) } override fun KotlinPlugin.onEnable0() { logger.info { "DoNothing.onEnable() called" } enableCalled = true } override fun KotlinPlugin.onDisable0() { logger.info { "DoNothing.onDisable() called" } } override fun onConsoleStartSuccessfully() { assert(enableCalled) { "DoNothing.onEnable() not called." } } } ================================================ FILE: mirai-console/backend/integration-test/test/testpoints/MCITBSelfAssertions.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.console.integrationtest.testpoints import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import kotlin.test.* /* MCITBSelfAssertions: 用于检查 Integration Test 可以正常加载 AbstractTestPointAsPlugin 与 外部测试插件 */ internal object MCITBSelfAssertions : AbstractTestPointAsPlugin() { override fun newPluginDescription(): JvmPluginDescription { return JvmPluginDescription( id = "net.mamoe.testpoint.mirai-console-self-assertions", version = "1.0.0", name = "MCITBSelfAssertions", ) } var called = false override fun KotlinPlugin.onEnable0() { called = true assertFails { error("") } assertTrue { true } assertFalse { false } assertFailsWith<InternalError> { throw InternalError("") } assertEquals("", "") assertSame(this, this) } override fun onConsoleStartSuccessfully() { assertTrue(called, "Mirai Console IntegrationTestBootstrap Internal Error") assertTrue("MCITSelfTestPlugin not found") { PluginManager.plugins.any { it.description.id == "net.mamoe.tester.mirai-console-self-test" } } } } ================================================ FILE: mirai-console/backend/integration-test/test/testpoints/PluginSharedLibraries.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest.testpoints import net.mamoe.console.integrationtest.AbstractTestPoint import org.objectweb.asm.ClassWriter import org.objectweb.asm.Opcodes import java.io.File import java.util.zip.ZipEntry import java.util.zip.ZipOutputStream internal object PluginSharedLibraries : AbstractTestPoint() { override fun beforeConsoleStartup() { if (System.getenv("CI").orEmpty().toBoolean()) { println("CI env") File("config/Console/PluginDependencies.yml").writeText( "repoLoc: ['https://repo.maven.apache.org/maven2']" ) } File("plugin-shared-libraries").mkdirs() File("plugin-shared-libraries/libraries.txt").writeText( """ io.github.karlatemp:unsafe-accessor:1.6.2 """.trimIndent() ) ZipOutputStream(File("plugin-shared-libraries/test.jar").outputStream().buffered()).use { zipOutput -> zipOutput.putNextEntry(ZipEntry("net/mamoe/console/it/psl/PluginSharedLib.class")) ClassWriter(0).also { writer -> writer.visit( Opcodes.V1_8, 0, "net/mamoe/console/it/psl/PluginSharedLib", null, "java/lang/Object", null ) }.toByteArray().let { zipOutput.write(it) } } } } ================================================ FILE: mirai-console/backend/integration-test/test/testpoints/plugin/PluginDataRenameToIdTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest.testpoints.plugin import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin import net.mamoe.console.integrationtest.assertNotExists import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.AutoSavePluginData import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import java.io.File import kotlin.test.assertEquals internal object PluginDataRenameToIdTest : AbstractTestPointAsPlugin() { object TestData : AutoSavePluginData("testdata") { public val test: String by value("") } object TestConf : AutoSavePluginConfig("testconf") { public val test: String by value("") } override fun newPluginDescription(): JvmPluginDescription { return JvmPluginDescription( id = "net.mamoe.testpoint.plugin-data-rename-to-id-test", version = "1.0.0", name = "PluginDataRenameToIdTest", ) } override fun beforeConsoleStartup() { File("config/PluginDataRenameToIdTest").mkdirs() File("config/PluginDataRenameToIdTest/test.txt").createNewFile() File("config/PluginDataRenameToIdTest/testconf.yml").writeText( """ test: a """.trimIndent() ) File("data/PluginDataRenameToIdTest").mkdirs() File("data/PluginDataRenameToIdTest/test.txt").createNewFile() File("data/PluginDataRenameToIdTest/testdata.yml").writeText( """ test: a """.trimIndent() ) } override fun KotlinPlugin.onLoad0(storage: PluginComponentStorage) { File("config/PluginDataRenameToIdTest").assertNotExists() File("data/PluginDataRenameToIdTest").assertNotExists() } override fun KotlinPlugin.onEnable0() { TestData.reload() TestConf.reload() TestData.save() TestConf.save() assertEquals("a", TestConf.test) assertEquals("a", TestData.test) } override fun onConsoleStartSuccessfully() { File("config/PluginDataRenameToIdTest/test.txt").assertNotExists() File("data/PluginDataRenameToIdTest/test.txt").assertNotExists() File("config/PluginDataRenameToIdTest/testconf.yml").assertNotExists() File("data/PluginDataRenameToIdTest/testdata.yml").assertNotExists() } } ================================================ FILE: mirai-console/backend/integration-test/test/testpoints/plugin/PluginDependOnErrorPlugin.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.console.integrationtest.testpoints.plugin import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.internal.plugin.ConsoleJvmPluginFuncCallbackStatus import net.mamoe.mirai.console.internal.plugin.ConsoleJvmPluginFuncCallbackStatusExcept import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import org.junit.jupiter.api.Assertions.assertFalse import kotlin.test.fail @ConsoleJvmPluginFuncCallbackStatusExcept.OnEnable(ConsoleJvmPluginFuncCallbackStatus.FAILED) internal object PluginDependOnErrorPlugin : AbstractTestPointAsPlugin() { private var isOnEnabledExecuted: Boolean = false override fun newPluginDescription(): JvmPluginDescription { return JvmPluginDescription( id = "net.mamoe.testpoint.plugin-depend-on-error-plugin", version = "1.0.0", name = "PluginDependOnErrorPlugin", ) { dependsOn("net.mamoe.testpoint.plugin-with-exception-test") } } override fun beforeConsoleStartup() { isOnEnabledExecuted = false } override fun KotlinPlugin.onLoad0(storage: PluginComponentStorage) { } override fun KotlinPlugin.onEnable0() { // unreachable isOnEnabledExecuted = true fail("net.mamoe.testpoint.plugin-depend-on-error-plugin enabled") } override fun onConsoleStartSuccessfully() { assertFalse { isOnEnabledExecuted } assertFalse { PluginManager .plugins .first { it.id == "net.mamoe.testpoint.plugin-with-exception-test" } .isEnabled } assertFalse { PluginManager .plugins .first { it.id == "net.mamoe.testpoint.plugin-depend-on-error-plugin" } .isEnabled } } } ================================================ FILE: mirai-console/backend/integration-test/test/testpoints/plugin/PluginOnDisableCalledOnlyOnceTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest.testpoints.plugin import kotlinx.atomicfu.atomic import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin internal object PluginOnDisableCalledOnlyOnceTest : AbstractTestPointAsPlugin() { override fun newPluginDescription(): JvmPluginDescription { return JvmPluginDescription( id = "net.mamoe.testpoint.plugin-on-disable-called-only-once", version = "1.0.0", name = "PluginOnDisableCalledOnlyOnce", ) {} } private val onDisableCalled = atomic(false) override fun KotlinPlugin.onDisable0() { if (!onDisableCalled.compareAndSet(expect = false, update = true)) { error("onDisable called multiple times!!") } } } ================================================ FILE: mirai-console/backend/integration-test/test/testpoints/plugin/PluginWithExceptionTest.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.console.integrationtest.testpoints.plugin import net.mamoe.console.integrationtest.AbstractTestPointAsPlugin import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.internal.plugin.ConsoleJvmPluginFuncCallbackStatus import net.mamoe.mirai.console.internal.plugin.ConsoleJvmPluginFuncCallbackStatusExcept import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.debug import org.junit.jupiter.api.Assertions.assertFalse import kotlin.test.assertEquals import kotlin.test.assertIs @ConsoleJvmPluginFuncCallbackStatusExcept.OnEnable(ConsoleJvmPluginFuncCallbackStatus.FAILED) internal object PluginWithExceptionTest : AbstractTestPointAsPlugin() { override fun newPluginDescription(): JvmPluginDescription { return JvmPluginDescription( id = "net.mamoe.testpoint.plugin-with-exception-test", version = "1.0.0", name = "PluginWithExceptionTest", ) } override fun exceptionHandler(exception: Throwable, step: JvmPluginExecutionStep, instance: KotlinPlugin) { instance.logger.debug { "PluginWithExceptionTestExceptionTest" } assertIs<Exception>(exception) assertEquals("PluginWithExceptionTestExceptionTest", exception.message) } override fun KotlinPlugin.onLoad0(storage: PluginComponentStorage) { } override fun KotlinPlugin.onEnable0() { throw Exception("PluginWithExceptionTestExceptionTest") } override fun onConsoleStartSuccessfully() { assertFalse { PluginManager .plugins .first { it.id == "net.mamoe.testpoint.plugin-with-exception-test" } .isEnabled } } } ================================================ FILE: mirai-console/backend/integration-test/testers/.gitignore ================================================ build.gradle.kts ================================================ FILE: mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ net.mamoe.console.integrationtest.ep.mcitselftest.MCITSelfTestPlugin ================================================ FILE: mirai-console/backend/integration-test/testers/MCITSelfTestPlugin/src/MCITSelfTestPlugin.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.console.integrationtest.ep.mcitselftest import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info import kotlin.test.assertTrue /* MCITSelfTestPlugin: 用于测试 Integration-test 可正常加载 @see /test/testpoints/MCITBSelfAssertions */ public object MCITSelfTestPlugin : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.mirai-console-self-test", version = "1.0.0", name = "MCITSelfTestPlugin", ) ) { override fun onEnable() { logger.info { "MCITSelfTestPlugin.onEnable() called" } assertTrue { true } } public fun someAction() { logger.info { "Called!" } } } ================================================ FILE: mirai-console/backend/integration-test/testers/README.md ================================================ # Integration Test - Sub Testers Integration Test 的测试插件, 放置在本文件夹内的全部插件均为 console 内部测试用插件 如果您不是正在修改 mirai-console, 则不需要阅读此文件及此模块 --- 创建新测试插件只需要在本文件夹创建新的目录, 然后重载 (Reimport gradle projects) 如果需要添加新的依赖, 请在 [`IntegrationTest/build.gradle.kts`](../build.gradle.kts) 添加相关依赖 (使用 `testApi`) 并标注哪个测试框架使用此依赖, 为何使用此依赖 如果需要自定义 `build.gradle.kts`, 请在 IDEA 右键 `build.gradle.kts` 并选择 `Git > Add File` ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/.module-group.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-after-2_11/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-after-2_11/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # mirai.jar.after211.After211 ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-after-2_11/src/After211.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package mirai.jar.after211 import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import kotlin.test.assertFalse import kotlin.test.assertTrue /* * 2.11 及以后打包的插件, 有 net.mamoe.tester.after211 替代 */ internal object After211 : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.after211", version = "1.0.0", name = "Test Plugin", ) ) { override fun onEnable() { assertFalse("net.mamoe.tester.before211 is loaded") { PluginManager.plugins.any { it.id == "net.mamoe.tester.before211" } } assertTrue("net.mamoe.tester.before211.nonew is not loaded") { PluginManager.plugins.any { it.id == "net.mamoe.tester.before211.nonew" } } } } ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-after-2_11-without-new/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-after-2_11-without-new/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # mirai.jar.before211.nonew.After211NoNew ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-after-2_11-without-new/src/After211NoNew.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package mirai.jar.before211.nonew import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import kotlin.test.assertTrue /* * 2.11 及以后打包的插件, 无新插件替代 */ internal object After211NoNew : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.before211.nonew", version = "1.0.0", name = "Test Plugin", ) ) { override fun onEnable() { assertTrue("net.mamoe.tester.after211 is not loaded") { PluginManager.plugins.any { it.id == "net.mamoe.tester.after211" } } } } ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-before-2_11/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-before-2_11/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # mirai.jar.before211.Before211 ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/mirai-jar-before-2_11/src/Before211.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package mirai.jar.before211 import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import kotlin.test.assertFalse /* * 2.11 以前打包的插件 */ internal object Before211 : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.before211", version = "1.0.0", name = "Test Plugin", ) ) { override fun onEnable() { assertFalse("Both before211 and after211 are loaded.") { PluginManager.plugins.any { it.id == "net.mamoe.tester.after211" } } throw AssertionError("Only net.mamoe.tester.before211 is loaded.") } } ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/same-pkg-1/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/same-pkg-1/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # samepkg.P ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/same-pkg-1/src/P.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package samepkg import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin /* same-pkg-1: 测试包名一样时插件可以正常加载 */ internal object P : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.samepkg-1", version = "1.0.0", name = "SamePkg 1", ) ) {} ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/same-pkg-2/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/same-pkg-2/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # samepkg.P ================================================ FILE: mirai-console/backend/integration-test/testers/mirai-plugin-compatibility/same-pkg-2/src/P.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package samepkg import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin /* same-pkg-2: 测试包名一样时插件可以正常加载 */ internal object P : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.samepkg-2", version = "1.0.0", name = "SamePkg 2", ) ) {} ================================================ FILE: mirai-console/backend/integration-test/testers/never-override-jdk-modules/module-jdk-module/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/never-override-jdk-modules/module-jdk-module/resources/mvn.txt ================================================ net.mamoe.consoleit.issue2141:javax-xml:1.0.0 ================================================ FILE: mirai-console/backend/integration-test/testers/never-override-jdk-modules/module-jdk-module/src/javax/xml/parsers/SAXParser.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package javax.xml.parsers public class SAXParser ================================================ FILE: mirai-console/backend/integration-test/testers/never-override-jdk-modules/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # neveroverridejdkmodules.NeverOverrideJdkModules ================================================ FILE: mirai-console/backend/integration-test/testers/never-override-jdk-modules/src/NeverOverrideJdkModules.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package neveroverridejdkmodules import net.mamoe.console.integrationtest.assertClassSame import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin public object NeverOverrideJdkModules : KotlinPlugin( JvmPluginDescription("net.mamoe.console.itest.never-override-jdk-modules", "0.0.0") ) { init { jvmPluginClasspath.downloadAndAddToPath( jvmPluginClasspath.pluginSharedLibrariesClassLoader, listOf("net.mamoe.consoleit.issue2141:javax-xml:1.0.0") ) } @Suppress("Since15") override fun onEnable() { val platformC = ClassLoader.getSystemClassLoader().loadClass("javax.xml.parsers.SAXParser") assertClassSame( platformC, Class.forName("javax.xml.parsers.SAXParser"), ) assertClassSame( platformC, jvmPluginClasspath.pluginClassLoader.loadClass("javax.xml.parsers.SAXParser"), ) assertClassSame( platformC, jvmPluginClasspath.pluginIndependentLibrariesClassLoader.loadClass("javax.xml.parsers.SAXParser"), ) assertClassSame( platformC, jvmPluginClasspath.pluginSharedLibrariesClassLoader.loadClass("javax.xml.parsers.SAXParser"), ) } } ================================================ FILE: mirai-console/backend/integration-test/testers/options-properties/independent-plugin/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/options-properties/independent-plugin/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ consoleittest.optionproperties.independent.Independent ================================================ FILE: mirai-console/backend/integration-test/testers/options-properties/independent-plugin/src/Independent.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package consoleittest.optionproperties.independent import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import kotlin.test.assertFails public object Independent : KotlinPlugin( JvmPluginDescription("net.mamoe.console.itest.options_properties.independent_plugin", "0.0.0") ) { override fun onEnable() { assertFails { // parent's class.loading.be-resolvable-to-independent = false Class.forName("consoleittest.optionproperties.main.OptionsProperties") } } } ================================================ FILE: mirai-console/backend/integration-test/testers/options-properties/resources/META-INF/mirai-console-plugin/options.properties ================================================ # suppress inspection "UnusedProperty" for whole file resources.resolve-console-system-resources=true class.loading.be-resolvable-to-independent=false class.loading.resolve-independent=false ================================================ FILE: mirai-console/backend/integration-test/testers/options-properties/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ consoleittest.optionproperties.main.OptionsProperties ================================================ FILE: mirai-console/backend/integration-test/testers/options-properties/src/OptionsProperties.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package consoleittest.optionproperties.main import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import kotlin.test.assertFails import kotlin.test.assertFalse import kotlin.test.assertTrue public object OptionsProperties : KotlinPlugin( JvmPluginDescription("net.mamoe.console.itest.options_properties.main", "0.0.0") ) { override fun PluginComponentStorage.onLoad() { assertTrue { jvmPluginClasspath.shouldResolveConsoleSystemResource } assertFalse { jvmPluginClasspath.shouldBeResolvableToIndependent } assertFalse { jvmPluginClasspath.shouldResolveIndependent } } override fun onEnable() { assertFails { // class.loading.load-independent = false Class.forName("consoleittest.optionproperties.independent.Independent") } } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-can-depends-on-mirai-console/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2023 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/dev/LICENSE # net.mamoe.console.itest.plugincandependsonmiraiconsole.PluginCanDependsOnMiraiConsole ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-can-depends-on-mirai-console/src/PluginCanDependsOnMiraiConsole.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.console.itest.plugincandependsonmiraiconsole import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin internal object PluginCanDependsOnMiraiConsole : KotlinPlugin( JvmPluginDescription("net.mamoe.tester.plugin-can-depends-mirai-console", "1.0.0") { dependsOn("net.mamoe.mirai-console") } ) { } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-moda/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-moda/resources/mvn.txt ================================================ net.mamoe.consoleit.issue2054:moda:1.0.0 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-moda/src/ModuleA.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package issue2054.modulea public object ModuleA { public fun act(b: () -> Unit) { b() } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-modb/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-modb/resources/mvn.txt ================================================ net.mamoe.consoleit.issue2054:modb:1.0.0 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-modb/src/ModuleB.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package issue2054.moduleb import issue2054.modulea.ModuleA public object ModuleB { public val getModuleA: Any get() = ModuleA public fun act(b: () -> Unit) { b() } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-private-issue2108/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-private-issue2108/resources/mvn.txt ================================================ net.mamoe.consoleit.issue2108:private-module:1.0.0 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/module-private-issue2108/src/PrivateModule.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package issue2108 public object PrivateModule { public fun stack(): Throwable = Throwable("Stack trace") } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # pdepdep2054.PDepDependOnDep ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/second-plugin/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/second-plugin/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # pdepdep2054sec.PDepDependOnDepSec ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/second-plugin/src/PDepDependOnDepSec.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package pdepdep2054sec import issue2054.modulea.ModuleA import issue2054.moduleb.ModuleB import issue2108.PrivateModule import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info import kotlin.test.assertSame public object PDepDependOnDepSec : KotlinPlugin( JvmPluginDescription("net.mamoe.console.itest.plugin-dep-dependon-dep-sec", "0.0.0") { dependsOn("net.mamoe.console.itest.plugin-dep-dependon-dep") } ) { override fun onEnable() { jvmPluginClasspath.downloadAndAddToPath( jvmPluginClasspath.pluginIndependentLibrariesClassLoader, listOf("net.mamoe.consoleit.issue2054:modb:1.0.0") ) jvmPluginClasspath.downloadAndAddToPath( jvmPluginClasspath.pluginIndependentLibrariesClassLoader, listOf("net.mamoe.consoleit.issue2108:private-module:1.0.0") ) assertSame(ModuleA, ModuleB.getModuleA) logger.info { "issue 2054" } ModuleB.act { ModuleA.act { logger.info(Throwable("Stack trace")) } } logger.info("issue 2108", PrivateModule.stack()) assertSame( jvmPluginClasspath.pluginIndependentLibrariesClassLoader, PrivateModule.javaClass.classLoader, "Failed to load private module from " + jvmPluginClasspath.pluginIndependentLibrariesClassLoader ) } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dep-dependon-dep-issue-2054/src/PDepDependOnDep.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package pdepdep2054 import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin public object PDepDependOnDep : KotlinPlugin( JvmPluginDescription("net.mamoe.console.itest.plugin-dep-dependon-dep", "0.0.0") ) { init { jvmPluginClasspath.downloadAndAddToPath( jvmPluginClasspath.pluginSharedLibrariesClassLoader, listOf("net.mamoe.consoleit.issue2054:moda:1.0.0") ) jvmPluginClasspath.downloadAndAddToPath( jvmPluginClasspath.pluginIndependentLibrariesClassLoader, listOf("net.mamoe.consoleit.issue2108:private-module:1.0.0") ) } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-depend-on-other/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.console.integrationtest.ep.dependonother.PluginDependOnOther ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-depend-on-other/src/PluginDependOnOther.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest.ep.dependonother import net.mamoe.console.integrationtest.ep.mcitselftest.MCITSelfTestPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.error import net.mamoe.mirai.utils.info import kotlin.test.assertFailsWith import kotlin.test.assertNotEquals import kotlin.test.assertSame /* PluginDependOnOther: 测试插件依赖其他插件的情况 */ public object PluginDependOnOther : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.plugin-depend-on-other", version = "1.0.0", name = "Plugin Depend On Other", ) { dependsOn("net.mamoe.tester.mirai-console-self-test") dependsOn("net.mamoe.tester.plugin-dynamic-dependencies-download") } ) { override fun onEnable() { logger.info { "Do dependency call: " + MCITSelfTestPlugin::class.java } logger.info { "No Depends on: " + Class.forName("samepkg.P") } logger.info(Throwable("Stack trace")) MCITSelfTestPlugin.someAction() logger.info { "Shared library: " + Class.forName("net.mamoe.console.it.psl.PluginSharedLib") } assertNotEquals(javaClass.classLoader, Class.forName("net.mamoe.console.it.psl.PluginSharedLib").classLoader) // dependencies-shared kotlin.run { val pluginDepDynDownload = Class.forName("net.mamoe.console.integrationtest.ep.pddd.P") val gsonC = Class.forName("com.google.gson.Gson") logger.info { "Gson located $gsonC <${gsonC.classLoader}>" } assertSame(gsonC, Class.forName(gsonC.name, false, pluginDepDynDownload.classLoader)) assertFailsWith<ClassNotFoundException> { val c = Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private in dynamic-dep-download logger.error { "C: $c, from: ${c.classLoader}" } } assertFailsWith<ClassNotFoundException> { Class.forName("net.mamoe.assertion.something.not.existing") } } // region https://github.com/mamoe/mirai/issues/1920 Class.forName("net.mamoe.console.integrationtest.ep.pddd.p2.PDOO_OtherClass") Class.forName("net.mamoe.console.integrationtest.ep.pddd.PDOO_OtherClass") // endregion } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-depend-on-other/src/issue1920/OtherClass1.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "PackageDirectoryMismatch", "ClassName") package net.mamoe.console.integrationtest.ep.pddd // https://github.com/mamoe/mirai/issues/1920 @PublishedApi internal class PDOO_OtherClass { companion object { init { Thread.dumpStack() println("OC Loaded") } } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-depend-on-other/src/issue1920/OtherClass2.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "PackageDirectoryMismatch", "ClassName") package net.mamoe.console.integrationtest.ep.pddd.p2 // https://github.com/mamoe/mirai/issues/1920 @PublishedApi internal class PDOO_OtherClass { companion object { init { Thread.dumpStack() println("OC Loaded") } } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/resources/META-INF/mirai-console-plugin/dependencies-private.txt ================================================ com.zaxxer:SparseBitSet:1.2 ## Test console non-hard-link override org.bouncycastle:bcprov-jdk15on:1.63 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/resources/META-INF/mirai-console-plugin/dependencies-shared.txt ================================================ com.google.code.gson:gson:2.8.9 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ net.mamoe.console.integrationtest.ep.pddd.P ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-dynamic-dependencies-download/src/P.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest.ep.pddd import net.mamoe.console.integrationtest.canVmLoad import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info import org.objectweb.asm.Opcodes import kotlin.test.assertEquals /* PluginDynamicDependenciesDownload: 测试动态运行时下载 */ internal object P : KotlinPlugin( JvmPluginDescription( id = "net.mamoe.tester.plugin-dynamic-dependencies-download", version = "1.0.0", name = "Plugin Dynamic Dependencies Download", ) ) { init { Class.forName("com.google.gson.Gson") // shared Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private } override fun PluginComponentStorage.onLoad() { Class.forName("com.google.gson.Gson") // shared Class.forName("com.zaxxer.sparsebits.SparseBitSet") // private // console-non-hard-link dependency // mirai-core used 1.64 current val bc = Class.forName("org.bouncycastle.LICENSE") assertEquals( "1.63.0", bc.`package`.implementationVersion, message = "$bc <- ${bc.classLoader}" ) // #2009 if (canVmLoad(Opcodes.V11)) { logger.info { "V11" } Class.forName("java.net.http.HttpClient") } } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-resolve-self-dependencies-over-console-ones/resources/META-INF/mirai-console-plugin/dependencies-private.txt ================================================ io.ktor:ktor-client-core-jvm:2.0.0 io.ktor:ktor-client-java-jvm:2.0.0 io.ktor:ktor-client-resources-jvm:2.0.0 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-resolve-self-dependencies-over-console-ones/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ pluginresolveselfdepoverconsoleones.PluginResolveSelfDependenciesOverConsoleOnes ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-resolve-self-dependencies-over-console-ones/src/PluginResolveSelfDependenciesOverConsoleOnes.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package pluginresolveselfdepoverconsoleones import io.ktor.client.* import io.ktor.client.engine.java.* import io.ktor.client.plugins.resources.* import net.mamoe.console.integrationtest.assertClassSame import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info // 若插件定义依赖, 则使用插件依赖而不要使用 mirai-console 依赖 public class PluginResolveSelfDependenciesOverConsoleOnes : KotlinPlugin(JvmPluginDescription("net.mamoe.tester.plugin-resolve-self-dependencies-over-console-ones", "1.0.0")) { override fun onEnable() { logger.info { "Plugin loaded" } logger.info { HttpClient(Java) { install(Resources) }.toString() } val hcC = Class.forName("io.ktor.client.HttpClient") assertClassSame(hcC, jvmPluginClasspath.pluginIndependentLibrariesClassLoader.loadClass(hcC.name)) } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ pluginuseconsoledepsfallback.PluginUseConsoleDepsFallback ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-use-console-deps-fallback/src/PluginUseConsoleDepsFallback.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package pluginuseconsoledepsfallback import io.ktor.client.* import io.ktor.client.engine.okhttp.* import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info // 若插件未定义依赖, 则使用 mirai-console 内置依赖 public class PluginUseConsoleDepsFallback : KotlinPlugin(JvmPluginDescription("net.mamoe.tester.plugin-use-console-deps-fallback", "1.0.0")) { override fun onEnable() { logger.info { "Plugin loaded" } logger.info { HttpClient(OkHttp).toString() // dependency is compileOnly } } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2023 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/dev/LICENSE # net.mamoe.console.itest.pluginwithpluginyml.PluginWithPluginYml ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml/resources/plugin.yml ================================================ id: net.mamoe.console.itest.plugin-with-yml version: 0.0.0 dependencies: - net.mamoe.console.itest.serviceloader ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml/src/PluginWithPluginYml.kt ================================================ package net.mamoe.console.itest.pluginwithpluginyml import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import kotlin.test.assertTrue /* * Copyright 2019-2023 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/dev/LICENSE */ internal object PluginWithPluginYml : KotlinPlugin() { override fun onEnable() { println(description) println(description.id) val pluginId = description.id assertTrue { PluginManager.plugins.first { it.description.id == pluginId } === PluginWithPluginYml } } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml-can-use-libraries-while-clinit/clinit-library/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml-can-use-libraries-while-clinit/clinit-library/resources/mvn.txt ================================================ net.mamoe.consoleit.plugin-with-yml:plugin-library:0.0.0 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml-can-use-libraries-while-clinit/clinit-library/src/PluginLibrary.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.console.itest.pluginwithpluginyml.library public object PluginLibrary { @JvmStatic public fun ok() { println("Plugin with plugin.yml using libraries under clinit ok") } } ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml-can-use-libraries-while-clinit/resources/META-INF/mirai-console-plugin/dependencies-private.txt ================================================ net.mamoe.consoleit.plugin-with-yml:plugin-library:0.0.0 ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml-can-use-libraries-while-clinit/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2023 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/dev/LICENSE # net.mamoe.console.itest.pluginwithpluginyml.clinit.PluginWithPluginYmlClinitTest ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml-can-use-libraries-while-clinit/resources/plugin.yml ================================================ id: net.mamoe.console.itest.plugin-with-yml-can-use-library-while-clinit version: 0.0.0 dependencies: - net.mamoe.console.itest.plugin-with-yml ================================================ FILE: mirai-console/backend/integration-test/testers/plugin-with-pluginyml-can-use-libraries-while-clinit/src/PluginWithPluginYmlClinitTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.console.itest.pluginwithpluginyml.clinit import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin internal class PluginWithPluginYmlClinitTest : KotlinPlugin() { companion object { init { // this is <clinit> Thread.dumpStack() Class.forName("net.mamoe.console.itest.pluginwithpluginyml.library.PluginLibrary") .getMethod("ok").invoke(null) } } } ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/module-service-loader-impl/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/module-service-loader-impl/resources/META-INF/services/net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/module-service-loader-impl/resources/test-res.txt ================================================ service-loader-impl ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/module-service-loader-impl/src/ServiceImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest.mod.serviceimpl import net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef public class ServiceImpl : ServiceTypedef { override fun act() { Throwable("Stacktrace").printStackTrace(System.out) } } ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/module-service-loader-typedef/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/module-service-loader-typedef/resources/test-res.txt ================================================ service-loader-typedef ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/module-service-loader-typedef/src/ServiceTypedef.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.integrationtest.mod.servicetypedef public interface ServiceTypedef { public fun act() {} } ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.console.itest.serviceloader.PMain ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/resources/test-res.txt ================================================ from plugin ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/.nested-module.txt ================================================ ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/resources/META-INF/services/net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.console.itest.serviceloader.ndep.PS ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.console.itest.serviceloader.ndep.PMain ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/resources/test-res.txt ================================================ from 2nd plugin ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/service-loader-2dep-plugin/src/PMain.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.console.itest.serviceloader.ndep import net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info import java.util.* import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue internal class PS : ServiceTypedef internal object PMain : KotlinPlugin(JvmPluginDescription("net.mamoe.console.itest.serviceloader-ndp", "0.0.0") { dependsOn("net.mamoe.console.itest.serviceloader") }) { override fun onEnable() { val loader = ServiceLoader.load( Class.forName("net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef"), javaClass.classLoader, ) val services = loader.asSequence().map { it.javaClass.name }.toMutableList() services.sort() services.forEach { service -> logger.info { "Service: $service" } } assertEquals( mutableListOf( "net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl", "net.mamoe.console.itest.serviceloader.ndep.PS", ), services ) assertEquals( "from 2nd plugin", javaClass.getResourceAsStream("/test-res.txt")!!.reader().use { it.readText() }.trim(), ) val tstRes = javaClass.classLoader.getResources("test-res.txt").asSequence().onEach { println(it) }.toMutableList() // /service-loader-2dep-plugin-0.0.0.jar!/test-res.txt // /service-loader-0.0.0.jar!/test-res.txt // /module-service-loader-typedef-0.0.0.jar!/test-res.txt // /module-service-loader-impl-0.0.0.jar!/test-res.txt assertEquals(4, tstRes.size) assertNotNull(javaClass.getResource("/net/mamoe/console/it/psl/PluginSharedLib.class").also { println(it) }) assertEquals( 1, javaClass.classLoader.getResources("net/mamoe/console/it/psl/PluginSharedLib.class") .asSequence().toList() .also { println(it) }.size ) // ************************* resources loading tests ************************* val miraiConsoleClassPath = "net/mamoe/mirai/console/MiraiConsole.class" val miraiBotClassPath = "net/mamoe/mirai/Bot.class" val allClassesPath = "META-INF/mirai-console/allclasses.txt" fun ClassLoader.getSizeOfResources(path: String): Int { return this.getResources(path).toList().size } assertNull(javaClass.getResource("/$miraiConsoleClassPath")) assertNull(javaClass.classLoader.getResource(miraiConsoleClassPath)) val miraiConsoleClassResourceCount = javaClass.classLoader.getSizeOfResources(miraiConsoleClassPath) .also { assertEquals(0, it) } assertNull(javaClass.getResource("/$miraiBotClassPath")) assertNull(javaClass.classLoader.getResource(miraiBotClassPath)) val allClassesResourceCount = javaClass.classLoader.getSizeOfResources(allClassesPath) .also { assertEquals(0, it) } jvmPluginClasspath.shouldResolveConsoleSystemResource = true assertNotNull(javaClass.classLoader.getResource(miraiConsoleClassPath)) assertNotNull(javaClass.classLoader.getResource(miraiBotClassPath)) assertTrue(javaClass.classLoader.getSizeOfResources(miraiConsoleClassPath) > miraiConsoleClassResourceCount) assertTrue(javaClass.classLoader.getSizeOfResources(allClassesPath) > allClassesResourceCount) jvmPluginClasspath.shouldResolveConsoleSystemResource = false assertNull(javaClass.getResource("/$miraiConsoleClassPath")) assertNull(javaClass.classLoader.getResource(miraiConsoleClassPath)) assertEquals(0, javaClass.classLoader.getSizeOfResources(miraiConsoleClassPath)) assertNull(javaClass.getResource("/$miraiBotClassPath")) assertNull(javaClass.classLoader.getResource(miraiBotClassPath)) assertEquals(0, javaClass.classLoader.getSizeOfResources(allClassesPath)) } } ================================================ FILE: mirai-console/backend/integration-test/testers/service-loader/src/PMain.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.console.itest.serviceloader import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info import java.io.File import java.util.* import kotlin.test.assertEquals internal object PMain : KotlinPlugin(JvmPluginDescription("net.mamoe.console.itest.serviceloader", "0.0.0")) { init { val access = jvmPluginClasspath val sharedCL = access.pluginSharedLibrariesClassLoader access.addToPath(sharedCL, File("modules/module-service-loader-typedef-0.0.0.jar")) access.addToPath(sharedCL, File("modules/module-service-loader-impl-0.0.0.jar")) } override fun onEnable() { @Suppress("LocalVariableName") val ServiceTypedef = Class.forName("net.mamoe.console.integrationtest.mod.servicetypedef.ServiceTypedef") val loader = ServiceLoader.load( ServiceTypedef, javaClass.classLoader, ).toList() val services = loader.asSequence().map { it.javaClass.name }.toMutableList() services.forEach { service -> logger.info { "Service: $service" } } assertEquals(mutableListOf("net.mamoe.console.integrationtest.mod.serviceimpl.ServiceImpl"), services) ServiceTypedef.getMethod("act").invoke(loader.first()) assertEquals( "from plugin", javaClass.getResourceAsStream("/test-res.txt")!!.reader().use { it.readText() }.trim(), ) val tstRes = javaClass.classLoader.getResources("test-res.txt").asSequence().onEach { println(it) }.toMutableList() // /service-loader-0.0.0.jar!/test-res.txt // /module-service-loader-typedef-0.0.0.jar!/test-res.txt // /module-service-loader-impl-0.0.0.jar!/test-res.txt assertEquals(3, tstRes.size) assertEquals( mutableListOf(), javaClass.classLoader.getResources("something/not/exists.bin").asSequence().toMutableList() ) } } ================================================ FILE: mirai-console/backend/integration-test/testers/tester.template.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") kotlin("plugin.serialization") id("java") } version = "0.0.0" kotlin { explicitApiWarning() } dependencies { api(project(":mirai-console.integration-test")) } ================================================ FILE: mirai-console/backend/mirai-console/.gitignore ================================================ src/internal/MiraiConsoleBuildConstants.kt src/internal/MiraiConsoleBuildDependencies.kt ================================================ FILE: mirai-console/backend/mirai-console/README.md ================================================ # Mirai Console - Backend Mirai Console 后端模块. 发布为 `net.mamoe:mirai-console`. ================================================ FILE: mirai-console/backend/mirai-console/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UnusedImport") import BinaryCompatibilityConfigurator.configureBinaryValidator import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter plugins { kotlin("jvm") kotlin("plugin.serialization") id("java") `maven-publish` id("me.him188.kotlin-jvm-blocking-bridge") id("me.him188.kotlin-dynamic-delegation") } version = Versions.console description = "Mirai Console Backend" kotlin { explicitApiWarning() optInForAllSourceSets("kotlinx.serialization.ExperimentalSerializationApi") optInForTestSourceSets("net.mamoe.mirai.console.ConsoleFrontEndImplementation") optInForTestSourceSets("net.mamoe.mirai.console.ConsoleExperimentalApi") optInForTestSourceSets("net.mamoe.mirai.console.ConsoleInternalApi") } // 搜索 mirai-console (包括 core) 直接使用并对外公开的类 (api) configurations.create("consoleRuntimeClasspath").attributes { attribute( Usage.USAGE_ATTRIBUTE, project.objects.named(Usage::class.java, Usage.JAVA_API) ) attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm) }.also { consoleRuntimeClasspath -> consoleRuntimeClasspath.exclude(group = "io.ktor") } dependencies { compileAndTestRuntime(project(":mirai-core-api")) compileAndTestRuntime(project(":mirai-core-utils")) compileAndTestRuntime(`kotlin-stdlib-jdk8`) compileAndTestRuntime(`kotlinx-atomicfu`) compileAndTestRuntime(`kotlinx-coroutines-core`) compileAndTestRuntime(`kotlinx-serialization-core`) compileAndTestRuntime(`kotlinx-serialization-json`) compileAndTestRuntime(`kotlin-reflect`) implementation(project(":mirai-console-compiler-annotations")) smartImplementation(`yamlkt`) smartImplementation(`jetbrains-annotations`) smartImplementation(`caller-finder`) smartImplementation(`maven-resolver-api`) smartImplementation(`maven-resolver-provider`) smartImplementation(`maven-resolver-impl`) smartImplementation(`maven-resolver-connector-basic`) smartImplementation(`maven-resolver-transport-http`) smartImplementation(`slf4j-api`) smartImplementation(`kotlin-jvm-blocking-bridge`) smartImplementation(`kotlin-dynamic-delegation`) smartApi(`kotlinx-coroutines-jdk8`) testApi(project(":mirai-core")) testApi(`kotlin-stdlib-jdk8`) testApi(`kotlinx-coroutines-test`) "consoleRuntimeClasspath"(project) "consoleRuntimeClasspath"(project(":mirai-core-utils")) "consoleRuntimeClasspath"(project(":mirai-core-api")) "consoleRuntimeClasspath"(project(":mirai-core")) } tasks { val task = register("generateBuildConstants") { group = "mirai" doLast { val now = Instant.now() project.file("src/internal/MiraiConsoleBuildConstants.kt").writeText( project.file("src/internal/MiraiConsoleBuildConstants.kt.template").readText() .replace("GENERATION_DATE", now.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)) .replace("BUILD_DATE", now.epochSecond.toString()) .replace("VERSION_CONSTANT", project.version.toString()) ) } } afterEvaluate { getByName("compileKotlin").dependsOn(task) } } tasks.withType<Test> { this.jvmArgs("-Dmirai.console.skip-end-user-readme") } tasks.getByName("compileKotlin").dependsOn( DependencyDumper.registerDumpTaskKtSrc( project, "consoleRuntimeClasspath", project.file("src/internal/MiraiConsoleBuildDependencies.kt"), "net.mamoe.mirai.console.internal.MiraiConsoleBuildDependencies" ) ) val graphDump = DependencyDumper.registerDumpClassGraph(project, "consoleRuntimeClasspath", "allclasses.txt") tasks.named<Copy>("processResources").configure { from(graphDump) { into("META-INF/mirai-console") } } configurePublishing("mirai-console") configureBinaryValidator(null) ================================================ FILE: mirai-console/backend/mirai-console/compatibility-validation/jvm/api/jvm.api ================================================ public final class net/mamoe/mirai/console/MalformedMiraiConsoleImplementationError : java/lang/Error { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public fun <init> (Ljava/lang/Throwable;)V } public abstract interface class net/mamoe/mirai/console/MiraiConsole : kotlinx/coroutines/CoroutineScope { public static final field INSTANCE Lnet/mamoe/mirai/console/MiraiConsole$INSTANCE; public abstract fun getBuildDate ()Ljava/time/Instant; public abstract fun getBuiltInPluginLoaders ()Ljava/util/List; public abstract fun getMainLogger ()Lnet/mamoe/mirai/utils/MiraiLogger; public fun getPluginCenter ()Lnet/mamoe/mirai/console/plugin/center/PluginCenter; public abstract fun getPluginManager ()Lnet/mamoe/mirai/console/plugin/PluginManager; public abstract fun getRootPath ()Ljava/nio/file/Path; public abstract fun getVersion ()Lnet/mamoe/mirai/console/util/SemVersion; public abstract fun isAnsiSupported ()Z } public final class net/mamoe/mirai/console/MiraiConsole$INSTANCE : net/mamoe/mirai/console/MiraiConsole { public static synthetic fun addBot$default (Lnet/mamoe/mirai/console/MiraiConsole$INSTANCE;JLjava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/Bot; public static synthetic fun addBot$default (Lnet/mamoe/mirai/console/MiraiConsole$INSTANCE;JLnet/mamoe/mirai/auth/BotAuthorization;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/Bot; public static synthetic fun addBot$default (Lnet/mamoe/mirai/console/MiraiConsole$INSTANCE;J[BLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/Bot; public fun getBuildDate ()Ljava/time/Instant; public fun getBuiltInPluginLoaders ()Ljava/util/List; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun getJob ()Lkotlinx/coroutines/Job; public fun getMainLogger ()Lnet/mamoe/mirai/utils/MiraiLogger; public fun getPluginCenter ()Lnet/mamoe/mirai/console/plugin/center/PluginCenter; public fun getPluginManager ()Lnet/mamoe/mirai/console/plugin/PluginManager; public fun getRootPath ()Ljava/nio/file/Path; public fun getVersion ()Lnet/mamoe/mirai/console/util/SemVersion; public final fun isActive ()Z public fun isAnsiSupported ()Z } public abstract interface class net/mamoe/mirai/console/MiraiConsoleFrontEndDescription { public fun getCompatibleBackendVersion ()Lnet/mamoe/mirai/console/util/SemVersion; public abstract fun getName ()Ljava/lang/String; public abstract fun getVendor ()Ljava/lang/String; public abstract fun getVersion ()Lnet/mamoe/mirai/console/util/SemVersion; public fun render ()Ljava/lang/String; } public final class net/mamoe/mirai/console/MiraiConsoleImplementation$ConsoleLaunchOptions { public field crashWhenPluginLoadFailed Z public fun <init> ()V } public final class net/mamoe/mirai/console/MiraiConsoleKt { public static final fun getRootDir (Lnet/mamoe/mirai/console/MiraiConsole;)Ljava/io/File; } public abstract class net/mamoe/mirai/console/command/AbstractCommand : net/mamoe/mirai/console/command/Command { public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;)V public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)V public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getDescription ()Ljava/lang/String; public final fun getOwner ()Lnet/mamoe/mirai/console/command/CommandOwner; public fun getPermission ()Lnet/mamoe/mirai/console/permission/Permission; public fun getPrefixOptional ()Z public final fun getPrimaryName ()Ljava/lang/String; public final fun getSecondaryNames ()[Ljava/lang/String; public fun getUsage ()Ljava/lang/String; } public abstract class net/mamoe/mirai/console/command/AbstractCommandSender : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/command/CommandSender { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getUser ()Lnet/mamoe/mirai/contact/User; public abstract fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/console/command/AbstractPluginCustomCommandSender : net/mamoe/mirai/console/command/AbstractCommandSender, net/mamoe/mirai/console/command/PluginCustomCommandSender { public fun <init> (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getName ()Ljava/lang/String; public final fun getOwner ()Lnet/mamoe/mirai/console/plugin/Plugin; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getUser ()Lnet/mamoe/mirai/contact/User; public fun isAnsiSupported ()Z public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected abstract fun sendMessage0 (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/console/command/AbstractPluginCustomCommandSenderJ : net/mamoe/mirai/console/command/AbstractPluginCustomCommandSender { public fun <init> (Lnet/mamoe/mirai/console/plugin/Plugin;)V public final fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected final fun sendMessage0 (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; protected abstract fun sendMessageImpl (Ljava/lang/String;)V } public abstract class net/mamoe/mirai/console/command/AbstractUserCommandSender : net/mamoe/mirai/console/command/AbstractCommandSender, net/mamoe/mirai/console/command/UserCommandSender { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getName ()Ljava/lang/String; 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 fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/BuiltInCommands { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands; public final fun getParentPermission ()Lnet/mamoe/mirai/console/permission/Permission; } public final class net/mamoe/mirai/console/command/BuiltInCommands$AutoLoginCommand : net/mamoe/mirai/console/command/CompositeCommand, net/mamoe/mirai/console/command/BuiltInCommandInternal { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands$AutoLoginCommand; public static synthetic fun add$default (Lnet/mamoe/mirai/console/command/BuiltInCommands$AutoLoginCommand;Lnet/mamoe/mirai/console/command/CommandSender;JLjava/lang/String;Lnet/mamoe/mirai/console/internal/data/builtins/AutoLoginConfig$Account$PasswordKind;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun clear (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun list (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun remove (Lnet/mamoe/mirai/console/command/CommandSender;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/BuiltInCommands$HelpCommand : net/mamoe/mirai/console/command/SimpleCommand, net/mamoe/mirai/console/command/BuiltInCommandInternal { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands$HelpCommand; public static final fun generateDefaultHelp (Lnet/mamoe/mirai/console/permission/PermitteeId;)Ljava/lang/String; public final fun handle (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/BuiltInCommands$LoginCommand : net/mamoe/mirai/console/command/SimpleCommand, net/mamoe/mirai/console/command/BuiltInCommandInternal { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands$LoginCommand; public final fun handle (Lnet/mamoe/mirai/console/command/CommandSender;JLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun handle (Lnet/mamoe/mirai/console/command/CommandSender;JLjava/lang/String;Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun handle (Lnet/mamoe/mirai/console/command/CommandSender;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun handle$default (Lnet/mamoe/mirai/console/command/BuiltInCommands$LoginCommand;Lnet/mamoe/mirai/console/command/CommandSender;JLjava/lang/String;Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/BuiltInCommands$LogoutCommand : net/mamoe/mirai/console/command/SimpleCommand, net/mamoe/mirai/console/command/BuiltInCommandInternal { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands$LogoutCommand; public final fun handle (Lnet/mamoe/mirai/console/command/CommandSender;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/BuiltInCommands$PermissionCommand : net/mamoe/mirai/console/command/CompositeCommand, net/mamoe/mirai/console/command/BuiltInCommandInternal { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands$PermissionCommand; public final fun cancel (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun cancelAll (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun listPermissions (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun permit (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun permittedPermissions (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/permission/PermitteeId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun permittedPermissions$default (Lnet/mamoe/mirai/console/command/BuiltInCommands$PermissionCommand;Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/permission/PermitteeId;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/BuiltInCommands$StatusCommand : net/mamoe/mirai/console/command/SimpleCommand, net/mamoe/mirai/console/command/BuiltInCommandInternal { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands$StatusCommand; public final fun handle (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/BuiltInCommands$StopCommand : net/mamoe/mirai/console/command/SimpleCommand, net/mamoe/mirai/console/command/BuiltInCommandInternal { public static final field INSTANCE Lnet/mamoe/mirai/console/command/BuiltInCommands$StopCommand; public final fun handle (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/console/command/Command { public static final field Companion Lnet/mamoe/mirai/console/command/Command$Companion; public static fun checkCommandName (Ljava/lang/String;)V public static fun getAllNames (Lnet/mamoe/mirai/console/command/Command;)[Ljava/lang/String; public abstract fun getDescription ()Ljava/lang/String; public abstract fun getOverloads ()Ljava/util/List; public abstract fun getOwner ()Lnet/mamoe/mirai/console/command/CommandOwner; public abstract fun getPermission ()Lnet/mamoe/mirai/console/permission/Permission; public abstract fun getPrefixOptional ()Z public abstract fun getPrimaryName ()Ljava/lang/String; public abstract fun getSecondaryNames ()[Ljava/lang/String; public abstract fun getUsage ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/Command$Companion { public final fun checkCommandName (Ljava/lang/String;)V public final fun getAllNames (Lnet/mamoe/mirai/console/command/Command;)[Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/command/CommandContext { public abstract fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun getSender ()Lnet/mamoe/mirai/console/command/CommandSender; } public final class net/mamoe/mirai/console/command/CommandExecuteResult$ExecutionFailed : net/mamoe/mirai/console/command/CommandExecuteResult$Failure { public fun <init> (Ljava/lang/Throwable;Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/parse/CommandCall;Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall;)V public fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall; public fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public fun getException ()Ljava/lang/Throwable; public fun getResolvedCall ()Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall; } public abstract class net/mamoe/mirai/console/command/CommandExecuteResult$Failure : net/mamoe/mirai/console/command/CommandExecuteResult { public fun <init> ()V } public final class net/mamoe/mirai/console/command/CommandExecuteResult$IllegalArgument : net/mamoe/mirai/console/command/CommandExecuteResult$Failure { public fun <init> (Lnet/mamoe/mirai/console/command/IllegalCommandArgumentException;Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/parse/CommandCall;Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall;)V public fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall; public fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public synthetic fun getException ()Ljava/lang/Throwable; public fun getException ()Lnet/mamoe/mirai/console/command/IllegalCommandArgumentException; public fun getResolvedCall ()Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall; } public final class net/mamoe/mirai/console/command/CommandExecuteResult$Intercepted : net/mamoe/mirai/console/command/CommandExecuteResult$Failure { public fun <init> (Lnet/mamoe/mirai/console/command/parse/CommandCall;Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall;Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/resolve/InterceptedReason;)V public fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall; public fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public synthetic fun getException ()Ljava/lang/Throwable; public fun getException ()Ljava/lang/Void; public final fun getReason ()Lnet/mamoe/mirai/console/command/resolve/InterceptedReason; public fun getResolvedCall ()Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall; } public final class net/mamoe/mirai/console/command/CommandExecuteResult$PermissionDenied : net/mamoe/mirai/console/command/CommandExecuteResult$Failure { public fun <init> (Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/parse/CommandCall;Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall;)V public fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall; public fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public synthetic fun getException ()Ljava/lang/Throwable; public fun getException ()Ljava/lang/Void; public fun getResolvedCall ()Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall; } public final class net/mamoe/mirai/console/command/CommandExecuteResult$Success : net/mamoe/mirai/console/command/CommandExecuteResult { public fun <init> (Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/parse/CommandCall;Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall;)V public fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall; public fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public synthetic fun getException ()Ljava/lang/Throwable; public fun getException ()Ljava/lang/Void; public fun getResolvedCall ()Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall; } public final class net/mamoe/mirai/console/command/CommandExecuteResult$UnmatchedSignature : net/mamoe/mirai/console/command/CommandExecuteResult$Failure { public fun <init> (Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/parse/CommandCall;Ljava/util/List;)V public fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall; public fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public synthetic fun getException ()Ljava/lang/Throwable; public fun getException ()Ljava/lang/Void; public final fun getFailureReasons ()Ljava/util/List; public fun getResolvedCall ()Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall; } public final class net/mamoe/mirai/console/command/CommandExecuteResult$UnresolvedCommand : net/mamoe/mirai/console/command/CommandExecuteResult$Failure { public fun <init> (Lnet/mamoe/mirai/console/command/parse/CommandCall;)V public fun getCall ()Lnet/mamoe/mirai/console/command/parse/CommandCall; public fun getCommand ()Ljava/lang/Void; public synthetic fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public synthetic fun getException ()Ljava/lang/Throwable; public fun getException ()Ljava/lang/Void; public fun getResolvedCall ()Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall; } public final class net/mamoe/mirai/console/command/CommandExecuteResultKt { } public final class net/mamoe/mirai/console/command/CommandExecutionException : java/lang/RuntimeException { public fun <init> (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/command/Command;Ljava/lang/String;Ljava/lang/Throwable;)V public final fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public final fun getName ()Ljava/lang/String; public final fun getSender ()Lnet/mamoe/mirai/console/command/CommandSender; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/command/CommandManager { public static final field INSTANCE Lnet/mamoe/mirai/console/command/CommandManager$INSTANCE; public static synthetic fun executeCommand$default (Lnet/mamoe/mirai/console/command/CommandManager;Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/message/data/Message;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun executeCommand$default (Lnet/mamoe/mirai/console/command/CommandManager;Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/message/data/Message;ZILjava/lang/Object;)Lnet/mamoe/mirai/console/command/CommandExecuteResult; public static synthetic fun executeCommand$default (Lnet/mamoe/mirai/console/command/CommandManager;Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/message/data/Message;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public abstract fun findDuplicateCommand (Lnet/mamoe/mirai/console/command/Command;)Lnet/mamoe/mirai/console/command/Command; public abstract fun getAllRegisteredCommands ()Ljava/util/List; public abstract fun getCommandPrefix ()Ljava/lang/String; public abstract fun getRegisteredCommands (Lnet/mamoe/mirai/console/command/CommandOwner;)Ljava/util/List; public abstract fun isCommandRegistered (Lnet/mamoe/mirai/console/command/Command;)Z public abstract fun matchCommand (Ljava/lang/String;)Lnet/mamoe/mirai/console/command/Command; public abstract fun registerCommand (Lnet/mamoe/mirai/console/command/Command;Z)Z public static synthetic fun registerCommand$default (Lnet/mamoe/mirai/console/command/CommandManager;Lnet/mamoe/mirai/console/command/Command;ZILjava/lang/Object;)Z public abstract fun unregisterAllCommands (Lnet/mamoe/mirai/console/command/CommandOwner;)V public abstract fun unregisterCommand (Lnet/mamoe/mirai/console/command/Command;)Z } public final class net/mamoe/mirai/console/command/CommandManager$INSTANCE : net/mamoe/mirai/console/command/CommandManager { public final synthetic fun findDuplicate (Lnet/mamoe/mirai/console/command/Command;)Lnet/mamoe/mirai/console/command/Command; public fun findDuplicateCommand (Lnet/mamoe/mirai/console/command/Command;)Lnet/mamoe/mirai/console/command/Command; public fun getAllRegisteredCommands ()Ljava/util/List; public fun getCommandPrefix ()Ljava/lang/String; public fun getRegisteredCommands (Lnet/mamoe/mirai/console/command/CommandOwner;)Ljava/util/List; public fun isCommandRegistered (Lnet/mamoe/mirai/console/command/Command;)Z public final synthetic fun isRegistered (Lnet/mamoe/mirai/console/command/Command;)Z public fun matchCommand (Ljava/lang/String;)Lnet/mamoe/mirai/console/command/Command; public final synthetic fun register (Lnet/mamoe/mirai/console/command/Command;Z)Z public static synthetic fun register$default (Lnet/mamoe/mirai/console/command/CommandManager$INSTANCE;Lnet/mamoe/mirai/console/command/Command;ZILjava/lang/Object;)Z public fun registerCommand (Lnet/mamoe/mirai/console/command/Command;Z)Z public final synthetic fun registeredCommands0 (Lnet/mamoe/mirai/console/command/CommandOwner;)Ljava/util/List; public final synthetic fun unregister (Lnet/mamoe/mirai/console/command/Command;)Z public final synthetic fun unregisterAll (Lnet/mamoe/mirai/console/command/CommandOwner;)V public fun unregisterAllCommands (Lnet/mamoe/mirai/console/command/CommandOwner;)V public fun unregisterCommand (Lnet/mamoe/mirai/console/command/Command;)Z } public final class net/mamoe/mirai/console/command/CommandManagerKt { public static synthetic fun execute0$default (Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/CommandSender;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun execute0$default (Lnet/mamoe/mirai/console/command/Command;Lnet/mamoe/mirai/console/command/CommandSender;[Lnet/mamoe/mirai/message/data/Message;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun execute0$default (Lnet/mamoe/mirai/console/command/CommandSender;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/console/command/CommandOwner : net/mamoe/mirai/console/permission/PermissionIdNamespace { public abstract fun getParentPermission ()Lnet/mamoe/mirai/console/permission/Permission; } public final class net/mamoe/mirai/console/command/CommandPermissionDeniedException : java/lang/RuntimeException { public fun <init> (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/command/Command;)V public final fun getCommand ()Lnet/mamoe/mirai/console/command/Command; public final fun getCommandSender ()Lnet/mamoe/mirai/console/command/CommandSender; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/command/CommandSender : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/permission/Permittee { public static final field Companion Lnet/mamoe/mirai/console/command/CommandSender$Companion; public static fun from (Lnet/mamoe/mirai/event/events/FriendMessageEvent;)Lnet/mamoe/mirai/console/command/FriendCommandSenderOnMessage; public static fun from (Lnet/mamoe/mirai/event/events/GroupMessageEvent;)Lnet/mamoe/mirai/console/command/MemberCommandSenderOnMessage; public static fun from (Lnet/mamoe/mirai/event/events/GroupTempMessageEvent;)Lnet/mamoe/mirai/console/command/GroupTempCommandSenderOnMessage; public static fun from (Lnet/mamoe/mirai/event/events/MessageEvent;)Lnet/mamoe/mirai/console/command/CommandSenderOnMessage; public static fun from (Lnet/mamoe/mirai/event/events/MessageSyncEvent;)Lnet/mamoe/mirai/console/command/OtherClientCommandSenderOnMessageSync; public static fun from (Lnet/mamoe/mirai/event/events/OtherClientMessageEvent;)Lnet/mamoe/mirai/console/command/OtherClientCommandSenderOnMessage; public static fun from (Lnet/mamoe/mirai/event/events/StrangerMessageEvent;)Lnet/mamoe/mirai/console/command/StrangerCommandSenderOnMessage; public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getName ()Ljava/lang/String; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getUser ()Lnet/mamoe/mirai/contact/User; public static fun of (Lnet/mamoe/mirai/contact/Friend;)Lnet/mamoe/mirai/console/command/FriendCommandSender; public static fun of (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/console/command/MemberCommandSender; public static fun of (Lnet/mamoe/mirai/contact/Member;Z)Lnet/mamoe/mirai/console/command/UserCommandSender; public static fun of (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/console/command/GroupTempCommandSender; public static fun of (Lnet/mamoe/mirai/contact/OtherClient;)Lnet/mamoe/mirai/console/command/OtherClientCommandSender; public static fun of (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/console/command/StrangerCommandSender; public static fun of (Lnet/mamoe/mirai/contact/User;Z)Lnet/mamoe/mirai/console/command/UserCommandSender; 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 final class net/mamoe/mirai/console/command/CommandSender$Companion { public final fun from (Lnet/mamoe/mirai/event/events/FriendMessageEvent;)Lnet/mamoe/mirai/console/command/FriendCommandSenderOnMessage; public final fun from (Lnet/mamoe/mirai/event/events/GroupMessageEvent;)Lnet/mamoe/mirai/console/command/MemberCommandSenderOnMessage; public final fun from (Lnet/mamoe/mirai/event/events/GroupTempMessageEvent;)Lnet/mamoe/mirai/console/command/GroupTempCommandSenderOnMessage; public final fun from (Lnet/mamoe/mirai/event/events/MessageEvent;)Lnet/mamoe/mirai/console/command/CommandSenderOnMessage; public final fun from (Lnet/mamoe/mirai/event/events/MessageSyncEvent;)Lnet/mamoe/mirai/console/command/OtherClientCommandSenderOnMessageSync; public final fun from (Lnet/mamoe/mirai/event/events/OtherClientMessageEvent;)Lnet/mamoe/mirai/console/command/OtherClientCommandSenderOnMessage; public final fun from (Lnet/mamoe/mirai/event/events/StrangerMessageEvent;)Lnet/mamoe/mirai/console/command/StrangerCommandSenderOnMessage; public final fun of (Lnet/mamoe/mirai/contact/Friend;)Lnet/mamoe/mirai/console/command/FriendCommandSender; public final fun of (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/console/command/MemberCommandSender; public final fun of (Lnet/mamoe/mirai/contact/Member;Z)Lnet/mamoe/mirai/console/command/UserCommandSender; public final fun of (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/console/command/GroupTempCommandSender; public final fun of (Lnet/mamoe/mirai/contact/OtherClient;)Lnet/mamoe/mirai/console/command/OtherClientCommandSender; public final fun of (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/console/command/StrangerCommandSender; public final fun of (Lnet/mamoe/mirai/contact/User;Z)Lnet/mamoe/mirai/console/command/UserCommandSender; } public final class net/mamoe/mirai/console/command/CommandSenderKt { public static final synthetic fun fold (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun fold$default (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun getBotOrNull (Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/Bot; public static final fun getGroupOrNull (Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Group; public static final fun isConsole (Lnet/mamoe/mirai/console/command/CommandSender;)Z public static final fun isNotConsole (Lnet/mamoe/mirai/console/command/CommandSender;)Z public static final fun isNotUser (Lnet/mamoe/mirai/console/command/CommandSender;)Z public static final fun isSystem (Lnet/mamoe/mirai/console/command/CommandSender;)Z public static final fun isUser (Lnet/mamoe/mirai/console/command/CommandSender;)Z } public abstract interface class net/mamoe/mirai/console/command/CommandSenderOnMessage : net/mamoe/mirai/console/command/CommandSender { public abstract fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; } public abstract class net/mamoe/mirai/console/command/CompositeCommand : net/mamoe/mirai/console/command/AbstractCommand, net/mamoe/mirai/console/command/Command, net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware { public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public final fun getOverloads ()Ljava/util/List; public fun getUsage ()Ljava/lang/String; } protected abstract interface annotation class net/mamoe/mirai/console/command/CompositeCommand$Description : java/lang/annotation/Annotation { public abstract fun value ()Ljava/lang/String; } protected abstract interface annotation class net/mamoe/mirai/console/command/CompositeCommand$SubCommand : java/lang/annotation/Annotation { public abstract fun value ()[Ljava/lang/String; } public final class net/mamoe/mirai/console/command/ConsoleCommandOwner : net/mamoe/mirai/console/command/CommandOwner { public static final field INSTANCE Lnet/mamoe/mirai/console/command/ConsoleCommandOwner; public fun getParentPermission ()Lnet/mamoe/mirai/console/permission/Permission; public fun permissionId (Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/PermissionId; } public final class net/mamoe/mirai/console/command/ConsoleCommandSender : net/mamoe/mirai/console/command/AbstractCommandSender, net/mamoe/mirai/console/command/SystemCommandSender { public static final field INSTANCE Lnet/mamoe/mirai/console/command/ConsoleCommandSender; public static final field NAME Ljava/lang/String; public fun getBot ()Ljava/lang/Void; public synthetic fun getBot ()Lnet/mamoe/mirai/Bot; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getName ()Ljava/lang/String; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$Console; public synthetic fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public fun getSubject ()Ljava/lang/Void; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getUser ()Ljava/lang/Void; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; public fun isAnsiSupported ()Z public final fun sendMessage (Ljava/lang/String;)Ljava/lang/Void; public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/console/command/FailureReason$ArgumentLengthMismatch : net/mamoe/mirai/console/command/FailureReason { public fun <init> ()V } public abstract class net/mamoe/mirai/console/command/FailureReason$InapplicableArgument : net/mamoe/mirai/console/command/FailureReason { public fun <init> ()V public abstract fun getParameter ()Lnet/mamoe/mirai/console/command/descriptor/CommandParameter; } public final class net/mamoe/mirai/console/command/FailureReason$InapplicableReceiverArgument : net/mamoe/mirai/console/command/FailureReason$InapplicableArgument { public fun <init> (Lnet/mamoe/mirai/console/command/descriptor/CommandReceiverParameter;Lnet/mamoe/mirai/console/command/CommandSender;)V public final fun getArgument ()Lnet/mamoe/mirai/console/command/CommandSender; public synthetic fun getParameter ()Lnet/mamoe/mirai/console/command/descriptor/CommandParameter; public fun getParameter ()Lnet/mamoe/mirai/console/command/descriptor/CommandReceiverParameter; } public final class net/mamoe/mirai/console/command/FailureReason$InapplicableValueArgument : net/mamoe/mirai/console/command/FailureReason$InapplicableArgument { public fun <init> (Lnet/mamoe/mirai/console/command/descriptor/CommandValueParameter;Lnet/mamoe/mirai/console/command/parse/CommandValueArgument;)V public final fun getArgument ()Lnet/mamoe/mirai/console/command/parse/CommandValueArgument; public synthetic fun getParameter ()Lnet/mamoe/mirai/console/command/descriptor/CommandParameter; public fun getParameter ()Lnet/mamoe/mirai/console/command/descriptor/CommandValueParameter; } public final class net/mamoe/mirai/console/command/FailureReason$NotEnoughArguments : net/mamoe/mirai/console/command/FailureReason$ArgumentLengthMismatch { public static final field INSTANCE Lnet/mamoe/mirai/console/command/FailureReason$NotEnoughArguments; } public final class net/mamoe/mirai/console/command/FailureReason$ResolutionAmbiguity : net/mamoe/mirai/console/command/FailureReason { public fun <init> (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun copy (Ljava/util/List;)Lnet/mamoe/mirai/console/command/FailureReason$ResolutionAmbiguity; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/command/FailureReason$ResolutionAmbiguity;Ljava/util/List;ILjava/lang/Object;)Lnet/mamoe/mirai/console/command/FailureReason$ResolutionAmbiguity; public fun equals (Ljava/lang/Object;)Z public final fun getAllCandidates ()Ljava/util/List; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/FailureReason$TooManyArguments : net/mamoe/mirai/console/command/FailureReason$ArgumentLengthMismatch { public static final field INSTANCE Lnet/mamoe/mirai/console/command/FailureReason$TooManyArguments; } public class net/mamoe/mirai/console/command/FriendCommandSender : net/mamoe/mirai/console/command/AbstractUserCommandSender, kotlinx/coroutines/CoroutineScope { public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getUser ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; 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 fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/FriendCommandSenderOnMessage : net/mamoe/mirai/console/command/FriendCommandSender, net/mamoe/mirai/console/command/CommandSenderOnMessage { public fun getFromEvent ()Lnet/mamoe/mirai/event/events/FriendMessageEvent; public synthetic fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; } public abstract interface class net/mamoe/mirai/console/command/GroupAwareCommandSender : net/mamoe/mirai/console/command/UserCommandSender { public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; } public class net/mamoe/mirai/console/command/GroupTempCommandSender : net/mamoe/mirai/console/command/TempCommandSender, kotlinx/coroutines/CoroutineScope { public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getUser ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; 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 fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/GroupTempCommandSenderOnMessage : net/mamoe/mirai/console/command/TempCommandSenderOnMessage, net/mamoe/mirai/console/command/CommandSenderOnMessage { public fun getFromEvent ()Lnet/mamoe/mirai/event/events/GroupTempMessageEvent; public synthetic fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; } public class net/mamoe/mirai/console/command/IllegalCommandArgumentException : java/lang/IllegalArgumentException { public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class net/mamoe/mirai/console/command/MemberCommandSender : net/mamoe/mirai/console/command/AbstractUserCommandSender, kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/command/GroupAwareCommandSender { public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Group; public final fun getUser ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; 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 fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/MemberCommandSenderOnMessage : net/mamoe/mirai/console/command/MemberCommandSender, net/mamoe/mirai/console/command/CommandSenderOnMessage { public fun getFromEvent ()Lnet/mamoe/mirai/event/events/GroupMessageEvent; public synthetic fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; } public class net/mamoe/mirai/console/command/OtherClientCommandSender : net/mamoe/mirai/console/command/AbstractCommandSender, kotlinx/coroutines/CoroutineScope { public final fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun getName ()Ljava/lang/String; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Friend; public final fun getUser ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; 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 fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/OtherClientCommandSenderOnMessage : net/mamoe/mirai/console/command/OtherClientCommandSender, net/mamoe/mirai/console/command/CommandSenderOnMessage { public synthetic fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; public fun getFromEvent ()Lnet/mamoe/mirai/event/events/OtherClientMessageEvent; } public final class net/mamoe/mirai/console/command/OtherClientCommandSenderOnMessageSync : net/mamoe/mirai/console/command/OtherClientCommandSender, net/mamoe/mirai/console/command/CommandSenderOnMessage { public synthetic fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; public fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageSyncEvent; } public abstract interface class net/mamoe/mirai/console/command/PluginCustomCommandSender : net/mamoe/mirai/console/command/CommandSender, net/mamoe/mirai/console/command/SystemCommandSender { public abstract fun getOwner ()Lnet/mamoe/mirai/console/plugin/Plugin; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public fun isAnsiSupported ()Z public abstract 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/console/command/PluginCustomCommandSender;Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract class net/mamoe/mirai/console/command/RawCommand : net/mamoe/mirai/console/command/Command { public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;Z)V public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getDescription ()Ljava/lang/String; public fun getOverloads ()Ljava/util/List; public fun getOwner ()Lnet/mamoe/mirai/console/command/CommandOwner; public fun getPermission ()Lnet/mamoe/mirai/console/permission/Permission; public fun getPrefixOptional ()Z public fun getPrimaryName ()Ljava/lang/String; public fun getSecondaryNames ()[Ljava/lang/String; public fun getUsage ()Ljava/lang/String; public fun onCommand (Lnet/mamoe/mirai/console/command/CommandContext;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onCommand (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract class net/mamoe/mirai/console/command/SimpleCommand : net/mamoe/mirai/console/command/AbstractCommand, net/mamoe/mirai/console/command/Command, net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware { public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public final fun getOverloads ()Ljava/util/List; public fun getUsage ()Ljava/lang/String; } protected abstract interface annotation class net/mamoe/mirai/console/command/SimpleCommand$Handler : java/lang/annotation/Annotation { } public class net/mamoe/mirai/console/command/StrangerCommandSender : net/mamoe/mirai/console/command/AbstractUserCommandSender, kotlinx/coroutines/CoroutineScope { public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public final fun getUser ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; 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 fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/StrangerCommandSenderOnMessage : net/mamoe/mirai/console/command/StrangerCommandSender, net/mamoe/mirai/console/command/CommandSenderOnMessage { public synthetic fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; public fun getFromEvent ()Lnet/mamoe/mirai/event/events/StrangerMessageEvent; } public abstract interface class net/mamoe/mirai/console/command/SystemCommandSender : net/mamoe/mirai/console/command/CommandSender { public abstract fun isAnsiSupported ()Z } public abstract class net/mamoe/mirai/console/command/TempCommandSender : net/mamoe/mirai/console/command/AbstractUserCommandSender, kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/command/GroupAwareCommandSender { public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getUser ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; } public abstract class net/mamoe/mirai/console/command/TempCommandSenderOnMessage : net/mamoe/mirai/console/command/GroupTempCommandSender, net/mamoe/mirai/console/command/CommandSenderOnMessage { public synthetic fun <init> (Lnet/mamoe/mirai/event/events/GroupTempMessageEvent;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getFromEvent ()Lnet/mamoe/mirai/event/events/GroupTempMessageEvent; public synthetic fun getFromEvent ()Lnet/mamoe/mirai/event/events/MessageEvent; } public abstract interface class net/mamoe/mirai/console/command/UserCommandSender : net/mamoe/mirai/console/command/CommandSender { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getUser ()Lnet/mamoe/mirai/contact/User; } public abstract class net/mamoe/mirai/console/command/descriptor/AbstractCommandValueArgumentParser : net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser { public static final field Companion Lnet/mamoe/mirai/console/command/descriptor/AbstractCommandValueArgumentParser$Companion; public fun <init> ()V protected static final synthetic fun checkArgument (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;ZLkotlin/jvm/functions/Function0;)V protected static final synthetic fun illegalArgument (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;Ljava/lang/String;Ljava/lang/Throwable;)Ljava/lang/Void; } public final class net/mamoe/mirai/console/command/descriptor/AbstractCommandValueArgumentParser$Companion { public static synthetic fun checkArgument$default (Lnet/mamoe/mirai/console/command/descriptor/AbstractCommandValueArgumentParser$Companion;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;ZLkotlin/jvm/functions/Function0;ILjava/lang/Object;)V public static synthetic fun illegalArgument$default (Lnet/mamoe/mirai/console/command/descriptor/AbstractCommandValueArgumentParser$Companion;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)Ljava/lang/Void; } public final class net/mamoe/mirai/console/command/descriptor/AbstractCommandValueParameter$UserDefinedType : net/mamoe/mirai/console/command/descriptor/AbstractCommandValueParameter { public static final field Companion Lnet/mamoe/mirai/console/command/descriptor/AbstractCommandValueParameter$UserDefinedType$Companion; public fun <init> (Ljava/lang/String;ZZLkotlin/reflect/KType;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Z public final fun component3 ()Z public final fun component4 ()Lkotlin/reflect/KType; public final fun copy (Ljava/lang/String;ZZLkotlin/reflect/KType;)Lnet/mamoe/mirai/console/command/descriptor/AbstractCommandValueParameter$UserDefinedType; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/command/descriptor/AbstractCommandValueParameter$UserDefinedType;Ljava/lang/String;ZZLkotlin/reflect/KType;ILjava/lang/Object;)Lnet/mamoe/mirai/console/command/descriptor/AbstractCommandValueParameter$UserDefinedType; public fun equals (Ljava/lang/Object;)Z public fun getName ()Ljava/lang/String; public fun getType ()Lkotlin/reflect/KType; public fun hashCode ()I public fun isOptional ()Z public fun isVararg ()Z public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/descriptor/AbstractCommandValueParameter$UserDefinedType$Companion { } public final class net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$Companion { public final fun isAcceptable (Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance;)Z public final fun isNotAcceptable (Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance;)Z } public final class net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$Direct : net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$Direct; } public final class net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$Impossible : net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$Impossible; } public final class net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$ResolutionAmbiguity : net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance { public fun <init> (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun copy (Ljava/util/List;)Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$ResolutionAmbiguity; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$ResolutionAmbiguity;Ljava/util/List;ILjava/lang/Object;)Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$ResolutionAmbiguity; public fun equals (Ljava/lang/Object;)Z public final fun getCandidates ()Ljava/util/List; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithContextualConversion : net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance { public fun <init> (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;)V public final fun component1 ()Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public final fun copy (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;)Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithContextualConversion; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithContextualConversion;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;ILjava/lang/Object;)Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithContextualConversion; public fun equals (Ljava/lang/Object;)Z public final fun getParser ()Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithTypeConversion : net/mamoe/mirai/console/command/descriptor/ArgumentAcceptance { public fun <init> (Lnet/mamoe/mirai/console/command/descriptor/TypeVariant;)V public final fun component1 ()Lnet/mamoe/mirai/console/command/descriptor/TypeVariant; public final fun copy (Lnet/mamoe/mirai/console/command/descriptor/TypeVariant;)Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithTypeConversion; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithTypeConversion;Lnet/mamoe/mirai/console/command/descriptor/TypeVariant;ILjava/lang/Object;)Lnet/mamoe/mirai/console/command/descriptor/ArgumentAcceptance$WithTypeConversion; public fun equals (Ljava/lang/Object;)Z public final fun getTypeVariant ()Lnet/mamoe/mirai/console/command/descriptor/TypeVariant; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/descriptor/BooleanValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/BooleanValueArgumentParser; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Boolean; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/ByteValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ByteValueArgumentParser; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Byte; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/console/command/descriptor/CommandArgumentContext { public static final field Companion Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$Companion; public static final field EMPTY Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public abstract fun get (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public abstract fun toList ()Ljava/util/List; } public final class net/mamoe/mirai/console/command/descriptor/CommandArgumentContext$Builtins : net/mamoe/mirai/console/command/descriptor/CommandArgumentContext { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$Builtins; public fun get (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public fun toList ()Ljava/util/List; } public final class net/mamoe/mirai/console/command/descriptor/CommandArgumentContext$Companion { } public final class net/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair { public static final field Companion Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair$Companion; public fun <init> (Lkotlin/reflect/KClass;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;)V public final fun component1 ()Lkotlin/reflect/KClass; public final fun component2 ()Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public final fun copy (Lkotlin/reflect/KClass;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;Lkotlin/reflect/KClass;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;ILjava/lang/Object;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair; public fun equals (Ljava/lang/Object;)Z public final fun getKlass ()Lkotlin/reflect/KClass; public final fun getParser ()Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public fun hashCode ()I public static final fun toPair (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)Lkotlin/Pair; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair$Companion { public final fun toPair (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)Lkotlin/Pair; } public abstract interface class net/mamoe/mirai/console/command/descriptor/CommandArgumentContextAware { public abstract fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; } public final class net/mamoe/mirai/console/command/descriptor/CommandArgumentContextBuilder : java/util/List, kotlin/jvm/internal/markers/KMutableList { public fun <init> ()V public synthetic fun add (ILjava/lang/Object;)V public fun add (ILnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)V public final fun add (Ljava/lang/Class;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContextBuilder; public synthetic fun add (Ljava/lang/Object;)Z public final fun add (Lkotlin/reflect/KClass;Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContextBuilder; public fun add (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)Z public fun addAll (ILjava/util/Collection;)Z public fun addAll (Ljava/util/Collection;)Z public final fun build ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public fun clear ()V public final fun contains (Ljava/lang/Object;)Z public fun contains (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)Z public fun containsAll (Ljava/util/Collection;)Z public synthetic fun get (I)Ljava/lang/Object; public fun get (I)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair; public fun getSize ()I public final fun indexOf (Ljava/lang/Object;)I public fun indexOf (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public final fun lastIndexOf (Ljava/lang/Object;)I public fun lastIndexOf (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)I public fun listIterator ()Ljava/util/ListIterator; public fun listIterator (I)Ljava/util/ListIterator; public synthetic fun remove (I)Ljava/lang/Object; public final fun remove (I)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair; public final fun remove (Ljava/lang/Object;)Z public fun remove (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)Z public fun removeAll (Ljava/util/Collection;)Z public fun removeAt (I)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair; public fun retainAll (Ljava/util/Collection;)Z public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object; public fun set (ILnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext$ParserPair; public final fun size ()I public fun subList (II)Ljava/util/List; public fun toArray ()[Ljava/lang/Object; public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; public final synthetic fun with (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContextBuilder; public final synthetic fun with (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContextBuilder; } public final class net/mamoe/mirai/console/command/descriptor/CommandArgumentContextKt { public static final synthetic fun buildCommandArgumentContext (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public static final fun plus (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;Ljava/util/List;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public static final fun plus (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; } public final class net/mamoe/mirai/console/command/descriptor/CommandArgumentParserException : net/mamoe/mirai/console/command/IllegalCommandArgumentException { public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class net/mamoe/mirai/console/command/descriptor/CommandDeclarationException : java/lang/RuntimeException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public fun <init> (Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/console/command/descriptor/CommandReceiverParameter$Companion { } public class net/mamoe/mirai/console/command/descriptor/CommandResolutionException : java/lang/RuntimeException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public fun <init> (Ljava/lang/Throwable;)V } public abstract interface class net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser { public static final field Companion Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser$Companion; public static fun map (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public abstract fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public static fun parse (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser$Companion { public final fun map (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public final fun parse (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/DoubleValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/DoubleValueArgumentParser; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Double; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/EmptyCommandArgumentContext : net/mamoe/mirai/console/command/descriptor/CommandArgumentContext { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/EmptyCommandArgumentContext; public fun get (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public fun toList ()Ljava/util/List; } public final class net/mamoe/mirai/console/command/descriptor/EnumValueArgumentParser { public fun <init> (Ljava/lang/Class;)V public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Enum; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/ExistingBotValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ExistingBotValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/Bot; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/Bot; } public final class net/mamoe/mirai/console/command/descriptor/ExistingContactValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ExistingContactValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Contact; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Contact; } public final class net/mamoe/mirai/console/command/descriptor/ExistingFriendValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ExistingFriendValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Friend; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Friend; } public final class net/mamoe/mirai/console/command/descriptor/ExistingGroupValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ExistingGroupValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Group; } public final class net/mamoe/mirai/console/command/descriptor/ExistingMemberValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ExistingMemberValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Member; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/Member; } public final class net/mamoe/mirai/console/command/descriptor/ExistingUserValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ExistingUserValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/User; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/console/command/descriptor/FloatValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/FloatValueArgumentParser; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Float; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/ImageValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ImageValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/message/data/Image; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/message/data/Image; } public final class net/mamoe/mirai/console/command/descriptor/IntValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/IntValueArgumentParser; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Integer; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/LongValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/LongValueArgumentParser; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Long; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/MappingCommandValueArgumentParser : net/mamoe/mirai/console/command/descriptor/AbstractCommandValueArgumentParser { public fun <init> (Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser;Lkotlin/jvm/functions/Function2;)V public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/command/descriptor/PermissionIdValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/PermissionIdValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/permission/PermissionId; } public final class net/mamoe/mirai/console/command/descriptor/PermitteeIdValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/PermitteeIdValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/permission/PermitteeId; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/permission/PermitteeId; } public final class net/mamoe/mirai/console/command/descriptor/PlainTextValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/PlainTextValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/message/data/PlainText; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/message/data/PlainText; } public final class net/mamoe/mirai/console/command/descriptor/RawContentValueArgumentParser : net/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/RawContentValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/message/data/MessageContent; public synthetic fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Lnet/mamoe/mirai/message/data/MessageContent;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/message/data/MessageContent; } public final class net/mamoe/mirai/console/command/descriptor/ShortValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/ShortValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Short; } public final class net/mamoe/mirai/console/command/descriptor/SimpleCommandArgumentContext : net/mamoe/mirai/console/command/descriptor/CommandArgumentContext { public fun <init> (Ljava/util/List;)V public fun get (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/console/command/descriptor/CommandValueArgumentParser; public final fun getList ()Ljava/util/List; public fun toList ()Ljava/util/List; } public final class net/mamoe/mirai/console/command/descriptor/StringValueArgumentParser { public static final field INSTANCE Lnet/mamoe/mirai/console/command/descriptor/StringValueArgumentParser; public synthetic fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/Object; public fun parse (Ljava/lang/String;Lnet/mamoe/mirai/console/command/CommandSender;)Ljava/lang/String; } public final class net/mamoe/mirai/console/command/descriptor/TypeVariant$Companion { } public abstract class net/mamoe/mirai/console/command/java/JCompositeCommand : net/mamoe/mirai/console/command/CompositeCommand { public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;)V public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;ILkotlin/jvm/internal/DefaultConstructorMarker;)V protected final fun addArgumentContext (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V public final fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public final fun getDescription ()Ljava/lang/String; public final fun getPermission ()Lnet/mamoe/mirai/console/permission/Permission; public final fun getPrefixOptional ()Z protected final fun setDescription (Ljava/lang/String;)V protected final fun setPermission (Lnet/mamoe/mirai/console/permission/Permission;)V protected final fun setPrefixOptional (Z)V } public abstract class net/mamoe/mirai/console/command/java/JRawCommand : net/mamoe/mirai/console/command/Command { public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;)V public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getDescription ()Ljava/lang/String; public fun getOverloads ()Ljava/util/List; public fun getOwner ()Lnet/mamoe/mirai/console/command/CommandOwner; public final fun getPermission ()Lnet/mamoe/mirai/console/permission/Permission; public final fun getPrefixOptional ()Z public fun getPrimaryName ()Ljava/lang/String; public fun getSecondaryNames ()[Ljava/lang/String; public fun getUsage ()Ljava/lang/String; public fun onCommand (Lnet/mamoe/mirai/console/command/CommandContext;Lnet/mamoe/mirai/message/data/MessageChain;)V public fun onCommand (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/message/data/MessageChain;)V protected final fun setDescription (Ljava/lang/String;)V protected final fun setPermission (Lnet/mamoe/mirai/console/permission/Permission;)V protected final fun setPrefixOptional (Z)V protected fun setUsage (Ljava/lang/String;)V } public abstract class net/mamoe/mirai/console/command/java/JSimpleCommand : net/mamoe/mirai/console/command/SimpleCommand { public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;)V public fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/command/CommandOwner;Ljava/lang/String;[Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;ILkotlin/jvm/internal/DefaultConstructorMarker;)V protected final fun addArgumentContext (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V public fun getContext ()Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext; public fun getDescription ()Ljava/lang/String; public fun getPermission ()Lnet/mamoe/mirai/console/permission/Permission; public fun getPrefixOptional ()Z protected fun setContext (Lnet/mamoe/mirai/console/command/descriptor/CommandArgumentContext;)V protected fun setDescription (Ljava/lang/String;)V protected fun setPermission (Lnet/mamoe/mirai/console/permission/Permission;)V protected fun setPrefixOptional (Z)V } public final class net/mamoe/mirai/console/command/parse/CommandCallParser$Companion { public final fun parseCommandCall (Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/command/parse/CommandCall; } public final class net/mamoe/mirai/console/command/parse/CommandValueArgumentKt { } public final class net/mamoe/mirai/console/command/resolve/CommandCallInterceptor$Companion { public final fun intercepted (Lnet/mamoe/mirai/console/command/parse/CommandCall;)Lnet/mamoe/mirai/console/command/resolve/InterceptResult; public final fun intercepted (Lnet/mamoe/mirai/console/command/resolve/ResolvedCommandCall;)Lnet/mamoe/mirai/console/command/resolve/InterceptResult; public final fun intercepted (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/command/resolve/InterceptResult; } public final class net/mamoe/mirai/console/command/resolve/CommandCallInterceptorKt { } public final class net/mamoe/mirai/console/command/resolve/CommandCallResolver$Companion { } public final class net/mamoe/mirai/console/command/resolve/CommandCallResolverKt { } public final class net/mamoe/mirai/console/command/resolve/InterceptedReason$Companion { public final fun create (Ljava/lang/String;)Lnet/mamoe/mirai/console/command/resolve/InterceptedReason; } public final class net/mamoe/mirai/console/command/resolve/ResolvedCommandCall$Companion { } public final class net/mamoe/mirai/console/command/resolve/ResolvedCommandCallKt { } public abstract class net/mamoe/mirai/console/data/AbstractPluginData : net/mamoe/mirai/console/data/PluginData { public fun <init> ()V public abstract fun getSaveName ()Ljava/lang/String; public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; public final fun getUpdaterSerializer ()Lkotlinx/serialization/KSerializer; public synthetic fun getUpdaterSerializer$mirai_console ()Lkotlinx/serialization/KSerializer; public final fun getValueNodes ()Ljava/util/List; public final fun provideDelegate (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; } public final class net/mamoe/mirai/console/data/AbstractPluginDataKt { } public class net/mamoe/mirai/console/data/AutoSavePluginConfig : net/mamoe/mirai/console/data/AutoSavePluginData, net/mamoe/mirai/console/data/PluginConfig { public fun <init> (Ljava/lang/String;)V } public class net/mamoe/mirai/console/data/AutoSavePluginData : net/mamoe/mirai/console/data/AbstractPluginData { public fun <init> (Ljava/lang/String;)V public final fun getSaveName ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/data/ListValue : net/mamoe/mirai/console/data/CompositeValue { } public final class net/mamoe/mirai/console/data/MemoryPluginDataStorage$Companion { public final fun create ()Lnet/mamoe/mirai/console/data/MemoryPluginDataStorage; } public final class net/mamoe/mirai/console/data/MultiFilePluginDataStorage$Companion { public final fun create (Ljava/nio/file/Path;)Lnet/mamoe/mirai/console/data/MultiFilePluginDataStorage; } public abstract interface class net/mamoe/mirai/console/data/PluginConfig : net/mamoe/mirai/console/data/PluginData { } public abstract interface class net/mamoe/mirai/console/data/PluginData { public abstract fun getSaveName ()Ljava/lang/String; public fun getSaveType ()Lnet/mamoe/mirai/console/data/PluginData$SaveType; public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; public abstract fun getUpdaterSerializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/data/PluginDataExtensions { public static final field INSTANCE Lnet/mamoe/mirai/console/data/PluginDataExtensions; public static final fun mapKeys (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun mapKeysImmutable (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun mapKeysImmutableNotNull (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun mapKeysNotNull (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withDefaultMap (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withDefaultMapImmutable (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withDefaultMapImmutableNotNull (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withDefaultMapNotNull (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withEmptyDefaultList (Lnet/mamoe/mirai/console/data/SerializerAwareValue;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withEmptyDefaultListImmutable (Lnet/mamoe/mirai/console/data/SerializerAwareValue;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withEmptyDefaultMap (Lnet/mamoe/mirai/console/data/SerializerAwareValue;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withEmptyDefaultMapImmutable (Lnet/mamoe/mirai/console/data/SerializerAwareValue;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withEmptyDefaultSet (Lnet/mamoe/mirai/console/data/SerializerAwareValue;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun withEmptyDefaultSetImmutable (Lnet/mamoe/mirai/console/data/SerializerAwareValue;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; } public final class net/mamoe/mirai/console/data/PluginDataExtensions$NotNullMutableMap : net/mamoe/mirai/console/data/PluginDataExtensions$NotNullMap, java/util/Map, kotlin/jvm/internal/markers/KMutableMap { public fun clear ()V public fun containsKey (Ljava/lang/Object;)Z public fun containsValue (Ljava/lang/Object;)Z public fun get (Ljava/lang/Object;)Ljava/lang/Object; public fun getEntries ()Ljava/util/Set; public fun getKeys ()Ljava/util/Set; public fun getOrDefault (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun getSize ()I public fun getValues ()Ljava/util/Collection; public fun isEmpty ()Z public fun put (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun putAll (Ljava/util/Map;)V public synthetic fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object; public fun putIfAbsent (Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Void; public fun remove (Ljava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/data/PluginDataKt { public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;B)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;C)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;D)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;F)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;I)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;J)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;Ljava/lang/String;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;S)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun value (Lnet/mamoe/mirai/console/data/PluginData;Z)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static final fun valueImpl (Lnet/mamoe/mirai/console/data/PluginData;Lkotlin/reflect/KType;Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; } public final class net/mamoe/mirai/console/data/PluginDataStorageKt { public static final synthetic fun getDirectory (Lnet/mamoe/mirai/console/data/MultiFilePluginDataStorage;)Ljava/io/File; } public class net/mamoe/mirai/console/data/ReadOnlyPluginConfig : net/mamoe/mirai/console/data/ReadOnlyPluginData, net/mamoe/mirai/console/data/PluginConfig { public fun <init> (Ljava/lang/String;)V } public class net/mamoe/mirai/console/data/ReadOnlyPluginData : net/mamoe/mirai/console/data/AbstractPluginData { public fun <init> (Ljava/lang/String;)V public final fun getSaveName ()Ljava/lang/String; } public final class net/mamoe/mirai/console/data/SerializableValue : net/mamoe/mirai/console/data/SerializerAwareValue, net/mamoe/mirai/console/data/Value { public static final field Companion Lnet/mamoe/mirai/console/data/SerializableValue$Companion; public fun <init> (Lnet/mamoe/mirai/console/data/Value;Lkotlinx/serialization/KSerializer;)V public static final fun create (Lnet/mamoe/mirai/console/data/Value;Lkotlinx/serialization/KSerializer;)Lnet/mamoe/mirai/console/data/SerializableValue; public fun equals (Ljava/lang/Object;)Z public fun get ()Ljava/lang/Object; public fun getSerializer ()Lkotlinx/serialization/KSerializer; public synthetic fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; public fun hashCode ()I public fun set (Ljava/lang/Object;)V public synthetic fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/data/SerializableValue$Companion { public final fun create (Lnet/mamoe/mirai/console/data/Value;Lkotlinx/serialization/KSerializer;)Lnet/mamoe/mirai/console/data/SerializableValue; } public abstract interface class net/mamoe/mirai/console/data/SerializerAwareValue : net/mamoe/mirai/console/data/Value { public static final field Companion Lnet/mamoe/mirai/console/data/SerializerAwareValue$Companion; public static fun deserialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/BinaryFormat;[B)V public static fun deserialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/StringFormat;Ljava/lang/String;)V public abstract fun getSerializer ()Lkotlinx/serialization/KSerializer; public static fun serialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/BinaryFormat;)[B public static fun serialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/StringFormat;)Ljava/lang/String; } public final class net/mamoe/mirai/console/data/SerializerAwareValue$Companion { public final fun deserialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/BinaryFormat;[B)V public final fun deserialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/StringFormat;Ljava/lang/String;)V public final fun serialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/BinaryFormat;)[B public final fun serialize (Lnet/mamoe/mirai/console/data/SerializerAwareValue;Lkotlinx/serialization/StringFormat;)Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/data/Value : kotlin/properties/ReadWriteProperty { public abstract fun get ()Ljava/lang/Object; public synthetic fun getValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; public abstract fun set (Ljava/lang/Object;)V public synthetic fun setValue (Ljava/lang/Object;Lkotlin/reflect/KProperty;Ljava/lang/Object;)V } public abstract interface annotation class net/mamoe/mirai/console/data/ValueDescription : java/lang/annotation/Annotation { public abstract fun value ()Ljava/lang/String; } public final class net/mamoe/mirai/console/data/ValueDescription$Impl : net/mamoe/mirai/console/data/ValueDescription { public fun <init> (Ljava/lang/String;)V public final synthetic fun value ()Ljava/lang/String; } public abstract interface annotation class net/mamoe/mirai/console/data/ValueName : java/lang/annotation/Annotation { public abstract fun value ()Ljava/lang/String; } public abstract class net/mamoe/mirai/console/data/java/JAutoSavePluginConfig : net/mamoe/mirai/console/data/AutoSavePluginConfig, net/mamoe/mirai/console/data/PluginConfig { public fun <init> (Ljava/lang/String;)V } public abstract class net/mamoe/mirai/console/data/java/JAutoSavePluginData : net/mamoe/mirai/console/data/AutoSavePluginData, net/mamoe/mirai/console/data/PluginConfig { public static final field Companion Lnet/mamoe/mirai/console/data/java/JAutoSavePluginData$Companion; public fun <init> (Ljava/lang/String;)V public static final fun createKType (Ljava/lang/Class;Z[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; public static final fun createKType (Ljava/lang/Class;[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; public final fun typedValue (Lkotlin/reflect/KType;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun typedValue (Lkotlin/reflect/KType;Ljava/lang/Object;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static synthetic fun typedValue$default (Lnet/mamoe/mirai/console/data/java/JAutoSavePluginData;Lkotlin/reflect/KType;Ljava/lang/Object;ILjava/lang/Object;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (B)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (C)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (D)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (F)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (I)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (J)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (S)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Z)Lnet/mamoe/mirai/console/data/SerializerAwareValue; } public final class net/mamoe/mirai/console/data/java/JAutoSavePluginData$Companion { public final fun createKType (Ljava/lang/Class;Z[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; public final fun createKType (Ljava/lang/Class;[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; } public abstract class net/mamoe/mirai/console/data/java/JavaAutoSavePluginConfig : net/mamoe/mirai/console/data/java/JavaAutoSavePluginData, net/mamoe/mirai/console/data/PluginConfig { public fun <init> (Ljava/lang/String;)V } public abstract class net/mamoe/mirai/console/data/java/JavaAutoSavePluginData : net/mamoe/mirai/console/data/AutoSavePluginData, net/mamoe/mirai/console/data/PluginConfig { public static final field Companion Lnet/mamoe/mirai/console/data/java/JavaAutoSavePluginData$Companion; public fun <init> (Ljava/lang/String;)V public static final fun createKType (Ljava/lang/Class;Z[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; public static final fun createKType (Ljava/lang/Class;[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; public final fun typedValue (Ljava/lang/String;Lkotlin/reflect/KType;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun typedValue (Ljava/lang/String;Lkotlin/reflect/KType;Ljava/lang/Object;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public static synthetic fun typedValue$default (Lnet/mamoe/mirai/console/data/java/JavaAutoSavePluginData;Ljava/lang/String;Lkotlin/reflect/KType;Ljava/lang/Object;ILjava/lang/Object;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;B)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;C)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;D)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;F)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;I)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;J)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;S)Lnet/mamoe/mirai/console/data/SerializerAwareValue; public final fun value (Ljava/lang/String;Z)Lnet/mamoe/mirai/console/data/SerializerAwareValue; } public final class net/mamoe/mirai/console/data/java/JavaAutoSavePluginData$Companion { public final fun createKType (Ljava/lang/Class;Z[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; public final fun createKType (Ljava/lang/Class;[Lkotlin/reflect/KType;)Lkotlin/reflect/KType; } public final class net/mamoe/mirai/console/enduserreadme/EndUserReadme { public static final field Companion Lnet/mamoe/mirai/console/enduserreadme/EndUserReadme$Companion; public static final field DELAY Ljava/lang/String; public static final field PAUSE Ljava/lang/String; public fun <init> ()V public final fun put (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public final fun putAll (Ljava/lang/String;)V } public final class net/mamoe/mirai/console/enduserreadme/EndUserReadme$Companion { } public final class net/mamoe/mirai/console/enduserreadme/EndUserReadme$Render { public fun <init> ()V public final fun delay ()V public final fun delay (I)V public final fun msg (Ljava/lang/String;)V public final fun pause ()V public final fun plusAssign (Ljava/lang/String;)V public final fun render ()Ljava/lang/String; public final fun unaryPlus (Ljava/lang/String;)V } public abstract class net/mamoe/mirai/console/events/AutoLoginEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/console/events/ConsoleEvent, net/mamoe/mirai/event/events/BotEvent { } public final class net/mamoe/mirai/console/events/AutoLoginEvent$Failure : net/mamoe/mirai/console/events/AutoLoginEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getCause ()Ljava/lang/Throwable; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/events/AutoLoginEvent$Success : net/mamoe/mirai/console/events/AutoLoginEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/events/ConsoleEvent : net/mamoe/mirai/event/Event { } public final class net/mamoe/mirai/console/events/EndUserReadmeInitializeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/console/events/ConsoleEvent { public final fun getReadme ()Lnet/mamoe/mirai/console/enduserreadme/EndUserReadme; } public final class net/mamoe/mirai/console/events/StartupEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/console/events/ConsoleEvent { public final fun getTimestamp ()J } public abstract class net/mamoe/mirai/console/extension/AbstractExtensionPoint : net/mamoe/mirai/console/extension/ExtensionPoint { public fun <init> (Lkotlin/reflect/KClass;)V public fun getExtensionType ()Lkotlin/reflect/KClass; } public abstract class net/mamoe/mirai/console/extension/AbstractInstanceExtensionPoint : net/mamoe/mirai/console/extension/AbstractExtensionPoint { } public abstract class net/mamoe/mirai/console/extension/AbstractSingletonExtensionPoint : net/mamoe/mirai/console/extension/AbstractExtensionPoint, net/mamoe/mirai/console/extension/SingletonExtensionPoint { public final fun getBuiltinImplementation ()Lkotlin/jvm/functions/Function0; public fun getSelectedInstance ()Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/console/extension/ComponentStorage { public abstract fun contribute (Lnet/mamoe/mirai/console/extension/ExtensionPoint;Lnet/mamoe/mirai/console/plugin/Plugin;Lkotlin/jvm/functions/Function0;)V public abstract fun contribute (Lnet/mamoe/mirai/console/extension/ExtensionPoint;Lnet/mamoe/mirai/console/plugin/Plugin;Lnet/mamoe/mirai/console/extension/Extension;)V public abstract fun getExtensions (Lnet/mamoe/mirai/console/extension/ExtensionPoint;)Lkotlin/sequences/Sequence; public abstract fun getExtensionsStream (Lnet/mamoe/mirai/console/extension/ExtensionPoint;)Ljava/util/stream/Stream; } public abstract interface class net/mamoe/mirai/console/extension/Extension { public fun getPriority ()I } public class net/mamoe/mirai/console/extension/ExtensionException : java/lang/RuntimeException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public fun <init> (Ljava/lang/Throwable;)V } public abstract interface class net/mamoe/mirai/console/extension/ExtensionPoint { public abstract fun getExtensionType ()Lkotlin/reflect/KClass; } public abstract interface class net/mamoe/mirai/console/extension/ExtensionRegistry { public abstract fun getExtension ()Lnet/mamoe/mirai/console/extension/Extension; public abstract fun getPlugin ()Lnet/mamoe/mirai/console/plugin/Plugin; } public abstract interface class net/mamoe/mirai/console/extension/FunctionExtension : net/mamoe/mirai/console/extension/Extension { } public abstract interface class net/mamoe/mirai/console/extension/FunctionExtensionPoint : net/mamoe/mirai/console/extension/ExtensionPoint { } public abstract interface class net/mamoe/mirai/console/extension/InstanceExtension : net/mamoe/mirai/console/extension/Extension { public abstract fun getInstance ()Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/console/extension/InstanceExtensionPoint : net/mamoe/mirai/console/extension/ExtensionPoint { } public final class net/mamoe/mirai/console/extension/PluginComponentStorage { public fun <init> (Lnet/mamoe/mirai/console/plugin/Plugin;)V public final fun contribute (Lnet/mamoe/mirai/console/extension/ExtensionPoint;Lkotlin/jvm/functions/Function0;)V public final fun contributeBotConfigurationAlterer (Lnet/mamoe/mirai/console/extensions/BotConfigurationAlterer;)V public final fun contributePermissionServiceProvider (Lkotlin/jvm/functions/Function0;)V public final fun contributePluginLoaderProvider (Lkotlin/jvm/functions/Function0;)V public final fun contributePostStartupExtension (Lnet/mamoe/mirai/console/extensions/PostStartupExtension;)V public final synthetic fun contributeSingletonExtensionSelector (Lkotlin/jvm/functions/Function0;)V public final fun runAfterStartup (Lkotlin/jvm/functions/Function0;)V } public abstract interface class net/mamoe/mirai/console/extension/SingletonExtension : net/mamoe/mirai/console/extension/Extension { public abstract fun getInstance ()Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/console/extension/SingletonExtensionPoint : net/mamoe/mirai/console/extension/ExtensionPoint { } public abstract interface class net/mamoe/mirai/console/extensions/BotConfigurationAlterer : net/mamoe/mirai/console/extension/FunctionExtension { public static final field ExtensionPoint Lnet/mamoe/mirai/console/extensions/BotConfigurationAlterer$ExtensionPoint; public abstract fun alterConfiguration (JLnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/utils/BotConfiguration; } public final class net/mamoe/mirai/console/extensions/BotConfigurationAlterer$ExtensionPoint : net/mamoe/mirai/console/extension/AbstractExtensionPoint { } public abstract interface class net/mamoe/mirai/console/extensions/PermissionServiceProvider : net/mamoe/mirai/console/extension/InstanceExtension { public static final field ExtensionPoint Lnet/mamoe/mirai/console/extensions/PermissionServiceProvider$ExtensionPoint; } public final class net/mamoe/mirai/console/extensions/PermissionServiceProvider$ExtensionPoint : net/mamoe/mirai/console/extension/AbstractInstanceExtensionPoint { } public final class net/mamoe/mirai/console/extensions/PermissionServiceProviderImpl : net/mamoe/mirai/console/extensions/PermissionServiceProvider { public fun <init> (Lnet/mamoe/mirai/console/permission/PermissionService;)V public synthetic fun getInstance ()Ljava/lang/Object; public fun getInstance ()Lnet/mamoe/mirai/console/permission/PermissionService; } public final class net/mamoe/mirai/console/extensions/PermissionServiceProviderImplLazy : net/mamoe/mirai/console/extensions/PermissionServiceProvider { public fun <init> (Lkotlin/jvm/functions/Function0;)V public synthetic fun getInstance ()Ljava/lang/Object; public fun getInstance ()Lnet/mamoe/mirai/console/permission/PermissionService; } public abstract interface class net/mamoe/mirai/console/extensions/PluginLoaderProvider : net/mamoe/mirai/console/extension/InstanceExtension { public static final field ExtensionPoint Lnet/mamoe/mirai/console/extensions/PluginLoaderProvider$ExtensionPoint; } public final class net/mamoe/mirai/console/extensions/PluginLoaderProvider$ExtensionPoint : net/mamoe/mirai/console/extension/AbstractExtensionPoint { } public final class net/mamoe/mirai/console/extensions/PluginLoaderProviderImpl : net/mamoe/mirai/console/extensions/PluginLoaderProvider { public fun <init> (Lnet/mamoe/mirai/console/plugin/loader/PluginLoader;)V public synthetic fun getInstance ()Ljava/lang/Object; public fun getInstance ()Lnet/mamoe/mirai/console/plugin/loader/PluginLoader; } public final class net/mamoe/mirai/console/extensions/PluginLoaderProviderImplLazy : net/mamoe/mirai/console/extensions/PluginLoaderProvider { public fun <init> (Lkotlin/jvm/functions/Function0;)V public synthetic fun getInstance ()Ljava/lang/Object; public fun getInstance ()Lnet/mamoe/mirai/console/plugin/loader/PluginLoader; } public abstract interface class net/mamoe/mirai/console/extensions/PostStartupExtension : net/mamoe/mirai/console/extension/FunctionExtension { public static final field ExtensionPoint Lnet/mamoe/mirai/console/extensions/PostStartupExtension$ExtensionPoint; public abstract fun invoke ()V } public final class net/mamoe/mirai/console/extensions/PostStartupExtension$ExtensionPoint : net/mamoe/mirai/console/extension/AbstractExtensionPoint { } public abstract interface class net/mamoe/mirai/console/extensions/SingletonExtensionSelector : net/mamoe/mirai/console/extension/FunctionExtension { public static final field ExtensionPoint Lnet/mamoe/mirai/console/extensions/SingletonExtensionSelector$ExtensionPoint; public abstract fun selectSingleton (Lkotlin/reflect/KClass;Ljava/util/Collection;)Lnet/mamoe/mirai/console/extension/Extension; } public final class net/mamoe/mirai/console/extensions/SingletonExtensionSelector$ExtensionPoint : net/mamoe/mirai/console/extension/AbstractExtensionPoint { } public final class net/mamoe/mirai/console/extensions/SingletonExtensionSelector$Registry { public fun <init> (Lnet/mamoe/mirai/console/plugin/Plugin;Lnet/mamoe/mirai/console/extension/Extension;)V public final fun component1 ()Lnet/mamoe/mirai/console/plugin/Plugin; public final fun component2 ()Lnet/mamoe/mirai/console/extension/Extension; public final fun copy (Lnet/mamoe/mirai/console/plugin/Plugin;Lnet/mamoe/mirai/console/extension/Extension;)Lnet/mamoe/mirai/console/extensions/SingletonExtensionSelector$Registry; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/extensions/SingletonExtensionSelector$Registry;Lnet/mamoe/mirai/console/plugin/Plugin;Lnet/mamoe/mirai/console/extension/Extension;ILjava/lang/Object;)Lnet/mamoe/mirai/console/extensions/SingletonExtensionSelector$Registry; public fun equals (Ljava/lang/Object;)Z public final fun getExtension ()Lnet/mamoe/mirai/console/extension/Extension; public final fun getPlugin ()Lnet/mamoe/mirai/console/plugin/Plugin; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/fontend/ProcessProgress : java/io/Closeable { public abstract fun close ()V public abstract fun markFailed ()V public abstract fun rerender ()V public abstract fun setTotalSize (J)V public abstract fun update (J)V public abstract fun update (JJ)V public fun updateText (Ljava/lang/CharSequence;)V public abstract fun updateText (Ljava/lang/String;)V } public final class net/mamoe/mirai/console/logging/AbstractLoggerController$LogPriority$Companion { public final fun by (Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;)Lnet/mamoe/mirai/console/logging/AbstractLoggerController$LogPriority; } public abstract class net/mamoe/mirai/console/permission/AbstractPermitteeId : net/mamoe/mirai/console/permission/PermitteeId { public static final field Companion Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$Companion; public synthetic fun <init> ([Lnet/mamoe/mirai/console/permission/PermitteeId;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getDirectParents ()[Lnet/mamoe/mirai/console/permission/PermitteeId; public static final fun parseFromString (Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyContact : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyContact; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyFriend : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyFriend; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyGroup : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyGroup; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyGroupTemp : net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyTemp { public fun <init> (J)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun copy (J)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyGroupTemp; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyGroupTemp;JILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyGroupTemp; public fun equals (Ljava/lang/Object;)Z public final fun getGroupId ()J public fun hashCode ()I } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyMember : net/mamoe/mirai/console/permission/AbstractPermitteeId { public fun <init> (J)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun copy (J)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyMember; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyMember;JILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyMember; public fun equals (Ljava/lang/Object;)Z public final fun getGroupId ()J public fun hashCode ()I } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyMemberFromAnyGroup : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyMemberFromAnyGroup; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyOtherClient : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyOtherClient; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyStranger : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyStranger; public fun asString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyTemp : net/mamoe/mirai/console/permission/AbstractPermitteeId { public fun <init> (J)V } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyTempFromAnyGroup : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyTempFromAnyGroup; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$AnyUser : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyUser; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$Companion { public final fun parseFromString (Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$Console : net/mamoe/mirai/console/permission/AbstractPermitteeId { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$Console; public fun asString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactFriend : net/mamoe/mirai/console/permission/AbstractPermitteeId { public fun <init> (J)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun copy (J)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactFriend; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactFriend;JILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactFriend; public fun equals (Ljava/lang/Object;)Z public final fun getId ()J public fun hashCode ()I } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroup : net/mamoe/mirai/console/permission/AbstractPermitteeId { public fun <init> (J)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun copy (J)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroup; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroup;JILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroup; public fun equals (Ljava/lang/Object;)Z public final fun getGroupId ()J public fun hashCode ()I } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroupTemp : net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactTemp { public fun <init> (JJ)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun component2 ()J public final fun copy (JJ)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroupTemp; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroupTemp;JJILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroupTemp; public fun equals (Ljava/lang/Object;)Z public final fun getGroupId ()J public final fun getMemberId ()J public fun hashCode ()I } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactMember : net/mamoe/mirai/console/permission/AbstractPermitteeId { public fun <init> (JJ)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun component2 ()J public final fun copy (JJ)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactMember; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactMember;JJILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactMember; public fun equals (Ljava/lang/Object;)Z public final fun getGroupId ()J public final fun getMemberId ()J public fun hashCode ()I } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactStranger : net/mamoe/mirai/console/permission/AbstractPermitteeId { public fun <init> (J)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun copy (J)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactStranger; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactStranger;JILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactStranger; public fun equals (Ljava/lang/Object;)Z public final fun getId ()J public fun hashCode ()I } public abstract class net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactTemp : net/mamoe/mirai/console/permission/AbstractPermitteeId { } public final class net/mamoe/mirai/console/permission/AbstractPermitteeId$ExactUser : net/mamoe/mirai/console/permission/AbstractPermitteeId { public fun <init> (J)V public fun asString ()Ljava/lang/String; public final fun component1 ()J public final fun copy (J)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactUser; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactUser;JILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactUser; public fun equals (Ljava/lang/Object;)Z public final fun getId ()J public fun hashCode ()I } public abstract interface class net/mamoe/mirai/console/permission/Permission { public static final field Companion Lnet/mamoe/mirai/console/permission/Permission$Companion; public abstract fun getDescription ()Ljava/lang/String; public abstract fun getId ()Lnet/mamoe/mirai/console/permission/PermissionId; public abstract fun getParent ()Lnet/mamoe/mirai/console/permission/Permission; public static fun getParentsWithSelf (Lnet/mamoe/mirai/console/permission/Permission;)Lkotlin/sequences/Sequence; public static fun getRootPermission ()Lnet/mamoe/mirai/console/permission/Permission; } public final class net/mamoe/mirai/console/permission/Permission$Companion { public final fun getParentsWithSelf (Lnet/mamoe/mirai/console/permission/Permission;)Lkotlin/sequences/Sequence; public final fun getRootPermission ()Lnet/mamoe/mirai/console/permission/Permission; } public final class net/mamoe/mirai/console/permission/PermissionId { public static final field Companion Lnet/mamoe/mirai/console/permission/PermissionId$Companion; public fun <init> (Ljava/lang/String;Ljava/lang/String;)V public static final fun checkPermissionIdName (Ljava/lang/String;)V public static final fun checkPermissionIdNamespace (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun copy (Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/PermissionId; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/permission/PermissionId;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/PermissionId; public fun equals (Ljava/lang/Object;)Z public final fun getName ()Ljava/lang/String; public final fun getNamespace ()Ljava/lang/String; public fun hashCode ()I public static final fun parseFromString (Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/PermissionId; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/permission/PermissionId$Companion { public final fun checkPermissionIdName (Ljava/lang/String;)V public final fun checkPermissionIdNamespace (Ljava/lang/String;)V public final fun parseFromString (Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/PermissionId; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/permission/PermissionId$PermissionIdAsStringSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/console/permission/PermissionId$PermissionIdAsStringSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/console/permission/PermissionId; 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/console/permission/PermissionId;)V } public abstract interface class net/mamoe/mirai/console/permission/PermissionIdNamespace { public abstract fun permissionId (Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/PermissionId; } public final class net/mamoe/mirai/console/permission/PermissionKt { public static final synthetic fun getRootPermission ()Lnet/mamoe/mirai/console/permission/Permission; } public final class net/mamoe/mirai/console/permission/PermissionRegistryConflictException : java/lang/Exception { public fun <init> (Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/permission/Permission;)V public final fun getExistingInstance ()Lnet/mamoe/mirai/console/permission/Permission; public final fun getNewInstance ()Lnet/mamoe/mirai/console/permission/Permission; } public abstract interface class net/mamoe/mirai/console/permission/PermissionService { public static final field Companion Lnet/mamoe/mirai/console/permission/PermissionService$Companion; public static fun cancel (Lnet/mamoe/mirai/console/permission/Permittee;[Lnet/mamoe/mirai/console/permission/Permission;Z)V public abstract fun cancel (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;Z)V public static fun cancel (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermissionId;Z)V public static synthetic fun cancel0 (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;Z)V public static fun findCorrespondingPermission (Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public static fun findCorrespondingPermissionOrFail (Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public abstract fun get (Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public static fun getCorrespondingPermission (Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public static fun getInstance ()Lnet/mamoe/mirai/console/permission/PermissionService; public static fun getOrFail (Lnet/mamoe/mirai/console/permission/PermissionService;Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public abstract fun getPermissionType ()Lkotlin/reflect/KClass; public static fun getPermittedPermissions (Lnet/mamoe/mirai/console/permission/Permittee;)Lkotlin/sequences/Sequence; public abstract fun getPermittedPermissions (Lnet/mamoe/mirai/console/permission/PermitteeId;)Lkotlin/sequences/Sequence; public static synthetic fun getPermittedPermissions0 (Lnet/mamoe/mirai/console/permission/PermitteeId;)Lkotlin/sequences/Sequence; public abstract fun getRegisteredPermissions ()Lkotlin/sequences/Sequence; public abstract fun getRootPermission ()Lnet/mamoe/mirai/console/permission/Permission; public static fun hasPermission (Lnet/mamoe/mirai/console/permission/Permittee;Lnet/mamoe/mirai/console/permission/Permission;)Z public static fun hasPermission (Lnet/mamoe/mirai/console/permission/Permittee;Lnet/mamoe/mirai/console/permission/PermissionId;)Z public static fun hasPermission (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;)Z public static fun hasPermission (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermissionId;)Z public static fun permit (Lnet/mamoe/mirai/console/permission/Permittee;[Lnet/mamoe/mirai/console/permission/Permission;)V public abstract fun permit (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;)V public static fun permit (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermissionId;)V public static fun permit0 (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;)V public abstract fun register (Lnet/mamoe/mirai/console/permission/PermissionId;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;)Lnet/mamoe/mirai/console/permission/Permission; public static synthetic fun register$default (Lnet/mamoe/mirai/console/permission/PermissionService;Lnet/mamoe/mirai/console/permission/PermissionId;Ljava/lang/String;Lnet/mamoe/mirai/console/permission/Permission;ILjava/lang/Object;)Lnet/mamoe/mirai/console/permission/Permission; public static fun testPermission (Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/permission/Permittee;)Z public static fun testPermission (Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z public static fun testPermission (Lnet/mamoe/mirai/console/permission/PermissionId;Lnet/mamoe/mirai/console/permission/Permittee;)Z public static fun testPermission (Lnet/mamoe/mirai/console/permission/PermissionId;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z public fun testPermission (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;)Z } public final class net/mamoe/mirai/console/permission/PermissionService$Companion { public final fun cancel (Lnet/mamoe/mirai/console/permission/Permittee;[Lnet/mamoe/mirai/console/permission/Permission;Z)V public final fun cancel (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermissionId;Z)V public final synthetic fun cancel0 (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;Z)V public final fun findCorrespondingPermission (Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public final fun findCorrespondingPermissionOrFail (Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public final fun getCorrespondingPermission (Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public final fun getInstance ()Lnet/mamoe/mirai/console/permission/PermissionService; public final fun getOrFail (Lnet/mamoe/mirai/console/permission/PermissionService;Lnet/mamoe/mirai/console/permission/PermissionId;)Lnet/mamoe/mirai/console/permission/Permission; public final fun getPermittedPermissions (Lnet/mamoe/mirai/console/permission/Permittee;)Lkotlin/sequences/Sequence; public final synthetic fun getPermittedPermissions0 (Lnet/mamoe/mirai/console/permission/PermitteeId;)Lkotlin/sequences/Sequence; public final fun hasPermission (Lnet/mamoe/mirai/console/permission/Permittee;Lnet/mamoe/mirai/console/permission/Permission;)Z public final fun hasPermission (Lnet/mamoe/mirai/console/permission/Permittee;Lnet/mamoe/mirai/console/permission/PermissionId;)Z public final fun hasPermission (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;)Z public final fun hasPermission (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermissionId;)Z public final fun permit (Lnet/mamoe/mirai/console/permission/Permittee;[Lnet/mamoe/mirai/console/permission/Permission;)V public final fun permit (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermissionId;)V public final fun permit0 (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/Permission;)V public final fun testPermission (Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/permission/Permittee;)Z public final fun testPermission (Lnet/mamoe/mirai/console/permission/Permission;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z public final fun testPermission (Lnet/mamoe/mirai/console/permission/PermissionId;Lnet/mamoe/mirai/console/permission/Permittee;)Z public final fun testPermission (Lnet/mamoe/mirai/console/permission/PermissionId;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z } public abstract interface class net/mamoe/mirai/console/permission/Permittee { public abstract fun getPermitteeId ()Lnet/mamoe/mirai/console/permission/PermitteeId; } public abstract interface class net/mamoe/mirai/console/permission/PermitteeId { public static final field Companion Lnet/mamoe/mirai/console/permission/PermitteeId$Companion; public abstract fun asString ()Ljava/lang/String; public static fun getAllParents (Lnet/mamoe/mirai/console/permission/PermitteeId;)Lkotlin/sequences/Sequence; public static fun getAllParentsWithSelf (Lnet/mamoe/mirai/console/permission/PermitteeId;)Lkotlin/sequences/Sequence; public abstract fun getDirectParents ()[Lnet/mamoe/mirai/console/permission/PermitteeId; public static fun hasChild (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z public static fun isChildOf (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z } public final class net/mamoe/mirai/console/permission/PermitteeId$Companion { public final fun getAllParents (Lnet/mamoe/mirai/console/permission/PermitteeId;)Lkotlin/sequences/Sequence; public final fun getAllParentsWithSelf (Lnet/mamoe/mirai/console/permission/PermitteeId;)Lkotlin/sequences/Sequence; public final synthetic fun getPermitteeId (Lnet/mamoe/mirai/contact/Group;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroup; public final synthetic fun getPermitteeId (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactMember; public final synthetic fun getPermitteeId (Lnet/mamoe/mirai/contact/OtherClient;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$AnyOtherClient; public final synthetic fun getPermitteeId (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactStranger; public final synthetic fun getPermitteeId (Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactUser; public final synthetic fun getPermitteeIdOnTemp (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/console/permission/AbstractPermitteeId$ExactGroupTemp; public final fun hasChild (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z public final fun isChildOf (Lnet/mamoe/mirai/console/permission/PermitteeId;Lnet/mamoe/mirai/console/permission/PermitteeId;)Z } public abstract interface class net/mamoe/mirai/console/plugin/NotYetLoadedPlugin : net/mamoe/mirai/console/plugin/Plugin { public abstract fun resolve ()Lnet/mamoe/mirai/console/plugin/Plugin; } public abstract interface class net/mamoe/mirai/console/plugin/Plugin : net/mamoe/mirai/console/command/CommandOwner { public abstract fun getLoader ()Lnet/mamoe/mirai/console/plugin/loader/PluginLoader; public abstract fun isEnabled ()Z } public abstract interface class net/mamoe/mirai/console/plugin/PluginFileExtensions { public abstract fun getConfigFolder ()Ljava/io/File; public abstract fun getConfigFolderPath ()Ljava/nio/file/Path; public abstract fun getDataFolder ()Ljava/io/File; public abstract fun getDataFolderPath ()Ljava/nio/file/Path; public fun resolveConfigFile (Ljava/lang/String;)Ljava/io/File; public fun resolveConfigFile (Ljava/nio/file/Path;)Ljava/io/File; public fun resolveConfigPath (Ljava/lang/String;)Ljava/nio/file/Path; public fun resolveConfigPath (Ljava/nio/file/Path;)Ljava/nio/file/Path; public fun resolveDataFile (Ljava/lang/String;)Ljava/io/File; public fun resolveDataFile (Ljava/nio/file/Path;)Ljava/io/File; public fun resolveDataPath (Ljava/lang/String;)Ljava/nio/file/Path; public fun resolveDataPath (Ljava/nio/file/Path;)Ljava/nio/file/Path; } public final class net/mamoe/mirai/console/plugin/PluginKt { public static final fun getAuthor (Lnet/mamoe/mirai/console/plugin/Plugin;)Ljava/lang/String; public static final fun getDependencies (Lnet/mamoe/mirai/console/plugin/Plugin;)Ljava/util/Set; public static final synthetic fun getDescription (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/plugin/description/PluginDescription; public static final fun getId (Lnet/mamoe/mirai/console/plugin/Plugin;)Ljava/lang/String; public static final fun getInfo (Lnet/mamoe/mirai/console/plugin/Plugin;)Ljava/lang/String; public static final fun getName (Lnet/mamoe/mirai/console/plugin/Plugin;)Ljava/lang/String; public static final fun getVersion (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/util/SemVersion; } public abstract interface class net/mamoe/mirai/console/plugin/PluginManager { public static final field INSTANCE Lnet/mamoe/mirai/console/plugin/PluginManager$INSTANCE; public fun disablePlugin (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun enablePlugin (Lnet/mamoe/mirai/console/plugin/Plugin;)V public abstract fun getBuiltInLoaders ()Ljava/util/List; public abstract fun getPluginDescription (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/plugin/description/PluginDescription; public abstract fun getPluginLibrariesFolder ()Ljava/io/File; public abstract fun getPluginLibrariesPath ()Ljava/nio/file/Path; public abstract fun getPluginLoaders ()Ljava/util/List; public abstract fun getPluginSharedLibrariesFolder ()Ljava/io/File; public abstract fun getPluginSharedLibrariesPath ()Ljava/nio/file/Path; public abstract fun getPlugins ()Ljava/util/List; public abstract fun getPluginsConfigFolder ()Ljava/io/File; public abstract fun getPluginsConfigPath ()Ljava/nio/file/Path; public abstract fun getPluginsDataFolder ()Ljava/io/File; public abstract fun getPluginsDataPath ()Ljava/nio/file/Path; public abstract fun getPluginsFolder ()Ljava/io/File; public abstract fun getPluginsPath ()Ljava/nio/file/Path; public fun loadPlugin (Lnet/mamoe/mirai/console/plugin/Plugin;)V } public final class net/mamoe/mirai/console/plugin/PluginManager$INSTANCE : net/mamoe/mirai/console/plugin/PluginManager { public final synthetic fun disable (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun disablePlugin (Lnet/mamoe/mirai/console/plugin/Plugin;)V public final synthetic fun enable (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun enablePlugin (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun getBuiltInLoaders ()Ljava/util/List; public final synthetic fun getDescription (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/plugin/description/PluginDescription; public fun getPluginDescription (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/plugin/description/PluginDescription; public fun getPluginLibrariesFolder ()Ljava/io/File; public fun getPluginLibrariesPath ()Ljava/nio/file/Path; public fun getPluginLoaders ()Ljava/util/List; public fun getPluginSharedLibrariesFolder ()Ljava/io/File; public fun getPluginSharedLibrariesPath ()Ljava/nio/file/Path; public fun getPlugins ()Ljava/util/List; public fun getPluginsConfigFolder ()Ljava/io/File; public fun getPluginsConfigPath ()Ljava/nio/file/Path; public fun getPluginsDataFolder ()Ljava/io/File; public fun getPluginsDataPath ()Ljava/nio/file/Path; public fun getPluginsFolder ()Ljava/io/File; public fun getPluginsPath ()Ljava/nio/file/Path; public final synthetic fun getSafeLoader (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/plugin/loader/PluginLoader; public final synthetic fun load (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun loadPlugin (Lnet/mamoe/mirai/console/plugin/Plugin;)V } public abstract interface class net/mamoe/mirai/console/plugin/ResourceContainer { public static final field Companion Lnet/mamoe/mirai/console/plugin/ResourceContainer$Companion; public static fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/console/plugin/ResourceContainer; public static fun create (Ljava/lang/ClassLoader;)Lnet/mamoe/mirai/console/plugin/ResourceContainer; public static fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/console/plugin/ResourceContainer; public fun getResource (Ljava/lang/String;)Ljava/lang/String; public fun getResource (Ljava/lang/String;Ljava/nio/charset/Charset;)Ljava/lang/String; public abstract fun getResourceAsStream (Ljava/lang/String;)Ljava/io/InputStream; } public final class net/mamoe/mirai/console/plugin/ResourceContainer$Companion { public final fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/console/plugin/ResourceContainer; public final fun create (Ljava/lang/ClassLoader;)Lnet/mamoe/mirai/console/plugin/ResourceContainer; public final fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/console/plugin/ResourceContainer; } public final class net/mamoe/mirai/console/plugin/center/PluginCenter$PluginInfo$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/console/plugin/center/PluginCenter$PluginInfo$$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/console/plugin/center/PluginCenter$PluginInfo; 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/console/plugin/center/PluginCenter$PluginInfo;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/plugin/center/PluginCenter$PluginInfo$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/plugin/center/PluginCenter$PluginInsight$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/console/plugin/center/PluginCenter$PluginInsight$$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/console/plugin/center/PluginCenter$PluginInsight; 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/console/plugin/center/PluginCenter$PluginInsight;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/plugin/center/PluginCenter$PluginInsight$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/plugin/description/IllegalPluginDescriptionException : java/lang/RuntimeException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public fun <init> (Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/console/plugin/description/PluginDependency { public static final field Companion Lnet/mamoe/mirai/console/plugin/description/PluginDependency$Companion; public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/String;Z)V public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Ljava/lang/String;Z)V public synthetic fun <init> (Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Z public final fun copy (Ljava/lang/String;Ljava/lang/String;Z)Lnet/mamoe/mirai/console/plugin/description/PluginDependency; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/plugin/description/PluginDependency;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/console/plugin/description/PluginDependency; public fun equals (Ljava/lang/Object;)Z public final fun getId ()Ljava/lang/String; public final fun getVersionRequirement ()Ljava/lang/String; public fun hashCode ()I public final fun isOptional ()Z public static final fun parseFromString (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/description/PluginDependency; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/plugin/description/PluginDependency$Companion { public final fun parseFromString (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/description/PluginDependency; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/plugin/description/PluginDependency$PluginDependencyAsStringSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/console/plugin/description/PluginDependency$PluginDependencyAsStringSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/console/plugin/description/PluginDependency; 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/console/plugin/description/PluginDependency;)V } public abstract interface class net/mamoe/mirai/console/plugin/description/PluginDescription { public static final field Companion Lnet/mamoe/mirai/console/plugin/description/PluginDescription$Companion; public abstract fun getAuthor ()Ljava/lang/String; public abstract fun getDependencies ()Ljava/util/Set; public abstract fun getId ()Ljava/lang/String; public abstract fun getInfo ()Ljava/lang/String; public abstract fun getName ()Ljava/lang/String; public abstract fun getVersion ()Lnet/mamoe/mirai/console/util/SemVersion; } public final class net/mamoe/mirai/console/plugin/description/PluginDescription$Companion { public final fun checkDependencies (Ljava/lang/String;Ljava/util/Set;)V public final fun checkPluginDescription (Lnet/mamoe/mirai/console/plugin/description/PluginDescription;)V public final fun checkPluginId (Ljava/lang/String;)V public final fun checkPluginName (Ljava/lang/String;)V public final fun getFORBIDDEN_ID_NAMES ()[Ljava/lang/String; public final fun getID_REGEX ()Lkotlin/text/Regex; } public abstract class net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin : net/mamoe/mirai/console/internal/plugin/JvmPluginInternal, net/mamoe/mirai/console/data/AutoSavePluginDataHolder, net/mamoe/mirai/console/plugin/jvm/JvmPlugin { public fun <init> ()V public fun <init> (Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;)V public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getAutoSaveIntervalMillis ()Lkotlin/ranges/LongRange; public final fun getDataHolderName ()Ljava/lang/String; public final fun getDescription ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; protected final fun getJvmPluginClasspath ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginClasspath; public final fun getLoader ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginLoader; public synthetic fun getLoader ()Lnet/mamoe/mirai/console/plugin/loader/PluginLoader; public final fun permissionId (Ljava/lang/String;)Lnet/mamoe/mirai/console/permission/PermissionId; public final fun reloadPluginConfig (Lnet/mamoe/mirai/console/data/PluginConfig;)V public final fun reloadPluginData (Lnet/mamoe/mirai/console/data/PluginData;)V public final fun savePluginConfig (Lnet/mamoe/mirai/console/data/PluginConfig;)V public final fun savePluginData (Lnet/mamoe/mirai/console/data/PluginData;)V protected final fun services (Ljava/lang/Class;)Lkotlin/Lazy; protected final synthetic fun services (Lkotlin/reflect/KClass;)Lkotlin/Lazy; } public final class net/mamoe/mirai/console/plugin/jvm/AbstractJvmPluginKt { public static final synthetic fun reloadPluginConfig (Lnet/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin;Lnet/mamoe/mirai/console/data/PluginConfig;)V public static final synthetic fun reloadPluginData (Lnet/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin;Lnet/mamoe/mirai/console/data/PluginData;)V public static final synthetic fun savePluginConfig (Lnet/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin;Lnet/mamoe/mirai/console/data/PluginConfig;)V public static final synthetic fun savePluginData (Lnet/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin;Lnet/mamoe/mirai/console/data/PluginData;)V } public abstract class net/mamoe/mirai/console/plugin/jvm/JavaPlugin : net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin, net/mamoe/mirai/console/plugin/jvm/JvmPlugin { public fun <init> ()V public fun <init> (Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;)V public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getScheduler ()Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler; } public abstract interface class net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler : kotlinx/coroutines/CoroutineScope { public static final field Companion Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler$Companion; public abstract fun async (Ljava/lang/Runnable;)Ljava/util/concurrent/Future; public abstract fun async (Ljava/util/concurrent/Callable;)Ljava/util/concurrent/Future; public static fun create ()Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler; public static fun create (Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler; public abstract fun delayed (JLjava/lang/Runnable;)Ljava/util/concurrent/CompletableFuture; public abstract fun delayed (JLjava/util/concurrent/Callable;)Ljava/util/concurrent/CompletableFuture; public abstract fun repeating (JLjava/lang/Runnable;)Ljava/util/concurrent/Future; } public final class net/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler$Companion { public final fun create ()Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler; public final fun create (Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler; public static synthetic fun create$default (Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler$Companion;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lnet/mamoe/mirai/console/plugin/jvm/JavaPluginScheduler; } public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPlugin : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/permission/PermissionIdNamespace, net/mamoe/mirai/console/plugin/Plugin, net/mamoe/mirai/console/plugin/PluginFileExtensions, net/mamoe/mirai/console/plugin/ResourceContainer { public static final field Companion Lnet/mamoe/mirai/console/plugin/jvm/JvmPlugin$Companion; public abstract fun getDescription ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public fun getLoader ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginLoader; public synthetic fun getLoader ()Lnet/mamoe/mirai/console/plugin/loader/PluginLoader; public abstract fun getLogger ()Lnet/mamoe/mirai/utils/MiraiLogger; public fun onDisable ()V public fun onEnable ()V public fun onLoad (Lnet/mamoe/mirai/console/extension/PluginComponentStorage;)V } public final class net/mamoe/mirai/console/plugin/jvm/JvmPlugin$Companion { public final synthetic fun onLoad (Lnet/mamoe/mirai/console/plugin/jvm/JvmPlugin;Lnet/mamoe/mirai/console/extension/PluginComponentStorage;)V } public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPluginClasspath { public abstract fun addToPath (Ljava/lang/ClassLoader;Ljava/io/File;)V public abstract fun downloadAndAddToPath (Ljava/lang/ClassLoader;Ljava/util/Collection;)V public abstract fun getPluginClassLoader ()Ljava/lang/ClassLoader; public abstract fun getPluginFile ()Ljava/io/File; public abstract fun getPluginIndependentLibrariesClassLoader ()Ljava/lang/ClassLoader; public abstract fun getPluginSharedLibrariesClassLoader ()Ljava/lang/ClassLoader; public abstract fun getShouldBeResolvableToIndependent ()Z public abstract fun getShouldResolveConsoleSystemResource ()Z public abstract fun getShouldResolveIndependent ()Z public abstract fun setShouldBeResolvableToIndependent (Z)V public abstract fun setShouldResolveConsoleSystemResource (Z)V public abstract fun setShouldResolveIndependent (Z)V } public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription : net/mamoe/mirai/console/plugin/description/PluginDescription { public static final field Companion Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription$Companion; public static fun loadFromResource ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public static fun loadFromResource (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public static fun loadFromResource (Ljava/lang/String;Ljava/lang/ClassLoader;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; } public final class net/mamoe/mirai/console/plugin/jvm/JvmPluginDescription$Companion { public final fun loadFromResource ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public final fun loadFromResource (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public final fun loadFromResource (Ljava/lang/String;Ljava/lang/ClassLoader;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public static synthetic fun loadFromResource$default (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription$Companion;Ljava/lang/String;Ljava/lang/ClassLoader;ILjava/lang/Object;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; } public final class net/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder { public fun <init> (Ljava/lang/String;Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/console/util/SemVersion;)V public final fun author (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun build ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public final fun dependsOn (Ljava/lang/String;Ljava/lang/String;Z)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun dependsOn (Ljava/lang/String;Z)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun dependsOn ([Lnet/mamoe/mirai/console/plugin/description/PluginDependency;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public static synthetic fun dependsOn$default (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder;Ljava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public static synthetic fun dependsOn$default (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun id (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun info (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun name (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun setDependencies (Ljava/util/Set;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun version (Ljava/lang/String;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; public final fun version (Lnet/mamoe/mirai/console/util/SemVersion;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionBuilder; } public final class net/mamoe/mirai/console/plugin/jvm/JvmPluginDescriptionKt { public static final synthetic fun JvmPluginDescription (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public static final synthetic fun JvmPluginDescription (Ljava/lang/String;Lnet/mamoe/mirai/console/util/SemVersion;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public static synthetic fun JvmPluginDescription$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public static synthetic fun JvmPluginDescription$default (Ljava/lang/String;Lnet/mamoe/mirai/console/util/SemVersion;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; } public abstract interface class net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/plugin/loader/FilePluginLoader { public static final field BuiltIn Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginLoader$BuiltIn; public abstract fun getClassLoaders ()Ljava/util/List; public abstract fun getConfigStorage ()Lnet/mamoe/mirai/console/data/PluginDataStorage; public abstract fun getDataStorage ()Lnet/mamoe/mirai/console/data/PluginDataStorage; public abstract fun getFileSuffix ()Ljava/lang/String; } public final class net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader$BuiltIn : net/mamoe/mirai/console/plugin/jvm/JvmPluginLoader { public synthetic fun disable (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun disable (Lnet/mamoe/mirai/console/plugin/jvm/JvmPlugin;)V public synthetic fun enable (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun enable (Lnet/mamoe/mirai/console/plugin/jvm/JvmPlugin;)V public fun getClassLoaders ()Ljava/util/List; public fun getConfigStorage ()Lnet/mamoe/mirai/console/data/PluginDataStorage; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getDataStorage ()Lnet/mamoe/mirai/console/data/PluginDataStorage; public fun getFileSuffix ()Ljava/lang/String; public synthetic fun getPluginDescription (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/plugin/description/PluginDescription; public fun getPluginDescription (Lnet/mamoe/mirai/console/plugin/jvm/JvmPlugin;)Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription; public fun listPlugins ()Ljava/util/List; public synthetic fun load (Lnet/mamoe/mirai/console/plugin/Plugin;)V public fun load (Lnet/mamoe/mirai/console/plugin/jvm/JvmPlugin;)V } public abstract class net/mamoe/mirai/console/plugin/jvm/KotlinPlugin : net/mamoe/mirai/console/plugin/jvm/AbstractJvmPlugin, net/mamoe/mirai/console/plugin/jvm/JvmPlugin { public fun <init> ()V public fun <init> (Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;)V public fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginDescription;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract class net/mamoe/mirai/console/plugin/loader/AbstractFilePluginLoader : net/mamoe/mirai/console/plugin/loader/FilePluginLoader { public fun <init> (Ljava/lang/String;)V protected abstract fun extractPlugins (Lkotlin/sequences/Sequence;)Ljava/util/List; public fun getFileSuffix ()Ljava/lang/String; public fun listPlugins ()Ljava/util/List; } public abstract interface class net/mamoe/mirai/console/plugin/loader/FilePluginLoader : net/mamoe/mirai/console/plugin/loader/PluginLoader { public abstract fun getFileSuffix ()Ljava/lang/String; } public class net/mamoe/mirai/console/plugin/loader/PluginLoadException : java/lang/RuntimeException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public fun <init> (Ljava/lang/Throwable;)V } public abstract interface class net/mamoe/mirai/console/plugin/loader/PluginLoader { public abstract fun disable (Lnet/mamoe/mirai/console/plugin/Plugin;)V public abstract fun enable (Lnet/mamoe/mirai/console/plugin/Plugin;)V public abstract fun getPluginDescription (Lnet/mamoe/mirai/console/plugin/Plugin;)Lnet/mamoe/mirai/console/plugin/description/PluginDescription; public abstract fun listPlugins ()Ljava/util/List; public abstract fun load (Lnet/mamoe/mirai/console/plugin/Plugin;)V } public class net/mamoe/mirai/console/util/AnsiMessageBuilder : java/io/Serializable, java/lang/Appendable { public static final field Companion Lnet/mamoe/mirai/console/util/AnsiMessageBuilder$Companion; public fun <init> (Ljava/lang/StringBuilder;)V public fun ansi (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public synthetic fun append (C)Ljava/lang/Appendable; public fun append (C)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (D)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (F)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (I)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (J)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public synthetic fun append (Ljava/lang/CharSequence;)Ljava/lang/Appendable; public fun append (Ljava/lang/CharSequence;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public synthetic fun append (Ljava/lang/CharSequence;II)Ljava/lang/Appendable; public fun append (Ljava/lang/CharSequence;II)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (Ljava/lang/Object;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (Ljava/lang/String;II)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (S)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun append (Z)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun blue ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static final fun create ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static final fun create (I)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static final fun create (IZ)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static final fun dropAnsi (Ljava/lang/String;)Ljava/lang/String; public fun emeraldGreen ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun equals (Ljava/lang/Object;)Z public static final fun from (Ljava/lang/StringBuilder;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static final fun from (Ljava/lang/StringBuilder;Z)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun getDelegate ()Ljava/lang/StringBuilder; public fun gold ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun gray ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun green ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun hashCode ()I public static final fun isAnsiSupported (Lnet/mamoe/mirai/console/command/CommandSender;)Z public fun lightBlue ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun lightCyan ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun lightGreen ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun lightPurple ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun lightRed ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun lightYellow ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun purple ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun red ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun reset ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public fun toString ()Ljava/lang/String; public fun white ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; } public final class net/mamoe/mirai/console/util/AnsiMessageBuilder$Companion { public final fun appendAnsi (Ljava/lang/StringBuilder;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun create ()Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun create (I)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun create (IZ)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static synthetic fun create$default (Lnet/mamoe/mirai/console/util/AnsiMessageBuilder$Companion;IZILjava/lang/Object;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun dropAnsi (Ljava/lang/String;)Ljava/lang/String; public final fun from (Ljava/lang/StringBuilder;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun from (Ljava/lang/StringBuilder;Z)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static synthetic fun from$default (Lnet/mamoe/mirai/console/util/AnsiMessageBuilder$Companion;Ljava/lang/StringBuilder;ZILjava/lang/Object;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public final fun isAnsiSupported (Lnet/mamoe/mirai/console/command/CommandSender;)Z } public final class net/mamoe/mirai/console/util/AnsiMessageBuilderKt { public static final synthetic fun AnsiMessageBuilder (I)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static synthetic fun AnsiMessageBuilder$default (IILjava/lang/Object;)Lnet/mamoe/mirai/console/util/AnsiMessageBuilder; public static final synthetic fun buildAnsiMessage (ILkotlin/jvm/functions/Function1;)Ljava/lang/String; public static synthetic fun buildAnsiMessage$default (ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/String; public static final synthetic fun sendAnsiMessage (Lnet/mamoe/mirai/console/command/CommandSender;ILkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun sendAnsiMessage (Lnet/mamoe/mirai/console/command/CommandSender;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendAnsiMessage$default (Lnet/mamoe/mirai/console/command/CommandSender;ILkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public abstract interface annotation class net/mamoe/mirai/console/util/ConsoleExperimentalApi : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/console/util/ConsoleInput { public static final field INSTANCE Lnet/mamoe/mirai/console/util/ConsoleInput$INSTANCE; public fun requestInput (Ljava/lang/String;)Ljava/lang/String; public abstract fun requestInput (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/util/ConsoleInput$INSTANCE : net/mamoe/mirai/console/util/ConsoleInput { public fun requestInput (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface annotation class net/mamoe/mirai/console/util/ConsoleInternalApi : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public final class net/mamoe/mirai/console/util/ConsoleUtils { public static final synthetic fun requestInput (Lnet/mamoe/mirai/console/MiraiConsole;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/console/util/MessageScope { public abstract fun getRealTarget ()Ljava/lang/Object; public fun sendMessage (Ljava/lang/String;)V public abstract fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)V public abstract fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/console/util/MessageScopeKt { public static final fun asMessageScope (Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun asMessageScope (Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun asMessageScope (Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final synthetic fun invoke (Lnet/mamoe/mirai/console/util/MessageScope;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;[Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;[Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;[Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;[Lnet/mamoe/mirai/console/util/MessageScope;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;[Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/command/CommandSender;[Lnet/mamoe/mirai/contact/Contact;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;[Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;[Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;[Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;[Lnet/mamoe/mirai/console/util/MessageScope;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;[Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/console/util/MessageScope;[Lnet/mamoe/mirai/contact/Contact;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;[Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;[Lnet/mamoe/mirai/console/command/CommandSender;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;[Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;[Lnet/mamoe/mirai/console/util/MessageScope;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;[Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun scopeWith (Lnet/mamoe/mirai/contact/Contact;[Lnet/mamoe/mirai/contact/Contact;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun toMessageScopeCommandSenderArray ([Lnet/mamoe/mirai/console/command/CommandSender;)Lnet/mamoe/mirai/console/util/MessageScope; public static final synthetic fun toMessageScopeCommandSenderFlow (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun toMessageScopeCommandSenderIterable (Ljava/lang/Iterable;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun toMessageScopeCommandSenderSequence (Lkotlin/sequences/Sequence;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun toMessageScopeContactArray ([Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/console/util/MessageScope; public static final synthetic fun toMessageScopeContactFlow (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun toMessageScopeContactIterable (Ljava/lang/Iterable;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun toMessageScopeContactSequence (Lkotlin/sequences/Sequence;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun toMessageScopeMessageScopeArray ([Lnet/mamoe/mirai/console/util/MessageScope;)Lnet/mamoe/mirai/console/util/MessageScope; public static final synthetic fun toMessageScopeMessageScopeFlow (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun toMessageScopeMessageScopeIterable (Ljava/lang/Iterable;)Lnet/mamoe/mirai/console/util/MessageScope; public static final fun toMessageScopeMessageScopeSequence (Lkotlin/sequences/Sequence;)Lnet/mamoe/mirai/console/util/MessageScope; } public final class net/mamoe/mirai/console/util/SemVersion : java/lang/Comparable { public static final field Companion Lnet/mamoe/mirai/console/util/SemVersion$Companion; public synthetic fun compareTo (Ljava/lang/Object;)I public fun compareTo (Lnet/mamoe/mirai/console/util/SemVersion;)I public final fun component1 ()I public final fun component2 ()I public final fun component3 ()Ljava/lang/Integer; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public static final synthetic fun contains (Lnet/mamoe/mirai/console/util/SemVersion$Requirement;Ljava/lang/String;)Z public static final synthetic fun contains (Lnet/mamoe/mirai/console/util/SemVersion$Requirement;Lnet/mamoe/mirai/console/util/SemVersion;)Z public final fun copy (IILjava/lang/Integer;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/console/util/SemVersion; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/util/SemVersion;IILjava/lang/Integer;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/console/util/SemVersion; public fun equals (Ljava/lang/Object;)Z public final fun equals (Lnet/mamoe/mirai/console/util/SemVersion;Z)Z public final fun getIdentifier ()Ljava/lang/String; public final fun getMajor ()I public final fun getMetadata ()Ljava/lang/String; public final fun getMinor ()I public final fun getPatch ()Ljava/lang/Integer; public fun hashCode ()I public static final fun parse (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/SemVersion; public static final fun parseRangeRequirement (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/SemVersion$Requirement; public static final fun satisfies (Lnet/mamoe/mirai/console/util/SemVersion;Ljava/lang/String;)Z public static final fun satisfies (Lnet/mamoe/mirai/console/util/SemVersion;Lnet/mamoe/mirai/console/util/SemVersion$Requirement;)Z public static final fun test (Lnet/mamoe/mirai/console/util/SemVersion$Requirement;Ljava/lang/String;)Z public fun toString ()Ljava/lang/String; public final fun toStructuredString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/util/SemVersion$Companion { public final synthetic fun contains (Lnet/mamoe/mirai/console/util/SemVersion$Requirement;Ljava/lang/String;)Z public final synthetic fun contains (Lnet/mamoe/mirai/console/util/SemVersion$Requirement;Lnet/mamoe/mirai/console/util/SemVersion;)Z public final fun parse (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/SemVersion; public final fun parseRangeRequirement (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/SemVersion$Requirement; public final fun satisfies (Lnet/mamoe/mirai/console/util/SemVersion;Ljava/lang/String;)Z public final fun satisfies (Lnet/mamoe/mirai/console/util/SemVersion;Lnet/mamoe/mirai/console/util/SemVersion$Requirement;)Z public final fun serializer ()Lkotlinx/serialization/KSerializer; public final fun test (Lnet/mamoe/mirai/console/util/SemVersion$Requirement;Ljava/lang/String;)Z } public final class net/mamoe/mirai/console/util/SemVersion$Requirement { public static final field Companion Lnet/mamoe/mirai/console/util/SemVersion$Requirement$Companion; public final fun component1 ()Ljava/lang/String; public final fun copy (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/SemVersion$Requirement; public static synthetic fun copy$default (Lnet/mamoe/mirai/console/util/SemVersion$Requirement;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/console/util/SemVersion$Requirement; public fun equals (Ljava/lang/Object;)Z public final fun getRule ()Ljava/lang/String; public fun hashCode ()I public final fun test (Lnet/mamoe/mirai/console/util/SemVersion;)Z public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/console/util/SemVersion$Requirement$Companion { public final synthetic fun invoke (Ljava/lang/String;)Lnet/mamoe/mirai/console/util/SemVersion$Requirement; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/console/util/SemVersion$Requirement$RequirementAsStringSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/console/util/SemVersion$Requirement$RequirementAsStringSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/console/util/SemVersion$Requirement; 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/console/util/SemVersion$Requirement;)V } public final class net/mamoe/mirai/console/util/SemVersion$SemVersionAsStringSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/console/util/SemVersion$SemVersionAsStringSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/console/util/SemVersion; 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/console/util/SemVersion;)V } ================================================ FILE: mirai-console/backend/mirai-console/resources/net/mamoe/mirai/console/internal/enduserreadme/readme.txt ================================================ ::mirai-console.greeting 欢迎使用 mirai-console。 在您正式开始使用 mirai-console 前,您需要完整阅读此用户须知。 此用户须知包含 mirai-console 本体及其所安装的插件的用户须知。 当相关的最终用户须知更新时,mirai-console 只会显示已更新部分,而不会重新完整显示整个用户须知。 ::mirai-console.usage 在使用 mirai-console 前,您需要完整阅读用户手册。 <delay>2 用户手册地址: GitHub: https://github.com/mamoe/mirai/blob/dev/docs/UserManual.md VuePress: https://docs.mirai.mamoe.net/UserManual.html <delay>3 当您遇到问题前,请先查阅 <delay>2 常见问题参考: https://docs.mirai.mamoe.net/Questions.html <delay>1 mirai 历史问题提问: https://github.com/mamoe/mirai/issues?q=is%3Aissue <delay>3 如果您使用的 mirai-console 来自一个单独整合包,您需要参考该整合包内的 `readme` 文件 ::mirai-console.issuing 在使用 mirai-console 的过程中,您可能会遇到各种问题。 在您向他人咨询前,您需要做好以下准备。 <delay>2 无论是 <delay>2 `- 在 mirai 主仓库发起 issue <delay>1 `- 在 mirai 论坛发起帖子 <delay>1 `- 在群聊向他人咨询 <delay>1 `- 在私聊向他人咨询 <delay>1 `- 或者更多 <delay>1 您都需要做好以下准备。 <delay>1 这不仅能让您更快解决问题,也是对被询问者的尊重。 <delay>1 1. 说明您正在使用的版本 <delay>2 版本号是确定问题的关键信息, <delay>1 mirai-console 的版本号会在 mirai-console 运行时就打印至控制台。 其他组件版本可以通过执行 /status 命令获取 <delay>3 2. 携带报错信息 / 携带日志 <delay>3 报错信息是分析问题的关键,没有日志相当于闭眼开车。 <delay>3 当您咨询时,一定要携带当时的日志 <delay>3 「没有日志我能做的事只有帮你算一卦」 <delay>3 标准的咨询模板参考: https://github.com/mamoe/mirai/issues/new?template=bug.yml ::mirai-core.EncryptService.alert Reference: https://github.com/mamoe/mirai/releases/tag/v2.15.0 关于包数据加密 / 签名 (Internal)(#2716) <delay>2 mirai 不会内置任何第三方 签名/加密 服务,而是提供 SPI 让用户自行实现。 <delay>2 mirai 已经提供了外部 EncryptService SPI 供用户对接。如果您没有能力自行对接,您可以考虑到论坛寻找社区对接。 <delay>2 在使用社区服务前,您需要了解并理解以下内容 <delay>2 <pause> 1. 确认服务来源 <delay>2 当您安装此服务后,所有的信息都会经过此消息服务。 <delay>2 这其中包括 Bot 的登录请求(包含密码,登录凭证等) <delay>2 Bot 发出去的所有信息 <delay>2 更多..... <delay>2 <pause> 2. 保护好网络,建立通讯防火墙 <delay>2 部分服务通讯链路是无加密的 <delay>1 如果您访问的服务位于公开网络,您的数据有被窃取、拦截的风险。 <delay>2 <pause> 3. 保护好日志。 <delay>2 并非所有日志都能直接传递给他人 <pause> 在您公开您的日志前,请先对日志中的关键信息进行抹除。 <pause> 部分相关服务使用 HTTP GET 请求传递数据体, 当远程服务出错时,服务对接可能会直接将此次请求的连接直接输出到日志中, 此日志可能包含了此次尝试 签名/加密 的内容, 而此内容可能包含关键信息。 <pause> 如果您无法分辨哪些请求需要被抹除时,您可以参考以下规则: <pause> 请求连接包含大量 Hex 文本,抹除 (Hex: 由 0-9 和 ABCDEF 组成的序列 ) <delay>2 <pause> 请求包含大量 Base64 文本,抹除 (如您不知道什么是 Base64 文本,您可以简单当做是超长的英文与符号组合) <delay>2 <pause> 请求连接过长,抹除(如连接日志换行了三次都还没有显示完全) <delay>2 <pause> ================================================ FILE: mirai-console/backend/mirai-console/src/MiraiConsole.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("WRONG_MODIFIER_CONTAINING_DECLARATION", "unused") @file:OptIn(ConsoleInternalApi::class) package net.mamoe.mirai.console import kotlinx.coroutines.* import me.him188.kotlin.dynamic.delegation.dynamicDelegation import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.console.MiraiConsole.INSTANCE import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.extensions.BotConfigurationAlterer import net.mamoe.mirai.console.fontend.ProcessProgress import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.center.PluginCenter import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.AnsiMessageBuilder import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.* import java.io.File import java.nio.file.Path import java.time.Instant /** * Mirai Console 后端功能入口. * * # 使用 Mirai Console * * ## 获取 Mirai Console 后端实例 * * 一般插件开发者只能通过 [MiraiConsole.INSTANCE] 获得 [MiraiConsole] 实例. * * ## Mirai Console 生命周期 * * [MiraiConsole] 实现[协程作用域][CoroutineScope]. [MiraiConsole] 生命周期与该[协程作用域][CoroutineScope]的相同. * * 在 [MiraiConsole] 实例构造后就视为*已开始[生存][Job.isActive]*. 随后才会[正式启动][MiraiConsoleImplementation.start] (初始化和加载插件等). * * [取消 Job][Job.cancel] 时会同时停止 [MiraiConsole], 并进行清理工作 (例如调用 [JvmPlugin.onDisable]. * * ## 获取插件管理器等功能实例 * * [MiraiConsole] 是后端功能入口, 可调用其 [MiraiConsole.pluginManager] 获取到 [PluginManager] 等实例. * * # 实现 Mirai Console * * ## 实现 Mirai Console 后端 * * [MiraiConsole] 不可直接实现. * * 要实现 Mirai Console 后端, 需实现接口 [MiraiConsoleImplementation] 为一个 `class` 切勿实现为 `object`(单例或静态). * * ## 启动 Mirai Console 后端 * * Mirai Console 后端 (即本 [MiraiConsole] 类实例) 不可单独 (直接) 启动, 需要配合一个任意的前端实现. * * Mirai Console 的启动时机由前端决定. 前端可在恰当的时机调用 [MiraiConsoleImplementation.start] 来启动一个 [MiraiConsoleImplementation]. * * [MiraiConsoleImplementation] 将会由 [bridge][MiraiConsoleImplementationBridge] 转接为 [MiraiConsole] 实现. 对 [MiraiConsole] 的调用都会被转发到前端实现的 [MiraiConsoleImplementation]. * * @see INSTANCE * @see MiraiConsoleImplementation */ @NotStableForInheritance public interface MiraiConsole : CoroutineScope { /** * Console 运行根目录, 由前端决定确切路径. * * 所有子模块都会在这个目录之下创建子目录. * * @see PluginManager.pluginsPath * @see PluginManager.pluginsDataPath * @see PluginManager.pluginsConfigPath */ public val rootPath: Path /** * Console 主日志. * * **实现细节**: 这个 [MiraiLogger] 的 [MiraiLogger.identity] 通常为 `main` * * **注意**: 插件不应该在任何时刻使用它. */ @ConsoleInternalApi public val mainLogger: MiraiLogger /** * 内建加载器列表, 一般需要包含 [JvmPluginLoader]. * * @return 不可变 [List] ([java.util.Collections.unmodifiableList]) */ public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> /** * 此 Console 后端构建时间 */ public val buildDate: Instant /** * 此 Console 后端版本号 */ public val version: SemVersion /** * [PluginManager] 实例. 在 [MiraiConsole] 生命周期内应保持不变. * * @since 2.10 */ public val pluginManager: PluginManager @ConsoleExperimentalApi public val pluginCenter: PluginCenter get() = throw UnsupportedOperationException("PluginCenter is not supported yet") /** * 创建一个 logger. 已弃用. 请使用 [MiraiLogger.Factory.create]. */ @Deprecated( "Please use the standard way in mirai-core to create loggers, i.e. MiraiLogger.Factory.INSTANCE.create()", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith( "MiraiLogger.Factory.create(yourClass::class, identity)", "net.mamoe.mirai.utils.MiraiLogger" ), ) @DeprecatedSinceMirai(warningSince = "2.13", errorSince = "2.14") // for removal @ConsoleExperimentalApi public fun createLogger(identity: String?): MiraiLogger /** * 是否支持使用 Ansi 输出彩色信息 * * 注: 不是每个前端都可能提供 `org.fusesource.jansi:jansi` 库支持, * 请不要直接使用 `org.fusesource.jansi:jansi` * * @see [AnsiMessageBuilder] */ @ConsoleExperimentalApi public val isAnsiSupported: Boolean /** * [MiraiConsole] 唯一实例. 一般插件开发者只能通过 [MiraiConsole.INSTANCE] 获得 [MiraiConsole] 实例. * * 对象以 [bridge][MiraiConsoleImplementationBridge] 实现, 将会桥接特定前端实现的 [MiraiConsoleImplementation] 到 [MiraiConsole]. */ public companion object INSTANCE : MiraiConsole by dynamicDelegation({ @OptIn(ConsoleFrontEndImplementation::class) MiraiConsoleImplementation.getBridge() }) { /** * 获取 [MiraiConsole] 的 [Job] */ // MiraiConsole.INSTANCE.getJob() public val job: Job get() = MiraiConsole.coroutineContext[Job] ?: throw MalformedMiraiConsoleImplementationError("Internal error: Job not found in MiraiConsole.coroutineContext") /** * 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录. * * 调用 [Bot.login] 可登录. * * @see Bot.instances 获取现有 [Bot] 实例列表 * @see BotConfigurationAlterer ExtensionPoint */ // don't static @ConsoleExperimentalApi("This is a low-level API and might be removed in the future.") public fun addBot(id: Long, password: String, configuration: BotConfiguration.() -> Unit = {}): Bot = addBotImpl(id, password, configuration) /** * 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录. * * 调用 [Bot.login] 可登录. * * @see Bot.instances 获取现有 [Bot] 实例列表 * @see BotConfigurationAlterer ExtensionPoint */ @ConsoleExperimentalApi("This is a low-level API and might be removed in the future.") public fun addBot(id: Long, password: ByteArray, configuration: BotConfiguration.() -> Unit = {}): Bot = addBotImpl(id, password, configuration) /** * 添加一个 [Bot] 实例到全局 Bot 列表, 但不登录. * * 调用 [Bot.login] 可登录. * * @see Bot.instances 获取现有 [Bot] 实例列表 * @see BotConfigurationAlterer ExtensionPoint */ @ConsoleExperimentalApi("This is a low-level API and might be removed in the future.") public fun addBot( id: Long, authorization: BotAuthorization, configuration: BotConfiguration.() -> Unit = {} ): Bot = addBotImpl(id, authorization, configuration) @OptIn(ConsoleFrontEndImplementation::class) private fun addBotImpl(id: Long, authorization: Any, configuration: BotConfiguration.() -> Unit = {}): Bot { when (authorization) { is String -> {} is ByteArray -> {} is BotAuthorization -> {} else -> throw IllegalArgumentException("Bad authorization type: `${authorization.javaClass.name}`. Require String, ByteArray or BotAuthorization") } var config = BotConfiguration().apply { workingDir = MiraiConsole.rootDir .resolve("bots") .resolve(id.toString()) .also { it.mkdirs() } mainLogger.verbose { "Bot $id working in $workingDir" } val deviceInRoot = MiraiConsole.rootDir.resolve("device.json") val deviceInWorkingDir = workingDir.resolve("device.json") val deviceInfoInWorkingDir = workingDir.resolve("deviceInfo.json") if (!deviceInWorkingDir.exists()) { when { deviceInfoInWorkingDir.exists() -> { // rename bots/id/deviceInfo.json to bots/id/device.json mainLogger.verbose { "Renaming $deviceInfoInWorkingDir to $deviceInWorkingDir" } deviceInfoInWorkingDir.renameTo(deviceInWorkingDir) } deviceInRoot.exists() -> { // copy root/device.json to bots/id/device.json mainLogger.verbose { "Coping $deviceInRoot to $deviceInWorkingDir" } deviceInRoot.copyTo(deviceInWorkingDir) } } } fileBasedDeviceInfo("device.json") redirectNetworkLogToDirectory() this.botLoggerSupplier = { MiraiLogger.Factory.create(Bot::class, "Bot.${it.id}") } parentCoroutineContext = MiraiConsole.childScopeContext("Bot $id") autoReconnectOnForceOffline() this.loginSolver = MiraiConsoleImplementation.getInstance().createLoginSolver(id, this) configuration() } config = GlobalComponentStorage.foldExtensions(BotConfigurationAlterer, config) { acc, extension -> extension.alterConfiguration(id, acc) } return when (authorization) { is ByteArray -> BotFactory.newBot(id, authorization, config) // pwd md5 is String -> BotFactory.newBot(id, authorization, config) // pwd is BotAuthorization -> BotFactory.newBot(id, authorization, config) // authorization else -> error("assert") } } @ConsoleExperimentalApi("This is a low-level API and might be removed in the future.") public val isActive: Boolean get() = job.isActive /** * 停止 Console 运行 * * Console 会在一个合适的时间进行关闭, 并不是调用马上关闭 Console */ @ConsoleExperimentalApi @JvmStatic public fun shutdown() { val consoleJob = job if (!consoleJob.isActive) return @OptIn(DelicateCoroutinesApi::class, ConsoleFrontEndImplementation::class) GlobalScope.launch { MiraiConsoleImplementation.shutdown() } } /** * 创建一个新的处理进度, 此进度将会在前端显示, 并且此进度需要[手动关闭][ProcessProgress.close] * * 注: 此 API 应该只在以下情况使用 * * - 插件初始化 (包括 onLoad, onEnable) * - 命令执行中 (控制台) * * 在其他情况使用可能会导致意外的情况 * * // implementation note: * 在 Terminal 前端中, 有处理进度存在时会停止命令输入 (即停止命令执行) */ @ConsoleExperimentalApi @JvmStatic public fun newProcessProgress(): ProcessProgress { @OptIn(ConsoleFrontEndImplementation::class) return MiraiConsoleImplementation.getInstance().createNewProcessProgress() } } } /** * @see MiraiConsole.rootPath */ public val MiraiConsole.rootDir: File get() = rootPath.toFile() /** * [MiraiConsoleImplementation] 实现有误时抛出. * * @see MiraiConsoleImplementation.start */ public class MalformedMiraiConsoleImplementationError : Error { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } ================================================ FILE: mirai-console/backend/mirai-console/src/MiraiConsoleFrontEndDescription.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.MiraiExperimentalApi /** * 有关前端实现的信息 */ public interface MiraiConsoleFrontEndDescription { /** * 此前端实现的名称 */ public val name: String /** * 此前端实现的提供者 */ public val vendor: String /** * 此前端实现的名称 */ public val version: SemVersion /** * 兼容的 [MiraiConsole] 后端版本号 * * 如 `Semver("[1.0.0, 2.0.0)", Semver.SemverType.IVY)` * * 返回 `null` 表示禁止 [MiraiConsole] 后端检查版本兼容性. */ @MiraiExperimentalApi public val compatibleBackendVersion: SemVersion? get() = null /** * 返回显示在 [MiraiConsole] 启动时的信息 */ public fun render(): String = "Frontend ${name}: version ${version}, provided by $vendor" } ================================================ FILE: mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.* import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.data.AutoSavePluginDataHolder import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.extension.ComponentStorage import net.mamoe.mirai.console.fontend.DefaultLoggingProcessProgress import net.mamoe.mirai.console.fontend.ProcessProgress import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScopeImpl import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl import net.mamoe.mirai.console.internal.plugin.impl import net.mamoe.mirai.console.internal.pluginManagerImpl import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.* import java.nio.file.Path import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.annotation.AnnotationTarget.* import kotlin.concurrent.thread import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass /** * 标记一个仅用于 [MiraiConsole] 前端实现的 API. * * 这些 API 只应由前端实现者使用, 而不应该被插件或其他调用者使用. */ @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @MustBeDocumented @ConsoleFrontEndImplementation public annotation class ConsoleFrontEndImplementation /** * 实现 [MiraiConsole] 的接口 * * **注意**: 随着 Console 的更新, 在版本号 `x.y.z` 的 `y` 修改时此接口可能就会发生 ABI 变动. 意味着前端实现着需要跟随 Console 更新. * * @see MiraiConsoleImplementation.start 启动 */ @ConsoleFrontEndImplementation public interface MiraiConsoleImplementation : CoroutineScope { /** * 获取原始 [MiraiConsoleImplementation] 实例. * * [MiraiConsoleImplementation.start] 实际上会创建 [MiraiConsoleImplementationBridge] 并启动该 bridge, 不会直接使用提供的 [MiraiConsoleImplementation] 实例. * [MiraiConsoleImplementation.getInstance] 获取到的将会是 bridge. 可通过 `bridge.origin` 获取原始在 [start] 传递的实例. * * @since 2.11.0-RC */ public val origin: MiraiConsoleImplementation get() = this /** * [MiraiConsole] 的 [CoroutineScope.coroutineContext], 必须拥有如下元素 * * - [Job]: 用于管理整个 [MiraiConsole] 的生命周期. 当此 [Job] 被 [Job.cancel] 后, [MiraiConsole] 就会结束. * - [CoroutineExceptionHandler]: 用于处理 [MiraiConsole] 所有协程抛出的 **未被捕捉** 的异常. 不是所有异常都会被传递到这里. */ public override val coroutineContext: CoroutineContext /** * Console 运行根目录绝对路径 (否则可能会被一些 native 插件覆盖相对路径) * @see MiraiConsole.rootPath 获取更多信息 */ public val rootPath: Path /** * 本前端实现的描述信息 */ public val frontEndDescription: MiraiConsoleFrontEndDescription /** * 内建加载器列表, 一般需要包含 [JvmPluginLoader]. * * @return 不可变的 [List], [Collections.unmodifiableList] */ public val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> /** * [JvmPluginLoader] 实例. 建议实现为 lazy: * * ``` * override val jvmPluginLoader: JvmPluginLoader by lazy { backendAccess.createDefaultJvmPluginLoader(coroutineContext) } * ``` * * @see BackendAccess.createDefaultJvmPluginLoader * @since 2.10.0-RC */ public val jvmPluginLoader: JvmPluginLoader /** * 由 Kotlin 用户实现 * * @see [ConsoleCommandSender] */ @ConsoleFrontEndImplementation public interface ConsoleCommandSenderImpl { @JvmSynthetic public suspend fun sendMessage(message: Message) @JvmSynthetic public suspend fun sendMessage(message: String) } /** * 由 Java 用户实现 * * @see [ConsoleCommandSender] */ @Suppress("INAPPLICABLE_JVM_NAME") @ConsoleFrontEndImplementation public interface JConsoleCommandSenderImpl : ConsoleCommandSenderImpl { @JvmName("sendMessage") public fun sendMessageJ(message: Message) @JvmName("sendMessage") public fun sendMessageJ(message: String) @JvmSynthetic public override suspend fun sendMessage(message: Message): Unit = withContext(Dispatchers.IO) { sendMessageJ(message) } @JvmSynthetic public override suspend fun sendMessage(message: String): Unit = withContext(Dispatchers.IO) { sendMessageJ(message) } } /** * [ConsoleCommandSender] */ public val consoleCommandSender: ConsoleCommandSenderImpl /** * [CommandManager] 实现, 建议实现为 lazy: * ``` * override val commandManager: CommandManager by lazy { backendAccess.createDefaultCommandManager(coroutineContext) } * ``` * * @since 2.10.0-RC * @see BackendAccess.createDefaultCommandManager */ public val commandManager: CommandManager @ConsoleExperimentalApi public val dataStorageForJvmPluginLoader: PluginDataStorage @ConsoleExperimentalApi public val configStorageForJvmPluginLoader: PluginDataStorage @ConsoleExperimentalApi public val dataStorageForBuiltIns: PluginDataStorage @ConsoleExperimentalApi public val configStorageForBuiltIns: PluginDataStorage /** * @see ConsoleInput 的实现 * @see JConsoleInput */ public val consoleInput: ConsoleInput /** * 供 Java 用户实现 [ConsoleInput] */ @Suppress("INAPPLICABLE_JVM_NAME") @ConsoleFrontEndImplementation public interface JConsoleInput : ConsoleInput { /** * @see ConsoleInput.requestInput */ @JvmName("requestInput") public fun requestInputJ(hint: String): String override suspend fun requestInput(hint: String): String { return runInterruptible(Dispatchers.IO) { requestInputJ(hint) } } } /** * 创建一个 [LoginSolver] * * **备注**: 此函数通常在构造 [Bot] 实例, 即 [MiraiConsole.addBot] 时调用. * * @param requesterBot 请求者 [Bot.id] * @param configuration 请求者 [Bot.configuration] * * @see LoginSolver.Default */ public fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver /** @see [MiraiConsole.newProcessProgress] */ public fun createNewProcessProgress(): ProcessProgress { return DefaultLoggingProcessProgress() } /** * 该前端是否支持使用 Ansi 输出彩色信息 * * 注: 若为 `true`, 建议携带 `org.fusesource.jansi:jansi` */ public val isAnsiSupported: Boolean get() = false /** * 前端预先定义的 [LoggerController], 以允许前端使用自己的配置系统 */ @ConsoleExperimentalApi public val loggerController: LoggerController get() = LoggerControllerImpl() /////////////////////////////////////////////////////////////////////////// // ConsoleDataScope /////////////////////////////////////////////////////////////////////////// /** * Mirai Console 内置的一些 [PluginConfig] 和 [PluginData] 的管理器. * * 建议实现为 lazy: * ``` * override val consoleDataScope: MiraiConsoleImplementation.ConsoleDataScope by lazy { * MiraiConsoleImplementation.ConsoleDataScope.createDefault( * coroutineContext, * dataStorageForBuiltIns, * configStorageForBuiltIns * ) * } * ``` * * @since 2.10.0-RC */ public val consoleDataScope: ConsoleDataScope /** * Mirai Console 内置的一些 [PluginConfig] 和 [PluginData] 的管理器. * * @since 2.10.0-RC */ @ConsoleFrontEndImplementation @NotStableForInheritance public interface ConsoleDataScope { @ConsoleExperimentalApi public val dataHolder: AutoSavePluginDataHolder @ConsoleExperimentalApi public val configHolder: AutoSavePluginDataHolder public fun addAndReloadConfig(config: PluginConfig) /** * @since 2.11.0-RC */ public fun <T : PluginData> find(type: KClass<T>): T? /** * @since 2.11.0-RC */ public fun <T : PluginData> get(type: KClass<T>): T = find(type) ?: throw NoSuchElementException(type.qualifiedName) public fun reloadAll() /** * @since 2.10.0-RC */ @ConsoleFrontEndImplementation public companion object { /** * @since 2.11.0-RC */ public inline fun <reified T : PluginData> ConsoleDataScope.find(): T? = find(T::class) /** * @since 2.11.0-RC */ public inline fun <reified T : PluginData> ConsoleDataScope.get(): T = get(T::class) @ConsoleExperimentalApi @JvmStatic public fun createDefault( coroutineContext: CoroutineContext, dataStorage: PluginDataStorage, configStorage: PluginDataStorage ): ConsoleDataScope = ConsoleDataScopeImpl(coroutineContext, dataStorage, configStorage) } } /// Hooks & Backend Access /** * 后端 在 [phase] 阶段执行前会调用此方法, 如果此方法抛出了一个错误会直接中断 console 初始化 * * @since 2.5.0-dev-2 */ public fun prePhase(phase: String) {} /** * 后端 在 [phase] 阶段执行后会调用此方法, 如果此方法抛出了一个错误会直接中断 console 初始化 * * @since 2.5.0-dev-2 */ public fun postPhase(phase: String) {} /** * 后端在 [start] 前会调用此方法 * * @since 2.5.0-dev-2 */ public fun preStart() {} /** * 后端在 [start] 后会调用此方法 * * @since 2.5.0-dev-2 */ public fun postStart() {} /** * 前端访问后端内部实现的桥 * * @see backendAccess * @since 2.5.0-dev-2 */ @ConsoleFrontEndImplementation public interface BackendAccess { // GlobalComponentStorage /** * 在 Mirai Console 第一个 phase 之后会包含内建 storages. */ public val globalComponentStorage: ComponentStorage // PluginManagerImpl.resolvedPlugins public val resolvedPlugins: MutableList<Plugin> /** * @since 2.10.0-RC */ public fun createDefaultJvmPluginLoader(coroutineContext: CoroutineContext): JvmPluginLoader = BuiltInJvmPluginLoaderImpl(coroutineContext + MiraiConsole.pluginManager.impl.coroutineContext.job) /** * @since 2.10.0-RC */ public fun createDefaultCommandManager(coroutineContext: CoroutineContext): CommandManager = CommandManagerImpl(coroutineContext) } /** * @see BackendAccess * @since 2.5.0-dev-2 * @throws IllegalStateException 当前端实例不是 `this` 时抛出 */ public val backendAccess: BackendAccess get() = backendAccessInstance /////////////////////////////////////////////////////////////////////////// // Logging /////////////////////////////////////////////////////////////////////////// /** * 创建一个 [MiraiLogger.Factory]. * * @since 2.13 */ public fun createLoggerFactory(context: FrontendLoggingInitContext): MiraiLogger.Factory /** * 前端 [MiraiLogger.Factory] 加载的上下文 * * 全局的日志工厂的初始化可以分为如下几步 * * 1. 接管 stdout (见 [System.setOut]), 将 stdout 重定向至屏幕. * 之后平台日志实现会将日志通过被接管的 stdout 输出至屏幕 * 2. 前端返回 [platformImplementation][acquirePlatformImplementation] 或者返回适配的 [MiraiLogger.Factory] */ @ConsoleFrontEndImplementation public interface FrontendLoggingInitContext { /** * 平台的日志实现, 这可能是使用 SLF4J 等日志框架转接的实例. * * 调用此函数会立即初始化平台日志实现. 在未完成准备工作前切勿使用此方法 */ public fun acquirePlatformImplementation(): MiraiLogger.Factory /** * 在完成 [MiraiLogger.Factory] 接管后马上执行 [action] */ public fun invokeAfterInitialization(action: () -> Unit) } /////////////////////////////////////////////////////////////////////////// // ConsoleLaunchOptions /////////////////////////////////////////////////////////////////////////// /** * Console 启动参数, 修改参数会改变默认行为 * @since 2.10.0-RC */ public class ConsoleLaunchOptions { @JvmField public var crashWhenPluginLoadFailed: Boolean = false } public val consoleLaunchOptions: ConsoleLaunchOptions get() = ConsoleLaunchOptions() @ConsoleFrontEndImplementation public companion object { private val backendAccessInstance = object : BackendAccess { override val globalComponentStorage: ComponentStorage get() = getBridge().globalComponentStorage override val resolvedPlugins: MutableList<Plugin> get() = MiraiConsole.pluginManagerImpl.resolvedPlugins } internal suspend fun shutdown() { val bridge = currentBridge ?: return if (!bridge.isActive) return bridge.shutdownDaemon.tryStart() Bot.instances.forEach { bot -> lateinit var logger: MiraiLogger kotlin.runCatching { logger = bot.logger bot.closeAndJoin() }.onFailure { t -> kotlin.runCatching { logger.error("Error in closing bot", t) } } } MiraiConsole.job.cancelAndJoin() } init { Runtime.getRuntime().addShutdownHook(thread(false, name = "Mirai Console Shutdown Hook") { if (instanceInitialized) { try { runBlocking { shutdown() } } catch (_: InterruptedException) { } } }) } @Volatile internal var currentBridge: MiraiConsoleImplementationBridge? = null internal val instanceInitialized: Boolean get() = currentBridge != null private val initLock = ReentrantLock() /** * 可由前端调用, 获取当前的 [MiraiConsoleImplementation] 实例. 注意该实例不是 [start] 时传递的实例, 而会是 [MiraiConsoleImplementationBridge]. * * 必须在 [start] 之后才能使用, 否则抛出 [UninitializedPropertyAccessException]. */ @JvmStatic @ConsoleFrontEndImplementation public fun getInstance(): MiraiConsoleImplementation = currentBridge ?: throw UninitializedPropertyAccessException() /** * @since 2.11 */ internal fun getBridge(): MiraiConsoleImplementationBridge = currentBridge ?: throw UninitializedPropertyAccessException() /** 由前端调用, 初始化 [MiraiConsole] 实例并启动 */ @OptIn(ConsoleInternalApi::class) @JvmStatic @ConsoleFrontEndImplementation @Throws(MalformedMiraiConsoleImplementationError::class) public fun MiraiConsoleImplementation.start(): Unit = initLock.withLock { val currentBridge = currentBridge if (currentBridge != null && currentBridge.isActive) { error( "Mirai Console is already initialized and is currently running. " + "Run MiraiConsole.cancel to kill old instance before starting another instance." ) } val newBridge = MiraiConsoleImplementationBridge(this) this@Companion.currentBridge = newBridge kotlin.runCatching { newBridge.doStart() }.onFailure { e -> kotlin.runCatching { MiraiConsole.mainLogger.error("Failed to init MiraiConsole.", e) }.onFailure { e.printStackTrace() } kotlin.runCatching { MiraiConsole.cancel() }.onFailure { it.printStackTrace() } } } } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/AbstractCommand.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission import net.mamoe.mirai.console.permission.Permission /** * [Command] 的基础实现 * * @see SimpleCommand * @see CompositeCommand * @see RawCommand */ public abstract class AbstractCommand @JvmOverloads constructor( public final override val owner: CommandOwner, public final override val primaryName: String, public final override val secondaryNames: Array<out String>, public override val description: String = "<no description available>", parentPermission: Permission = owner.parentPermission, ) : Command { @ExperimentalCommandDescriptors override val prefixOptional: Boolean get() = false init { Command.checkCommandName(primaryName) secondaryNames.forEach(Command.Companion::checkCommandName) } public override val usage: String get() = description public override val permission: Permission by lazy { findOrCreateCommandPermission(parentPermission) } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/BuiltInCommands.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class) package net.mamoe.mirai.console.command import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.Bot import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get import net.mamoe.mirai.console.command.CommandManager.INSTANCE.allRegisteredCommands import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map import net.mamoe.mirai.console.command.descriptor.PermissionIdValueArgumentParser import net.mamoe.mirai.console.command.descriptor.PermitteeIdValueArgumentParser import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants import net.mamoe.mirai.console.internal.command.builtin.LoginCommandImpl import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.* import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN import net.mamoe.mirai.console.internal.data.builtins.DataScope import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.permission.getPermittedPermissionsAndSource import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal import net.mamoe.mirai.console.internal.plugin.MiraiConsoleAsPlugin import net.mamoe.mirai.console.internal.pluginManagerImpl import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService.Companion.cancel import net.mamoe.mirai.console.permission.PermissionService.Companion.findCorrespondingPermissionOrFail import net.mamoe.mirai.console.permission.PermissionService.Companion.hasPermission import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.plugin.version import net.mamoe.mirai.console.util.* import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiExperimentalApi import java.lang.management.ManagementFactory import java.lang.management.MemoryMXBean import java.lang.management.MemoryUsage import java.time.ZoneId import java.time.format.DateTimeFormatter import kotlin.system.exitProcess @ConsoleExperimentalApi @Suppress("EXPOSED_SUPER_INTERFACE") public interface BuiltInCommand : Command // for identification @OptIn(ConsoleExperimentalApi::class) internal interface BuiltInCommandInternal : Command, BuiltInCommand /** * 内建指令列表 * * [查看文档](https://github.com/mamoe/mirai-console/docs/BuiltInCommands.md) */ @Suppress("unused", "RESTRICTED_CONSOLE_COMMAND_OWNER") public object BuiltInCommands { @ConsoleExperimentalApi public val parentPermission: Permission by lazy { PermissionService.INSTANCE.register( ConsoleCommandOwner.permissionId("*"), "The parent of any built-in commands" ) } internal val all: Array<out Command> by lazy { this::class.nestedClasses.mapNotNull { it.objectInstance as? Command }.toTypedArray() } internal fun registerAll() { BuiltInCommands::class.nestedClasses.forEach { (it.objectInstance as? Command)?.register() } } public object HelpCommand : SimpleCommand( ConsoleCommandOwner, "help", description = "查看指令帮助", ), BuiltInCommandInternal { /** * @since 2.8.0 */ // for https://github.com/mamoe/mirai-console/issues/416 @JvmStatic public fun generateDefaultHelp(permitteeId: PermitteeId): String { return allRegisteredCommands .asSequence() .filter { permitteeId.hasPermission(it.permission) } .joinToString("\n\n") { command -> val lines = command.usage.lines() if (lines.isEmpty()) "/${command.primaryName} ${command.description}" else "◆ " + lines.first() + "\n" + lines.drop(1).joinToString("\n") { " $it" } }.lines().filterNot(String::isBlank).joinToString("\n") } @Handler public suspend fun CommandSender.handle() { sendMessage(generateDefaultHelp(this.permitteeId)) } } public object StopCommand : SimpleCommand( ConsoleCommandOwner, "stop", "shutdown", "exit", description = "关闭 Mirai Console", ), BuiltInCommandInternal { private val closingLock = Mutex() @OptIn( DelicateCoroutinesApi::class, ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class, ConsoleInternalApi::class ) @Handler public suspend fun CommandSender.handle() { GlobalScope.launch { kotlin.runCatching { closingLock.withLock { if (!MiraiConsole.isActive) return@withLock sendMessage("Stopping mirai-console") kotlin.runCatching { MiraiConsoleImplementation.shutdown() }.fold( onSuccess = { runIgnoreException<EventCancelledException> { sendMessage("mirai-console stopped successfully.") } }, onFailure = { @OptIn(ConsoleInternalApi::class) MiraiConsole.mainLogger.error("Exception in stop", it) runIgnoreException<EventCancelledException> { sendMessage( it.localizedMessage ?: it.message ?: it.toString() ) } } ) } }.exceptionOrNull()?.let(MiraiConsole.mainLogger::error) exitProcess(0) } } } public object LogoutCommand : SimpleCommand( ConsoleCommandOwner, "logout", "登出", description = "登出一个账号", ), BuiltInCommandInternal { @OptIn(ConsoleExperimentalApi::class) @Handler public suspend fun CommandSender.handle( @Name("qq") id: Long ) { if (Bot.getInstanceOrNull(id)?.close() == null) { sendMessage("$id 未登录") } else { sendMessage("$id 已登出") } } } private val loginCommandInstance = LoginCommandImpl() public object LoginCommand : SimpleCommand( ConsoleCommandOwner, loginCommandInstance.primaryName, * loginCommandInstance.secondaryNames, description = loginCommandInstance.description, ), BuiltInCommandInternal { @OptIn(ConsoleExperimentalApi::class) @Handler @JvmOverloads public suspend fun CommandSender.handle( @Name("qq") id: Long, password: String? = null, protocol: BotConfiguration.MiraiProtocol? = null, ) { loginCommandInstance.run { handle(id, password, protocol) } } } public object PermissionCommand : CompositeCommand( ConsoleCommandOwner, "permission", "权限", "perm", description = "管理权限", overrideContext = buildCommandArgumentContext { PermitteeId::class with PermitteeIdValueArgumentParser Permission::class with PermissionIdValueArgumentParser.map { id -> kotlin.runCatching { id.findCorrespondingPermissionOrFail() }.getOrElse { throw CommandArgumentParserException("指令不存在: $id", it) } } }, ), BuiltInCommandInternal { // TODO: 2020/9/10 improve Permission command /* 用于解析权限继承关系 */ private class PermTree( val perm: Permission, val sub: MutableList<PermissionId> = mutableListOf(), var linked: Boolean = false, var implicit: Boolean = false, val source: MutableList<PermitteeId> = mutableListOf(), ) { companion object { fun sortView(view: PermTree) { view.sub.sortWith { p1, p2 -> val namespaceCompare = p1.namespace compareTo p2.namespace if (namespaceCompare != 0) return@sortWith namespaceCompare if (p1.name == p2.name) return@sortWith 0 // ? if (p1.name == "*") return@sortWith -1 if (p2.name == "*") return@sortWith 1 return@sortWith p1.name compareTo p2.name } } } } private fun renderDepth(depth: Int, sb: AnsiMessageBuilder) { repeat(depth) { sb.append(" | ") } } @OptIn(ConsoleExperimentalApi::class) @Description("授权一个权限") @SubCommand("permit", "grant", "add") public suspend fun CommandSender.permit( @Name("被许可人 ID") target: PermitteeId, @Name("权限 ID") permission: Permission, ) { target.permit(permission) sendMessage("OK") } @OptIn(ConsoleExperimentalApi::class) @Description("撤销一个权限") @SubCommand("cancel", "deny", "remove") public suspend fun CommandSender.cancel( @Name("被许可人 ID") target: PermitteeId, @Name("权限 ID") permission: Permission, ) { target.cancel(permission, false) sendMessage("OK") } @OptIn(ConsoleExperimentalApi::class) @Description("撤销一个权限及其所有子权限") @SubCommand("cancelAll", "denyAll", "removeAll") public suspend fun CommandSender.cancelAll( @Name("被许可人 ID") target: PermitteeId, @Name("权限 ID") permission: Permission, ) { target.cancel(permission, true) sendMessage("OK") } @OptIn(ConsoleExperimentalApi::class) @Description("查看被授权权限列表") @SubCommand("permittedPermissions", "pp", "grantedPermissions", "gp") public suspend fun CommandSender.permittedPermissions( @Name("被许可人 ID") target: PermitteeId, @Name("显示全部") all: Boolean = true, ) { val grantedPermissions = target.getPermittedPermissionsAndSource().toList() if (grantedPermissions.isEmpty()) { sendMessage("${target.asString()} 未被授予任何权限. 使用 `${CommandManager.commandPrefix}permission grant` 给予权限.") } else if (all) { val allPermissions = PermissionService.INSTANCE.getRegisteredPermissions().toList() val permMapping = mutableMapOf<PermissionId, PermTree>() grantedPermissions.forEach { (source, granted) -> val m = permMapping[granted.id] ?: kotlin.run { PermTree(granted).also { it.implicit = false; permMapping[granted.id] = it } } m.source.add(source) } val root = PermissionService.INSTANCE.rootPermission fun linkPmTree(permTree: PermTree) { allPermissions.forEach { perm -> if (perm.id == root.id) return@forEach if (perm.parent.id == permTree.perm.id) { permTree.sub.add(perm.id) val subp = permMapping[perm.id] ?: kotlin.run { val p = PermTree(perm) p.implicit = true permMapping[perm.id] = p linkPmTree(p) p } subp.linked = true } } } permMapping.values.toList().forEach { linkPmTree(it) } permMapping.values.forEach { PermTree.sortView(it) } @Suppress("LocalVariableName") val BG_BLACK = "\u001B[40m" fun render(depth: Int, view: PermTree, sb: AnsiMessageBuilder) { if (view.implicit) { sb.gray() sb.append(view.perm.id) sb.append(" (implicit)\n") sb.reset().white().ansi(BG_BLACK) } else { sb.append(view.perm.id) if (view.source.isNotEmpty()) { sb.append(' ').gray().append('(') sb.append("from ") view.source.joinTo(sb) sb.append(')').reset().white().ansi(BG_BLACK) } sb.append('\n') } view.sub.forEach { sub -> val subView = permMapping[sub] ?: error("Error in resolving $sub") renderDepth(depth, sb) sb.append(" |- ") render(depth + 1, subView, sb) } } sendAnsiMessage { ansi(BG_BLACK).white() permMapping.forEach { (_, tree) -> if (!tree.linked) { render(0, tree, this) } } } } else { sendMessage(grantedPermissions.map { it.second.id }.toSet().joinToString("\n")) } } @Description("查看所有权限列表") @SubCommand("listPermissions", "lp") public suspend fun CommandSender.listPermissions() { val rootView = PermTree(PermissionService.INSTANCE.rootPermission) val mappings = mutableMapOf<PermissionId, PermTree>() val permListSnapshot = PermissionService.INSTANCE.getRegisteredPermissions().toList() permListSnapshot.forEach { perm -> mappings[perm.id] = PermTree(perm) } mappings[rootView.perm.id] = rootView permListSnapshot.forEach { perm -> if (perm.id == rootView.perm.id) return@forEach val parentView = mappings[perm.parent.id] ?: error("Can't find parent of ${perm.id}: ${perm.parent.id}") parentView.sub.add(perm.id) } mappings.values.forEach { PermTree.sortView(it) } //*:* // | `- // |- ..... // | | `- // | |- ...... // | | | `- // | | |- val prefixed = 50 fun render(depth: Int, view: PermTree, sb: AnsiMessageBuilder) { kotlin.run { // render perm id var doReset = false val permId = view.perm.id if (permId == rootView.perm.id || permId.name.endsWith("*")) { doReset = true sb.red() } sb.append(permId) if (doReset) { sb.reset() } } val linePrefixLen = (depth * 3) + 1 + view.perm.id.let { it.name.length + it.namespace.length } + (if (depth == 0) 0 else 1) val descFlatten = view.perm.description.replace("\r\n", "\n").replace("\r", "\n") if (!descFlatten.contains('\n') && linePrefixLen < prefixed) { if (descFlatten.isNotBlank()) { repeat(prefixed - linePrefixLen) { sb.append(' ') } sb.append(" ").append(descFlatten) } } else { descFlatten.splitToSequence('\n').forEach { line -> sb.append('\n') renderDepth(depth, sb) sb.append(" | `- ").append(line) } } sb.append('\n') view.sub.forEach { sub -> val subView = mappings[sub] ?: return@forEach renderDepth(depth, sb) sb.append(" |- ") render(depth + 1, subView, sb) } } sendAnsiMessage { render(0, rootView, this) } } } public object AutoLoginCommand : CompositeCommand( ConsoleCommandOwner, "autoLogin", "自动登录", description = "自动登录设置", overrideContext = buildCommandArgumentContext { @OptIn(ConsoleExperimentalApi::class) ConfigurationKey::class with ConfigurationKey.Parser } ), BuiltInCommandInternal { @Description("查看自动登录账号列表") @SubCommand @OptIn(ConsoleExperimentalApi::class) public suspend fun CommandSender.list() { val config = DataScope.get<AutoLoginConfig>() sendMessage(buildString { for (account in config.accounts) { if (account.account == "123456") continue append("- ") append("账号: ") append(account.account) appendLine() append(" 密码: ") append(account.password.value) appendLine() if (account.configuration.isNotEmpty()) { appendLine(" 配置:") for ((key, value) in account.configuration) { append(" $key = $value") } appendLine() } } }) } @Description("添加自动登录, passwordKind 可选 PLAIN 或 MD5") @SubCommand @ConsoleExperimentalApi public suspend fun CommandSender.add(account: Long, password: String, passwordKind: PasswordKind = PLAIN) { val config = DataScope.get<AutoLoginConfig>() val accountStr = account.toString() if (config.accounts.any { it.account == accountStr }) { sendMessage("已有相同账号在自动登录配置中. 请先删除该账号.") return } config.accounts.add(AutoLoginConfig.Account(accountStr, Password(passwordKind, password))) sendMessage("已成功添加 '$account'.") } @OptIn(ConsoleExperimentalApi::class) @Description("清除所有配置") @SubCommand public suspend fun CommandSender.clear() { val config = DataScope.get<AutoLoginConfig>() config.accounts.clear() sendMessage("已清除所有自动登录配置.") } @OptIn(ConsoleExperimentalApi::class) @Description("删除一个账号") @SubCommand public suspend fun CommandSender.remove(account: Long) { val config = DataScope.get<AutoLoginConfig>() val accountStr = account.toString() if (config.accounts.removeIf { it.account == accountStr }) { sendMessage("已成功删除 '$account'.") return } sendMessage("账号 '$account' 未配置自动登录.") } @ConsoleExperimentalApi @Description("设置一个账号的一个配置项") @SubCommand public suspend fun CommandSender.setConfig(account: Long, configKey: ConfigurationKey, value: String) { val config = DataScope.get<AutoLoginConfig>() val accountStr = account.toString() val oldAccount = config.accounts.find { it.account == accountStr } ?: kotlin.run { sendMessage("未找到账号 $account.") return } if (value.isEmpty()) return removeConfig(account, configKey) val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply { put(configKey, value) }) config.accounts.remove(oldAccount) config.accounts.add(newAccount) sendMessage("成功修改 '$account' 的配置 '$configKey' 为 '$value'") } @Description("删除一个账号的一个配置项") @ConsoleExperimentalApi @SubCommand public suspend fun CommandSender.removeConfig(account: Long, configKey: ConfigurationKey) { val config = DataScope.get<AutoLoginConfig>() val accountStr = account.toString() val oldAccount = config.accounts.find { it.account == accountStr } ?: kotlin.run { sendMessage("未找到账号 $account.") return } val newAccount = oldAccount.copy(configuration = oldAccount.configuration.toMutableMap().apply { remove(configKey) }) config.accounts.remove(oldAccount) config.accounts.add(newAccount) sendMessage("成功删除 '$account' 的配置 '$configKey'.") } } public object StatusCommand : SimpleCommand( ConsoleCommandOwner, "status", "states", "状态", description = "获取 Mirai Console 运行状态" ), BuiltInCommandInternal { internal interface MemoryUsageGet { val heapMemoryUsage: MUsage val nonHeapMemoryUsage: MUsage val objectPendingFinalizationCount: Int } private val memoryUsageGet: MemoryUsageGet = kotlin.runCatching { ByMemoryMXBean }.getOrElse { ByRuntime } internal object ByMemoryMXBean : MemoryUsageGet { private val memoryMXBean: MemoryMXBean = ManagementFactory.getMemoryMXBean() private val MemoryUsage.m: MUsage get() = MUsage( committed, init, used, max ) override val heapMemoryUsage: MUsage get() = memoryMXBean.heapMemoryUsage.m override val nonHeapMemoryUsage: MUsage get() = memoryMXBean.nonHeapMemoryUsage.m override val objectPendingFinalizationCount: Int get() = memoryMXBean.objectPendingFinalizationCount } internal object ByRuntime : MemoryUsageGet { override val heapMemoryUsage: MUsage get() { val runtime = Runtime.getRuntime() return MUsage( committed = 0, init = 0, used = runtime.maxMemory() - runtime.freeMemory(), max = runtime.maxMemory() ) } override val nonHeapMemoryUsage: MUsage get() = MUsage(-1, -1, -1, -1) override val objectPendingFinalizationCount: Int get() = -1 } internal data class MUsage( val committed: Long, val init: Long, val used: Long, val max: Long, ) @OptIn(MiraiExperimentalApi::class) @Handler public suspend fun CommandSender.handle() { sendAnsiMessage { val buildDateFormatted = MiraiConsoleBuildConstants.buildDate.atZone(ZoneId.systemDefault()) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) append("Running MiraiConsole v") gold().append(MiraiConsoleBuildConstants.versionConst) reset().append(", built on ") lightBlue().append(buildDateFormatted).reset().append(".\n") append(MiraiConsoleImplementation.getInstance().frontEndDescription.render()).append("\n\n") append("Permission Service: ").append( if (PermissionService.INSTANCE is BuiltInPermissionService) { lightYellow() "Built In Permission Service" } else { val plugin = GlobalComponentStorage.getPreferredExtension(PermissionServiceProvider).plugin if (plugin == null) { PermissionService.INSTANCE.toString() } else { green().append(plugin.name).reset().append(" v").gold() plugin.version.toString() } } ) reset().append("\n\n") append("Plugins: ") val resolvedPlugins = MiraiConsole.pluginManagerImpl.resolvedPlugins.asSequence() .filter { it !is MiraiConsoleAsPlugin } // skip mirai-console in status .toList() if (resolvedPlugins.isEmpty()) { gray().append("<none>") } else { resolvedPlugins.joinTo(this) { plugin -> if (plugin.isEnabled) { green().append(plugin.name).reset().append(" v").gold() } else { red().append(plugin.name) if (plugin is JvmPluginInternal) { append("(").append(plugin.currentPluginStatus.name.lowercase()).append(")") } else { append("(disabled)") } reset().append(" v").gold() } plugin.version.toString() } } reset().append("\n\n") append("Object Pending Finalization Count: ") .emeraldGreen() .append(memoryUsageGet.objectPendingFinalizationCount) .reset() .append("\n") val l1 = arrayOf("committed", "init", "used", "max") val l2 = renderMemoryUsage(memoryUsageGet.heapMemoryUsage) val l3 = renderMemoryUsage(memoryUsageGet.nonHeapMemoryUsage) val lmax = calculateMax(l1, l2.first, l3.first) append(" ") l1.forEachIndexed { index, s -> if (index != 0) append(" | ") renderMUNum(lmax[index], s.length) { append(s); reset() } } reset() append("\n") fun rendMU(l: Pair<Array<String>, LongArray>) { val max = l.second[3] val e50 = max / 2 val e90 = max * 90 / 100 l.first.forEachIndexed { index, s -> if (index != 0) append(" | ") renderMUNum(lmax[index], s.length) { if (index == 3) { // MAX append(s) } else { if (max < 0L) { append(s) } else { val v = l.second[index] when { v < e50 -> { green() } v < e90 -> { lightRed() } else -> { red() } } append(s) reset() } } } } } append(" Heap Memory: ") rendMU(l2) append("\nNon-Heap Memory: ") rendMU(l3) } } private fun AnsiMessageBuilder.renderMemoryUsage(usage: MUsage) = arrayOf( renderMemoryUsageNumber(usage.committed), renderMemoryUsageNumber(usage.init), renderMemoryUsageNumber(usage.used), renderMemoryUsageNumber(usage.max), ) to longArrayOf( usage.committed, usage.init, usage.used, usage.max, ) private fun calculateMax( vararg lines: Array<String> ): IntArray = IntArray(lines[0].size) { r -> lines.maxOf { it[r].length } } } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/Command.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.CommandArgumentContextAware import net.mamoe.mirai.console.command.descriptor.CommandSignature import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId /** * 指令 * * ### 权限 * 每个指令都会被分配一个权限 [permission]. 默认没有人拥有这个权限. 请通过 [BuiltInCommands.PermissionCommand] 赋予权限. * * @see CommandManager.registerCommand 注册这个指令 * * @see RawCommand 无参数解析, 接收原生参数的指令 * @see CompositeCommand 复合指令 * @see SimpleCommand 简单的, 支持参数自动解析的指令 * * @see CommandArgumentContextAware */ public interface Command { /** * 主指令名. 将会参与构成 [Permission.id]. * * 不允许包含 [空格][Char.isWhitespace], '.', ':'. */ @ResolveContext(COMMAND_NAME) public val primaryName: String /** * 次要指令名 * @see Command.primaryName 获取主指令名 */ @ResolveContext(COMMAND_NAME) public val secondaryNames: Array<out String> /** * 指令可能的参数列表. */ @ExperimentalCommandDescriptors public val overloads: List<@JvmWildcard CommandSignature> /** * 用法说明, 用于发送给用户. [usage] 一般包含 [description]. */ public val usage: String /** * 描述, 用于显示在 [BuiltInCommands.HelpCommand] */ public val description: String /** * 为此指令分配的权限. * * ### 实现约束 * - [Permission.id] 应由 [CommandOwner.permissionId] 创建. 因此保证相同的 [PermissionId.namespace] * - [PermissionId.name] 应为 [主指令名][primaryName] */ public val permission: Permission /** * 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选. * * 会影响聊天语境中的解析. * * #### 实验性 API * 由于指令解析允许被扩展, 此属性可能不适用所有解析器, 因此还未决定是否保留. */ @ExperimentalCommandDescriptors public val prefixOptional: Boolean /** * 指令拥有者. * @see CommandOwner */ @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) public val owner: CommandOwner public companion object { /** * 获取所有指令名称 (包含 [primaryName] 和 [secondaryNames]). * * @return 数组大小至少为 1. 第一个元素总是 [primaryName]. 随后是保持原顺序的 [secondaryNames] */ @JvmStatic public val Command.allNames: Array<String> get() = arrayOf(primaryName, *secondaryNames) /** * 检查指令名的合法性. 在非法时抛出 [IllegalArgumentException] */ @JvmStatic @Throws(IllegalArgumentException::class) public fun checkCommandName(@ResolveContext(COMMAND_NAME) name: String) { when { name.isBlank() -> throw IllegalArgumentException("Command name should not be blank.") name.any { it.isWhitespace() } -> throw IllegalArgumentException("Spaces are not yet allowed in command name.") name.contains(':') -> throw IllegalArgumentException("':' is forbidden in command name.") name.contains('.') -> throw IllegalArgumentException("'.' is forbidden in command name.") } } } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/CommandContext.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.utils.NotStableForInheritance /** * 指令执行环境 * @since 2.12 */ @NotStableForInheritance public interface CommandContext { /** * 指令发送者 */ public val sender: CommandSender /** * 触发指令的原消息链,包含元数据,也包含指令名。 * * 示例内容:`messageChainOf(MessageSource(...), PlainText("/test"), PlainText("arg1"))` */ public val originalMessage: MessageChain } internal class CommandContextImpl( override val sender: CommandSender, override val originalMessage: MessageChain ) : CommandContext ================================================ FILE: mirai-console/backend/mirai-console/src/command/CommandExecuteResult.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.resolve.InterceptedReason import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall import kotlin.contracts.contract /** * 指令的执行返回 */ @ExperimentalCommandDescriptors public sealed class CommandExecuteResult { /** 指令执行时发生的错误 (如果有) */ public abstract val exception: Throwable? /** 尝试执行的指令 (如果匹配到) */ public abstract val command: Command? /** 解析的 [CommandCall] (如果匹配到) */ public abstract val call: CommandCall? /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public abstract val resolvedCall: ResolvedCommandCall? // abstract val to allow smart casting /** 指令执行成功 */ public class Success( /** 尝试执行的指令 */ public override val command: Command, /** 解析的 [CommandCall] (如果匹配到) */ public override val call: CommandCall, /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public override val resolvedCall: ResolvedCommandCall, ) : CommandExecuteResult() { /** 指令执行时发生的错误, 总是 `null` */ public override val exception: Nothing? get() = null } /** 指令执行失败 */ public abstract class Failure : CommandExecuteResult() /** 执行执行时发生了一个非法参数错误 */ public class IllegalArgument( /** 指令执行时发生的错误 */ public override val exception: IllegalCommandArgumentException, /** 尝试执行的指令 */ public override val command: Command, /** 解析的 [CommandCall] (如果匹配到) */ public override val call: CommandCall, /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public override val resolvedCall: ResolvedCommandCall, ) : Failure() /** 指令方法调用过程出现了错误 */ public class ExecutionFailed( /** 指令执行时发生的错误 */ public override val exception: Throwable, /** 尝试执行的指令 */ public override val command: Command, /** 解析的 [CommandCall] (如果匹配到) */ public override val call: CommandCall, /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public override val resolvedCall: ResolvedCommandCall, ) : Failure() /** 没有匹配的指令 */ public class UnresolvedCommand( /** 解析的 [CommandCall] (如果匹配到) */ public override val call: CommandCall?, ) : Failure() { /** 指令执行时发生的错误, 总是 `null` */ public override val exception: Nothing? get() = null /** 尝试执行的指令, 总是 `null` */ public override val command: Nothing? get() = null /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public override val resolvedCall: ResolvedCommandCall? get() = null } /** 没有匹配的指令 */ public class Intercepted( /** 解析的 [CommandCall] (如果匹配到) */ public override val call: CommandCall?, /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public override val resolvedCall: ResolvedCommandCall?, /** 尝试执行的指令 (如果匹配到) */ public override val command: Command?, /** 拦截原因 */ public val reason: InterceptedReason, ) : Failure() { /** 指令执行时发生的错误, 总是 `null` */ public override val exception: Nothing? get() = null } /** 权限不足 */ public class PermissionDenied( /** 尝试执行的指令 */ public override val command: Command, /** 解析的 [CommandCall] (如果匹配到) */ public override val call: CommandCall, /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public override val resolvedCall: ResolvedCommandCall, ) : Failure() { /** 指令执行时发生的错误, 总是 `null` */ public override val exception: Nothing? get() = null } /** 没有匹配的指令 */ public class UnmatchedSignature( /** 尝试执行的指令 */ public override val command: Command, /** 解析的 [CommandCall] (如果匹配到) */ public override val call: CommandCall, /** 尝试执行的指令 */ @ExperimentalCommandDescriptors public val failureReasons: List<UnmatchedCommandSignature>, ) : Failure() { /** 指令执行时发生的错误, 总是 `null` */ public override val exception: Nothing? get() = null /** 解析的 [ResolvedCommandCall] (如果匹配到) */ public override val resolvedCall: ResolvedCommandCall? get() = null } } @ExperimentalCommandDescriptors public class UnmatchedCommandSignature( public val signature: CommandSignature, public val failureReason: FailureReason, ) @ExperimentalCommandDescriptors public sealed class FailureReason { public class InapplicableReceiverArgument( public override val parameter: CommandReceiverParameter<*>, public val argument: CommandSender, ) : InapplicableArgument() public class InapplicableValueArgument( public override val parameter: CommandValueParameter<*>, public val argument: CommandValueArgument, ) : InapplicableArgument() public abstract class InapplicableArgument : FailureReason() { public abstract val parameter: CommandParameter<*> } public abstract class ArgumentLengthMismatch : FailureReason() public data class ResolutionAmbiguity( /** * Including [self][UnmatchedCommandSignature.signature]. */ public val allCandidates: List<CommandSignature>, ) : FailureReason() public object TooManyArguments : ArgumentLengthMismatch() public object NotEnoughArguments : ArgumentLengthMismatch() } /** * 当 [this] 为 [CommandExecuteResult.Success] 时返回 `true` */ @ExperimentalCommandDescriptors @JvmSynthetic public fun CommandExecuteResult.isSuccess(): Boolean { contract { returns(true) implies (this@isSuccess is CommandExecuteResult.Success) returns(false) implies (this@isSuccess !is CommandExecuteResult.Success) } return this is CommandExecuteResult.Success } /** * 当 [this] 为 [CommandExecuteResult.ExecutionFailed], [CommandExecuteResult.IllegalArgument] , [CommandExecuteResult.UnmatchedSignature] 或 [CommandExecuteResult.UnresolvedCommand] 时返回 `true` */ @ExperimentalCommandDescriptors @JvmSynthetic public fun CommandExecuteResult.isFailure(): Boolean { contract { returns(true) implies (this@isFailure !is CommandExecuteResult.Success) returns(false) implies (this@isFailure is CommandExecuteResult.Success) } return this !is CommandExecuteResult.Success } ================================================ FILE: mirai-console/backend/mirai-console/src/command/CommandExecutionException.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.command /** * 在 [CommandManager.executeCommand] 中抛出异常时包装的异常. */ public class CommandExecutionException( /** * 执行者 */ public val sender: CommandSender, /** * 执行过程发生异常的指令 */ public val command: Command, /** * 匹配到的指令名 */ public val name: String, cause: Throwable, ) : RuntimeException( "Exception while executing command '${command.primaryName}'", cause ) { public override fun toString(): String = "CommandExecutionException(command=$command, name='$name')" } ================================================ FILE: mirai-console/backend/mirai-console/src/command/CommandManager.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress( "NOTHING_TO_INLINE", "unused", "MemberVisibilityCanBePrivate", "INAPPLICABLE_JVM_NAME" ) @file:JvmName("CommandManagerKt") package net.mamoe.mirai.console.command import me.him188.kotlin.dynamic.delegation.dynamicDelegation import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.CommandManager.INSTANCE.executeCommand import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall import net.mamoe.mirai.console.internal.command.executeCommandImpl import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.* /** * 指令管理器 */ public interface CommandManager { /** * 获取所有已经注册了指令列表. * * @return 这一时刻的浅拷贝. */ public val allRegisteredCommands: List<Command> /** * 指令前缀, 如 '/' */ public val commandPrefix: String /** * 获取已经注册了的属于这个 [CommandOwner] 的指令列表. * * @return 这一时刻的浅拷贝. */ public fun getRegisteredCommands(owner: CommandOwner): List<Command> /** * 取消注册所有属于 [owner] 的指令 */ public fun unregisterAllCommands(owner: CommandOwner) /** * 注册一个指令. * * @param override 是否覆盖重名指令. * * 若原有指令 P, 其 [Command.secondaryNames] 为 'a', 'b', 'c'. * 新指令 Q, 其 [Command.secondaryNames] 为 'b', 将会覆盖原指令 A 注册的 'b'. * * 即注册完成后, 'a' 和 'c' 将会解析到指令 P, 而 'b' 会解析到指令 Q. * * @return * 若已有重名指令, 且 [override] 为 `false`, 返回 `false`; * 若已有重名指令, 但 [override] 为 `true`, 覆盖原有指令并返回 `true`. * * * 注意: [内建指令][BuiltInCommands] 也可以被覆盖. */ public fun registerCommand(command: Command, override: Boolean = false): Boolean /** * 查找并返回重名的指令. 返回重名指令. */ public fun findDuplicateCommand(command: Command): Command? /** * 取消注册这个指令. * * 若指令未注册, 返回 `false`. */ public fun unregisterCommand(command: Command): Boolean /** * 当 [command] 已经 [注册][registerCommand] 时返回 `true` */ public fun isCommandRegistered(command: Command): Boolean /** * 解析并执行一个指令. * * ### 指令解析流程 * 1. [CommandCallParser] 将 [MessageChain] 解析为 [CommandCall] * 2. [CommandCallResolver] 将 [CommandCall] 解析为 [ResolvedCommandCall] * 1. [message] 的第一个消息元素的 [内容][Message.contentToString] 被作为指令名, 在已注册指令列表中搜索. (包含 [Command.prefixOptional] 相关的处理) * 2. 参数语法分析. * 在当前的实现下, [message] 被以空格和 [SingleMessage] 分割. * 如 "MessageChain("foo bar", [Image], " test")" 被分割为 "foo", "bar", [Image], "test". * 注意: 字符串与消息元素之间不需要空格, 会被强制分割. 如 "bar[mirai:image:]" 会被分割为 "bar" 和 [Image] 类型的消息元素. * 3. 参数解析. 各类型指令实现不同. 详见 [RawCommand], [CompositeCommand], [SimpleCommand] * * ### 扩展 * 参数语法分析过程可能会被扩展, 插件可以自定义处理方式 ([CommandCallParser]), 因此可能不会简单地使用空格分隔. * * @param message 一条完整的指令. 如 "/managers add 123456.123456" * @param checkPermission 为 `true` 时检查权限 * * @see CommandCallParser * @see CommandCallResolver * * @see CommandSender.executeCommand * @see Command.execute * * @return 执行结果 */ @ExperimentalCommandDescriptors @JvmBlockingBridge public suspend fun executeCommand( caller: CommandSender, message: Message, checkPermission: Boolean = true, ): CommandExecuteResult { return executeCommandImpl(message, caller, checkPermission) } /** * 执行一个确切的指令 * * @param command 目标指令 * @param arguments 参数列表 * * @see executeCommand 获取更多信息 * @see Command.execute */ @ConsoleExperimentalApi @JvmName("executeCommand") @ExperimentalCommandDescriptors @JvmSynthetic public suspend fun executeCommand( sender: CommandSender, command: Command, arguments: Message = emptyMessageChain(), checkPermission: Boolean = true, ): CommandExecuteResult { // TODO: 2020/10/18 net.mamoe.mirai.console.command.CommandManager.execute val chain = buildMessageChain { append(CommandManager.commandPrefix) append(command.primaryName) append(' ') append(arguments) } return CommandManager.executeCommand(sender, chain, checkPermission) } /** * 从 [指令名称][commandName] 匹配对应的 [Command]. * * #### 实现细节 * - [commandName] 带有 [commandPrefix] 时可以匹配到所有指令 * - [commandName] 不带有 [commandPrefix] 时只能匹配到 [Command.prefixOptional] 的指令 * * @param commandName 可能带有或不带有 [commandPrefix]. */ public fun matchCommand(commandName: String): Command? /** * [CommandManager] 实例. 转发所有调用到 [MiraiConsoleImplementation.commandManager]. */ public companion object INSTANCE : CommandManager by (dynamicDelegation { @OptIn(ConsoleFrontEndImplementation::class) MiraiConsoleImplementation.getInstance().commandManager }) { /** * @see CommandManager.getRegisteredCommands */ @get:JvmName("registeredCommands0") @get:JvmSynthetic public inline val CommandOwner.registeredCommands: List<Command> get() = getRegisteredCommands(this) /** * @see CommandManager.registerCommand */ @JvmSynthetic public inline fun Command.register(override: Boolean = false): Boolean = registerCommand(this, override) /** * @see CommandManager.unregisterCommand */ @JvmSynthetic public inline fun Command.unregister(): Boolean = unregisterCommand(this) /** * @see CommandManager.isCommandRegistered */ @get:JvmSynthetic public inline val Command.isRegistered: Boolean get() = isCommandRegistered(this) /** * @see CommandManager.unregisterAll */ @JvmSynthetic public inline fun CommandOwner.unregisterAll(): Unit = unregisterAllCommands(this) /** * @see CommandManager.findDuplicate */ @JvmSynthetic public inline fun Command.findDuplicate(): Command? = findDuplicateCommand(this) } } /** * 解析并执行一个指令 * * @param message 一条完整的指令. 如 "/managers add 123456.123456" * @param checkPermission 为 `true` 时检查权限 * * @return 执行结果 * @see executeCommand */ @JvmName("execute0") @ExperimentalCommandDescriptors @JvmSynthetic public suspend inline fun CommandSender.executeCommand( message: String, checkPermission: Boolean = true, ): CommandExecuteResult = CommandManager.executeCommand(this, PlainText(message).toMessageChain(), checkPermission) /** * 执行一个确切的指令 * @see executeCommand 获取更多信息 */ @JvmName("execute0") @ExperimentalCommandDescriptors @JvmSynthetic public suspend inline fun Command.execute( sender: CommandSender, vararg arguments: Message = emptyArray(), checkPermission: Boolean = true, ): CommandExecuteResult = @OptIn(ConsoleExperimentalApi::class) CommandManager.executeCommand(sender, this, arguments.toMessageChain(), checkPermission) /** * 执行一个确切的指令 * @see executeCommand 获取更多信息 */ @JvmName("execute0") @ExperimentalCommandDescriptors @JvmSynthetic public suspend inline fun Command.execute( sender: CommandSender, arguments: String = "", checkPermission: Boolean = true, ): CommandExecuteResult = execute(sender, PlainText(arguments), checkPermission = checkPermission) ================================================ FILE: mirai-console/backend/mirai-console/src/command/CommandOwner.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionIdNamespace import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 指令的所有者. [JvmPlugin] 是一个 [CommandOwner]. * * @see CommandManager.unregisterAllCommands 取消注册所有属于一个 [CommandOwner] 的指令 * @see CommandManager.registeredCommands 获取已经注册了的属于一个 [CommandOwner] 的指令列表. */ public interface CommandOwner : PermissionIdNamespace { /** * 在构造指令时, [Command.permission] 默认会使用 [parentPermission] 作为 [Permission.parent] */ public val parentPermission: Permission } /** * 代表控制台所有者. 所有的 mirai-console 内建的指令都属于 [ConsoleCommandOwner]. * * 插件注册指令时不应该使用 [ConsoleCommandOwner]. */ public object ConsoleCommandOwner : CommandOwner { @OptIn(ConsoleExperimentalApi::class) public override val parentPermission: Permission get() = BuiltInCommands.parentPermission public override fun permissionId( @ResolveContext(PERMISSION_NAME) name: String, ): PermissionId = PermissionId("console", name) } ================================================ FILE: mirai-console/backend/mirai-console/src/command/CommandPermissionDeniedException.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.command /** * 在 [CommandManager.executeCommand] 中, [CommandSender] 未拥有 [Command.permission] 所要求的权限时抛出的异常. * * 总是作为 [CommandExecutionException.cause]. */ public class CommandPermissionDeniedException( /** * 执行者 */ public val commandSender: CommandSender, /** * 执行过程发生异常的指令 */ public val command: Command, ) : RuntimeException("Permission denied while executing command '${command.primaryName}'") { public override fun toString(): String = "CommandPermissionDeniedException(command=$command)" } ================================================ FILE: mirai-console/backend/mirai-console/src/command/CommandSender.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress( "NOTHING_TO_INLINE", "FunctionName", "unused", "MemberVisibilityCanBePrivate" ) package net.mamoe.mirai.console.command import kotlinx.coroutines.CoroutineScope import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.asMemberCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.asTempCommandSender import net.mamoe.mirai.console.command.CommandSender.Companion.toCommandSender import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import net.mamoe.mirai.console.permission.AbstractPermitteeId import net.mamoe.mirai.console.permission.Permittee import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.* import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.childScope import net.mamoe.mirai.utils.childScopeContext import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext // region base interface define /** * 指令发送者. * * 只有 [CommandSender] 才能 [执行指令][CommandManager.executeCommand] * * ## 获得指令发送者 * - [MessageEvent.toCommandSender] * - [FriendMessageEvent.toCommandSender] * - [GroupMessageEvent.toCommandSender] * - [StrangerMessageEvent.toCommandSender] * - [OtherClientMessageEvent.toCommandSender] * - [MessageSyncEvent.toCommandSender] * * - [Member.asCommandSender] * - [NormalMember.asTempCommandSender] * - [Member.asMemberCommandSender] * - [Friend.asCommandSender] * - [User.asCommandSender] * - [Stranger.asCommandSender] * - [OtherClient.asCommandSender] * * ## 实现 [CommandSender] * 在任何时候都不要实现 [CommandSender] (包括使用委托). 必须使用上述扩展获取 [CommandSender] 实例. * * 除了以下情况: * - Console 前端可实现 [ConsoleCommandSender] * - 插件可继承 [AbstractPluginCustomCommandSender] * * ## 子类型 * * 所有 [CommandSender] 都应继承 [AbstractCommandSender]. * * [AbstractCommandSender] 是密封类, 一级子类为: * - [AbstractUserCommandSender] 代表用户 * - [AbstractPluginCustomCommandSender] 代表插件 * - [ConsoleCommandSender] 代表控制台 * * 二级子类, 当指令由插件 [主动执行][CommandManager.executeCommand] 时, 插件应使用 [toCommandSender] 或 [asCommandSender], 因此, * - 若在群聊环境, 对应 [CommandSender] 为 [MemberCommandSender] * - 若在私聊环境, 对应 [CommandSender] 为 [FriendCommandSender] * - 若在临时会话环境, 对应 [CommandSender] 为 [TempCommandSender] * - 若在陌生人会话环境, 对应 [CommandSender] 为 [StrangerCommandSender] * - 若在其他客户端会话环境, 对应 [CommandSender] 为 [OtherClientCommandSender] * * 三级子类, 当真实收到由用户执行的指令时: * - 若在群聊环境, 对应 [CommandSender] 为 [MemberCommandSenderOnMessage] * - 若在私聊环境, 对应 [CommandSender] 为 [FriendCommandSenderOnMessage] * - 若在临时会话环境, 对应 [CommandSender] 为 [TempCommandSenderOnMessage] * - 若在陌生人会话环境, 对应 [CommandSender] 为 [StrangerCommandSenderOnMessage] * - 若在其他客户端会话环境, 对应 [CommandSender] 为 [OtherClientCommandSenderOnMessage] * * * 类型关系如图. 箭头指向的是父类. * * ``` * CoroutineScope * ↑ * | * +----------> CommandSender <---------+---------------------+---------------------------------------------------+ * | ↑ | | | * | | | | | * SystemCommandSender <-------+ | UserCommandSender GroupAwareCommandSender CommandSenderOnMessage * ↑ | | ↑ ↑ ↑ * PluginCustomCommandSender | | | | | * ↑ | | | | | * | +-------------+ AbstractCommandSender | | | * | | ↑ | | | * | | | sealed | | | * | | +---------------+-------------+ | | | * | | | | | | | | * | | | | | | | | * | | | | | | | | * | | | | | | | | * | | | | | | | | * | | | | | | | | * | | | | | | | | } * | ConsoleCommandSender | AbstractUserCommandSender | | } * | | ↑ | | } 一级子类 * AbstractPluginCustomCommandSender -------+ | | | } * | | | } * | | | * | | | * | sealed | | * | | | * +----------------------+ | | * | | | | * | +------+------------+---------------+ | * | | | | * | | | | } * FriendCommandSender MemberCommandSender GroupTempCommandSender | } 二级子类 * ↑ ↑ ↑ | } * | | | | * | | | | } * FriendCommandSenderOnMessage MemberCommandSenderOnMessage GroupTempCommandSenderOnMessage | } 三级子类 * | | | | } * | | | | * +-----------------------------+----------------------------+---------------------+ * ``` * * ## Scoping: [MessageScope] * 在处理多个消息对象时, 可通过 [MessageScope] 简化操作. * * 查看 [MessageScope] 以获取更多信息. * * @see ConsoleCommandSender 控制台 * @see UserCommandSender [User] ([群成员][Member], [好友][Friend]) * @see toCommandSender * @see asCommandSender * @see PluginCustomCommandSender 自定义 CommandSender */ public interface CommandSender : CoroutineScope, Permittee { /** * 与这个 [CommandSender] 相关的 [Bot]. * 当通过控制台执行时为 `null`. */ public val bot: Bot? /** * 与这个 [CommandSender] 相关的 [Contact]. * * - 当一个群员执行指令时, [subject] 为所在 [群][Group] * - 当通过控制台执行时为 `null`. */ public val subject: Contact? /** * 指令原始发送*人*. * - 当通过控制台执行时为 `null`. */ public val user: User? /** * [User.nameCardOrNick] 或 [ConsoleCommandSender.NAME] */ public val name: String /** * 立刻发送一条消息. * * 对于 [MemberCommandSender], 这个函数总是发送给所在群 */ @JvmBlockingBridge public suspend fun sendMessage(message: Message): MessageReceipt<Contact>? /** * 立刻发送一条消息. * * 对于 [MemberCommandSender], 这个函数总是发送给所在群 */ @JvmBlockingBridge public suspend fun sendMessage(message: String): MessageReceipt<Contact>? public companion object { /////////////////////////////////////////////////////////////////////////// // Constructors /////////////////////////////////////////////////////////////////////////// /** * 构造 [FriendCommandSenderOnMessage] */ @JvmName("from") @JvmStatic public fun FriendMessageEvent.toCommandSender(): FriendCommandSenderOnMessage = FriendCommandSenderOnMessage(this) /** * 构造 [MemberCommandSenderOnMessage] */ @JvmName("from") @JvmStatic public fun GroupMessageEvent.toCommandSender(): MemberCommandSenderOnMessage = MemberCommandSenderOnMessage(this) /** * 构造 [TempCommandSenderOnMessage] */ @JvmStatic @JvmName("from") public fun GroupTempMessageEvent.toCommandSender(): GroupTempCommandSenderOnMessage = GroupTempCommandSenderOnMessage(this) /** * 构造 [StrangerCommandSenderOnMessage] */ @JvmStatic @JvmName("from") public fun StrangerMessageEvent.toCommandSender(): StrangerCommandSenderOnMessage = StrangerCommandSenderOnMessage(this) /** * 构造 [OtherClientCommandSenderOnMessage] */ @JvmStatic @JvmName("from") public fun OtherClientMessageEvent.toCommandSender(): OtherClientCommandSenderOnMessage = OtherClientCommandSenderOnMessage(this) /** * 构造 [OtherClientCommandSenderOnMessageSync] * @since 2.13 */ @JvmStatic @JvmName("from") public fun MessageSyncEvent.toCommandSender(): OtherClientCommandSenderOnMessageSync = OtherClientCommandSenderOnMessageSync(this) /** * 构造 [CommandSenderOnMessage] */ @JvmStatic @JvmName("from") @Suppress("UNCHECKED_CAST") public fun <T : MessageEvent> T.toCommandSender(): CommandSenderOnMessage<T> = when (this) { is FriendMessageEvent -> toCommandSender() is GroupMessageEvent -> toCommandSender() is GroupTempMessageEvent -> toCommandSender() is StrangerMessageEvent -> toCommandSender() is OtherClientMessageEvent -> toCommandSender() is MessageSyncEvent -> toCommandSender() else -> throw IllegalArgumentException("Unsupported MessageEvent: ${this::class.qualifiedNameOrTip}") } as CommandSenderOnMessage<T> /** * 得到 [TempCommandSender] */ @JvmStatic @JvmName("of") public fun NormalMember.asTempCommandSender(): GroupTempCommandSender = GroupTempCommandSender(this) /** * 得到 [MemberCommandSender] */ @JvmStatic @JvmName("of") public fun Member.asMemberCommandSender(): MemberCommandSender = MemberCommandSender(this) /** * 得到 [MemberCommandSender] 或 [TempCommandSender] * @see asTempCommandSender * @see asMemberCommandSender */ @JvmStatic @JvmName("of") public fun Member.asCommandSender(isTemp: Boolean): UserCommandSender { return if (isTemp && this is NormalMember) asTempCommandSender() else asMemberCommandSender() } /** * 得到 [FriendCommandSender] */ @JvmStatic @JvmName("of") public fun Friend.asCommandSender(): FriendCommandSender = FriendCommandSender(this) /** * 得到 [StrangerCommandSender] */ @JvmStatic @JvmName("of") public fun Stranger.asCommandSender(): StrangerCommandSender = StrangerCommandSender(this) /** * 得到 [OtherClientCommandSender] */ @JvmStatic @JvmName("of") public fun OtherClient.asCommandSender(): OtherClientCommandSender = OtherClientCommandSender(this) /** * 得到 [UserCommandSender] * * @param isTemp */ @JvmStatic @JvmName("of") public fun User.asCommandSender(isTemp: Boolean): UserCommandSender = when (this) { is Friend -> this.asCommandSender() is Member -> if (isTemp && this is NormalMember) GroupTempCommandSender(this) else MemberCommandSender(this) is Stranger -> this.asCommandSender() else -> error("stub") } } } /** * 一个来自内部系统的命令执行者. * * 包括 * - [PluginCustomCommandSender] - 来自插件的命令执行者 * - [ConsoleCommandSender] - 控制台 */ public sealed interface SystemCommandSender : CommandSender { /** * 当前 [SystemCommandSender] 是否支持 Ansi 信息 * * @see AnsiMessageBuilder * @see CommandSender.sendAnsiMessage */ public val isAnsiSupported: Boolean } /** * 一个来自插件自行实现的 [CommandSender]. * * [PluginCustomCommandSender] 不一定拥有全部的权限. [PluginCustomCommandSender] 可以以其他身份执行命令. * 默认情况下 [PluginCustomCommandSender] 以 [ConsoleCommandSender] 的身份执行命令 * * @see PermitteeId * @see AbstractPluginCustomCommandSender */ public interface PluginCustomCommandSender : CommandSender, SystemCommandSender { override val isAnsiSupported: Boolean get() = false override val permitteeId: PermitteeId get() = AbstractPermitteeId.Console public val owner: Plugin override suspend fun sendMessage(message: Message): Nothing? { return sendMessage(message.toString()) } override suspend fun sendMessage(message: String): Nothing? } /** * 控制台指令执行者. 代表由控制台执行指令 * * 控制台拥有一切指令的执行权限. * * 不建议在 [CompositeCommand] 中使用 [ConsoleCommandSender], * 使用 [SystemCommandSender] 以允许其他插件执行 * */ public object ConsoleCommandSender : AbstractCommandSender(), SystemCommandSender { public const val NAME: String = "ConsoleCommandSender" public override val bot: Nothing? get() = null public override val subject: Nothing? get() = null public override val user: Nothing? get() = null public override val name: String get() = NAME public override fun toString(): String = NAME public override val permitteeId: AbstractPermitteeId.Console = AbstractPermitteeId.Console public override val coroutineContext: CoroutineContext by lazy { MiraiConsole.childScopeContext(NAME) } @OptIn(ConsoleFrontEndImplementation::class) override val isAnsiSupported: Boolean get() = MiraiConsoleImplementation.getInstance().isAnsiSupported @OptIn(ConsoleFrontEndImplementation::class) @JvmBlockingBridge public override suspend fun sendMessage(message: Message): Nothing? { MiraiConsoleImplementation.getInstance().consoleCommandSender.sendMessage(message) return null } @OptIn(ConsoleFrontEndImplementation::class) @JvmBlockingBridge public override suspend fun sendMessage(message: String): Nothing? { MiraiConsoleImplementation.getInstance().consoleCommandSender.sendMessage(message) return null } } /** * 知道 [Group] 环境的 [UserCommandSender] * * 可能的子类: * * - [MemberCommandSender] 代表一个 [群员][Member] 执行指令 * - [TempCommandSender] 代表一个 [群员][Member] 通过临时会话执行指令 */ public interface GroupAwareCommandSender : UserCommandSender { public val group: Group } /** * 代表一个用户执行指令 * * @see MemberCommandSender 代表一个 [群员][Member] 执行指令 * @see FriendCommandSender 代表一个 [好友][Friend] 执行指令 * @see TempCommandSender 代表一个 [群员][Member] 通过临时会话执行指令 * @see StrangerCommandSender 代表一个 [陌生人][Stranger] 执行指令 * * @see CommandSenderOnMessage */ public interface UserCommandSender : CommandSender { /** * @see MessageEvent.sender */ public override val user: User // override nullability /** * @see MessageEvent.subject */ public override val subject: Contact // override nullability public override val bot: Bot // override nullability } /** * 代表一个真实 [用户][User] 主动私聊机器人或在群内发送消息而执行指令 * * @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. * @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 */ public interface CommandSenderOnMessage<T : MessageEvent> : CommandSender { /** * 消息源 [MessageEvent] */ public val fromEvent: T } /** * 所有 [CommandSender] 都必须继承自此对象. * @see CommandSender 查看更多信息 */ public sealed class AbstractCommandSender : CommandSender, CoroutineScope { public abstract override val bot: Bot? public abstract override val subject: Contact? public abstract override val user: User? public abstract override fun toString(): String } // endregion // region extension functions /** * 当 [this] 为 [ConsoleCommandSender] 时返回 `true` */ public fun CommandSender.isConsole(): Boolean { contract { returns(true) implies (this@isConsole is ConsoleCommandSender) } return this is ConsoleCommandSender } /** * 当 [this] 为 [SystemCommandSender] 时返回 `true` */ public fun CommandSender.isSystem(): Boolean { contract { returns(true) implies (this@isSystem is SystemCommandSender) } return this is SystemCommandSender } /** * 当 [this] 不为 [ConsoleCommandSender] 时返回 `true` */ public fun CommandSender.isNotConsole(): Boolean { contract { returns(true) implies (this@isNotConsole !is ConsoleCommandSender) } return this !is ConsoleCommandSender } /** * 当 [this] 为 [UserCommandSender] 时返回 `true` */ public fun CommandSender.isUser(): Boolean { contract { returns(true) implies (this@isUser is UserCommandSender) } return this is UserCommandSender } /** * 当 [this] 不为 [UserCommandSender], 即为 [SystemCommandSender] 时返回 `true` */ public fun CommandSender.isNotUser(): Boolean { contract { returns(true) implies (this@isNotUser is SystemCommandSender) } return this !is UserCommandSender } /** * 折叠 [CommandSender] 的可能性. * * - 当 [this] 为 [SystemCommandSender] 时执行 [ifIsSystem] * - 当 [this] 为 [UserCommandSender] 时执行 [ifIsUser] * - 否则执行 [otherwise] * * ### 示例 * ``` * // 当一个指令执行过程出错 * val exception: Exception = ... * * sender.fold( * ifIsSystem = { // this: SystemCommandSender * sendMessage(exception.stackTraceToString()) // 展示整个 stacktrace * }, * ifIsUser = { // this: UserCommandSender * sendMessage(exception.message.toString()) // 只展示 Exception.message * } * ) * ``` * * @return [ifIsSystem], [ifIsUser] 或 [otherwise] 执行结果. */ @JvmSynthetic public inline fun <R> CommandSender.fold( ifIsSystem: SystemCommandSender.() -> R, ifIsUser: UserCommandSender.() -> R, otherwise: CommandSender.() -> R = { error("CommandSender ${this::class.qualifiedName} is not supported") }, ): R { contract { callsInPlace(ifIsSystem, InvocationKind.AT_MOST_ONCE) callsInPlace(ifIsUser, InvocationKind.AT_MOST_ONCE) callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE) } return when (val sender = this) { is SystemCommandSender -> ifIsSystem(sender) is UserCommandSender -> ifIsUser(sender) else -> otherwise(sender) } } /** * 折叠 [UserCommandSender] 的两种可能性, 即在群内发送或在私聊环境发送. * * - 当 [this] 为 [MemberCommandSender] 时执行 [inGroup] * - 当 [this] 为 [TempCommandSender] 或 [FriendCommandSender] 时执行 [inPrivate] * * ### 实验性 API * * 这是预览版本 API. 如果你对 [UserCommandSender.foldContext] 有建议, 请在 [mamoe/mirai-console/issues](https://github.com/mamoe/mirai-console/issues/new) 提交. * * @return [inGroup] 或 [inPrivate] 执行结果. */ @JvmSynthetic @ConsoleExperimentalApi public inline fun <R> UserCommandSender.foldContext( inGroup: MemberCommandSender.() -> R, inPrivate: UserCommandSender.() -> R, ): R { contract { callsInPlace(inGroup, InvocationKind.AT_MOST_ONCE) callsInPlace(inPrivate, InvocationKind.AT_MOST_ONCE) } return when (val sender = this) { is MemberCommandSender -> inGroup(sender) else -> inPrivate(sender) } } /** * 尝试获取 [Bot]. * * 当 [UserCommandSender] 时返回 [UserCommandSender.bot], 否则返回 `null` * * ### 契约 * 本函数定义契约, * - 若返回非 `null` 实例, Kotlin 编译器认为 [this] 是 [UserCommandSender] 实例并执行智能类型转换. * - 若返回 `null`, Kotlin 编译器认为 [this] 是 [SystemCommandSender] 实例并执行智能类型转换. */ public fun CommandSender.getBotOrNull(): Bot? { contract { returns(null) implies (this@getBotOrNull is SystemCommandSender) returnsNotNull() implies (this@getBotOrNull is UserCommandSender) } return this.castOrNull<UserCommandSender>()?.bot } /** * 尝试获取 [Group]. * * 当 [GroupAwareCommandSender] 时返回 [GroupAwareCommandSender.group], 否则返回 `null` * * ### 契约 * 本函数定义契约, * - 若返回非 `null` 实例, Kotlin 编译器认为 [this] 是 [GroupAwareCommandSender] 实例并执行智能类型转换. * - 若返回 `null`, Kotlin 编译器认为 [this] 是 [FriendCommandSender] 实例并执行智能类型转换. */ public fun CommandSender.getGroupOrNull(): Group? { contract { returns(null) implies (this@getGroupOrNull is FriendCommandSender) returnsNotNull() implies (this@getGroupOrNull is GroupAwareCommandSender) } return this.castOrNull<GroupAwareCommandSender>()?.group } // endregion // region implementations /////////////////////////////////////////////////////////////////////////// // UserCommandSender /////////////////////////////////////////////////////////////////////////// /** * [UserCommandSender] 的实现 */ public sealed class AbstractUserCommandSender : UserCommandSender, AbstractCommandSender() { public override val bot: Bot get() = user.bot // don't final public final override val name: String get() = user.nameCardOrNick @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt<Contact> = sendMessage(PlainText(message)) @JvmBlockingBridge public override suspend fun sendMessage(message: Message): MessageReceipt<Contact> = user.sendMessage(message) } /** * 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see FriendCommandSenderOnMessage 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 */ public open class FriendCommandSender internal constructor( public final override val user: Friend, ) : AbstractUserCommandSender(), CoroutineScope by user.childScope("FriendCommandSender") { public override val subject: Contact get() = user public override fun toString(): String = "FriendCommandSender($user)" public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactFriend(user.id) @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt<Friend> = sendMessage(PlainText(message)) @JvmBlockingBridge public override suspend fun sendMessage(message: Message): MessageReceipt<Friend> = user.sendMessage(message) } /** * 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see MemberCommandSenderOnMessage 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. */ public open class MemberCommandSender internal constructor( public final override val user: Member, ) : AbstractUserCommandSender(), GroupAwareCommandSender, CoroutineScope by user.childScope("MemberCommandSender") { public final override val group: Group get() = user.group public override val subject: Group get() = group public override fun toString(): String = "MemberCommandSender($user)" public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactMember(group.id, user.id) @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt<Group> = sendMessage(PlainText(message)) @JvmBlockingBridge public override suspend fun sendMessage(message: Message): MessageReceipt<Group> = subject.sendMessage(message) } /** * 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 */ @Deprecated( "mirai 正计划支持其他渠道发起的临时会话, 届时此事件会变动. 原 TempCommandSender 已更改为 GroupTempCommandSender", replaceWith = ReplaceWith("GroupTempCommandSender", "net.mamoe.mirai.console.command.GroupTempCommandSender"), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.0", hiddenSince = "2.10") public sealed class TempCommandSender( public override val user: NormalMember, ) : AbstractUserCommandSender(), GroupAwareCommandSender, CoroutineScope by user.childScope("TempCommandSender") /** * 代表一个 [群员][Member] 通过临时会话执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see TempCommandSenderOnMessage 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 */ @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") public open class GroupTempCommandSender internal constructor( public final override val user: NormalMember, ) : @Suppress("DEPRECATION_ERROR") TempCommandSender(user), CoroutineScope by user.childScope("GroupTempCommandSender") { public override val group: Group get() = user.group public override val subject: NormalMember get() = user public override fun toString(): String = "GroupTempCommandSender($user)" public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactGroupTemp(user.group.id, user.id) @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt<Member> = sendMessage(PlainText(message)) @JvmBlockingBridge public override suspend fun sendMessage(message: Message): MessageReceipt<Member> = user.sendMessage(message) } /** * 代表一个 [陌生人][Stranger] 通过私聊执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see StrangerCommandSenderOnMessage 代表一个 [陌生人][Stranger] 主动在私聊发送消息执行指令 */ public open class StrangerCommandSender internal constructor( public final override val user: Stranger, ) : AbstractUserCommandSender(), CoroutineScope by user.childScope("StrangerCommandSender") { public override val subject: Stranger get() = user public override fun toString(): String = "StrangerCommandSender($user)" public override val permitteeId: PermitteeId = AbstractPermitteeId.ExactStranger(user.id) @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt<Stranger> = sendMessage(PlainText(message)) @JvmBlockingBridge public override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> = user.sendMessage(message) } /** * 代表一个 [其他客户端][OtherClient] 通过私聊执行指令, 但不一定是通过私聊方式, 也有可能是由插件在代码直接执行 ([CommandManager.executeCommand]) * @see OtherClientCommandSenderOnMessage 代表一个[其他客户端][OtherClient] 主动在私聊发送消息执行指令 */ public open class OtherClientCommandSender internal constructor( public val client: OtherClient, ) : AbstractCommandSender(), CoroutineScope by client.childScope("OtherClientCommandSender") { public final override val user: Friend get() = client.bot.asFriend public final override val bot: Bot get() = client.bot public final override val name: String get() = client.bot.nick public override val subject: Friend get() = user public override fun toString(): String = "OtherClientCommandSender($user)" public override val permitteeId: PermitteeId = AbstractPermitteeId.AnyOtherClient @JvmBlockingBridge public override suspend fun sendMessage(message: String): MessageReceipt<OtherClient> = sendMessage(PlainText(message)) @JvmBlockingBridge public override suspend fun sendMessage(message: Message): MessageReceipt<OtherClient> = client.sendMessage(message) } /////////////////////////////////////////////////////////////////////////// // CommandSenderOnMessage /////////////////////////////////////////////////////////////////////////// /** * 代表一个真实的 [好友][Friend] 主动在私聊消息执行指令 * @see FriendCommandSender 代表一个 [好友][Friend] 执行指令, 但不一定是通过私聊方式 */ public class FriendCommandSenderOnMessage internal constructor( public override val fromEvent: FriendMessageEvent, ) : FriendCommandSender(fromEvent.sender), CommandSenderOnMessage<FriendMessageEvent> /** * 代表一个真实的 [群员][Member] 主动在群内发送消息执行指令. * @see MemberCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过群内发消息方式 */ public class MemberCommandSenderOnMessage internal constructor( public override val fromEvent: GroupMessageEvent, ) : MemberCommandSender(fromEvent.sender), CommandSenderOnMessage<GroupMessageEvent> /** * 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 * @see TempCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过私聊方式 */ @Deprecated( "mirai 正计划支持其他渠道发起的临时会话, 届时此事件会变动. 原 TempCommandSenderOnMessage 已更改为 GroupTempCommandSenderOnMessage", replaceWith = ReplaceWith( "GroupTempCommandSenderOnMessage", "net.mamoe.mirai.console.command.GroupTempCommandSenderOnMessage" ), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.0", hiddenSince = "2.10") public sealed class TempCommandSenderOnMessage( public override val fromEvent: GroupTempMessageEvent, ) : GroupTempCommandSender(fromEvent.sender), CommandSenderOnMessage<GroupTempMessageEvent> /** * 代表一个 [群员][Member] 主动在临时会话发送消息执行指令 * @see TempCommandSender 代表一个 [群员][Member] 执行指令, 但不一定是通过私聊方式 */ public class GroupTempCommandSenderOnMessage internal constructor( public override val fromEvent: GroupTempMessageEvent, ) : @Suppress("DEPRECATION_ERROR") TempCommandSenderOnMessage(fromEvent), CommandSenderOnMessage<GroupTempMessageEvent> /** * 代表一个 [陌生人][Stranger] 主动在私聊发送消息执行指令 * @see StrangerCommandSender 代表一个 [陌生人][Stranger] 执行指令, 但不一定是通过私聊方式 */ public class StrangerCommandSenderOnMessage internal constructor( public override val fromEvent: StrangerMessageEvent, ) : StrangerCommandSender(fromEvent.sender), CommandSenderOnMessage<StrangerMessageEvent> /** * 代表一个 [其他客户端][OtherClient] 主动在私聊发送消息执行指令 * @see OtherClientCommandSender 代表一个 [其他客户端][OtherClient] 执行指令, 但不一定是通过私聊方式 */ public class OtherClientCommandSenderOnMessage internal constructor( public override val fromEvent: OtherClientMessageEvent, ) : OtherClientCommandSender(fromEvent.client), CommandSenderOnMessage<OtherClientMessageEvent> /** * 代表一个 [其他客户端][OtherClient] 主动在群内、好友聊天等发送消息执行指令 * @see OtherClientCommandSender 代表一个 [其他客户端][OtherClient] 执行指令, 但不一定是通过私聊方式 * @since 2.13 */ public class OtherClientCommandSenderOnMessageSync internal constructor( public override val fromEvent: MessageSyncEvent, ) : OtherClientCommandSender(fromEvent.client), CommandSenderOnMessage<MessageSyncEvent> // endregion // region PluginCustomCommandSender implementations /** * 所有 [PluginCustomCommandSender] 的父类 * * Java 见 [AbstractPluginCustomCommandSenderJ] */ public abstract class AbstractPluginCustomCommandSender( final override val owner: Plugin, ) : AbstractCommandSender(), PluginCustomCommandSender { override val coroutineContext: CoroutineContext by lazy { if (owner is CoroutineScope) { return@lazy owner.childScopeContext(name) } // Fallback return@lazy MiraiConsole.childScopeContext("PluginCustomCommandSender-$name") } override val name: String get() = owner.name override fun toString(): String = name override val isAnsiSupported: Boolean get() = false override val permitteeId: PermitteeId get() = AbstractPermitteeId.Console override val bot: Bot? get() = null override val subject: Contact? get() = null override val user: User? get() = null override suspend fun sendMessage(message: String): Nothing? { sendMessage0(message) return null } protected abstract suspend fun sendMessage0(message: String) } @JavaFriendlyApi public abstract class AbstractPluginCustomCommandSenderJ( owner: Plugin ) : AbstractPluginCustomCommandSender(owner) { protected abstract fun sendMessageImpl(message: String) final override suspend fun sendMessage(message: String): Nothing? { sendMessageImpl(message) return null } final override suspend fun sendMessage0(message: String) { sendMessageImpl(message) } } // endregion ================================================ FILE: mirai-console/backend/mirai-console/src/command/CompositeCommand.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JCompositeCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.CommandReflector import net.mamoe.mirai.console.internal.command.CompositeCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.annotation.AnnotationRetention.RUNTIME import kotlin.annotation.AnnotationTarget.FUNCTION /** * 复合指令. 指令注册时候会通过反射构造指令解析器. * * Java 示例查看 [JCompositeCommand]. * * Kotlin 示例: * ``` * @OptIn(ConsoleExperimentalAPI::class) * object MyCompositeCommand : CompositeCommand( * MyPluginMain, "manage", // "manage" 是主指令名 * description = "示例指令", permission = MyCustomPermission, * // prefixOptional = true // 还有更多参数可填, 此处忽略 * ) { * * // [参数智能解析] * // * // 在控制台执行 "/manage <群号>.<群员> <持续时间>", * // 或在聊天群内发送 "/manage <@一个群员> <持续时间>", * // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>", * // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>" * // 时调用这个函数 * @SubCommand // 表示这是一个子指令,使用函数名作为子指令名称 * suspend fun CommandSender.mute(target: Member, duration: Int) { // 通过 /manage mute <target> <duration> 调用 * sendMessage("/manage mute 被调用了, 参数为: $target, $duration") * * val result = kotlin.runCatching { * target.mute(duration).toString() * }.getOrElse { * it.stackTraceToString() * } // 失败时返回堆栈信息 * * sendMessage("结果: $result") * } * * @SubCommand * suspend fun SystemCommandSender.foo() { * // 使用 SystemCommandSender 作为接收者,表示指令只能由系统(控制台或其他插件)执行。 * // 当用户尝试在聊天环境执行时将会收到错误提示。 * } * * @SubCommand("list", "查看列表") // 可以设置多个子指令名。此时函数名会被忽略。 * suspend fun CommandSender.list() { // 执行 "/manage list" 时调用这个函数 * sendMessage("/manage list 被调用了") * } * * @SubCommand * suspend fun CommandContext.repeat() { * // 使用 CommandContext 作为参数,可以获得触发指令的原消息链 originalMessage,其中包含 MessageMetadata。 * sender.sendMessage(originalMessage) * } * * // 支持 Image 类型, 需在聊天中执行此指令. * @SubCommand * suspend fun UserCommandSender.test(image: Image) { // 执行 "/manage test <一张图片>" 时调用这个函数 * // 由于 Image 类型消息只可能在聊天环境,可以直接使用 UserCommandSender。 * sendMessage("/manage image 被调用了, 图片是 ${image.imageId}") * } * } * ``` * * @see buildCommandArgumentContext */ public abstract class CompositeCommand( @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, description: String = "no description available", parentPermission: Permission = owner.parentPermission, overrideContext: CommandArgumentContext = EmptyCommandArgumentContext, ) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission), CommandArgumentContextAware { private val reflector by lazy { CommandReflector(this, CompositeCommandSubCommandAnnotationResolver) } @ExperimentalCommandDescriptors public final override val overloads: List<@JvmWildcard CommandSignatureFromKFunction> by lazy { reflector.findSubCommands().also { reflector.validate(it) } } /** * 自动根据带有 [SubCommand] 注解的函数签名生成 [usage]. 也可以被覆盖. */ public override val usage: String by lazy { @OptIn(ExperimentalCommandDescriptors::class) reflector.generateUsage(overloads) } /** * 智能参数解析环境 */ // open since 2.12 public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext /** * 标记一个函数为子指令, 当 [value] 为空时使用函数名. * @param value 子指令名 */ @Retention(RUNTIME) @Target(FUNCTION) protected annotation class SubCommand( @ResolveContext(COMMAND_NAME) vararg val value: String = [], ) /** 指令描述 */ @Retention(RUNTIME) @Target(FUNCTION) protected annotation class Description(val value: String) /** 参数名, 将参与构成 [usage] */ @ConsoleExperimentalApi("Classname might change") @Retention(RUNTIME) @Target(AnnotationTarget.VALUE_PARAMETER) protected annotation class Name(val value: String) } ================================================ FILE: mirai-console/backend/mirai-console/src/command/IllegalCommandArgumentException.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException /** * 在处理参数时遇到的 _正常_ 错误. 如参数不符合规范, 参数值越界等. * * [message] 将会发送给指令调用方. * * 如果指令调用方是 [ConsoleCommandSender], * 还会将 [cause], [suppressedExceptions] 发送给 [ConsoleCommandSender] (如果存在) * * @see CommandArgumentParserException */ public open class IllegalCommandArgumentException @JvmOverloads constructor( message: String, cause: Throwable? = null, ) : IllegalArgumentException(message, cause) ================================================ FILE: mirai-console/backend/mirai-console/src/command/RawCommand.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.buildMessageChain /** * 无参数解析, 只会接收原消息链的指令. Java 查看 [JRawCommand]. * * ```kotlin * object MyCommand : RawCommand( * MyPluginMain, "name", // 使用插件主类对象作为指令拥有者;设置主指令名为 "name" * // 可选: * "name2", "name3", // 增加两个次要名称 * usage = "/name arg1 arg2", // 设置用法,将会在 /help 展示 * description = "这是一个测试指令", // 设置描述,将会在 /help 展示 * prefixOptional = true, // 设置指令前缀是可选的,即使用 `test` 也能执行指令而不需要 `/test` * ) { * override suspend fun CommandContext.onCommand(args: MessageChain) { * } * } * ``` * * @see JRawCommand 供 Java 用户继承. * * @see SimpleCommand 简单指令 * @see CompositeCommand 复合指令 */ public abstract class RawCommand( /** * 指令拥有者. 通常建议使用插件主类. * @see CommandOwner */ @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) public override val owner: CommandOwner, /** 主指令名. */ @ResolveContext(COMMAND_NAME) public override val primaryName: String, /** 次要指令名. */ @ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String, /** 用法说明, 用于发送给用户 */ public override val usage: String = "<no usages given>", /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ public override val description: String = "<no descriptions given>", /** 指令父权限 */ parentPermission: Permission = owner.parentPermission, /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ @OptIn(ExperimentalCommandDescriptors::class) public override val prefixOptional: Boolean = false, ) : Command { public override val permission: Permission by lazy { findOrCreateCommandPermission(parentPermission) } @ExperimentalCommandDescriptors override val overloads: List<@JvmWildcard CommandSignature> = listOf( CommandSignatureImpl( receiverParameter = CommandReceiverParameter.Context(false), valueParameters = listOf( AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>( "args", true ) ) ) { call -> val sender = call.caller val arguments = call.rawValueArguments val context = CommandContextImpl(sender, call.originalMessage) context.onCommand(buildMessageChain { arguments.forEach { +it.value } }) } ) /** * 在指令被执行时调用. * * @param args 指令参数. * * @see CommandManager.executeCommand 查看更多信息 */ public open suspend fun CommandSender.onCommand(args: MessageChain) { } /** * 在指令被执行时调用. * * @param args 指令参数. * @see CommandManager.executeCommand 查看更多信息 * * @since 2.12 */ public open suspend fun CommandContext.onCommand(args: MessageChain) { return sender.onCommand(args) } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/SimpleCommand.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.CommandReflector import net.mamoe.mirai.console.internal.command.IllegalCommandDeclarationException import net.mamoe.mirai.console.internal.command.SimpleCommandSubCommandAnnotationResolver import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.annotation.AnnotationTarget.FUNCTION import kotlin.annotation.AnnotationTarget.VALUE_PARAMETER /** * 简单的, 支持参数自动解析的指令. * * 要查看指令解析流程, 参考 [CommandManager.executeCommand]. * 要查看参数解析方式, 参考 [CommandValueArgumentParser]. * * Java 示例查看 [JSimpleCommand]. * * Kotlin 示例: * ``` * object MySimpleCommand : SimpleCommand( * MyPlugin, "tell", * description = "Message somebody" * ) { * @Handler * suspend fun CommandSender.onCommand(target: User, message: String) { * target.sendMessage(message) * } * } * ``` * * 其中 `CommandSender` 也可以替换为 `CommandContext`,可通过 [CommandContext.originalMessage] 获得触发指令的原消息链。 * * @see JSimpleCommand Java 实现 * @see [CommandManager.executeCommand] */ public abstract class SimpleCommand( @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, description: String = "no description available", parentPermission: Permission = owner.parentPermission, overrideContext: CommandArgumentContext = EmptyCommandArgumentContext, ) : Command, AbstractCommand(owner, primaryName, secondaryNames = secondaryNames, description, parentPermission), CommandArgumentContextAware { private val reflector by lazy { CommandReflector(this, SimpleCommandSubCommandAnnotationResolver) } @OptIn(ConsoleExperimentalApi::class) @ExperimentalCommandDescriptors public final override val overloads: List<@JvmWildcard CommandSignatureFromKFunction> by lazy { reflector.findSubCommands().also { reflector.validate(it) if (it.isEmpty()) throw IllegalCommandDeclarationException( this, "SimpleCommand must have at least one subcommand, whereas zero present." ) } } /** * 自动根据带有 [Handler] 注解的函数签名生成 [usage]. 也可以被覆盖. */ public override val usage: String by lazy { @OptIn(ExperimentalCommandDescriptors::class) reflector.generateUsage(overloads) } /** * 标注指令处理器 */ @Target(FUNCTION) protected annotation class Handler /** 参数名, 将参与构成 [usage] */ @ConsoleExperimentalApi("Classname might change") @Target(VALUE_PARAMETER) protected annotation class Name(val value: String) /** * 指令参数环境. 默认为 [CommandArgumentContext.Builtins] `+` `overrideContext` */ public override val context: CommandArgumentContext = CommandArgumentContext.Builtins + overrideContext } ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "unused", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.Bot import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext.ParserPair import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.utils.MiraiExperimentalApi import java.time.* import java.time.temporal.TemporalAccessor import java.util.* import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf /** * 指令参数环境, 即 [CommandValueArgumentParser] 的集合, 用于 [CompositeCommand] 和 [SimpleCommand]. * * 在指令解析时, 总是从 [CommandArgumentContextAware.context] 搜索相关解析器 * * 要构造 [CommandArgumentContext], 参考 [buildCommandArgumentContext] * * @see SimpleCommandArgumentContext 简单实现 * @see EmptyCommandArgumentContext 空实现, 类似 [emptyList] * * @see CommandArgumentContext.Builtins 内建 [CommandValueArgumentParser] * * @see buildCommandArgumentContext DSL 构造 */ public interface CommandArgumentContext { /** * [KClass] 到 [CommandValueArgumentParser] 的匹配 */ public data class ParserPair<T : Any>( val klass: KClass<T>, val parser: CommandValueArgumentParser<T>, ) { public companion object { @JvmStatic public fun <T : Any> ParserPair<T>.toPair(): Pair<KClass<T>, CommandValueArgumentParser<T>> = klass to parser } } /** * 获取一个 [kClass] 类型的解析器. */ public operator fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? public fun toList(): List<ParserPair<*>> public companion object { /** * For Java callers. * * @see EmptyCommandArgumentContext */ @JvmField // public static final CommandArgumentContext EMPTY; public val EMPTY: CommandArgumentContext = EmptyCommandArgumentContext } private object EnumCommandArgumentContext : CommandArgumentContext { private val cache = WeakHashMap<Class<*>, CommandValueArgumentParser<*>>() private val enumKlass = Enum::class override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? { return if (kClass.isSubclassOf(enumKlass)) { val jclass = kClass.java.asSubclass(Enum::class.java) @Suppress("UNCHECKED_CAST") (cache[jclass] ?: kotlin.run { EnumValueArgumentParser(jclass).also { cache[jclass] = it } }) as CommandValueArgumentParser<T> } else null } override fun toList(): List<ParserPair<*>> = emptyList() } private object TemporalCommandArgumentContext : CommandArgumentContext { private val cache = HashMap<KClass<*>, CommandValueArgumentParser<*>>() @OptIn(MiraiExperimentalApi::class) private fun <T : TemporalAccessor> put(kClass: KClass<T>, now: () -> T, parse: (CharSequence) -> T) { cache[kClass] = TemporalArgumentParser(kClass.java, now, parse) } init { put(Instant::class, Instant::now, Instant::parse) put(Year::class, Year::now, Year::parse) put(YearMonth::class, YearMonth::now, YearMonth::parse) put(LocalDate::class, LocalDate::now, LocalDate::parse) put(LocalTime::class, LocalTime::now, LocalTime::parse) put(LocalDateTime::class, LocalDateTime::now, LocalDateTime::parse) put(OffsetTime::class, OffsetTime::now, OffsetTime::parse) put(OffsetDateTime::class, OffsetDateTime::now, OffsetDateTime::parse) put(ZonedDateTime::class, ZonedDateTime::now, ZonedDateTime::parse) put(MonthDay::class, MonthDay::now, MonthDay::parse) put(ZoneOffset::class, { OffsetDateTime.now().offset }, { ZoneOffset.of(it.toString()) }) } @Suppress("UNCHECKED_CAST") override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? { return cache[kClass] as CommandValueArgumentParser<T>? } override fun toList(): List<ParserPair<*>> = emptyList() } /** * 内建的默认 [CommandValueArgumentParser] */ public object Builtins : CommandArgumentContext by listOf( EnumCommandArgumentContext, buildCommandArgumentContext { Int::class with IntValueArgumentParser Byte::class with ByteValueArgumentParser Short::class with ShortValueArgumentParser Boolean::class with BooleanValueArgumentParser String::class with StringValueArgumentParser Long::class with LongValueArgumentParser Double::class with DoubleValueArgumentParser Float::class with FloatValueArgumentParser Image::class with ImageValueArgumentParser PlainText::class with PlainTextValueArgumentParser Contact::class with ExistingContactValueArgumentParser User::class with ExistingUserValueArgumentParser Member::class with ExistingMemberValueArgumentParser Group::class with ExistingGroupValueArgumentParser Friend::class with ExistingFriendValueArgumentParser Bot::class with ExistingBotValueArgumentParser PermissionId::class with PermissionIdValueArgumentParser PermitteeId::class with PermitteeIdValueArgumentParser MessageContent::class with RawContentValueArgumentParser }, TemporalCommandArgumentContext, ).fold(EmptyCommandArgumentContext, CommandArgumentContext::plus) } /** * 拥有 [buildCommandArgumentContext] 的类 * * @see SimpleCommand * @see CompositeCommand */ public interface CommandArgumentContextAware { /** * [CommandValueArgumentParser] 的集合 */ public val context: CommandArgumentContext } /** * @see CommandArgumentContext.EMPTY */ public object EmptyCommandArgumentContext : CommandArgumentContext by SimpleCommandArgumentContext(listOf()) /** * 合并两个 [buildCommandArgumentContext], [replacer] 将会替换 [this] 中重复的 parser. */ public operator fun CommandArgumentContext.plus(replacer: CommandArgumentContext): CommandArgumentContext { if (replacer === EmptyCommandArgumentContext) return this if (this == EmptyCommandArgumentContext) return replacer return object : CommandArgumentContext { override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? = replacer[kClass] ?: this@plus[kClass] override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList() } } /** * 合并 [this] 与 [replacer], [replacer] 将会替换 [this] 中重复的 parser. */ public operator fun CommandArgumentContext.plus(replacer: List<ParserPair<*>>): CommandArgumentContext { if (replacer.isEmpty()) return this if (this === EmptyCommandArgumentContext) return SimpleCommandArgumentContext(replacer) return object : CommandArgumentContext { @Suppress("UNCHECKED_CAST") override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? = replacer.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser as CommandValueArgumentParser<T>? ?: this@plus[kClass] override fun toList(): List<ParserPair<*>> = replacer.toList() + this@plus.toList() } } /** * 自定义 [buildCommandArgumentContext] * * @see buildCommandArgumentContext */ @Suppress("UNCHECKED_CAST") public class SimpleCommandArgumentContext( public val list: List<ParserPair<*>>, ) : CommandArgumentContext { override fun <T : Any> get(kClass: KClass<T>): CommandValueArgumentParser<T>? = (this.list.firstOrNull { kClass == it.klass }?.parser ?: this.list.firstOrNull { kClass.isSubclassOf(it.klass) }?.parser) as CommandValueArgumentParser<T>? override fun toList(): List<ParserPair<*>> = list } /** * 构建一个 [buildCommandArgumentContext]. * * Kotlin 实现: * ``` * val context = buildCommandArgumentContext { * Int::class with IntArgParser * Member::class with ExistingMemberArgParser * Group::class with { s: String, sender: CommandSender -> * Bot.getInstance(s.toLong()).getGroup(s.toLong()) * } * Bot::class with { s: String -> * Bot.getInstance(s.toLong()) * } * } * ``` * * Java 实现: * ```java * CommandArgumentContext context = * new CommandArgumentContextBuilder() * .add(clazz1, parser1) * .add(String.class, new CommandArgumentParser<String>() { * public String parse(String raw, CommandSender sender) { * // ... * } * }) * // 更多 add * .build() * ``` * * @see CommandArgumentContextBuilder * @see buildCommandArgumentContext */ @JvmSynthetic public fun buildCommandArgumentContext(block: CommandArgumentContextBuilder.() -> Unit): CommandArgumentContext { contract { callsInPlace(block, EXACTLY_ONCE) } return CommandArgumentContextBuilder().apply(block).build() } /** * 参考 [buildCommandArgumentContext] */ public class CommandArgumentContextBuilder : MutableList<ParserPair<*>> by mutableListOf() { /** * 添加一个指令解析器. */ @JvmName("add") public infix fun <T : Any> Class<T>.with(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder = this.kotlin with parser /** * 添加一个指令解析器 */ @JvmName("add") public inline infix fun <T : Any> KClass<T>.with(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder { add(ParserPair(this, parser)) return this@CommandArgumentContextBuilder } /** * 添加一个指令解析器 */ @JvmSynthetic @LowPriorityInOverloadResolution public inline infix fun <T : Any> KClass<T>.with( crossinline parser: CommandValueArgumentParser<T>.(s: String, sender: CommandSender) -> T, ): CommandArgumentContextBuilder { add(ParserPair(this, object : CommandValueArgumentParser<T> { override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender) })) return this@CommandArgumentContextBuilder } /** * 添加一个指令解析器 */ @JvmSynthetic public inline infix fun <T : Any> KClass<T>.with( crossinline parser: CommandValueArgumentParser<T>.(s: String) -> T, ): CommandArgumentContextBuilder { add(ParserPair(this, object : CommandValueArgumentParser<T> { override fun parse(raw: String, sender: CommandSender): T = parser(raw) })) return this@CommandArgumentContextBuilder } @JvmSynthetic public inline fun <reified T : Any> add(parser: CommandValueArgumentParser<T>): CommandArgumentContextBuilder { add(ParserPair(T::class, parser)) return this@CommandArgumentContextBuilder } /** * 添加一个指令解析器 */ @ConsoleExperimentalApi @JvmSynthetic public inline infix fun <reified T : Any> add( crossinline parser: CommandValueArgumentParser<*>.(s: String) -> T, ): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser<T> { override fun parse(raw: String, sender: CommandSender): T = parser(raw) } /** * 添加一个指令解析器 */ @ConsoleExperimentalApi @JvmSynthetic @LowPriorityInOverloadResolution public inline infix fun <reified T : Any> add( crossinline parser: CommandValueArgumentParser<*>.(s: String, sender: CommandSender) -> T, ): CommandArgumentContextBuilder = T::class with object : CommandValueArgumentParser<T> { override fun parse(raw: String, sender: CommandSender): T = parser(raw, sender) } /** * 完成构建, 得到 [CommandArgumentContext] */ public fun build(): CommandArgumentContext = SimpleCommandArgumentContext(this.distinctByReversed { it.klass }) } internal inline fun <T, K> List<T>.distinctByReversed(selector: (T) -> K): List<T> { val set = HashSet<K>() val list = ArrayList<T>() for (i in this.indices.reversed()) { val element = this[i] if (set.add(element.let(selector))) { list.add(element) } } return list } ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPOSED_SUPER_CLASS") package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.Bot import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.CommandSender.Companion.asCommandSender import net.mamoe.mirai.console.internal.command.fuzzySearchMember import net.mamoe.mirai.console.permission.AbstractPermitteeId import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.console.permission.RootPermission import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.chineseLength import java.time.temporal.TemporalAccessor /** * 使用 [String.toInt] 解析 */ public object IntValueArgumentParser : InternalCommandValueArgumentParserExtensions<Int>() { public override fun parse(raw: String, sender: CommandSender): Int = raw.toIntOrNull() ?: illegalArgument("无法解析 $raw 为整数") } /** * 使用 [String.toLong] 解析 */ public object LongValueArgumentParser : InternalCommandValueArgumentParserExtensions<Long>() { public override fun parse(raw: String, sender: CommandSender): Long = raw.toLongOrNull() ?: illegalArgument("无法解析 $raw 为长整数") } /** * 使用 [String.toShort] 解析 */ public object ShortValueArgumentParser : InternalCommandValueArgumentParserExtensions<Short>() { public override fun parse(raw: String, sender: CommandSender): Short = raw.toShortOrNull() ?: illegalArgument("无法解析 $raw 为短整数") } /** * 使用 [String.toByte] 解析 */ public object ByteValueArgumentParser : InternalCommandValueArgumentParserExtensions<Byte>() { public override fun parse(raw: String, sender: CommandSender): Byte = raw.toByteOrNull() ?: illegalArgument("无法解析 $raw 为字节") } /** * 使用 [String.toDouble] 解析 */ public object DoubleValueArgumentParser : InternalCommandValueArgumentParserExtensions<Double>() { public override fun parse(raw: String, sender: CommandSender): Double = raw.toDoubleOrNull() ?: illegalArgument("无法解析 $raw 为小数") } /** * 使用 [String.toFloat] 解析 */ public object FloatValueArgumentParser : InternalCommandValueArgumentParserExtensions<Float>() { public override fun parse(raw: String, sender: CommandSender): Float = raw.toFloatOrNull() ?: illegalArgument("无法解析 $raw 为小数") } /** * 直接返回 [String], 或取用 [SingleMessage.contentToString] */ public object StringValueArgumentParser : InternalCommandValueArgumentParserExtensions<String>() { public override fun parse(raw: String, sender: CommandSender): String = raw } /** * 解析 [String] 通过 [Image]. */ public object ImageValueArgumentParser : InternalCommandValueArgumentParserExtensions<Image>() { public override fun parse(raw: String, sender: CommandSender): Image { return kotlin.runCatching { Image(raw) }.getOrElse { illegalArgument("无法解析 $raw 为图片.") } } override fun parse(raw: MessageContent, sender: CommandSender): Image { if (raw is Image) return raw return super.parse(raw, sender) } } public object PlainTextValueArgumentParser : InternalCommandValueArgumentParserExtensions<PlainText>() { public override fun parse(raw: String, sender: CommandSender): PlainText { return PlainText(raw) } override fun parse(raw: MessageContent, sender: CommandSender): PlainText { if (raw is PlainText) return raw return super.parse(raw, sender) } } /** * 当字符串内容为(不区分大小写) "true", "yes", "enabled", "on", "1" */ public object BooleanValueArgumentParser : InternalCommandValueArgumentParserExtensions<Boolean>() { public override fun parse(raw: String, sender: CommandSender): Boolean = raw.trim().let { str -> str.equals("true", ignoreCase = true) || str.equals("yes", ignoreCase = true) || str.equals("enabled", ignoreCase = true) || str.equals("on", ignoreCase = true) || str.equals("1", ignoreCase = true) } } /** * 根据 [Bot.id] 解析一个登录后的 [Bot] */ public object ExistingBotValueArgumentParser : InternalCommandValueArgumentParserExtensions<Bot>() { public override fun parse(raw: String, sender: CommandSender): Bot = if (raw == "~") sender.inferBotOrFail() else raw.findBotOrFail() public override fun parse(raw: MessageContent, sender: CommandSender): Bot = if (raw is At) { Bot.getInstanceOrNull(raw.target) ?: illegalArgument("@ 的对象不是一个 Bot") } else super.parse(raw, sender) } /** * 解析任意一个存在的好友. */ public object ExistingFriendValueArgumentParser : InternalCommandValueArgumentParserExtensions<Friend>() { private val syntax = """ - 机器人号码.好友号码 - ~ (指代指令调用人自己作为好友. 仅聊天环境下) 当只登录了一个机器人时,机器人号码可省略 """.trimIndent() public override fun parse(raw: String, sender: CommandSender): Friend { if (raw == "~") return sender.inferFriendOrFail() val components = raw.split(".") return when (components.size) { 2 -> components.let { (botId, groupId) -> botId.findBotOrFail().findFriendOrFail(groupId) } 1 -> components.let { (groupId) -> sender.inferBotOrFail().findFriendOrFail(groupId) } else -> illegalArgument("好友语法错误. \n${syntax}") } } public override fun parse(raw: MessageContent, sender: CommandSender): Friend { if (raw is At) { checkArgument(sender is MemberCommandSender) return sender.inferBotOrFail().getFriend(raw.target) ?: illegalArgument("At 的对象 ${raw.target} 非 Bot 好友") } else { illegalArgument("无法解析 $raw 为好友") } } } /** * 解析任意一个存在的群. */ public object ExistingGroupValueArgumentParser : InternalCommandValueArgumentParserExtensions<Group>() { private val syntax = """ - 机器人号码.群号码 - ~ (指代指令调用人自己所在群. 仅群聊天环境下) 当只登录了一个机器人时,机器人号码可省略 """.trimIndent() public override fun parse(raw: String, sender: CommandSender): Group { if (raw == "~") return sender.inferGroupOrFail() val components = raw.split(".") return when (components.size) { 2 -> components.let { (botId, groupId) -> botId.findBotOrFail().findGroupOrFail(groupId) } 1 -> components.let { (groupId) -> sender.inferBotOrFail().findGroupOrFail(groupId) } 0 -> components.let { sender.inferGroupOrFail() } else -> illegalArgument("群语法错误. \n${syntax}") } } } public object ExistingUserValueArgumentParser : InternalCommandValueArgumentParserExtensions<User>() { private val syntax: String = """ - 机器人号码.群号码.群员号码 - 机器人号码.群号码.群员名片 (模糊搜索) - ~ (指代指令调用人自己. 仅聊天环境下) - 机器人号码.群号码.$ (随机成员) - 机器人号码.好友号码 当处于一个群内时,机器人号码和群号码参数都可省略 当只登录了一个机器人时,机器人号码可省略 """.trimIndent() override fun parse(raw: String, sender: CommandSender): User { return parseImpl( sender, raw, ExistingMemberValueArgumentParser::parse, ExistingFriendValueArgumentParser::parse ) } override fun parse(raw: MessageContent, sender: CommandSender): User { return parseImpl( sender, raw, ExistingMemberValueArgumentParser::parse, ExistingFriendValueArgumentParser::parse ) } private fun <T> parseImpl( sender: CommandSender, raw: T, parseFunction: (T, CommandSender) -> User, parseFunction2: (T, CommandSender) -> User, ): User { if (sender.inferGroup() != null) { kotlin.runCatching { return parseFunction(raw, sender) }.recoverCatching { return parseFunction2(raw, sender) }.getOrElse { illegalArgument("无法推断目标好友或群员. \n$syntax") } } kotlin.runCatching { return parseFunction2(raw, sender) }.getOrElse { illegalArgument("无法推断目标好友或群员. \n$syntax") } } } public object ExistingContactValueArgumentParser : InternalCommandValueArgumentParserExtensions<Contact>() { private val syntax: String = """ - 机器人号码.群号码.群员号码 - 机器人号码.群号码.群员名片 (模糊搜索) - ~ (指代指令调用人自己. 仅聊天环境下) - 机器人号码.群号码.$ (随机成员) - 机器人号码.好友号码 - 机器人号码.群号码 当处于一个群内时,机器人号码和群号码参数都可省略 当只登录了一个机器人时,机器人号码可省略 """.trimIndent() override fun parse(raw: String, sender: CommandSender): Contact { return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse) } override fun parse(raw: MessageContent, sender: CommandSender): Contact { return parseImpl(sender, raw, ExistingUserValueArgumentParser::parse, ExistingGroupValueArgumentParser::parse) } private fun <T> parseImpl( sender: CommandSender, raw: T, parseFunction: (T, CommandSender) -> Contact, parseFunction2: (T, CommandSender) -> Contact, ): Contact { kotlin.runCatching { return parseFunction(raw, sender) }.recoverCatching { return parseFunction2(raw, sender) }.getOrElse { illegalArgument("无法推断目标好友、群或群员。 \n$syntax") } } } /** * 解析任意一个群成员. */ public object ExistingMemberValueArgumentParser : InternalCommandValueArgumentParserExtensions<Member>() { private val syntax: String = """ - 机器人号码.群号码.群员号码 - 机器人号码.群号码.群员名片 (模糊搜索) - ~ (指代指令调用人自己. 仅聊天环境下) - 机器人号码.群号码.${'$'} (随机成员) 当处于一个群内时,机器人号码和群号码参数都可省略 当只登录了一个机器人时,机器人号码可省略 """.trimIndent() public override fun parse(raw: String, sender: CommandSender): Member { if (raw == "~") return (sender as? MemberCommandSender)?.user ?: illegalArgument("当前语境下无法推断自身作为群员") if (raw == "\$") return (sender as? MemberCommandSender)?.group?.members?.randomOrNull() ?: illegalArgument("当前语境下无法推断随机群员") val components = raw.split(".") return when (components.size) { 3 -> components.let { (botId, groupId, memberIdOrCard) -> botId.findBotOrFail().findGroupOrFail(groupId).findMemberOrFail(memberIdOrCard) } 2 -> components.let { (groupId, memberIdOrCard) -> sender.inferBotOrFail().findGroupOrFail(groupId).findMemberOrFail(memberIdOrCard) } 1 -> components.let { (memberIdOrCard) -> sender.inferGroupOrFail().findMemberOrFail(memberIdOrCard) } else -> illegalArgument("群成员语法错误. \n$syntax") } } public override fun parse(raw: MessageContent, sender: CommandSender): Member { return if (raw is At) { checkArgument(sender is MemberCommandSender) val bot = sender.inferBotOrFail() val group = sender.inferGroupOrFail() if (raw.target == bot.id) { return group.botAsMember } group[raw.target] ?: illegalArgument("无法找到群员 ${raw.target}") } else { parse(raw.content, sender) } } } public object PermissionIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermissionId>() { override fun parse(raw: String, sender: CommandSender): PermissionId { return kotlin.runCatching { PermissionId.parseFromString(raw) }.getOrElse { if (raw == "*") return RootPermission.id // for convenience illegalArgument("无法解析 $raw 为权限 ID.") } } } public object PermitteeIdValueArgumentParser : InternalCommandValueArgumentParserExtensions<PermitteeId>() { override fun parse(raw: String, sender: CommandSender): PermitteeId { return if (raw == "~") sender.permitteeId else { kotlin.runCatching { AbstractPermitteeId.parseFromString(raw) }.getOrElse { val long = raw.toLongOrNull() if (long != null) { return AbstractPermitteeId.ExactUser(long)// for convenience } illegalArgument("无法解析 $raw 为被许可人 ID.") } } } override fun parse(raw: MessageContent, sender: CommandSender): PermitteeId { if (raw is At) { return ExistingUserValueArgumentParser.parse(raw, sender).asCommandSender(false).permitteeId } return super.parse(raw, sender) } } /** 直接返回原始参数 [MessageContent] */ public object RawContentValueArgumentParser : CommandValueArgumentParser<MessageContent> { override fun parse(raw: String, sender: CommandSender): MessageContent = PlainText(raw) override fun parse(raw: MessageContent, sender: CommandSender): MessageContent = raw } /** * 解析参数为枚举 [T] * * 注: * - 当枚举值大小写无冲突时会尝试忽略大小写 * - 当大小写驼峰可用时会尝试使用大小写驼峰 * * 例如: * ``` * enum class StdType { STD_IN, STD_OUT, STD_ERR } * ``` * 对于 StdType 有以下值可用: * - `STD_IN`, `STD_OUT`, `STD_ERR` (忽视大小写) * - `stdIn`, `stdOut`, `stdErr` (不忽视大小写) * * @since 2.2 */ public class EnumValueArgumentParser<T : Enum<T>>( private val type: Class<T>, ) : InternalCommandValueArgumentParserExtensions<T>() { // 此 Exception 仅用于中断 enum 搜索, 不需要使用堆栈信息 private object NoEnumException : RuntimeException() init { check(Enum::class.java.isAssignableFrom(type)) { "$type not a enum class" } } private fun <T> Sequence<T>.hasDuplicates(): Boolean = iterator().hasDuplicates() private fun <T> Iterator<T>.hasDuplicates(): Boolean { val observed = HashSet<T>() for (elem in this) { if (!observed.add(elem)) return true } return false } @Suppress("NOTHING_TO_INLINE") private inline fun noConstant(): Nothing { throw NoEnumException } private val delegate: (String) -> T = kotlin.run { val enums = type.enumConstants.asSequence() // step 1: 分析是否能够忽略大小写 if (enums.map { it.name.lowercase() }.hasDuplicates()) { ({ java.lang.Enum.valueOf(type, it) }) } else { // step 2: 分析是否能使用小驼峰命名 val lowerCaseEnumDirection = enums.map { it.name.lowercase() to it }.toList().toMap() val camelCase = enums.mapNotNull { elm -> val name = elm.name.split('_') if (name.size == 1) { // No splitter null } else { buildString { val iterator = name.iterator() append(iterator.next().lowercase()) for (v in iterator) { if (v.isEmpty()) continue append(v[0].uppercase()) append(v.substring(1, v.length).lowercase()) } } to elm } } val camelCaseDirection = if (( enums.map { it.name.lowercase() } + camelCase.map { it.first.lowercase() } ).hasDuplicates() ) { // 确认驼峰命名与源没有冲突 emptyMap() } else { camelCase.toList().toMap() } ({ camelCaseDirection[it] ?: lowerCaseEnumDirection[it.lowercase()] ?: noConstant() }) } } override fun parse(raw: String, sender: CommandSender): T { return try { delegate(raw) } catch (e: Throwable) { illegalArgument("无法解析 $raw 为 ${type.simpleName}") } } } /** * 解析参数为时间 [T] * @param now 返回当前时间 * @param parse 从字符串解析时间 * @since 2.10 */ @MiraiExperimentalApi public class TemporalArgumentParser<T : TemporalAccessor>( private val type: Class<T>, private val now: () -> T, private val parse: (CharSequence) -> T, ) : InternalCommandValueArgumentParserExtensions<T>() { override fun parse(raw: String, sender: CommandSender): T { return try { if (raw.equals(other = "now", ignoreCase = true)) { now.invoke() } else { parse.invoke(raw) } } catch (e: Throwable) { illegalArgument("无法解析 $raw 为 ${type.javaClass}") } } } internal abstract class InternalCommandValueArgumentParserExtensions<T : Any> : AbstractCommandValueArgumentParser<T>() { private fun String.parseToLongOrFail(): Long = toLongOrNull() ?: illegalArgument("无法解析 $this 为整数") protected fun Long.findBotOrFail(): Bot = Bot.getInstanceOrNull(this) ?: illegalArgument("无法找到 Bot: $this") protected fun String.findBotOrFail(): Bot = Bot.getInstanceOrNull(this.parseToLongOrFail()) ?: illegalArgument("无法找到 Bot: $this") protected fun Bot.findGroupOrFail(id: Long): Group = getGroup(id) ?: illegalArgument("无法找到群: $this") protected fun Bot.findGroupOrFail(id: String): Group = getGroup(id.parseToLongOrFail()) ?: illegalArgument("无法找到群: $this") protected fun Bot.findFriendOrFail(id: String): Friend = getFriend(id.parseToLongOrFail()) ?: illegalArgument("无法找到好友: $this") protected fun Bot.findMemberOrFail(id: String): Friend = getFriend(id.parseToLongOrFail()) ?: illegalArgument("无法找到群员: $this") protected fun Group.findMemberOrFail(idOrCard: String): Member { if (idOrCard == "\$") return members.randomOrNull() ?: illegalArgument("当前语境下无法推断随机群员") idOrCard.toLongOrNull()?.let { get(it) }?.let { return it } this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard) }?.let { return it } this.members.singleOrNull { it.nameCardOrNick.contains(idOrCard, ignoreCase = true) }?.let { return it } val candidates = this.fuzzySearchMember(idOrCard) candidates.singleOrNull()?.let { if (it.second == 1.0) return it.first // single match } if (candidates.isEmpty()) { illegalArgument("无法找到成员 $idOrCard") } else { var index = 1 illegalArgument( "无法找到成员 $idOrCard。 多个成员满足搜索结果或匹配度不足: \n\n" + candidates.joinToString("\n", limit = 6) { val percentage = (it.second * 100).toDecimalPlace(0) "#${index++}(${percentage}%)${it.first.nameCardOrNick.truncate(10)}(${it.first.id})" // #1 15.4% } ) } } protected fun CommandSender.inferBotOrFail(): Bot = (this as? UserCommandSender)?.bot ?: Bot.instancesSequence.singleOrNull() ?: illegalArgument("当前语境下无法推断目标 Bot, 因为目前有多个 Bot 在线.") protected fun CommandSender.inferGroupOrFail(): Group = inferGroup() ?: illegalArgument("当前语境下无法推断目标群") protected fun CommandSender.inferGroup(): Group? = (this as? GroupAwareCommandSender)?.group protected fun CommandSender.inferFriendOrFail(): Friend = (this as? FriendCommandSender)?.user ?: illegalArgument("当前语境下无法推断目标好友") } internal fun Double.toDecimalPlace(n: Int): String = "%.${n}f".format(this) internal fun String.truncate(lengthLimit: Int, replacement: String = "..."): String = buildString { var lengthSum = 0 for (char in this@truncate) { lengthSum += char.chineseLength if (lengthSum > lengthLimit) { append(replacement) return toString() } else append(char) } return toString() } ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/CommandParameter.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.CommandContext import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createOptional import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.UserDefinedType.Companion.createRequired import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isAcceptable import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.resolve.ResolvedCommandValueArgument import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.content import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf /** * Inherited instances must be [CommandValueParameter] or [CommandReceiverParameter] */ @ExperimentalCommandDescriptors public sealed interface CommandParameter<T : Any?> { public val name: String? public val isOptional: Boolean /** * Reified type of [T] */ public val type: KType } @ExperimentalCommandDescriptors public abstract class AbstractCommandParameter<T> : CommandParameter<T> { override fun toString(): String = buildString { append(name) append(": ") append(type.classifierAsKClass().simpleName) append(if (type.isMarkedNullable) "?" else "") } } /** * Inherited instances must be [AbstractCommandValueParameter]. * * ### Implementation details * * [CommandValueParameter] should: * - implement [equals], [hashCode] since used in [ResolvedCommandValueArgument]. * - implement [toString] to produce user-friendly textual representation of this parameter with type info. * */ @ExperimentalCommandDescriptors public interface CommandValueParameter<T : Any?> : CommandParameter<T> { public val isVararg: Boolean /** * Checks whether this [CommandValueParameter] accepts [argument]. * * An [argument] can be accepted if: * - [CommandValueArgument.type] is subtype of, or equals to [CommandValueParameter.type] (nullability considered), or * - [CommandValueArgument.typeVariants] produces * * @return `true` if [argument] may be accepted through any approach mentioned above. * * @see accepting */ public fun accepts(argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?): Boolean = accepting(argument, commandArgumentContext).isAcceptable public fun accepting( argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext? ): ArgumentAcceptance } @ExperimentalCommandDescriptors public sealed class ArgumentAcceptance( /** * Higher means more acceptable */ @ConsoleExperimentalApi public val acceptanceLevel: Int, ) { public object Direct : ArgumentAcceptance(Int.MAX_VALUE) public data class WithTypeConversion( public val typeVariant: TypeVariant<*>, ) : ArgumentAcceptance(20) public data class WithContextualConversion( public val parser: CommandValueArgumentParser<*>, ) : ArgumentAcceptance(10) public data class ResolutionAmbiguity( public val candidates: List<TypeVariant<*>>, ) : ArgumentAcceptance(0) public object Impossible : ArgumentAcceptance(-1) public companion object { @OptIn(ConsoleExperimentalApi::class) @JvmStatic public val ArgumentAcceptance.isAcceptable: Boolean get() = acceptanceLevel > 0 @OptIn(ConsoleExperimentalApi::class) @JvmStatic public val ArgumentAcceptance.isNotAcceptable: Boolean get() = acceptanceLevel <= 0 } } @ExperimentalCommandDescriptors public sealed class CommandReceiverParameter<T> : CommandParameter<T>, AbstractCommandParameter<T>() { /** * @since 2.12 */ @ExperimentalCommandDescriptors public class Sender( override val isOptional: Boolean, override val type: KType, ) : CommandReceiverParameter<CommandSender>() { init { val classifier = type.classifier require(classifier is KClass<*>) { "CommandReceiverParameter.Sender.type.classifier must be KClass." } require(classifier.isSubclassOf(CommandSender::class)) { "CommandReceiverParameter.Sender.type.classifier must be subclass of CommandSender." } } } /** * @since 2.12 */ @ExperimentalCommandDescriptors public class Context( override val isOptional: Boolean, override val type: KType = typeOf<CommandContext>(), ) : CommandReceiverParameter<CommandContext>() { init { val classifier = type.classifier require(classifier is KClass<*>) { "CommandReceiverParameter.Context.type.classifier must be KClass." } require(classifier.isSubclassOf(CommandContext::class)) { "CommandReceiverParameter.Context.type.classifier must be subclass of CommandContext." } } } override val name: String get() = NAME public companion object { public const val NAME: String = "<receiver>" } } internal val ANY_TYPE = typeOf<Any>() internal val ARRAY_OUT_ANY_TYPE = typeOf<Array<out Any?>>() internal val BASE_ARRAY_TYPES = mapOf( typeOf<ByteArray>() to typeOf<Byte>(), typeOf<CharArray>() to typeOf<Char>(), typeOf<ShortArray>() to typeOf<Short>(), typeOf<IntArray>() to typeOf<Int>(), typeOf<LongArray>() to typeOf<Long>(), typeOf<FloatArray>() to typeOf<Float>(), typeOf<DoubleArray>() to typeOf<Double>() ) @ExperimentalCommandDescriptors public sealed class AbstractCommandValueParameter<T> : CommandValueParameter<T>, AbstractCommandParameter<T>() { override fun toString(): String = buildString { if (isVararg) append("vararg ") append(super.toString()) if (isOptional) { append(" = ...") } } public override fun accepting( argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext? ): ArgumentAcceptance { if (isVararg) { // BaseArray or Array<T> val arrayElementType = BASE_ARRAY_TYPES[this.type] ?: this.type.arguments.single().type return acceptingImpl(arrayElementType ?: ANY_TYPE, argument, commandArgumentContext) } return acceptingImpl(this.type, argument, commandArgumentContext) } protected open fun acceptingImpl( expectingType: KType, argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext?, ): ArgumentAcceptance { if (argument.type.isSubtypeOf(expectingType)) return ArgumentAcceptance.Direct argument.typeVariants.associateWith { typeVariant -> if (typeVariant.outType.isSubtypeOf(expectingType)) { // TODO: 2020/10/11 resolution ambiguity return ArgumentAcceptance.WithTypeConversion(typeVariant) } } expectingType.classifierAsKClassOrNull()?.let { commandArgumentContext?.get(it) }?.let { parser -> return ArgumentAcceptance.WithContextualConversion(parser) } return ArgumentAcceptance.Impossible } @ConsoleExperimentalApi public data class StringConstant( @ConsoleExperimentalApi public override val name: String?, public val expectingValue: String, public val ignoreCase: Boolean, ) : AbstractCommandValueParameter<String>() { public override val type: KType get() = STRING_TYPE public override val isOptional: Boolean get() = false public override val isVararg: Boolean get() = false init { require(expectingValue.isNotBlank()) { "expectingValue must not be blank" } require(expectingValue.none(Char::isWhitespace)) { "expectingValue must not contain whitespace" } } override fun toString(): String = "<$expectingValue>" override fun acceptingImpl( expectingType: KType, argument: CommandValueArgument, commandArgumentContext: CommandArgumentContext? ): ArgumentAcceptance { return if (argument.value.content.equals(expectingValue, ignoreCase)) { ArgumentAcceptance.Direct } else ArgumentAcceptance.Impossible } private companion object { val STRING_TYPE = typeOf<String>() } } /** * @see createOptional * @see createRequired */ public data class UserDefinedType<T>( public override val name: String?, public override val isOptional: Boolean, public override val isVararg: Boolean, public override val type: KType, ) : AbstractCommandValueParameter<T>() { override fun toString(): String = super.toString() init { requireNotNull(type.classifierAsKClassOrNull()) { "type.classifier must be KClass." } if (isVararg) check(type.isSubtypeOf(ARRAY_OUT_ANY_TYPE) || type in BASE_ARRAY_TYPES) { "type must be subtype of Array if vararg. Given $type." } } public companion object { @JvmStatic public inline fun <reified T : Any> createOptional(name: String, isVararg: Boolean): UserDefinedType<T> { return UserDefinedType(name, true, isVararg, typeOf<T>()) } @JvmStatic public inline fun <reified T : Any> createRequired(name: String, isVararg: Boolean): UserDefinedType<T> { return UserDefinedType(name, false, isVararg, typeOf<T>()) } } } /** * Extended by [CommandValueArgumentParser] */ @ConsoleExperimentalApi public abstract class Extended<T> : AbstractCommandValueParameter<T>() // For implementer: take care of toString() } ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/CommandSignature.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall import kotlin.reflect.KFunction /** * 指令签名. 表示指令定义的需要的参数. * * @see AbstractCommandSignature */ @ExperimentalCommandDescriptors public interface CommandSignature { /** * 接收者参数, 为 [CommandSender] 子类 */ public val receiverParameter: CommandReceiverParameter<*>? /** * 形式 值参数. */ public val valueParameters: List<AbstractCommandValueParameter<*>> /** * 调用这个指令. */ public suspend fun call(resolvedCommandCall: ResolvedCommandCall) } /** * 来自 [KFunction] 反射得到的 [CommandSignature] * * @see CommandSignatureFromKFunctionImpl */ @ExperimentalCommandDescriptors public interface CommandSignatureFromKFunction : CommandSignature { public val originFunction: KFunction<*> } /** * @see CommandSignatureImpl * @see CommandSignatureFromKFunctionImpl */ @ExperimentalCommandDescriptors public abstract class AbstractCommandSignature : CommandSignature { override fun toString(): String { val receiverParameter = receiverParameter return if (receiverParameter == null) { "CommandSignature(${valueParameters.joinToString()})" } else { "CommandSignature($receiverParameter, ${valueParameters.joinToString()})" } } } @ExperimentalCommandDescriptors public open class CommandSignatureImpl( override val receiverParameter: CommandReceiverParameter<*>?, override val valueParameters: List<AbstractCommandValueParameter<*>>, private val onCall: suspend CommandSignatureImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, ) : CommandSignature, AbstractCommandSignature() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } } @ExperimentalCommandDescriptors public open class CommandSignatureFromKFunctionImpl( override val receiverParameter: CommandReceiverParameter<*>?, override val valueParameters: List<AbstractCommandValueParameter<*>>, override val originFunction: KFunction<*>, private val onCall: suspend CommandSignatureFromKFunctionImpl.(resolvedCommandCall: ResolvedCommandCall) -> Unit, ) : CommandSignatureFromKFunction, AbstractCommandSignature() { override suspend fun call(resolvedCommandCall: ResolvedCommandCall) { return onCall(resolvedCommandCall) } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.Bot import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.map import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.parse import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermitteeId import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** * 指令参数解析器. 用于解析字符串或 [SingleMessage] 到特定参数类型. * * ### 参数解析 * * 如 [SimpleCommand] 中的示例: * ``` * suspend fun CommandSender.mute(target: Member, duration: Int) * ``` * [CommandManager] 总是从 [SimpleCommand.context] 搜索一个 [T] 为 [Member] 的 [CommandValueArgumentParser], 并调用其 [CommandValueArgumentParser.parse] * * ### 内建指令解析器 * - 基础类型: [ByteValueArgumentParser], [ShortValueArgumentParser], [IntValueArgumentParser], [LongValueArgumentParser] * [FloatValueArgumentParser], [DoubleValueArgumentParser], * [BooleanValueArgumentParser], [StringValueArgumentParser] * * - [Bot]: [ExistingBotValueArgumentParser] * - [Friend]: [ExistingFriendValueArgumentParser] * - [Group]: [ExistingGroupValueArgumentParser] * - [Member]: [ExistingMemberValueArgumentParser] * - [User]: [ExistingUserValueArgumentParser] * - [Contact]: [ExistingContactValueArgumentParser] * * - [PermitteeId]: [PermitteeIdValueArgumentParser] * - [PermissionId]: [PermissionIdValueArgumentParser] * * * @see SimpleCommand 简单指令 * @see CompositeCommand 复合指令 * * @see buildCommandArgumentContext 指令参数环境, 即 [CommandValueArgumentParser] 的集合 */ public interface CommandValueArgumentParser<out T : Any> { /** * 解析一个字符串为 [T] 类型参数 * * **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException]. * 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户. * * @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出. * * @see CommandArgumentParserException */ @Throws(CommandArgumentParserException::class) public fun parse(raw: String, sender: CommandSender): T /** * 解析一个消息内容元素为 [T] 类型参数 * * **实现提示**: 在解析时遇到意料之中的问题, 如无法找到目标群员, 可抛出 [CommandArgumentParserException]. * 此异常将会被特殊处理, 不会引发一个错误, 而是作为指令调用成功的情况, 将错误信息发送给用户. * * @throws CommandArgumentParserException 当解析时遇到*意料之中*的问题时抛出. * * @see CommandArgumentParserException */ @Throws(CommandArgumentParserException::class) public fun parse(raw: MessageContent, sender: CommandSender): T = parse(raw.content, sender) public companion object { /** * 解析一个字符串或 [SingleMessage] 为 [T] 类型参数 * * @throws IllegalArgumentException 当 [raw] 既不是 [SingleMessage], 也不是 [String] 时抛出. * * @see CommandValueArgumentParser.parse */ @JvmStatic @Throws(IllegalArgumentException::class) public fun <T : Any> CommandValueArgumentParser<T>.parse(raw: Message, sender: CommandSender): T { return when (raw) { is PlainText -> parse(raw.content, sender) is MessageContent -> parse(raw, sender) else -> throw IllegalArgumentException("Illegal raw argument type: ${raw::class.qualifiedName}") } } /** * 使用原 [this] 解析, 成功后使用 [mapper] 映射为另一个类型. */ @JvmStatic public fun <Original : Any, Result : Any> CommandValueArgumentParser<Original>.map( mapper: MappingCommandValueArgumentParser<Original, Result>.(Original) -> Result, ): CommandValueArgumentParser<Result> = MappingCommandValueArgumentParser(this, mapper) } } /** * @see CommandValueArgumentParser 的基础实现. */ public abstract class AbstractCommandValueArgumentParser<T : Any> : CommandValueArgumentParser<T> { public companion object { /** * 抛出一个 [CommandArgumentParserException] 的捷径 * * @throws CommandArgumentParserException */ @JvmStatic @JvmSynthetic @Throws(CommandArgumentParserException::class) protected inline fun CommandValueArgumentParser<*>.illegalArgument( message: String, cause: Throwable? = null ): Nothing = throw CommandArgumentParserException(message, cause) /** * 检查参数 [condition]. 当它为 `false` 时调用 [message] 并以其返回值作为消息, 抛出异常 [CommandArgumentParserException] * * @throws CommandArgumentParserException */ @JvmStatic @Throws(CommandArgumentParserException::class) @JvmSynthetic protected inline fun CommandValueArgumentParser<*>.checkArgument( condition: Boolean, crossinline message: () -> String = { "Check failed." }, ) { contract { returns() implies condition callsInPlace(message, InvocationKind.AT_MOST_ONCE) } if (!condition) illegalArgument(message()) } } } /** * @see CommandValueArgumentParser.map */ public class MappingCommandValueArgumentParser<T : Any, R : Any>( private val original: CommandValueArgumentParser<T>, private val mapper: MappingCommandValueArgumentParser<T, R>.(T) -> R, ) : AbstractCommandValueArgumentParser<R>() { override fun parse(raw: String, sender: CommandSender): R = mapper(original.parse(raw, sender)) override fun parse(raw: MessageContent, sender: CommandSender): R = mapper(original.parse(raw, sender)) } ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/Exceptions.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate", "unused") package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.IllegalCommandArgumentException import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueArgumentParser.Companion.illegalArgument import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import kotlin.reflect.KType internal val KType.qualifiedName: String get() = this.classifierAsKClassOrNull()?.qualifiedNameOrTip ?: classifier.toString() @ExperimentalCommandDescriptors public open class NoValueArgumentMappingException( public val argument: CommandValueArgument, public val forType: KType, ) : CommandResolutionException("Cannot find a CommandArgument mapping for ${forType.qualifiedName}") public open class CommandResolutionException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } @ExperimentalCommandDescriptors public open class CommandDeclarationClashException( public val command: Command, public val signatures: List<CommandSignature>, ) : CommandDeclarationException("Declaration clash for command '${command.primaryName}': \n${signatures.joinToString("\n")}") public open class CommandDeclarationException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } /** * 在解析参数时遇到的 _正常_ 错误. 如参数不符合规范等. * * [message] 将会发送给指令调用方. * * @see IllegalCommandArgumentException * @see CommandValueArgumentParser * @see AbstractCommandValueArgumentParser.illegalArgument */ public class CommandArgumentParserException @JvmOverloads constructor( message: String, cause: Throwable? = null, ) : IllegalCommandArgumentException(message, cause) ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/ExperimentalCommandDescriptors.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.command.descriptor import kotlin.annotation.AnnotationTarget.* /** * 标记一个实验性的指令解释器 API. * * 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 不建议在发行版本中使用这些 API. * * @since 1.0-RC */ @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @MustBeDocumented @ExperimentalCommandDescriptors public annotation class ExperimentalCommandDescriptors( val message: String = "Command descriptors are an experimental API.", ) ================================================ FILE: mirai-console/backend/mirai-console/src/command/descriptor/TypeVariant.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.command.descriptor import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.internal.data.castOrNull import net.mamoe.mirai.console.internal.data.kClassQualifiedName import net.mamoe.mirai.message.data.* import kotlin.reflect.KType import kotlin.reflect.typeOf /** * Intrinsic variant of an [CommandValueArgument]. * * The *intrinsic* reveals the independent conversion property of this type. * Conversion with [TypeVariant] is out of any contextual resource, * except the [output type][TypeVariant.outType] declared by the [TypeVariant] itself. * * * [TypeVariant] is not necessary for all [CommandValueArgument]s. * * @param OutType the type this [TypeVariant] can map a argument [Message] to . * * @see CommandValueArgument.typeVariants */ @ExperimentalCommandDescriptors public interface TypeVariant<out OutType> { /** * The reified type of [OutType] */ public val outType: KType /** * Maps an [valueArgument] to [outType] * * @see CommandValueArgument.value */ public fun mapValue(valueArgument: Message): OutType public companion object { /** * Creates a [TypeVariant] with reified [OutType]. */ @OptIn(ExperimentalStdlibApi::class) @JvmSynthetic public inline operator fun <reified OutType> invoke(crossinline block: (valueParameter: Message) -> OutType): TypeVariant<OutType> { return object : TypeVariant<OutType> { override val outType: KType = typeOf<OutType>() override fun mapValue(valueArgument: Message): OutType = block(valueArgument) } } } } @ExperimentalCommandDescriptors public object MessageContentTypeVariant : TypeVariant<MessageContent> { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf<MessageContent>() override fun mapValue(valueArgument: Message): MessageContent = valueArgument.castOrNull<MessageContent>() ?: error("Accepts MessageContent only but given ${valueArgument.kClassQualifiedName}") } @ExperimentalCommandDescriptors public object MessageChainTypeVariant : TypeVariant<MessageChain> { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf<MessageChain>() override fun mapValue(valueArgument: Message): MessageChain = valueArgument.toMessageChain() } @ExperimentalCommandDescriptors public object ContentStringTypeVariant : TypeVariant<String> { @OptIn(ExperimentalStdlibApi::class) override val outType: KType = typeOf<String>() override fun mapValue(valueArgument: Message): String = valueArgument.content } ================================================ FILE: mirai-console/backend/mirai-console/src/command/java/JCompositeCommand.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command.java import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.command.descriptor.plus import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.permission.Permission /** * 复合指令. 指令注册时候会通过反射构造指令解析器. * * 示例: * ```java * public final class MyCompositeCommand extends CompositeCommand { * public static final MyCompositeCommand INSTANCE = new MyCompositeCommand(); * * private MyCompositeCommand() { * super(MyPluginMain.INSTANCE, "manage") // "manage" 是主指令名 * } * * // [参数智能解析] * // * // * // 在控制台执行 "/manage <群号>.<群员> <持续时间>", * // 或在聊天群内发送 "/manage <@一个群员> <持续时间>", * // 或在聊天群内发送 "/manage <目标群员的群名> <持续时间>", * // 或在聊天群内发送 "/manage <目标群员的账号> <持续时间>" * // 时调用这个函数 * @SubCommand * public void mute(CommandSender sender, Member target, int duration) { // 通过 /manage mute <target> <duration> 调用. * sender.sendMessage("/manage mute 被调用了, 参数为: " + target + ", " + duration); * * * String result; * try { * result = target.mute(duration).toString(); * } catch(Exception e) { * result = ExceptionsKt.stackTraceToString(e); * } * * sender.sendMessage("结果: " + result); * } * * @SubCommand * public void list(CommandSender sender) { // 执行 "/manage list" 时调用这个方法 * sender.sendMessage("/manage list 被调用了"); * } * * @SubCommand * public void repeat(CommandContext context) { * // 使用 CommandContext 作为参数,可以获得触发指令的原消息链 originalMessage,其中包含 MessageMetadata。 * context.getSender().sendMessage(context.getOriginalMessage()); * } * * // 支持 Image 类型, 需在聊天中执行此指令. * @SubCommand * public void test(CommandSender sender, Image image) { // 执行 "/manage test <一张图片>" 时调用这个方法 * sender.sendMessage("/manage image 被调用了, 图片是 " + image.imageId) * } * } * ``` * * Kotlin 示例查看 [CompositeCommand] * * @see buildCommandArgumentContext */ public abstract class JCompositeCommand @JvmOverloads constructor( @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, parentPermission: Permission = owner.parentPermission, ) : CompositeCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = parentPermission) { /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ public final override var description: String = super.description protected set public final override var permission: Permission = super.permission protected set /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ @ExperimentalCommandDescriptors public final override var prefixOptional: Boolean = false protected set /** * 智能参数解析环境 * @since 2.12 */ public final override var context: CommandArgumentContext = super.context private set /** * 增加智能参数解析环境 * @since 2.12 */ protected fun addArgumentContext(context: CommandArgumentContext) { this.context += context } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/java/JRawCommand.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command.java import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.internal.command.findOrCreateCommandPermission import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.buildMessageChain import net.mamoe.mirai.utils.runBIO /** * 供 Java 用户继承 * * 请在构造时设置相关属性. * * ```java * public final class MyCommand extends JRawCommand { * public static final MyCommand INSTANCE = new MyCommand(); * private MyCommand() { * super(MyPluginMain.INSTANCE, "test"); // 使用插件主类对象作为指令拥有者;设置主指令名为 "test" * // 可选设置如下属性 * setUsage("/test"); // 设置用法,这将会在 /help 中展示 * setDescription("这是一个测试指令"); // 设置描述,也会在 /help 中展示 * setPrefixOptional(true); // 设置指令前缀是可选的,即使用 `test` 也能执行指令而不需要 `/test` * } * * @Override * public void onCommand(@NotNull CommandSender sender, @NotNull MessageChain args) { * // 处理指令 * } * } * ``` * * @see JRawCommand */ public abstract class JRawCommand @JvmOverloads constructor( /** * 指令拥有者. * @see CommandOwner */ @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) public override val owner: CommandOwner, @ResolveContext(COMMAND_NAME) public override val primaryName: String, @ResolveContext(COMMAND_NAME) public override vararg val secondaryNames: String, parentPermission: Permission = owner.parentPermission, ) : Command { /** 用法说明, 用于发送给用户 */ public override var usage: String = "<no usages given>" protected set /** 指令描述, 用于显示在 [BuiltInCommands.HelpCommand] */ public final override var description: String = "<no descriptions given>" protected set /** 指令权限 */ public final override var permission: Permission = findOrCreateCommandPermission(parentPermission) protected set /** 为 `true` 时表示 [指令前缀][CommandManager.commandPrefix] 可选 */ @ExperimentalCommandDescriptors public final override var prefixOptional: Boolean = false protected set @ExperimentalCommandDescriptors override val overloads: List<@JvmWildcard CommandSignature> = listOf( CommandSignatureImpl( receiverParameter = CommandReceiverParameter.Context(false), valueParameters = listOf( AbstractCommandValueParameter.UserDefinedType.createRequired<Array<out Message>>( "args", true ) ) ) { call -> val sender = call.caller val arguments = call.rawValueArguments runBIO { onCommand( CommandContextImpl(sender, call.originalMessage), buildMessageChain { arguments.forEach { +it.value } } ) } } ) /** * 在指令被执行时调用. * * @param args 指令参数. * * @see CommandManager.executeCommand 查看更多信息 * @since 2.8 */ public open fun onCommand(sender: CommandSender, args: MessageChain) {} /** * 在指令被执行时调用. * * @param args 指令参数. * * @see CommandManager.executeCommand 查看更多信息 * @since 2.12 */ public open fun onCommand(context: CommandContext, args: MessageChain) { onCommand(context.sender, args) } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/java/JSimpleCommand.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command.java import net.mamoe.mirai.console.command.CommandContext import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.command.descriptor.CommandArgumentContext import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.plus import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.permission.Permission /** * Java 实现: * ```java * public final class MySimpleCommand extends JSimpleCommand { * public static final MySimpleCommand INSTANCE = new MySimpleCommand(); * private MySimpleCommand() { * super(MyPlugin.INSTANCE, "tell"); * // 可选设置如下属性 * setDescription("这是一个测试指令"); * setUsage("/tell <target> <message>"); // 如不设置则自动根据带有 @Handler 的方法生成 * setPermission(CommandPermission.Operator.INSTANCE); * setPrefixOptional(true); * } * * @Handler * public void onCommand(CommandSender sender, User target, String message) { * target.sendMessage(message); * } * } * ``` * * 其中 `CommandSender` 也可以替换为 `CommandContext`,可通过 [CommandContext.originalMessage] 获得触发指令的原消息链。 * * @see SimpleCommand * @see [CommandManager.executeCommand] */ public abstract class JSimpleCommand @JvmOverloads constructor( @ResolveContext(RESTRICTED_CONSOLE_COMMAND_OWNER) owner: CommandOwner, @ResolveContext(COMMAND_NAME) primaryName: String, @ResolveContext(COMMAND_NAME) vararg secondaryNames: String, basePermission: Permission = owner.parentPermission, ) : SimpleCommand(owner, primaryName, secondaryNames = secondaryNames, parentPermission = basePermission) { public override var description: String = super.description protected set public override var permission: Permission = super.permission protected set @ExperimentalCommandDescriptors public override var prefixOptional: Boolean = super.prefixOptional protected set /** * 指令参数环境. */ public override var context: CommandArgumentContext = super.context protected set /** * 增加智能参数解析环境 * @since 2.12 */ protected fun addArgumentContext(context: CommandArgumentContext) { this.context += context } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/parse/CommandCall.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalStdlibApi::class) package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall import net.mamoe.mirai.message.data.MessageChain /** * Unresolved [CommandCall]. * * ### Implementation details * [CommandCall] should be _immutable_, * meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer. * * @see CommandCallParser * @see CommandCallResolver * * @see ResolvedCommandCall */ @ExperimentalCommandDescriptors public interface CommandCall { /** * The [CommandSender] responsible to this call. */ public val caller: CommandSender /** * One of callee [Command]'s [Command.allNames]. * * Generally [CommandCallResolver] use [calleeName] to find target [Command] registered in [CommandManager] */ public val calleeName: String /** * Explicit value arguments parsed from raw [MessageChain] or implicit ones deduced by the [CommandCallResolver]. */ public val valueArguments: List<CommandValueArgument> /** * Original message * @since 2.12 */ public val originalMessage: MessageChain // maybe add contextual arguments, i.e. from MessageMetadata } @ExperimentalCommandDescriptors public class CommandCallImpl( override val caller: CommandSender, override val calleeName: String, override val valueArguments: List<CommandValueArgument>, override val originalMessage: MessageChain, ) : CommandCall ================================================ FILE: mirai-console/backend/mirai-console/src/command/parse/CommandCallParser.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.command.resolve.ResolvedCommandCall import net.mamoe.mirai.console.extensions.CommandCallParserProvider import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.message.data.MessageChain /** * Lexical and syntactical parser for transforming a [MessageChain] into [CommandCall] * * @see CommandCallResolver The call resolver for [CommandCall] to become [ResolvedCommandCall] * @see CommandCallParserProvider The extension point * * @see SpaceSeparatedCommandCallParser */ @ExperimentalCommandDescriptors public interface CommandCallParser { /** * Lexically and syntactically parse a [message] into [CommandCall], but performs nothing about resolving a call. * * @return `null` if unable to parse (i.e. due to syntax errors). */ public fun parse(caller: CommandSender, message: MessageChain): CommandCall? public companion object { /** * Calls [CommandCallParser]s provided by [CommandCallParserProvider] in [GlobalComponentStorage] sequentially, * returning the first non-null result, `null` otherwise. */ @JvmStatic public fun MessageChain.parseCommandCall(sender: CommandSender): CommandCall? { GlobalComponentStorage.useEachExtensions(CommandCallParserProvider) { provider -> provider.instance.parse(sender, this@parseCommandCall)?.let { return it } } return null } } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/parse/CommandValueArgument.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.internal.data.castOrInternalError import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.SingleMessage import kotlin.reflect.KType import kotlin.reflect.full.isSubtypeOf import kotlin.reflect.typeOf /** * @see CommandValueArgument */ @ExperimentalCommandDescriptors public interface CommandArgument /** * @see DefaultCommandValueArgument */ @ExperimentalCommandDescriptors public interface CommandValueArgument : CommandArgument { public val type: KType /** * [MessageContent] if single argument * [MessageChain] is vararg */ public val value: Message /** * Intrinsic variants of this argument. * * @see TypeVariant */ public val typeVariants: List<TypeVariant<*>> } /** * The [CommandValueArgument] that doesn't vary in type (remaining [MessageContent]). */ @ConsoleExperimentalApi @ExperimentalCommandDescriptors public data class DefaultCommandValueArgument( public override val value: Message, ) : CommandValueArgument { @OptIn(ExperimentalStdlibApi::class) override val type: KType = typeOf<MessageContent>() override val typeVariants: List<TypeVariant<*>> = listOf( MessageContentTypeVariant, MessageChainTypeVariant, ContentStringTypeVariant, ) } @ExperimentalCommandDescriptors public fun <T> CommandValueArgument.mapValue(typeVariant: TypeVariant<T>): T = typeVariant.mapValue(this.value) //@OptIn(ExperimentalStdlibApi::class) //@ExperimentalCommandDescriptors //public inline fun <reified T> CommandValueArgument.mapToType(): T = // mapToTypeOrNull() ?: throw NoValueArgumentMappingException(this, typeOf<T>()) // //@OptIn(ExperimentalStdlibApi::class) //@ExperimentalCommandDescriptors //public fun <T> CommandValueArgument.mapToType(type: KType): T = // mapToTypeOrNull(type) ?: throw NoValueArgumentMappingException(this, type) @ExperimentalCommandDescriptors public fun <T> CommandValueArgument.mapToTypeOrNull(expectingType: KType, context: (KType, Message) -> T?): T? { if (expectingType.isSubtypeOf(ARRAY_OUT_ANY_TYPE) || expectingType in BASE_ARRAY_TYPES) { val arrayElementType = BASE_ARRAY_TYPES[expectingType] ?: expectingType.arguments.single().type ?: ANY_TYPE val result = ArrayList<Any?>() when (val value = value) { is MessageChain -> { for (message in value) { result.add(mapToTypeOrNullImpl(arrayElementType, message) ?: context(arrayElementType, message)) } } else -> { // single value.castOrInternalError<SingleMessage>() result.add(mapToTypeOrNullImpl(arrayElementType, value) ?: context(arrayElementType, value)) } } @Suppress("UNCHECKED_CAST") return when (expectingType) { typeOf<ByteArray>() -> (result as List<Byte>).toByteArray() typeOf<CharArray>() -> (result as List<Char>).toCharArray() typeOf<ShortArray>() -> (result as List<Short>).toShortArray() typeOf<IntArray>() -> (result as List<Int>).toIntArray() typeOf<LongArray>() -> (result as List<Long>).toLongArray() typeOf<FloatArray>() -> (result as List<Float>).toFloatArray() typeOf<DoubleArray>() -> (result as List<Double>).toDoubleArray() else -> result.toArray(arrayElementType.createArray(result.size)) } as T? } @Suppress("UNCHECKED_CAST") return (mapToTypeOrNullImpl(expectingType, value) ?: context(expectingType, value)) as T? } private fun KType.createArray(size: Int): Array<Any?> { return java.lang.reflect.Array.newInstance(this.classifierAsKClass().javaObjectType, size).castOrInternalError() } @OptIn(ExperimentalCommandDescriptors::class) private fun CommandValueArgument.mapToTypeOrNullImpl(expectingType: KType, value: Message): Any? { @OptIn(ExperimentalStdlibApi::class) val result = typeVariants .filter { it.outType.isSubtypeOf(expectingType) } .ifEmpty { return null } .reduce { acc, typeVariant -> if (acc.outType.isSubtypeOf(typeVariant.outType)) acc else typeVariant } @Suppress("UNCHECKED_CAST") return result.mapValue(value) } //@ExperimentalCommandDescriptors //public inline fun <reified T> CommandValueArgument.mapToTypeOrNull(): T? { // @OptIn(ExperimentalStdlibApi::class) // return mapToTypeOrNull(typeOf<T>()) //} ================================================ FILE: mirai-console/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command.parse import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.extensions.CommandCallParserProvider import net.mamoe.mirai.console.internal.command.flattenCommandComponents import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.content @ConsoleExperimentalApi @ExperimentalCommandDescriptors public object SpaceSeparatedCommandCallParser : CommandCallParser { @ConsoleExperimentalApi @ExperimentalCommandDescriptors public object Provider : CommandCallParserProvider { override val instance: CommandCallParser get() = SpaceSeparatedCommandCallParser override val priority: Int get() = -1 } override fun parse(caller: CommandSender, message: MessageChain): CommandCall? { val flatten = message.flattenCommandComponents().filterIsInstance<MessageContent>() if (flatten.isEmpty()) return null return CommandCallImpl( caller = caller, calleeName = flatten.first().content, valueArguments = flatten.drop(1).map(::DefaultCommandValueArgument), originalMessage = message ) } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command.resolve import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.descriptor.ArgumentAcceptance.Companion.isNotAcceptable import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.parse.DefaultCommandValueArgument import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.safeCast import net.mamoe.mirai.message.data.emptyMessageChain import net.mamoe.mirai.message.data.toMessageChain /** * Builtin implementation of [CommandCallResolver] */ @ConsoleExperimentalApi @ExperimentalCommandDescriptors public object BuiltInCommandCallResolver : CommandCallResolver { internal object Provider : CommandCallResolverProvider { override val instance: CommandCallResolver = BuiltInCommandCallResolver override val priority: Int get() = -1 } override fun resolve(call: CommandCall): CommandResolveResult { val callee = CommandManager.matchCommand(call.calleeName) ?: return CommandResolveResult(CommandExecuteResult.UnresolvedCommand(call)) val valueArguments = call.valueArguments val context = callee.safeCast<CommandArgumentContextAware>()?.context val errorSink = ErrorSink() val signature = resolveImpl(call.caller, callee, valueArguments, context, errorSink) ?: kotlin.run { return CommandResolveResult(errorSink.createFailure(call, callee)) } return CommandResolveResult( ResolvedCommandCallImpl( call.caller, callee, signature.signature, signature.zippedArguments.map { it.second }, context ?: EmptyCommandArgumentContext, call.originalMessage, ) ) } private data class ResolveData( val signature: CommandSignature, val zippedArguments: List<Pair<AbstractCommandValueParameter<*>, CommandValueArgument>>, val argumentAcceptances: List<ArgumentAcceptanceWithIndex>, val remainingParameters: List<AbstractCommandValueParameter<*>>, ) { val remainingOptionalCount: Int = remainingParameters.count { it.isOptional } } private data class ArgumentAcceptanceWithIndex( val index: Int, val acceptance: ArgumentAcceptance, ) private class ErrorSink { private val unmatchedCommandSignatures = mutableListOf<UnmatchedCommandSignature>() private val resolutionAmbiguities = mutableListOf<CommandSignature>() fun reportUnmatched(failure: UnmatchedCommandSignature) { unmatchedCommandSignatures.add(failure) } fun reportAmbiguity(resolutionAmbiguity: CommandSignature) { resolutionAmbiguities.add(resolutionAmbiguity) } fun createFailure(call: CommandCall, command: Command): CommandExecuteResult.Failure { val failureReasons = unmatchedCommandSignatures.toMutableList() val rA = FailureReason.ResolutionAmbiguity(resolutionAmbiguities) failureReasons.addAll(resolutionAmbiguities.map { UnmatchedCommandSignature(it, rA) }) return CommandExecuteResult.UnmatchedSignature(command, call, unmatchedCommandSignatures) } } private fun CommandSignature.toResolveData( caller: CommandSender, valueArguments: List<CommandValueArgument>, context: CommandArgumentContext?, errorSink: ErrorSink, ): ResolveData? { val signature = this val receiverParameter = signature.receiverParameter if (receiverParameter != null) { when (receiverParameter) { is CommandReceiverParameter.Context -> { // accepts any sender } is CommandReceiverParameter.Sender -> { if (!receiverParameter.type.classifierAsKClass().isInstance(caller)) { errorSink.reportUnmatched( UnmatchedCommandSignature( signature, FailureReason.InapplicableReceiverArgument(receiverParameter, caller) ) )// not compatible receiver return null } } } } val valueParameters = signature.valueParameters val zipped = valueParameters.zip(valueArguments).toMutableList() val remainingParameters = valueParameters.drop(zipped.size).toMutableList() if (remainingParameters.any { !it.isOptional && !it.isVararg }) { errorSink.reportUnmatched( UnmatchedCommandSignature( signature, FailureReason.NotEnoughArguments ) )// not enough args. // vararg can be empty. return null } return if (zipped.isEmpty()) { ResolveData( signature = signature, zippedArguments = emptyList(), argumentAcceptances = emptyList(), remainingParameters = remainingParameters, ) } else { if (valueArguments.size > valueParameters.size && zipped.last().first.isVararg) { // merge vararg arguments val (varargParameter, _) = zipped.removeLast() zipped.add( varargParameter to DefaultCommandValueArgument( valueArguments.drop(zipped.size).map { it.value }.toMessageChain() ) ) } else { // add default empty vararg argument val remainingVararg = remainingParameters.find { it.isVararg } if (remainingVararg != null) { zipped.add(remainingVararg to DefaultCommandValueArgument(emptyMessageChain())) remainingParameters.remove(remainingVararg) } } ResolveData( signature = signature, zippedArguments = zipped, argumentAcceptances = zipped.mapIndexed { index, (parameter, argument) -> val accepting = parameter.accepting(argument, context) if (accepting.isNotAcceptable) { errorSink.reportUnmatched( UnmatchedCommandSignature( signature, FailureReason.InapplicableValueArgument(parameter, argument) ) )// argument type not assignable return null } ArgumentAcceptanceWithIndex(index, accepting) }, remainingParameters = remainingParameters ) } } private fun resolveImpl( caller: CommandSender, callee: Command, valueArguments: List<CommandValueArgument>, context: CommandArgumentContext?, errorSink: ErrorSink, ): ResolveData? { callee.overloads .mapNotNull l@{ signature -> signature.toResolveData(caller, valueArguments, context, errorSink) } .also { result -> result.takeSingleResolveData()?.let { return it } } .takeLongestMatches() .ifEmpty { return null } .also { result -> result.takeSingleResolveData()?.let { return it } } // take single ArgumentAcceptance.Direct .also { list -> val candidates = list .asSequence() .flatMap { phase -> phase.argumentAcceptances.filter { it.acceptance is ArgumentAcceptance.Direct } .map { phase to it } }.toList() candidates.singleOrNull()?.let { return it.first } // single Direct if (candidates.distinctBy { it.second.index }.size != candidates.size) { // Resolution ambiguity /* open class A open class AA: A() open class C open class CC: C() fun foo(a: A, c: CC) = 1 fun foo(a: AA, c: C) = 1 */ // The call is foo(AA(), C()) or foo(A(), CC()) candidates.forEach { candidate -> errorSink.reportAmbiguity(candidate.first.signature) } } } return null } private fun Collection<Any>.takeSingleResolveData() = asSequence().filterIsInstance<ResolveData>().singleOrNull() /* open class A open class B : A() open class C : A() open class D : C() open class BB : B() fun foo(a: A, c: C) = 1 //fun foo(a: A, c: A) = 1 //fun foo(a: A, c: C, def: Int = 0) = 1 fun foo(a: B, c: C, d: D) = "" fun foo(b: BB, a: A, d: C) = 1.0 fun main() { val a = foo(D(), D()) // int val b = foo(A(), C()) // int val d = foo(BB(), c = C(), D()) // string } */ private fun List<ResolveData>.takeLongestMatches(): Collection<ResolveData> { if (isEmpty()) return emptyList() return associateWith { it.signature.valueParameters.size - it.remainingOptionalCount * 1.001 // slightly lower priority with optional defaults. }.let { m -> val maxMatch = m.values.maxByOrNull { it } m.filter { it.value == maxMatch }.keys } } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt ================================================ /* * 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("unused", "NOTHING_TO_INLINE") package net.mamoe.mirai.console.command.resolve import kotlinx.serialization.Serializable import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.extensions.CommandCallInterceptorProvider import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.util.UNREACHABLE_CLAUSE import net.mamoe.mirai.console.util.safeCast import net.mamoe.mirai.message.data.Message import org.jetbrains.annotations.Contract import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** * 指令解析和调用拦截器. 用于在指令各解析阶段拦截或转换调用. */ @ExperimentalCommandDescriptors public interface CommandCallInterceptor { /** * 在指令[语法解析][CommandCallParser]前调用. * * @return `null` 表示未处理 */ public fun interceptBeforeCall( message: Message, caller: CommandSender, ): InterceptResult<Message>? = null /** * 在指令[语法解析][CommandCallParser]后调用. * * @return `null` 表示未处理 */ public fun interceptCall( call: CommandCall, ): InterceptResult<CommandCall>? = null /** * 在指令[调用解析][CommandCallResolver]后调用. * * @return `null` 表示未处理 */ public fun interceptResolvedCall( call: ResolvedCommandCall, ): InterceptResult<ResolvedCommandCall>? = null public companion object { /** * 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall]. * 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [Message] */ @JvmStatic public fun Message.intercepted(caller: CommandSender): InterceptResult<Message> { return GlobalComponentStorage.foldExtensions(CommandCallInterceptorProvider, this@intercepted) { acc, ext -> val intercepted = ext.instance.interceptBeforeCall(acc, caller) intercepted?.fold( onIntercepted = { return intercepted }, otherwise = { it } ) ?: acc }.let { InterceptResult(it) } } /** * 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall]. * 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [CommandCall] */ @JvmStatic public fun CommandCall.intercepted(): InterceptResult<CommandCall> { return GlobalComponentStorage.foldExtensions(CommandCallInterceptorProvider, this@intercepted) { acc, ext -> val intercepted = ext.instance.interceptCall(acc) intercepted?.fold( onIntercepted = { return intercepted }, otherwise = { it } ) ?: acc }.let { InterceptResult(it) } } /** * 使用 [CommandCallInterceptor] 依次调用 [interceptBeforeCall]. * 在第一个拦截时返回拦截原因, 在所有 [CommandCallInterceptor] 都处理完成后返回结果 [ResolvedCommandCall] */ @JvmStatic public fun ResolvedCommandCall.intercepted(): InterceptResult<ResolvedCommandCall> { return GlobalComponentStorage.foldExtensions(CommandCallInterceptorProvider, this@intercepted) { acc, ext -> val intercepted = ext.instance.interceptResolvedCall(acc) intercepted?.fold( onIntercepted = { return intercepted }, otherwise = { it } ) ?: acc }.let { InterceptResult(it) } } } } /** * [CommandCallInterceptor] 拦截结果 */ @ExperimentalCommandDescriptors public class InterceptResult<T> internal constructor( private val _value: Any?, @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?, ) { /** * 构造一个 [InterceptResult], 以 [value] 继续处理后续指令执行. */ public constructor(value: T) : this(value as Any?, null) /** * 构造一个 [InterceptResult], 以 [原因][reason] 中断指令执行. */ public constructor(reason: InterceptedReason) : this(reason as Any?, null) @get:Contract(pure = true) public val value: T? @Suppress("UNCHECKED_CAST") get() { val value = this._value return if (value is InterceptedReason) null else value as T } @get:Contract(pure = true) public val reason: InterceptedReason? get() = this._value.safeCast() } @ExperimentalCommandDescriptors public inline fun <T, R> InterceptResult<T>.fold( onIntercepted: (reason: InterceptedReason) -> R, otherwise: (call: T) -> R, ): R { contract { callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE) callsInPlace(otherwise, InvocationKind.AT_MOST_ONCE) } value?.let { return otherwise(it) } reason?.let { return onIntercepted(it) } UNREACHABLE_CLAUSE } @ExperimentalCommandDescriptors public inline fun <T : R, R> InterceptResult<T>.getOrElse(onIntercepted: (reason: InterceptedReason) -> R): R { contract { callsInPlace(onIntercepted, InvocationKind.AT_MOST_ONCE) } reason?.let(onIntercepted) return value!! } /** * 创建一个 [InterceptedReason] * * @see InterceptedReason.create */ @ExperimentalCommandDescriptors @JvmSynthetic public inline fun InterceptedReason(message: String): InterceptedReason = InterceptedReason.create(message) /** * 拦截原因 */ @ExperimentalCommandDescriptors public interface InterceptedReason { public val message: String public companion object { /** * 创建一个 [InterceptedReason] */ public fun create(message: String): InterceptedReason = InterceptedReasonData(message) } } @OptIn(ExperimentalCommandDescriptors::class) @Serializable private data class InterceptedReasonData(override val message: String) : InterceptedReason ================================================ FILE: mirai-console/backend/mirai-console/src/command/resolve/CommandCallResolver.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command.resolve import net.mamoe.mirai.console.command.CommandExecuteResult import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.safeCast import org.jetbrains.annotations.Contract import kotlin.contracts.InvocationKind import kotlin.contracts.contract @ExperimentalCommandDescriptors public class CommandResolveResult private constructor( internal val value: Any?, ) { @get:Contract(pure = true) public val call: ResolvedCommandCall? get() = value.safeCast() @get:Contract(pure = true) public val failure: CommandExecuteResult.Failure? get() = value.safeCast() public constructor(call: ResolvedCommandCall) : this(call as Any?) public constructor(failure: CommandExecuteResult.Failure) : this(failure as Any) } @ExperimentalCommandDescriptors public inline fun <R> CommandResolveResult.fold( onSuccess: (ResolvedCommandCall?) -> R, onFailure: (CommandExecuteResult.Failure) -> R, ): R { contract { callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE) callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) } failure?.let(onFailure)?.let { return it } return call.let(onSuccess) } @ExperimentalCommandDescriptors public inline fun CommandResolveResult.getOrElse( onFailure: (CommandExecuteResult.Failure) -> ResolvedCommandCall?, ): ResolvedCommandCall { contract { callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) } failure?.let(onFailure)?.let { return it } return call!! } /** * The resolver converting a [CommandCall] into [ResolvedCommandCall] based on registered [] * * @see CommandCallResolverProvider The provider to instances of this class * @see BuiltInCommandCallResolver The builtin implementation */ @ExperimentalCommandDescriptors public interface CommandCallResolver { public fun resolve(call: CommandCall): CommandResolveResult public companion object { @JvmName("resolveCall") @ConsoleExperimentalApi @ExperimentalCommandDescriptors public fun CommandCall.resolve(): CommandResolveResult { return GlobalComponentStorage.getExtensions(CommandCallResolverProvider).first() .extension.instance .resolve(this@resolve) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.command.resolve import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.CompositeCommand import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser.Companion.parse import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.command.parse.mapToTypeOrNull import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.cast import net.mamoe.mirai.message.data.MessageChain /** * The resolved [CommandCall]. * * ### Implementation details * [ResolvedCommandCall] should be _immutable_, * meaning all of its properties must be *pure* and should be implemented as an immutable property, or delegated by a lazy initializer. * * @see ResolvedCommandCallImpl */ @ExperimentalCommandDescriptors public interface ResolvedCommandCall { /** * The [CommandSender] responsible to this call. */ public val caller: CommandSender /** * The callee [Command] */ public val callee: Command /** * The callee [CommandSignature], specifically a sub command from [CompositeCommand] */ public val calleeSignature: CommandSignature /** * Original arguments */ public val rawValueArguments: List<CommandValueArgument> /** * Resolved value arguments arranged mapping the [CommandSignature.valueParameters] by index. * * **Default implementation details**: Lazy calculation. */ @ConsoleExperimentalApi public val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> /** * @since 2.12 */ public val originalMessage: MessageChain public companion object } /** * Resolved [CommandValueParameter] for [ResolvedCommandCall.resolvedValueArguments] */ @ExperimentalCommandDescriptors public data class ResolvedCommandValueArgument<T>( val parameter: CommandValueParameter<T>, /** * Argument value expected by the [parameter] */ val value: T, ) // Don't move into companion, compilation error /** * Invoke this resolved call. */ @ExperimentalCommandDescriptors public suspend inline fun ResolvedCommandCall.call() { return this@call.calleeSignature.call(this@call) } /** * Default implementation. */ @ExperimentalCommandDescriptors public class ResolvedCommandCallImpl( override val caller: CommandSender, override val callee: Command, override val calleeSignature: CommandSignature, override val rawValueArguments: List<CommandValueArgument>, private val context: CommandArgumentContext, override val originalMessage: MessageChain, ) : ResolvedCommandCall { @ConsoleExperimentalApi override val resolvedValueArguments: List<ResolvedCommandValueArgument<*>> by lazy { calleeSignature.valueParameters.zip(rawValueArguments).map { (parameter, argument) -> val value = argument.mapToTypeOrNull(parameter.type) { type, message -> context[type.classifierAsKClass()]?.parse(message, caller) } ?: throw NoValueArgumentMappingException(argument, parameter.type) ResolvedCommandValueArgument(parameter.cast(), value) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/data/AbstractPluginData.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import net.mamoe.mirai.console.internal.data.PluginDataImpl import net.mamoe.mirai.console.internal.data.getAnnotationListForValueSerialization import net.mamoe.mirai.console.internal.data.valueName import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.reflect.KProperty /** * [PluginData] 的默认实现. 支持使用 `by value()` 等委托方法创建 [Value] 并跟踪其改动. * * ### 实现注意 * 此类型处于实验性阶段. 使用其中定义的属性和函数是安全的, 但将来可能会新增成员抽象函数. * * @see PluginData */ public abstract class AbstractPluginData : PluginData, PluginDataImpl() { /** * 这个 [PluginData] 保存时使用的名称. */ @ConsoleExperimentalApi public abstract override val saveName: String /** * 添加了追踪的 [ValueNode] 列表, 即通过 `by value` 初始化的属性列表. * * 它们的修改会被跟踪, 并触发 [onValueChanged]. * * @see provideDelegate */ @ConsoleExperimentalApi public val valueNodes: MutableList<ValueNode<*>> = mutableListOf() /** * 供手动实现时值跟踪使用 (如 Java 用户). 一般 Kotlin 用户需使用 [provideDelegate] */ @ConsoleExperimentalApi public open fun <T : SerializerAwareValue<*>> track( value: T, /** * 值名称. * * 如果属性带有 [ValueName], 则使用 [ValueName.value], * 否则使用 [属性名称][KProperty.name] * * @see [ValueNode.value] */ valueName: String, annotations: List<Annotation>, ): T = value.apply { this@AbstractPluginData.valueNodes.add(ValueNode(valueName, this, annotations, this.serializer)) } /** * 使用 `by value()` 时自动调用此方法, 添加对 [Value] 的值修改的跟踪, 并创建 [ValueNode] 加入 [valueNodes] */ @OptIn(ConsoleExperimentalApi::class) public operator fun <T : SerializerAwareValue<*>> T.provideDelegate( thisRef: Any?, property: KProperty<*>, ): T = track(this, property.valueName, property.getAnnotationListForValueSerialization()) /** * 所有 [valueNodes] 更新和保存序列化器. */ @ConsoleExperimentalApi public final override val updaterSerializer: KSerializer<Unit> get() = super.updaterSerializer public override val serializersModule: SerializersModule get() = EmptySerializersModule() /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. */ @ConsoleExperimentalApi public override fun onValueChanged(value: Value<*>) { // no-op by default } /** * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 */ @ConsoleExperimentalApi public override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) { // no-op by default } /** * 由 [track] 创建, 来自一个通过 `by value` 初始化的属性节点. */ @ConsoleExperimentalApi public data class ValueNode<T>( /** * 节点名称. * * 如果属性带有 [ValueName], 则使用 [ValueName.value], * 否则使用 [属性名称][KProperty.name] */ val valueName: String, /** * 属性值代理 */ val value: Value<out T>, /** * 注解列表 */ val annotations: List<Annotation>, /** * 属性值更新器 */ val updaterSerializer: KSerializer<Unit>, ) } /** * 获取这个 [KProperty] 委托的 [Value] * * 示例: * ``` * object MyData : AutoSavePluginData(PluginMain) { * val list: List<String> by value() * } * * val value: Value<List<String>> = MyData.findBackingFieldValue(MyData::list) * ``` * * @see PluginData */ @ConsoleExperimentalApi public fun <T> AbstractPluginData.findBackingFieldValue(property: KProperty<T>): Value<out T>? = findBackingFieldValue(property.valueName) /** * 获取这个 [KProperty] 委托的 [Value] * * 示例: * ``` * object MyData : AutoSavePluginData(PluginMain) { * @ValueName("theList") * val list: List<String> by value() * val int: Int by value() * } * * val value: Value<List<String>> = MyData.findBackingFieldValue("theList") // 需使用 @ValueName 标注的名称 * val intValue: Value<Int> = MyData.findBackingFieldValue("int") * ``` * * @see PluginData */ @ConsoleExperimentalApi public fun <T> AbstractPluginData.findBackingFieldValue(propertyValueName: String): Value<out T>? { @Suppress("UNCHECKED_CAST") return this.valueNodes.find { it.valueName == propertyValueName }?.value as Value<out T>? } /** * 获取这个 [KProperty] 委托的 [Value] * * 示例: * ``` * object MyData : AutoSavePluginData(PluginMain) { * val list: List<String> by value() * } * * val value: PluginData.ValueNode<List<String>> = MyData.findBackingFieldValueNode(MyData::list) * ``` * * @see PluginData */ @ConsoleExperimentalApi public fun <T> AbstractPluginData.findBackingFieldValueNode(property: KProperty<T>): AbstractPluginData.ValueNode<out T>? { @Suppress("UNCHECKED_CAST") return this.valueNodes.find { it.valueName == property.name } as AbstractPluginData.ValueNode<out T>? } ================================================ FILE: mirai-console/backend/mirai-console/src/data/AutoSavePluginConfig.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.data import kotlinx.coroutines.Job /** * 链接自动保存的 [PluginConfig]. * * 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存 * * 若 [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], 则 [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. * * @see PluginConfig * @see AutoSavePluginData */ public open class AutoSavePluginConfig public constructor(saveName: String) : AutoSavePluginData(saveName), PluginConfig ================================================ FILE: mirai-console/backend/mirai-console/src/data/AutoSavePluginData.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "PropertyName", "PrivatePropertyName") package net.mamoe.mirai.console.data import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.TimedTask import net.mamoe.mirai.console.util.launchTimedTask import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.error import net.mamoe.mirai.utils.withSwitch /** * 链接自动保存的 [PluginData]. * * 当任一相关 [Value] 的值被修改时, 将在一段时间无其他修改时保存 * * 若 [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], 则 [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. * * @see PluginData */ public open class AutoSavePluginData private constructor( // KEEP THIS PRIMARY CONSTRUCTOR FOR FUTURE USE: WE'LL SUPPORT SERIALIZERS_MODULE FOR POLYMORPHISM @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?, ) : AbstractPluginData() { @OptIn(ConsoleExperimentalApi::class) private lateinit var owner_: AutoSavePluginDataHolder @OptIn(ConsoleExperimentalApi::class) private val autoSaveIntervalMillis_: LongRange get() = owner_.autoSaveIntervalMillis @OptIn(ConsoleExperimentalApi::class) private lateinit var storage_: PluginDataStorage @ConsoleExperimentalApi public final override val saveName: String get() = _saveName // bug private lateinit var _saveName: String public constructor(saveName: String) : this(null) { _saveName = saveName } @OptIn(ConsoleInternalApi::class, ConsoleExperimentalApi::class) private fun logException(e: Throwable) { owner_.coroutineContext[CoroutineExceptionHandler]?.handleException(owner_.coroutineContext, e) ?.let { return } MiraiConsole.mainLogger.error( "An exception occurred when saving config ${this@AutoSavePluginData::class.qualifiedNameOrTip} " + "but CoroutineExceptionHandler not found in PluginDataHolder.coroutineContext for ${owner_::class.qualifiedNameOrTip}", e ) } @ConsoleExperimentalApi override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) { check(owner is AutoSavePluginDataHolder) { "owner must be AutoSavePluginDataHolder for AutoSavePluginData" } if (this::storage_.isInitialized) { check(storage == this.storage_) { "AutoSavePluginData is already initialized with one storage and cannot be reinitialized with another." } } this.storage_ = storage this.owner_ = owner owner_.coroutineContext[Job]?.invokeOnCompletion { save() } saverTask = owner_.launchTimedTask( intervalMillis = autoSaveIntervalMillis_.first, coroutineContext = CoroutineName("AutoSavePluginData.saver: ${this::class.qualifiedNameOrTip}") ) { save() } if (shouldPerformAutoSaveWheneverChanged()) { // 定时自动保存, 用于 kts 序列化的对象 owner_.launch(CoroutineName("AutoSavePluginData.timedAutoSave: ${this::class.qualifiedNameOrTip}")) { while (isActive) { runIgnoreException<CancellationException> { delay(autoSaveIntervalMillis_.last) } ?: return@launch doSave() } } } } private var saverTask: TimedTask? = null /** * @return `true` 时, 一段时间后, 即使无属性改变, 也会进行保存. */ @ConsoleExperimentalApi protected open fun shouldPerformAutoSaveWheneverChanged(): Boolean { return true } @ConsoleExperimentalApi public final override fun onValueChanged(value: Value<*>) { debuggingLogger1.error { "onValueChanged: $value" } saverTask?.setChanged() } private fun save() { kotlin.runCatching { doSave() }.onFailure { e -> logException(e) } } @OptIn(ConsoleExperimentalApi::class) private fun doSave() { debuggingLogger1.error { "doSave: ${this::class.qualifiedName}" } storage_.store(owner_, this) } } internal val debuggingLogger1 by lazy { MiraiLogger.Factory.create(AutoSavePluginData::class, "console.debug").withSwitch(false) } @Suppress("RESULT_CLASS_IN_RETURN_TYPE") internal inline fun <R> MiraiLogger.runCatchingLog(message: String? = null, block: () -> R): Result<R> { return kotlin.runCatching { block() }.onFailure { if (message != null) { error(message, it) } else error(it) } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution internal inline fun <R> MiraiLogger.runCatchingLog(message: (Throwable) -> String, block: () -> R): R? { return kotlin.runCatching { block() }.onFailure { error(message(it), it) }.getOrNull() } @Suppress("SpellCheckingInspection") private const val MAGIC_NUMBER_CFST_INIT: Long = Long.MAX_VALUE ================================================ FILE: mirai-console/backend/mirai-console/src/data/AutoSavePluginDataHolder.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.data import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 可以持有相关 [AutoSavePluginData] 的对象. * * ### 实现 [AutoSavePluginDataHolder] * [CoroutineScope.coroutineContext] 中应用 [CoroutineExceptionHandler] * * @see net.mamoe.mirai.console.plugin.jvm.JvmPlugin */ @ConsoleExperimentalApi public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope { /** * [AutoSavePluginData] 每次自动保存时间间隔 * * - 区间的左端点为最小间隔, 一个 [Value] 被修改后, 若此时间段后无其他修改, 将触发自动保存; 若有, 将重新开始计时. * - 区间的右端点为最大间隔, 一个 [Value] 被修改后, 最多不超过这个时间段后就会被保存. * * 若 [AutoSavePluginDataHolder.coroutineContext] 含有 [Job], * 则 [AutoSavePluginData] 会通过 [Job.invokeOnCompletion] 在 Job 完结时触发自动保存. * * @see LongRange Java 用户使用 [LongRange] 的构造器创建 * @see Long.rangeTo Kotlin 用户使用 [Long.rangeTo] 创建, 如 `3000..50000` */ @ConsoleExperimentalApi public val autoSaveIntervalMillis: LongRange } ================================================ FILE: mirai-console/backend/mirai-console/src/data/PluginConfig.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.data import net.mamoe.mirai.console.data.java.JavaAutoSavePluginConfig /** * 一个插件的配置数据, 用于和用户交互. * * 用户可通过 mirai-console 前端 (如在 Android 中可视化实现) 修改这些配置, 修改会自动写入这个对象中. * * **提示**: * 插件内部的数据应用 [PluginData] 存储, 而不能使用 [PluginConfig]. * * ## 实现 * 对使用者来说, [PluginConfig] 与 [PluginData] 实现几乎相同. 目前仅需在 [PluginData] 使用的基础上添加接口实现即可. * * ### Kotlin * 在 [PluginData] 的示例基础上, 修改对象定义 * ``` * // 原 * object MyPluginData : AutoSavePluginData() * // 修改为 * object MyPluginConfig : AutoSavePluginConfig() * ``` * 即可将一个 [PluginData] 变更为 [PluginConfig]. * * ### Java * 见 [JavaAutoSavePluginConfig] * * @see PluginData */ public interface PluginConfig : PluginData { /** * 警告: [PluginConfig] 的实现处于实验性阶段. * * 自主实现 [PluginConfig] 将得不到兼容性保障. 请仅考虑使用 [AutoSavePluginConfig] */ } ================================================ FILE: mirai-console/backend/mirai-console/src/data/PluginData.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress( "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", ) @file:JvmName("PluginDataKt") package net.mamoe.mirai.console.data import kotlinx.serialization.KSerializer import kotlinx.serialization.modules.SerializersModule import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_NO_ARG_CONSTRUCTOR import net.mamoe.mirai.console.data.java.JavaAutoSavePluginData import net.mamoe.mirai.console.internal.data.createInstanceSmart import net.mamoe.mirai.console.internal.data.valueFromKTypeImpl import net.mamoe.mirai.console.internal.data.valueImpl import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.reloadPluginData import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.typeOf /** * 一个插件内部的, 对用户隐藏的数据对象. 可包含对多个 [Value] 的值变更的跟踪. 典型的实现为 [AbstractPluginData]. * * [AbstractPluginData] 不涉及有关数据的存储, 而是只维护数据结构: [属性节点列表][AbstractPluginData.valueNodes]. * * 有关存储方案, 请查看 [PluginDataStorage]. * * **注意**: [PluginData] 总应该是单例的. * * ## [JvmPlugin] 的实现方案 * * 要修改保存时的名称, 请参考 [ValueName] * * ### 使用 Kotlin * * 在 [JvmPlugin] 的典型实现方式: * ``` * object PluginMain : KotlinPlugin() * * object MyPluginData : AutoSavePluginData() { * var list: MutableList<String> by value(mutableListOf("a", "b")) // mutableListOf("a", "b") 是初始值, 可以省略 * val custom: Map<Long, CustomData> by value() // 使用 kotlinx-serialization 序列化的类型. * var long: Long by value(0) // 允许 var * var int by value(0) // 可以使用类型推断, 但更推荐使用 `var long: Long by value(0)` 这种定义方式. * * // 将 MutableMap<Long, Long> 映射到 MutableMap<Bot, Long>. * val botToLongMap: MutableMap<Bot, Long> by value<MutableMap<Long, Long>>().mapKeys(Bot::getInstance, Bot::id) * } * * @Serializable // kotlinx.serialization: https://github.com/Kotlin/kotlinx.serialization * data class CustomData( * // ... * ) * ``` * * 使用时, 可以方便地直接调用, 如: * ``` * val theList: MutableList<String> = AccountPluginData.list * ``` * * 但也注意, 不要存储 `AccountPluginData.list`. 它可能受不到值跟踪. 若必要存储, 请使用 [AbstractPluginData.findBackingFieldValue] * * ### 使用 Java * * 参考 [JavaAutoSavePluginData] * * ## 非引用赋值 * * 由于实现特殊, 赋值时不会写其引用. 即: * ``` * val list = ArrayList<String>("A") * MyPluginData.list = list // 赋值给 PluginData 的委托属性是非引用的 * println(MyPluginData.list) // "[A]" * * list.add("B") * println(list) // "[A, B]" * println(MyPluginData.list) // "[A]" // !! 由于 `list` 的引用并未赋值给 `MyPluginData.list`. * ``` * * 另一个更容易出错的示例: * ``` * // MyPluginData.nestedMap: MutableMap<Long, List<Long>> by value() * val newList = MyPluginData.map.getOrPut(1, ::mutableListOf) * newList.add(1) // 不会添加到 MyPluginData.nestedMap 中, 因为 `mutableListOf` 创建的 MutableList 被非引用 (浅拷贝) 地添加进了 MyPluginData.nestedMap * ``` * * 一个解决方案是对 [SerializerAwareValue] 做映射或相关修改. 如 [PluginDataExtensions]. * * 要查看详细的解释,请查看 [docs/PluginData.md](/mirai-console/docs/PluginData.md) * * ## 实现注意 * 此类型处于实验性阶段. 使用其中定义的属性和函数是安全的, 但将来可能会新增成员抽象函数. * * 继承 [AbstractPluginData] 比继承 [PluginData] 更安全, 尽管 [AbstractPluginData] 也不稳定. * * @see AbstractJvmPlugin.reloadPluginData 通过 [JvmPlugin] 获取指定 [PluginData] 实例. * @see PluginDataStorage [PluginData] 存储仓库 * @see PluginDataExtensions 相关 [SerializerAwareValue] 映射函数 */ @NotStableForInheritance public interface PluginData { /** * 这个 [PluginData] 保存时使用的名称. */ @ConsoleExperimentalApi public val saveName: String /** * [PluginData] 序列化时使用的格式的枚举. */ @ConsoleExperimentalApi public enum class SaveType(@ConsoleExperimentalApi public val extension: String) { YAML("yml"), JSON("json") } /** * 决定这个 [PluginData] 序列化时使用的格式, 默认为 YAML. * 具体实现格式由 [PluginDataStorage] 决定. */ @ConsoleExperimentalApi public val saveType: SaveType get() = SaveType.YAML @ConsoleExperimentalApi public val updaterSerializer: KSerializer<Unit> /** * 当所属于这个 [PluginData] 的 [Value] 的 [值][Value.value] 被修改时被调用. * 调用者为 [Value] 的实现. */ @ConsoleExperimentalApi public fun onValueChanged(value: Value<*>) /** * 序列化本对象数据时使用的 [SerializersModule]. 用于支持多态序列化等. * 在序列化时会先使用 [PluginData.serializersModule], 再对无法找到 serializer 的类型使用 [MessageSerializers.serializersModule]. * * ### 使用示例 * * 假设你编写了一个类型 `ChatHistory` 用来存储一个群的消息记录: * ``` * data class ChatHistory( * val groupId: Long, * val chain: List<MessageChain>, * ) * ``` * * 要在 [PluginData] 中支持它, 需要首先为 `ChatHistory` 编写 [KSerializer]. * * 一种方式是为其添加 [kotlinx.serialization.Serializable]: * * ``` * @Serializable * data class ChatHistory( * val groupId: Long, * val chain: List<MessageChain>, * ) * ``` * * 编译器将会自动生成一个 [KSerializer], 可通过 `ChatHistory.Companion.serializer()` 获取. * * 然后在 [PluginData] 定义中添加该 [KSerializer]: * ``` * object MyData : AutoSavePluginData("save") { * // 注意, serializersModule 需要早于其他属性定义初始化 * override val serializersModule = SerializersModule { * contextual(ChatHistory::class, ChatHistory.serializers()) // 为 ChatHistory 指定 KSerializer * } * * val histories: Map<Long, ChatHistory> by value() * } * ``` * * 然而, 即使不覆盖 `serializersModule` 提供 [KSerializer], mirai 也会通过反射尝试获取. * * 但对于不是使用 `@Serializable` 注解方式, 或者是 `interface`, `abstract class` 等的抽象类型, 则必须覆盖 `serializersModule` 并提供其 [KSerializer]. * * * * @see SerializersModule * * @since 2.11 */ public val serializersModule: SerializersModule // 该属性在 2.0 增加, 但在 2.11 才正式支持并删除 @MiraiExperimentalApi /** * 当这个 [PluginData] 被放入一个 [PluginDataStorage] 时调用 */ @ConsoleExperimentalApi public fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) } // don't default = 0, cause ambiguity //// region PluginData_value_primitives CODEGEN //// /** * 创建一个 [Byte] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default) /** * 创建一个 [Short] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Short): SerializerAwareValue<Short> = valueImpl(default) /** * 创建一个 [Int] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Int): SerializerAwareValue<Int> = valueImpl(default) /** * 创建一个 [Long] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Long): SerializerAwareValue<Long> = valueImpl(default) /** * 创建一个 [Float] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Float): SerializerAwareValue<Float> = valueImpl(default) /** * 创建一个 [Double] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Double): SerializerAwareValue<Double> = valueImpl(default) /** * 创建一个 [Char] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Char): SerializerAwareValue<Char> = valueImpl(default) /** * 创建一个 [Boolean] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: Boolean): SerializerAwareValue<Boolean> = valueImpl(default) /** * 创建一个 [String] 类型的 [Value], 并设置初始值为 [default] */ public fun PluginData.value(default: String): SerializerAwareValue<String> = valueImpl(default) //// endregion PluginData_value_primitives CODEGEN //// /** * 通过具体化类型创建一个 [SerializerAwareValue], 并设置初始值. * * 2.11 起, 本函数会优先根据返回值推断类型. 如下示例: * ``` * var singleMessage: SingleMessage by value(PlainText("str")) // value 的类型为 SerializerAwareValue<SingleMessage> * ``` * 这符合正常的类型定义逻辑. * * @param T 具体化参数类型 T. 在 2.11 以前, 支持: * - 基础数据类型 * - 标准库集合类型 ([List], [Map], [Set]) * - 标准库数据类型 ([Map.Entry], [Pair], [Triple]) * - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的, 可以通过反射获取 [KSerializer] 的类型 * * 2.11 起, 还支持: * - [MessageSerializers] 支持的所有类型, 如 [MessageChain]. * - 在 [PluginData.serializersModule] 自定义支持的类型 */ @OptIn(ConsoleExperimentalApi::class) @LowPriorityInOverloadResolution public inline fun <reified T> PluginData.value( default: T, crossinline apply: T.() -> Unit = {}, ): SerializerAwareValue<@kotlin.internal.Exact T> { /* * 使用 `@Exact` 的 trick (自 2.11.0-RC) * * 使用前: * * ``` * var singleMessage: SingleMessage by value(PlainText("str")) * ``` * * `value` 的 reified [T] 根据其参数推断为 [PlainText], 则会使用 `PlainText.serializer()`. * * 可以通过序列化后的 YAML 文本直观地感受问题: * ```yaml * singleMessage: * content: str * ``` * 那么将来若 `singleMessage` 的值变更为非 [PlainText] 类型, 将会无法序列化. * * 附使用 `@Exact` 时的正确结果 (使用 [SingleMessage] 的序列化器 (来自 [MessageSerializers.serializersModule])): * * ```yaml * singleMessage: * type: PlainText * value: * content: str * ``` * * 相关测试: [net.mamoe.mirai.console.data.PluginDataTest.supports message chain] */ return valueFromKType(typeOf<T>(), default).also { it.value.apply() } } /** * 通过具体化类型创建一个 [SerializerAwareValue]. */ @ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) @LowPriorityInOverloadResolution public inline fun <@ResolveContext(RESTRICTED_NO_ARG_CONSTRUCTOR) reified T> PluginData.value(apply: T.() -> Unit = {}): SerializerAwareValue<@kotlin.internal.Exact T> = valueImpl<T>(typeOf<T>(), T::class).also { it.value.apply() } @OptIn(ConsoleExperimentalApi::class) @Suppress("UNCHECKED_CAST") @PublishedApi internal fun <T> PluginData.valueImpl(type: KType, classifier: KClass<*>): SerializerAwareValue<T> = valueFromKType(type, classifier.run { objectInstance ?: createInstanceSmart() } as T) /** * 通过一个特定的 [KType] 创建 [Value], 并设置初始值. * * 对于 [Map], [Set], [List] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap], [LinkedHashSet], [ArrayList] 等相关类型. * 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用*无参构造器*构造实例. * * @param T 具体化参数类型 T. 仅支持: * - 基础数据类型, [String] * - 标准库集合类型 ([List], [Map], [Set]) * - 标准库数据类型 ([Map.Entry], [Pair], [Triple]) * - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的类 */ @Suppress("UNCHECKED_CAST") @ConsoleExperimentalApi public fun <T> PluginData.valueFromKType(type: KType, default: T): SerializerAwareValue<T> = (valueFromKTypeImpl(type) as SerializerAwareValue<Any?>).apply { this.value = default } as SerializerAwareValue<T> ================================================ FILE: mirai-console/backend/mirai-console/src/data/PluginDataExtensions.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package net.mamoe.mirai.console.data import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault import net.mamoe.mirai.console.internal.data.ShadowMap import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.internal.LowPriorityInOverloadResolution /** * [PluginData] 相关一些扩展 */ public object PluginDataExtensions { @ConsoleExperimentalApi public open class NotNullMap<K, V> internal constructor( private val delegate: Map<K, V> ) : Map<K, V> by delegate { override fun get(key: K): V = delegate[key] ?: error("Internal error: delegate[key] returned null for NotNullMap.get") @Deprecated( "getOrDefault on NotNullMap always returns the value in the map, and defaultValue will never be returned.", level = DeprecationLevel.WARNING, replaceWith = ReplaceWith("this.get(key)") ) // diagnostic deprecation override fun getOrDefault(key: K, defaultValue: V): V { return super.getOrDefault(key, defaultValue) } } @OptIn(ConsoleExperimentalApi::class) @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") // as designed public class NotNullMutableMap<K, V> internal constructor( private val delegate: MutableMap<K, V> ) : MutableMap<K, V> by delegate, NotNullMap<K, V>(delegate) { override fun get(key: K): V = delegate[key] ?: error("Internal error: delegate[key] returned null for NotNullMutableMap.get") @Deprecated( "getOrDefault on NotNullMutableMap always returns the value in the map, and defaultValue will never be returned.", level = DeprecationLevel.WARNING, replaceWith = ReplaceWith("this.get(key)") ) // diagnostic deprecation override fun getOrDefault(key: K, defaultValue: V): V { return super<MutableMap>.getOrDefault(key, defaultValue) } @Deprecated( "putIfAbsent on NotNullMutableMap always does nothing.", level = DeprecationLevel.WARNING, replaceWith = ReplaceWith("") ) // diagnostic deprecation override fun putIfAbsent(key: K, value: V): Nothing? = null } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [LinkedHashMap], 再从 [this] 中取出链接自动保存的 [LinkedHashMap]. ([MutableMap.getOrPut] 的替代) * * @see withDefault */ @JvmName("withEmptyDefaultMapImmutable") @JvmStatic public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, Map<InnerE, InnerV>>> { return this.withDefault { LinkedHashMap() } } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [LinkedHashMap], 再从 [this] 中取出链接自动保存的 [LinkedHashMap]. ([MutableMap.getOrPut] 的替代) * @see withDefault */ @JvmName("withEmptyDefaultMap") @JvmStatic public fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, MutableMap<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableMap<InnerE, InnerV>>> { return this.withDefault { LinkedHashMap() } } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [ArrayList], 再从 [this] 中取出链接自动保存的 [ArrayList]. * @see withDefault */ @JvmName("withEmptyDefaultListImmutable") @JvmStatic public fun <K, E> SerializerAwareValue<MutableMap<K, List<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, List<E>>> { return this.withDefault { ArrayList() } } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [ArrayList], 再从 [this] 中取出链接自动保存的 [ArrayList]. * @see withDefault */ @JvmName("withEmptyDefaultList") @JvmStatic public fun <K, E> SerializerAwareValue<MutableMap<K, MutableList<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableList<E>>> { return this.withDefault { ArrayList() } } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [LinkedHashSet], 再从 [this] 中取出链接自动保存的 [LinkedHashSet]. * @see withDefault */ @JvmName("withEmptyDefaultSetImmutable") @JvmStatic public fun <K, E> SerializerAwareValue<MutableMap<K, Set<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, Set<E>>> { return this.withDefault { LinkedHashSet() } } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先放入一个 [LinkedHashSet], 再从 [this] 中取出链接自动保存的 [LinkedHashSet]. * @see withDefault */ @JvmName("withEmptyDefaultSet") @JvmStatic public fun <K, E> SerializerAwareValue<MutableMap<K, MutableSet<E>>>.withEmptyDefault(): SerializerAwareValue<NotNullMutableMap<K, MutableSet<E>>> { return this.withDefault { LinkedHashSet() } } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值 */ @OptIn(ConsoleExperimentalApi::class) @JvmStatic @JvmName("withDefaultMapImmutableNotNull") public fun <K, V : Any> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<NotNullMap<K, V>> { @Suppress("UNCHECKED_CAST") // magic return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<NotNullMap<K, V>> } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值 */ @JvmStatic @LowPriorityInOverloadResolution @JvmName("withDefaultMapImmutable") public fun <K, V> SerializerAwareValue<Map<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<Map<K, V>> { @Suppress("UNCHECKED_CAST") // magic return (this as SerializerAwareValue<MutableMap<K, V>>).withDefault(defaultValueComputer) as SerializerAwareValue<Map<K, V>> } @OptIn(ConsoleExperimentalApi::class) @JvmStatic @JvmName("withDefaultMapNotNull") public fun <K, V : Any> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<NotNullMutableMap<K, V>> { val origin = this @Suppress("UNCHECKED_CAST") return SerializableValue( object : CompositeMapValue<K, V> { private val instance = NotNullMutableMap(createDelegateInstance(origin, defaultValueComputer)) override var value: Map<K, V> get() = instance set(value) { origin.value = value as MutableMap<K, V> // erased cast } } as Value<NotNullMutableMap<K, V>>, // erased cast this.serializer ) } /** * 创建一个代理对象, 当 [Map.get] 返回 `null` 时先调用 [defaultValueComputer] 并放入 [Map], 再返回调用的返回值 */ @OptIn(ConsoleExperimentalApi::class) @LowPriorityInOverloadResolution @JvmStatic @JvmName("withDefaultMap") public fun <K, V> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<MutableMap<K, V>> { val origin = this @Suppress("UNCHECKED_CAST") return SerializableValue( object : CompositeMapValue<K, V> { private val instance = createDelegateInstance(origin, defaultValueComputer) override var value: Map<K, V> get() = instance set(value) { origin.value = value as MutableMap<K, V> // erased cast } } as Value<MutableMap<K, V>>, // erased cast this.serializer ) } private fun <K, V> createDelegateInstance( origin: SerializerAwareValue<MutableMap<K, V>>, defaultValueComputer: (K) -> V, ): MutableMap<K, V> { return object : MutableMap<K, V>, AbstractMap<K, V>() { override val entries: MutableSet<MutableMap.MutableEntry<K, V>> get() = origin.value.entries override val keys: MutableSet<K> get() = origin.value.keys override val values: MutableCollection<V> get() = origin.value.values override fun clear() = origin.value.clear() override fun putAll(from: Map<out K, V>) = origin.value.putAll(from) override fun remove(key: K): V? = origin.value.remove(key) override fun put(key: K, value: V): V? = origin.value.put(key, value) override fun get(key: K): V? { // the only difference val result = origin.value[key] if (result != null) return result put(key, defaultValueComputer(key)) return origin.value[key] } } } /** * 替换 [MutableMap] 的 key */ @OptIn(ConsoleExperimentalApi::class) @JvmName("mapKeysNotNull") @JvmStatic public fun <OldK, NewK, V : Any> SerializerAwareValue<NotNullMutableMap<OldK, V>>.mapKeys( oldToNew: (OldK) -> NewK, newToOld: (NewK) -> OldK, ): SerializerAwareValue<NotNullMutableMap<NewK, V>> { val origin = this @Suppress("UNCHECKED_CAST") return SerializableValue( object : CompositeMapValue<NewK, V> { private val instance = NotNullMutableMap(ShadowMap({ origin.value }, oldToNew, newToOld, { it }, { it })) override var value: Map<NewK, V> get() = instance set(value) { origin.value = value.mapKeysTo(NotNullMutableMap(LinkedHashMap())) { it.key.let(newToOld) } // erased cast } } as Value<NotNullMutableMap<NewK, V>>, // erased cast this.serializer ) } /** * 替换 [MutableMap] 的 key */ @OptIn(ConsoleExperimentalApi::class) @JvmName("mapKeys") @JvmStatic public fun <OldK, NewK, V> SerializerAwareValue<MutableMap<OldK, V>>.mapKeys( oldToNew: (OldK) -> NewK, newToOld: (NewK) -> OldK, ): SerializerAwareValue<MutableMap<NewK, V>> { val origin = this @Suppress("UNCHECKED_CAST") return SerializableValue( object : CompositeMapValue<NewK, V> { private val instance = ShadowMap({ origin.value }, oldToNew, newToOld, { it }, { it }) override var value: Map<NewK, V> get() = instance set(value) { origin.value = value.mapKeysTo(LinkedHashMap()) { it.key.let(newToOld) } // erased cast } } as Value<MutableMap<NewK, V>>, // erased cast this.serializer ) } /** * 替换 [Map] 的 key */ @OptIn(ConsoleExperimentalApi::class) @JvmName("mapKeysImmutable") @JvmStatic public fun <OldK, NewK, V> SerializerAwareValue<Map<OldK, V>>.mapKeys( oldToNew: (OldK) -> NewK, newToOld: (NewK) -> OldK, ): SerializerAwareValue<Map<NewK, V>> { val origin = this @Suppress("UNCHECKED_CAST") return SerializableValue( object : CompositeMapValue<NewK, V> { // casting Map to MutableMap is OK here, as we don't call mutable functions private val instance = ShadowMap({ origin.value as MutableMap<OldK, V> }, oldToNew, newToOld, { it }, { it }) override var value: Map<NewK, V> get() = instance set(value) { origin.value = value.mapKeysTo(LinkedHashMap()) { it.key.let(newToOld) } // erased cast } } as Value<Map<NewK, V>>, // erased cast this.serializer ) } /** * 替换 [Map] 的 key */ @OptIn(ConsoleExperimentalApi::class) @JvmName("mapKeysImmutableNotNull") @JvmStatic public fun <OldK, NewK, V : Any> SerializerAwareValue<NotNullMap<OldK, V>>.mapKeys( oldToNew: (OldK) -> NewK, newToOld: (NewK) -> OldK, ): SerializerAwareValue<NotNullMap<NewK, V>> { val origin = this @Suppress("UNCHECKED_CAST") return SerializableValue( object : CompositeMapValue<NewK, V> { // casting Map to MutableMap is OK here, as we don't call mutable functions private val instance = NotNullMap(ShadowMap({ origin.value as MutableMap<OldK, V> }, oldToNew, newToOld, { it }, { it })) override var value: Map<NewK, V> get() = instance set(value) { origin.value = value.mapKeysTo(NotNullMutableMap(LinkedHashMap())) { it.key.let(newToOld) } // erased cast } } as Value<NotNullMap<NewK, V>>, // erased cast this.serializer ) } } ================================================ FILE: mirai-console/backend/mirai-console/src/data/PluginDataHolder.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST", "unused") package net.mamoe.mirai.console.data import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 可以持有相关 [PluginData] 实例的对象, 作为 [PluginData] 实例的拥有者. * * @see PluginDataStorage.load * @see PluginDataStorage.store * * @see AutoSavePluginDataHolder 支持自动保存 */ @ConsoleExperimentalApi public interface PluginDataHolder { /** * 保存时使用的分类名 */ @ConsoleExperimentalApi public val dataHolderName: String } /* public interface PluginDataHolder { /** * 创建一个 [PluginData] 实例. * * 注意, 此时的 [PluginData] 并没有绑定 [PluginDataStorage], 因此无法进行保存等操作. * * @see Companion.newPluginDataInstance * @see KClass.createType */ public fun <T : PluginData> newPluginDataInstance(type: KType): T = newPluginDataInstanceUsingReflection<PluginData>(type) as T public companion object { /** * 创建一个 [PluginData] 实例. * * @see PluginDataHolder.newPluginDataInstance */ @JvmSynthetic public inline fun <reified T : PluginData> PluginDataHolder.newPluginDataInstance(): T { return this.newPluginDataInstance(typeOf0<T>()) } } */ /* public interface AutoSavePluginDataHolder : PluginDataHolder, CoroutineScope { /** * 仅支持确切的 [PluginData] 类型 */ public override fun <T : PluginData> newPluginDataInstance(type: KType): T { val classifier = type.classifier?.cast<KClass<PluginData>>() require(classifier != null && classifier.java == PluginData::class.java) { "Cannot create PluginData instance. AutoSavePluginDataHolder supports only PluginData type." } return AutoSavePluginData(this, classifier) as T // T is always PluginData } */ ================================================ FILE: mirai-console/backend/mirai-console/src/data/PluginDataStorage.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST", "unused") package net.mamoe.mirai.console.data import net.mamoe.mirai.console.internal.data.MemoryPluginDataStorageImpl import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import java.io.File import java.nio.file.Path /** * [数据对象][PluginData] 存储仓库. * * ## 职责 * [PluginDataStorage] 类似于一个数据库, 它只承担将序列化之后的数据保存到数据库中, 和从数据库取出这个对象的任务. * * * 此为较低层的 API, 一般插件开发者不会接触. * * [JvmPluginLoader] 实现一个 [PluginDataStorage], 用于管理所有 [JvmPlugin] 的 [PluginData] 实例. * * ### 实现 [PluginDataStorage] * 无特殊需求. 实现两个成员函数即可. 可参考内建 [MultiFilePluginDataStorageImpl] * * @see PluginDataHolder * @see JvmPluginLoader.dataStorage */ @ConsoleExperimentalApi public interface PluginDataStorage { /** * 读取一个实例. 并为 [instance] [设置 [PluginDataStorage]][PluginData.onInit] */ @ConsoleExperimentalApi public fun load(holder: PluginDataHolder, instance: PluginData) /** * 保存一个实例. * * **实现细节**: 调用 [PluginData.updaterSerializer] */ @ConsoleExperimentalApi public fun store(holder: PluginDataHolder, instance: PluginData) /* public companion object { /** * 通过反射 * 读取一个实例. * * 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] */ @ConsoleExperimentalAPI @JvmStatic public fun <T : PluginData> PluginDataStorage.load(holder: PluginDataHolder, dataClass: KClass<T>): T { @Suppress("UNCHECKED_CAST") val instance = with(dataClass){ objectInstance ?: createInstanceOrNull() ?: throw IllegalArgumentException( "Cannot create PluginData instance. Make sure dataClass is PluginData::class.java or a Kotlin's object, " + "or has a constructor which either has no parameters or all parameters of which are optional" ) } load(holder, instance) return instance } /** * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] */ @JvmStatic public fun <T : PluginData> PluginDataStorage.load(holder: PluginDataHolder, dataClass: Class<T>): T = this.load(holder, dataClass.java) /** * 读取一个实例. 在 [T] 实例创建后 [设置 [PluginDataStorage]][PluginData.setStorage] */ @JvmSynthetic public inline fun <reified T : PluginData> PluginDataStorage.load(holder: PluginDataHolder): T = this.load(holder, T::class) } */ } /** * 在内存存储所有 [PluginData] 实例的 [PluginDataStorage]. 在内存数据丢失后相关 [PluginData] 实例也会丢失. * @see PluginDataStorage */ @ConsoleExperimentalApi public interface MemoryPluginDataStorage : PluginDataStorage { public companion object { /** * 创建一个 [MemoryPluginDataStorage] 实例. */ @JvmStatic @JvmName("create") // @JvmOverloads public operator fun invoke(): MemoryPluginDataStorage = MemoryPluginDataStorageImpl() } } /** * 用多个文件存储 [PluginData] 实例的 [PluginDataStorage]. */ @ConsoleExperimentalApi public interface MultiFilePluginDataStorage : PluginDataStorage { /** * 存放 [PluginData] 的目录. */ public val directoryPath: Path public companion object { /** * 创建一个 [MultiFilePluginDataStorage] 实例. * * @see directory 存放 [PluginData] 的目录. */ @JvmStatic @JvmName("create") public operator fun invoke(directory: Path): MultiFilePluginDataStorage = MultiFilePluginDataStorageImpl(directory) } } @ConsoleExperimentalApi @get:JvmSynthetic public inline val MultiFilePluginDataStorage.directory: File get() = this.directoryPath.toFile() ================================================ FILE: mirai-console/backend/mirai-console/src/data/ReadOnlyPluginConfig.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.data /** * 只读的 [PluginConfig]. 插件只能读取其值, 但值可能在后台被前端 (用户) 修改. * * @see PluginConfig * @see AutoSavePluginData * @since 1.1 */ public open class ReadOnlyPluginConfig public constructor(saveName: String) : ReadOnlyPluginData(saveName), PluginConfig ================================================ FILE: mirai-console/backend/mirai-console/src/data/ReadOnlyPluginData.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.data import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.error /** * 只读的 [PluginData]. 插件只能读取其值, 但值可能在后台被前端 (用户) 修改. * * @see PluginData * @see AutoSavePluginData * @since 1.1 */ public open class ReadOnlyPluginData private constructor( // KEEP THIS PRIMARY CONSTRUCTOR FOR FUTURE USE: WE'LL SUPPORT SERIALIZERS_MODULE FOR POLYMORPHISM @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?, ) : AbstractPluginData() { @ConsoleExperimentalApi public final override val saveName: String get() = _saveName private lateinit var _saveName: String public constructor(saveName: String) : this(null) { _saveName = saveName } @ConsoleExperimentalApi override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) { } @ConsoleExperimentalApi public final override fun onValueChanged(value: Value<*>) { debuggingLogger1.error { "onValueChanged: $value" } } } ================================================ FILE: mirai-console/backend/mirai-console/src/data/Value.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused", "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME") package net.mamoe.mirai.console.data import kotlinx.serialization.BinaryFormat import kotlinx.serialization.KSerializer import kotlinx.serialization.StringFormat import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.data.setValueBySerializer import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /** * 表示一个值代理. * * [Value.value] 可以像 Kotlin 的 `var` 一样被修改, 然而它也可能被用户修改, 如通过 UI 前端, 或通过自动重载. * * 一些常用的基础类型实现由代码生成创建特性的优化. * * @see PluginData 容纳 [Value] 的数据对象 * * @see PrimitiveValue 基础数据类型实现 * @see CompositeValue 复合数据类型实现 */ public interface Value<T> : ReadWriteProperty<Any?, T> { @get:JvmName("get") @set:JvmName("set") public var value: T @JvmSynthetic // avoid ambiguity with property `value` public override operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value @JvmSynthetic public override operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value = value } } /** * 可被序列化的 [Value]. */ public class SerializableValue<T>( @JvmField internal val delegate: Value<T>, /** * 用于更新和保存 [delegate] 的序列化器 */ public override val serializer: KSerializer<Unit> ) : Value<T> by delegate, SerializerAwareValue<T> { public override fun toString(): String = delegate.toString() public override fun equals(other: Any?): Boolean { if (other === this) return true if (other?.javaClass != this.javaClass) return false @Suppress("UNCHECKED_CAST") other as SerializableValue<T> if (other.delegate != this.delegate) return false // if (other.serializer != this.serializer) return false // TODO: 2020/9/9 serializers should be checked here, but it will cause incomparable issue when putting a SerializableValue as a Key return true } override fun hashCode(): Int { @Suppress("UnnecessaryVariable", "CanBeVal") var result = delegate.hashCode() // result = 31 * result + serializer.hashCode() // TODO: 2020/9/9 serializers should be checked here, but it will cause incomparable issue when putting a SerializableValue as a Key return result } public companion object { @JvmStatic @JvmName("create") public fun <T> Value<T>.serializableValueWith( serializer: KSerializer<T> ): SerializableValue<T> { return SerializableValue( this, serializer.map(serializer = { this.value }, deserializer = { this.setValueBySerializer(it) }) ) } } } /** * 带有显式 [序列化器][serializer] 的 [Value]. * * @see SerializableValue 简单实现 * @see PluginData.value 创建一个这样的 [SerializerAwareValue] */ public interface SerializerAwareValue<T> : Value<T> { /** * 用于更新 [value] 的序列化器. 在反序列化时不会创建新的 [T] 对象实例. * * - 序列化: `val text: String = Yaml.default.encodeToString(serializer, Unit)` * - 反序列化 (本质上是更新 [value], 不创建新的 [T] 实例): `Yaml.default.decodeFromString(serializer, text)` */ public val serializer: KSerializer<Unit> public companion object { /** * 使用 [指定格式格式][format] 序列化一个 [SerializerAwareValue] */ @JvmStatic public fun <T> SerializerAwareValue<T>.serialize(format: StringFormat): String { return format.encodeToString(this.serializer, Unit) } /** * 使用 [指定格式格式][format] 序列化一个 [SerializerAwareValue] */ @JvmStatic public fun <T> SerializerAwareValue<T>.serialize(format: BinaryFormat): ByteArray { return format.encodeToByteArray(this.serializer, Unit) } /** * 使用 [指定格式格式][format] 反序列化 (更新) 一个 [SerializerAwareValue] */ @JvmStatic public fun <T> SerializerAwareValue<T>.deserialize(format: StringFormat, string: String) { format.decodeFromString(this.serializer, string) } /** * 使用 [指定格式格式][format] 反序列化 (更新) 一个 [SerializerAwareValue] */ @JvmStatic public fun <T> SerializerAwareValue<T>.deserialize(format: BinaryFormat, bytes: ByteArray) { format.decodeFromByteArray(this.serializer, bytes) } } } /** * 基础数据类型 [Value] * * 9 个被认为是 *基础类型* 的类型: * - 整数: [Byte], [Short], [Int], [Long] * - 浮点: [Float], [Double] * - [Boolean] * - [Char], [String] * * 注意: 目前这些类型都会被装箱, 由于泛型 T. 在将来可能会有优化处理. * *Primitive* 仅表示一个类型是上面 9 种类型之一. */ @ConsoleExperimentalApi public interface PrimitiveValue<T> : Value<T> //// region PrimitiveValues CODEGEN //// /** * 表示一个不可空 [Byte] [Value]. */ @ConsoleExperimentalApi public interface ByteValue : PrimitiveValue<Byte> /** * 表示一个不可空 [Short] [Value]. */ @ConsoleExperimentalApi public interface ShortValue : PrimitiveValue<Short> /** * 表示一个不可空 [Int] [Value]. */ @ConsoleExperimentalApi public interface IntValue : PrimitiveValue<Int> /** * 表示一个不可空 [Long] [Value]. */ @ConsoleExperimentalApi public interface LongValue : PrimitiveValue<Long> /** * 表示一个不可空 [Float] [Value]. */ @ConsoleExperimentalApi public interface FloatValue : PrimitiveValue<Float> /** * 表示一个不可空 [Double] [Value]. */ @ConsoleExperimentalApi public interface DoubleValue : PrimitiveValue<Double> /** * 表示一个不可空 [Char] [Value]. */ @ConsoleExperimentalApi public interface CharValue : PrimitiveValue<Char> /** * 表示一个不可空 [Boolean] [Value]. */ @ConsoleExperimentalApi public interface BooleanValue : PrimitiveValue<Boolean> /** * 表示一个不可空 [String] [Value]. */ @ConsoleExperimentalApi public interface StringValue : PrimitiveValue<String> //// endregion PrimitiveValues CODEGEN //// /** * 复合数据类型实现 */ @ConsoleExperimentalApi public interface CompositeValue<T> : Value<T> /** * @see [CompositeListValue] * @see [PrimitiveListValue] */ @OptIn(ConsoleExperimentalApi::class) public interface ListValue<E> : CompositeValue<List<E>> /** * 复合数据类型的 [List] * * @param E 不是基础数据类型 */ @ConsoleExperimentalApi public interface CompositeListValue<E> : ListValue<E> /** * 针对基础类型优化的 [List] * * @param E 是基础类型 */ @ConsoleExperimentalApi public interface PrimitiveListValue<E> : ListValue<E> //// region PrimitiveListValue CODEGEN //// @ConsoleExperimentalApi public interface PrimitiveIntListValue : PrimitiveListValue<Int> @ConsoleExperimentalApi public interface PrimitiveLongListValue : PrimitiveListValue<Long> // TODO + codegen //// endregion PrimitiveListValue CODEGEN //// /** * @see [CompositeSetValue] * @see [PrimitiveSetValue] */ @ConsoleExperimentalApi public interface SetValue<E> : CompositeValue<Set<E>> /** * 复合数据类型 [Set] * @param E 是基础数据类型 */ @ConsoleExperimentalApi public interface CompositeSetValue<E> : SetValue<E> /** * 基础数据类型 [Set] * @param E 是基础数据类型 */ @ConsoleExperimentalApi public interface PrimitiveSetValue<E> : SetValue<E> //// region PrimitiveSetValue CODEGEN //// @ConsoleExperimentalApi public interface PrimitiveIntSetValue : PrimitiveSetValue<Int> @ConsoleExperimentalApi public interface PrimitiveLongSetValue : PrimitiveSetValue<Long> // TODO + codegen //// endregion PrimitiveSetValue CODEGEN //// /** * @see [CompositeMapValue] * @see [PrimitiveMapValue] */ @ConsoleExperimentalApi public interface MapValue<K, V> : CompositeValue<Map<K, V>> @ConsoleExperimentalApi public interface CompositeMapValue<K, V> : MapValue<K, V> @ConsoleExperimentalApi public interface PrimitiveMapValue<K, V> : MapValue<K, V> //// region PrimitiveMapValue CODEGEN //// @ConsoleExperimentalApi public interface PrimitiveIntIntMapValue : PrimitiveMapValue<Int, Int> @ConsoleExperimentalApi public interface PrimitiveIntLongMapValue : PrimitiveMapValue<Int, Long> // TODO + codegen //// endregion PrimitiveSetValue CODEGEN //// @ConsoleExperimentalApi public interface ReferenceValue<T> : Value<T> ================================================ FILE: mirai-console/backend/mirai-console/src/data/ValueDescription.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.data import kotlinx.serialization.SerialInfo /** * 序列化之后的注释. * * 例: * ``` * object AccountPluginData : PluginData by ... { * @ValueDescription(""" * 一个 map * """) * val map: Map<String, String> by value("a" to "b") * } * ``` * * 将被保存为配置 (YAML 作为示例): * ```yaml * AccountPluginData: * # 一个 map * map: * a: b * ``` * * @see net.mamoe.yamlkt.Comment */ @SerialInfo @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) public annotation class ValueDescription( /** * 将会被 [String.trimIndent] 处理 */ val value: String, ) ================================================ FILE: mirai-console/backend/mirai-console/src/data/ValueName.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.data /** * 序列化之后的名称. * * 例: * ``` * object AccountPluginData : AutoSavePluginData() { * @ValueName("info") * val map: Map<String, String> by value("a" to "b") * } * ``` * * 将被保存为配置 (YAML 作为示例): * ```yaml * AccountPluginData: * info: * a: b * ``` * * @see PluginData * @see Value */ @Target(AnnotationTarget.PROPERTY, AnnotationTarget.CLASS) @Retention(AnnotationRetention.RUNTIME) public annotation class ValueName(val value: String) ================================================ FILE: mirai-console/backend/mirai-console/src/data/java/JAutoSavePluginConfig.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "EXPOSED_SUPER_CLASS") package net.mamoe.mirai.console.data.java import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * 一个插件的配置数据, 用于和用户交互. * * 用户可通过 mirai-console 前端 (如在 Android 中可视化实现) 修改这些配置, 修改会自动写入这个对象中. * * **提示**: * 插件内部的数据应用 [PluginData] 存储, 而不能使用 [PluginConfig]. * * ### 实现 * * 在 [JAutoSavePluginData] 的示例基础上, 修改类定义 * ```java * // 原 * public class AccountPluginData extends JAutoSavePluginData { * // 修改为 * public class AccountPluginConfig extends JAutoSavePluginConfig { * ``` * 即可将一个 [PluginData] 变更为 [PluginConfig]. * * @see JAutoSavePluginData * @see PluginConfig */ @Deprecated( "请使用 JavaAutoSavePluginConfig", replaceWith = ReplaceWith("JavaAutoSavePluginConfig", "net.mamoe.mirai.console.data.java.JavaAutoSavePluginConfig"), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") public abstract class JAutoSavePluginConfig public constructor(saveName: String) : AutoSavePluginConfig(saveName), PluginConfig ================================================ FILE: mirai-console/backend/mirai-console/src/data/java/JAutoSavePluginData.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "EXPOSED_SUPER_CLASS") package net.mamoe.mirai.console.data.java import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.setValueBySerializer import net.mamoe.mirai.console.internal.data.valueImpl import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.utils.DeprecatedSinceMirai import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.KTypeProjection import kotlin.reflect.KVariance import kotlin.reflect.full.createType /** * 供 Java 用户使用的 [PluginData]. 参考 [PluginData] 以获取更多信息. * * 在 [JvmPlugin] 的典型实现方式: * ``` * // PluginMain.java * public final class PluginMain extends JavaPlugin { * public static PluginMain INSTANCE; * public PluginMain() { * INSTANCE = this; * } * @Override * public onLoad() { * this.reloadPluginData(MyPluginData.INSTANCE); // 读取文件等 * } * } * * // MyPluginData.java * public class MyPluginData extends JAutoSavePluginData { * public static final MyPluginData INSTANCE = new MyPluginData(); * * public final Value<String> string = value("test"); // 默认值 "test" * * public final Value<List<String>> list = typedValue(createKType(List.class, createKType(String.class))); // 无默认值, 自动创建空 List * * public final Value<Map<Long, Object>> custom = typedValue( * createKType(Map.class, createKType(Long.class), createKType(Object.class)), * new HashMap<Long, Object>() {{ // 带默认值 * put(123L, "ok"); * }} * ); * } * ``` * * 使用时, 需要使用 `.get()`, 如: * ``` * Value<List<String>> theList = MyPluginData.INSTANCE.list; // 获取 Value 实例. Value 代表一个追踪自动保存的值. * List<String> actualList = theList.get(); * theList.set(); * ``` * * **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象. * * @see PluginData */ @Deprecated( "请使用 JavaAutoSavePluginData", replaceWith = ReplaceWith("JavaAutoSavePluginData", "net.mamoe.mirai.console.data.java.JavaAutoSavePluginData"), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") public abstract class JAutoSavePluginData public constructor(saveName: String) : AutoSavePluginData(saveName), PluginConfig { //// region JAutoSavePluginData_value_primitives CODEGEN //// /** * 创建一个 [Byte] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Byte): SerializerAwareValue<Byte> = valueImpl(default) /** * 创建一个 [Short] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Short): SerializerAwareValue<Short> = valueImpl(default) /** * 创建一个 [Int] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Int): SerializerAwareValue<Int> = valueImpl(default) /** * 创建一个 [Long] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Long): SerializerAwareValue<Long> = valueImpl(default) /** * 创建一个 [Float] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Float): SerializerAwareValue<Float> = valueImpl(default) /** * 创建一个 [Double] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Double): SerializerAwareValue<Double> = valueImpl(default) /** * 创建一个 [Char] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Char): SerializerAwareValue<Char> = valueImpl(default) /** * 创建一个 [Boolean] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: Boolean): SerializerAwareValue<Boolean> = valueImpl(default) /** * 创建一个 [String] 类型的 [Value], 并设置初始值为 [default] */ public fun value(default: String): SerializerAwareValue<String> = valueImpl(default) //// endregion JAutoSavePluginData_value_primitives CODEGEN //// /** * 构造一个支持泛型的 [Value]. * * 对于 [Map], [Set], [List], [ConcurrentMap] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap], [LinkedHashSet], [ArrayList], [ConcurrentHashMap] 等相关类型. * 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用*无参构造器*构造实例. * * @param type Kotlin 类型. 可通过 [createKType] 获得 * * @param T 类型 T. 仅支持: * - 基础数据类型, [String] * - 标准库集合类型 ([List], [Map], [Set], [ConcurrentMap]) * - 标准库数据类型 ([Map.Entry], [Pair], [Triple]) * - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的 **Kotlin** 类 */ @JvmOverloads public fun <T : Any> typedValue(type: KType, default: T? = null): SerializerAwareValue<T> { val value = valueImpl<T>(type, type.classifier!!.cast()) if (default != null) value.setValueBySerializer(default) return value } public companion object { /** * 根据 [Class] 及泛型参数获得一个类型 * * 如要获得一个 `Map<String, Long>` 的类型, * ```java * KType type = JPluginDataHelper.createKType(Map.java, createKType(String.java), createKType(Long.java)) * ``` * * @param genericArguments 带有顺序的泛型参数 */ @JvmStatic public fun <T : Any> createKType(clazz: Class<T>, nullable: Boolean, vararg genericArguments: KType): KType { return clazz.kotlin.createType(genericArguments.map { KTypeProjection(KVariance.INVARIANT, it) }, nullable) } /** * 根据 [Class] 及泛型参数获得一个不可为 `null` 的类型 * * @see createKType */ @JvmStatic public fun <T : Any> createKType(clazz: Class<T>, vararg genericArguments: KType): KType { return createKType(clazz, false, *genericArguments) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/data/java/JavaAutoSavePluginConfig.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "EXPOSED_SUPER_CLASS") package net.mamoe.mirai.console.data.java import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.util.JavaFriendlyApi /** * 一个插件的配置数据, 用于和用户交互. * * 用户可通过 mirai-console 前端 (如在 Android 中可视化实现) 修改这些配置, 修改会自动写入这个对象中. * * **提示**: * 插件内部的数据应用 [PluginData] 存储, 而不能使用 [PluginConfig]. * * ### 实现 * * 在 [JavaAutoSavePluginData] 的示例基础上, 修改类定义 * ```java * // 原 * public class AccountPluginData extends JavaAutoSavePluginData { * // 修改为 * public class AccountPluginConfig extends JavaAutoSavePluginConfig { * ``` * 即可将一个 [PluginData] 变更为 [PluginConfig]. * * @see JavaAutoSavePluginData * @see PluginConfig * * @since 2.11 */ @JavaFriendlyApi public abstract class JavaAutoSavePluginConfig public constructor(saveName: String) : JavaAutoSavePluginData(saveName), PluginConfig ================================================ FILE: mirai-console/backend/mirai-console/src/data/java/JavaAutoSavePluginData.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "EXPOSED_SUPER_CLASS") @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.data.java import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.internal.data.setValueBySerializer import net.mamoe.mirai.console.internal.data.valueImpl import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.JavaFriendlyApi import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.KTypeProjection import kotlin.reflect.KVariance import kotlin.reflect.full.createType /** * 供 Java 用户使用的 [PluginData]. 参考 [PluginData] 以获取更多信息. * * 仍然更推荐在项目中混用 Java 和 Kotlin, 使用 Kotlin 编写 [PluginData]. * 也可以使用其他持久化库替代 [PluginData]. * * 在 [JvmPlugin] 的典型实现方式: * ``` * // PluginMain.java * public final class PluginMain extends JavaPlugin { * public static PluginMain INSTANCE; * public PluginMain() { * INSTANCE = this; * } * @Override * public onLoad() { * this.reloadPluginData(MyPluginData.INSTANCE); // 读取文件等 * } * } * * // MyPluginData.java * public class MyPluginData extends JavaAutoSavePluginData { * public static final MyPluginData INSTANCE = new MyPluginData(); * * public final Value<String> string = value("test"); // 默认值 "test" * * public final Value<List<String>> list = typedValue(createKType(List.class, createKType(String.class))); // 无默认值, 自动创建空 List * * public final Value<Map<Long, Object>> custom = typedValue( * createKType(Map.class, createKType(Long.class), createKType(Object.class)), * new HashMap<Long, Object>() {{ // 带默认值 * put(123L, "ok"); * }} * ); * } * ``` * * 使用时, 需要使用 `.get()`, 如: * ``` * Value<List<String>> theList = MyPluginData.INSTANCE.list; // 获取 Value 实例. Value 代表一个追踪自动保存的值. * List<String> actualList = theList.get(); * theList.set(); * ``` * * **注意**: 由于实现特殊, 请不要在初始化 Value 时就使用 `.get()`. 这可能会导致自动保存追踪失效. 必须在使用时才调用 `.get()` 获取真实数据对象. * * @see PluginData * @since 2.11 */ @JavaFriendlyApi public abstract class JavaAutoSavePluginData public constructor(saveName: String) : AutoSavePluginData(saveName), PluginConfig { //// region JavaAutoSavePluginData_value_primitives CODEGEN //// /** * 创建一个名称为 [name], 类型为 [Byte] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Byte): SerializerAwareValue<Byte> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [Short] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Short): SerializerAwareValue<Short> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [Int] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Int): SerializerAwareValue<Int> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [Long] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Long): SerializerAwareValue<Long> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [Float] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Float): SerializerAwareValue<Float> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [Double] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Double): SerializerAwareValue<Double> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [Char] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Char): SerializerAwareValue<Char> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [Boolean] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: Boolean): SerializerAwareValue<Boolean> = valueImpl(default).apply { track(this, name, emptyList()) } /** * 创建一个名称为 [name], 类型为 [String] 的 [Value], 并设置初始值为 [default]. */ public fun value(name: String, default: String): SerializerAwareValue<String> = valueImpl(default).apply { track(this, name, emptyList()) } //// endregion JavaAutoSavePluginData_value_primitives CODEGEN //// /** * 创建一个支持泛型的 [Value]. * * 对于 [Map], [Set], [List], [ConcurrentMap] 等标准库类型, 这个函数会尝试构造 [LinkedHashMap], [LinkedHashSet], [ArrayList], [ConcurrentHashMap] 等相关类型. * 而对于自定义数据类型, 本函数只会反射获取 [objectInstance][KClass.objectInstance] 或使用*无参构造器*构造实例. * * @param type Kotlin 类型. 可通过 [createKType] 获得 * * @param T 类型 T. 仅支持: * - 基础数据类型, [String] * - 标准库集合类型 ([List], [Map], [Set], [ConcurrentMap]) * - 标准库数据类型 ([Map.Entry], [Pair], [Triple]) * - 使用 [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization) 的 [Serializable] 标记的 **Kotlin** 类 */ @JvmOverloads public fun <T : Any> typedValue(name: String, type: KType, default: T? = null): SerializerAwareValue<T> { val value = valueImpl<T>(type, type.classifier!!.cast()) if (default != null) value.setValueBySerializer(default) track(value, name, emptyList()) return value } /** * @since 2.11 */ @JavaFriendlyApi public companion object { /** * 根据 [Class] 及泛型参数获得一个类型 * * 如要获得一个 `Map<String, Long>` 的类型, * ```java * KType type = JPluginDataHelper.createKType(Map.java, createKType(String.java), createKType(Long.java)) * ``` * * @param genericArguments 带有顺序的泛型参数 */ @JvmStatic public fun <T : Any> createKType(clazz: Class<T>, nullable: Boolean, vararg genericArguments: KType): KType { return clazz.kotlin.createType(genericArguments.map { KTypeProjection(KVariance.INVARIANT, it) }, nullable) } /** * 根据 [Class] 及泛型参数获得一个不可为 `null` 的类型 * * @see createKType */ @JvmStatic public fun <T : Any> createKType(clazz: Class<T>, vararg genericArguments: KType): KType { return createKType(clazz, false, *genericArguments) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/enduserreadme/EndUserReadme.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.enduserreadme import java.util.* /** * 最终用户须知 * * @since 2.16.0 */ public class EndUserReadme { public companion object { public const val PAUSE: String = "<pause>" public const val DELAY: String = "<delay>" } internal val pages: MutableMap<String, String> = linkedMapOf() public class Render { private val msgs = mutableListOf<String>() @KeepDetermination public operator fun String.unaryPlus() { msg(this) } @KeepDetermination public operator fun plusAssign(s: String) { msg(s) } @KeepDetermination public fun pause() { msg(PAUSE) } @KeepDetermination public fun delay() { msg(DELAY) } /** * 延迟一段时间 * * @param time 单位:秒 */ @KeepDetermination public fun delay(time: Int) { msg(DELAY + time) } @KeepDetermination public fun msg(message: String) { msgs.add(message) } public fun render(): String = msgs.joinToString(separator = "\n") } @KeepDetermination public fun put(category: String, render: Render.() -> Unit) { pages[category] = Render().also(render).render() } /** * 同时添加多个须知定义 * * 格式: * ```text * * ::category.c1 * * Here is c1 * * delay 2s * <delay>2 * * paused * <pause> * * ::category.c1 * * Here is c2 * * ``` */ @KeepDetermination public fun putAll(fullText: String) { if (fullText.isBlank()) return val lines = LinkedList(fullText.lines()) var category: String val buffer = mutableListOf<String>() while (true) { if (lines.isEmpty()) return val rm = lines.removeFirst() if (rm.isBlank()) continue if (rm.startsWith("::")) { category = rm.substring(2) break } throw IllegalArgumentException("First non-empty line must be category define: $rm") } fun flush() { while (buffer.isNotEmpty()) { if (buffer.first().isBlank()) { buffer.removeAt(0) continue } break } while (buffer.isNotEmpty()) { if (buffer.last().isBlank()) { buffer.removeAt(buffer.lastIndex) continue } break } pages[category] = buffer.joinToString(separator = "\n") buffer.clear() } while (lines.isNotEmpty()) { val rm = lines.removeFirst() if (rm.startsWith("::")) { flush() category = rm.substring(2) continue } buffer.add(rm) } flush() } @DslMarker private annotation class KeepDetermination } ================================================ FILE: mirai-console/backend/mirai-console/src/events/AutoLoginEvent.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.events import net.mamoe.mirai.Bot import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.console.internal.* import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.utils.MiraiInternalApi /** * 自动登录执行后广播的事件 * @property bot 登录的BOT * @see MiraiConsoleImplementationBridge.doStart * @since 2.15 */ public sealed class AutoLoginEvent : BotEvent, ConsoleEvent, AbstractEvent() { /** * 登录成功 */ public class Success @MiraiInternalApi constructor( override val bot: Bot ) : AutoLoginEvent() { override fun toString(): String { return "AutoLoginEvent.Success(bot=${bot.id}, protocol=${bot.configuration.protocol}, heartbeatStrategy=${bot.configuration.heartbeatStrategy})" } } /** * 登录失败 */ public class Failure @MiraiInternalApi constructor( override val bot: Bot, public val cause: Throwable ) : AutoLoginEvent() { override fun toString(): String { return "AutoLoginEvent.Failure(bot=${bot.id}, protocol=${bot.configuration.protocol}, cause=${cause})" } } } ================================================ FILE: mirai-console/backend/mirai-console/src/events/CommandExecutionEvent.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.events /* data class CommandExecutionEvent( // TODO: 2020/6/26 impl CommandExecutionEvent val sender: CommandSender, val command: Command, val rawArgs: Array<Any> ) : CancellableEvent, ConsoleEvent, AbstractEvent() { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as CommandExecutionEvent if (sender != other.sender) return false if (command != other.command) return false if (!rawArgs.contentEquals(other.rawArgs)) return false return true } override fun hashCode(): Int { var result = sender.hashCode() result = 31 * result + command.hashCode() result = 31 * result + rawArgs.contentHashCode() return result } } */ ================================================ FILE: mirai-console/backend/mirai-console/src/events/ConsoleEvent.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.events import net.mamoe.mirai.event.Event /** * 表示来自 mirai-console 的事件 */ public interface ConsoleEvent : Event ================================================ FILE: mirai-console/backend/mirai-console/src/events/EndUserReadmeInitializeEvent.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.events import net.mamoe.mirai.console.enduserreadme.EndUserReadme import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.utils.MiraiInternalApi public class EndUserReadmeInitializeEvent @MiraiInternalApi constructor( public val readme: EndUserReadme, ) : ConsoleEvent, AbstractEvent() ================================================ FILE: mirai-console/backend/mirai-console/src/events/StartupEvent.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.events import net.mamoe.mirai.console.extensions.PostStartupExtension import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.console.internal.* import net.mamoe.mirai.utils.MiraiInternalApi /** * 在 Console 启动完成后广播的事件 * @property timestamp 启动完成的时间戳 * @see MiraiConsoleImplementationBridge.doStart * @see PostStartupExtension * @since 2.15 */ public class StartupEvent @MiraiInternalApi constructor( public val timestamp: Long ) : ConsoleEvent, AbstractEvent() ================================================ FILE: mirai-console/backend/mirai-console/src/extension/ComponentStorage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad import net.mamoe.mirai.utils.MiraiExperimentalApi import java.util.stream.Stream /** * 组件容器, 容纳 [Plugin] 注册的 [Extension]. * * 插件可在 [JvmPlugin.onLoad] 时提供扩展. 前端可在 [MiraiConsoleImplementation.BackendAccess.globalComponentStorage] 获取全局组件容器. * 目前未允许获取全局组件容器. 如有需求请 [提交 issues](https://github.com/mamoe/mirai/issues/new/choose). * * 实现细节: 线程安全. * * @see Extension * @see JvmPlugin.onLoad */ public interface ComponentStorage { /** * 注册一个扩展 */ public fun <E : Extension> contribute( extensionPoint: ExtensionPoint<E>, plugin: Plugin, extensionInstance: E, ) // 2.11: E changed to T (only naming) /** * 注册一个扩展. [lazyInstance] 将会在 [getExtensions] 时才会计算. */ public fun <E : Extension> contribute( extensionPoint: ExtensionPoint<E>, plugin: Plugin, lazyInstance: () -> E, ) // 2.11: E changed to T (only naming) /** * 获取优先级最高的 [ExtensionPoint] 扩展实例. 在未找到任何注册的实例时抛出 [NoSuchElementException]. * * @since 2.11 */ @MiraiExperimentalApi public fun <E : Extension> getPreferredExtension( extensionPoint: ExtensionPoint<E>, ): ExtensionRegistry<E> { return getExtensions(extensionPoint).firstOrNull() ?: throw NoSuchElementException("No extension registered for $extensionPoint") } /** * 获取注册的 [ExtensionPoint] 扩展实例列表. 返回的 [Sequence] 以 [Extension.priority] 倒序排序. * * @since 2.11 */ public fun <E : Extension> getExtensions( extensionPoint: ExtensionPoint<E>, ): Sequence<ExtensionRegistry<E>> /** * 获取注册的 [ExtensionPoint] 扩展实例列表. 返回的 [Stream] 以 [Extension.priority] 倒序排序. * * @since 2.11 */ public fun <E : Extension> getExtensionsStream( extensionPoint: ExtensionPoint<E>, ): Stream<ExtensionRegistry<E>> } /** * 仅前端实现可用 */ @ConsoleFrontEndImplementation public interface ComponentStorageInternal : ComponentStorage { /** * 注册一个由 Mirai Console 实现的扩展 (因此没有相关 [Plugin]). [lazyInstance] 将会在 [getExtensions] 时才会计算. */ public fun <E : Extension> contributeConsole( extensionPoint: ExtensionPoint<E>, lazyInstance: () -> E, ) /** * 注册一个由 Mirai Console 实现的扩展 (因此没有相关 [Plugin]). */ public fun <E : Extension> contributeConsole( extensionPoint: ExtensionPoint<E>, instance: E, ) { @Suppress("USELESS_CAST") // bug contributeConsole(extensionPoint, { instance } as () -> E) } } ================================================ FILE: mirai-console/backend/mirai-console/src/extension/Extension.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.command.parse.SpaceSeparatedCommandCallParser import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.extensions.PluginLoaderProvider import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * 表示一个扩展. * * ### 获取扩展 * Console 不允许插件获取自己或其他插件注册的扩展 * * ### 注册扩展 * 插件仅能在 [JvmPlugin.onLoad] 阶段注册扩展 * * ```kotlin * object MyPlugin : KotlinPlugin( /* ... */ ) { * fun PluginComponentStorage.onLoad() { * contributePermissionService { /* ... */ } * contributePluginLoader { /* ... */ } * contribute(ExtensionPoint) { /* ... */ } * } * } * ``` * * @see ComponentStorage */ public interface Extension { /** * 优先级. 越高越先使用. 内嵌的 [SpaceSeparatedCommandCallParser] 拥有优先级 0. * * 若两个 [InstanceExtension] 有相同的优先级, 将会优先使用内嵌的实现, 再按 [ComponentStorage.contribute] 顺序依次使用. * * @since 2.11 */ // https://github.com/mamoe/mirai/issues/1860 @ConsoleExperimentalApi public val priority: Int get() = 0 } /** * 增加一些函数 (方法)的扩展 */ public interface FunctionExtension : Extension /** * 为某单例服务注册的 [Extension]. * * 若同时有多个实例可用, 将会使用 [net.mamoe.mirai.console.extensions.SingletonExtensionSelector.selectSingleton] 选择 * * @see PermissionServiceProvider */ @Deprecated( "Please use InstanceExtension instead.", replaceWith = ReplaceWith("InstanceExtension"), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") public interface SingletonExtension<T> : Extension { public val instance: T } /** * 为一些实例注册的 [Extension]. * * @see PluginLoaderProvider */ public interface InstanceExtension<T> : Extension { public val instance: T } ================================================ FILE: mirai-console/backend/mirai-console/src/extension/ExtensionException.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.extension /** * 在调用一个 extension 时遇到的异常. */ public open class ExtensionException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } ================================================ FILE: mirai-console/backend/mirai-console/src/extension/ExtensionPoint.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.reflect.KClass /** * 表示一个扩展接入点(扩展类型). 在 Kotlin 由 [Extension] 的伴生对象实现, 在 Java 可通过静态字段提供. * * 在[注册扩展][ComponentStorage.contribute]时需要提供其 [ExtensionPoint], [ExtensionPoint] 也可以用于获取所有注册的扩展 ([ComponentStorage.getExtensions]). * * @see AbstractExtensionPoint */ public interface ExtensionPoint<T : Extension> { /** * 扩展实例 [T] 的类型 */ public val extensionType: KClass<T> } public abstract class AbstractExtensionPoint<T : Extension>( public override val extensionType: KClass<T>, ) : ExtensionPoint<T> /** * 表示一个 [SingletonExtension] 的 [ExtensionPoint] */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Please use InstanceExtensionPoint instead.", replaceWith = ReplaceWith("InstanceExtensionPoint"), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") public interface SingletonExtensionPoint<T : SingletonExtension<*>> : ExtensionPoint<T> /** * 表示一个 [InstanceExtension] 的 [ExtensionPoint] */ public interface InstanceExtensionPoint<T : InstanceExtension<*>> : ExtensionPoint<T> /** * 表示一个 [FunctionExtension] 的 [ExtensionPoint] */ public interface FunctionExtensionPoint<T : FunctionExtension> : ExtensionPoint<T> public abstract class AbstractInstanceExtensionPoint<E : InstanceExtension<T>, T> /** * @since 2.10 */ @ConsoleExperimentalApi public constructor( extensionType: KClass<E> ) : AbstractExtensionPoint<E>(extensionType) @Deprecated( "Please use AbstractInstanceExtensionPoint instead.", replaceWith = ReplaceWith( "AbstractInstanceExtension", "net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint" ), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") @Suppress("DEPRECATION", "DEPRECATION_ERROR") public abstract class AbstractSingletonExtensionPoint<E : SingletonExtension<T>, T> /** * @since 2.10 */ @ConsoleExperimentalApi constructor( extensionType: KClass<E>, /** * 内建的实现. */ @ConsoleExperimentalApi public val builtinImplementation: () -> T, ) : AbstractExtensionPoint<E>(extensionType), SingletonExtensionPoint<E> { /** * @since 2.0 */ @Suppress("USELESS_CAST") // compiler bug @ConsoleExperimentalApi public constructor(extensionType: KClass<E>, builtinImplementation: T) : this( extensionType, { builtinImplementation } as () -> T ) /** * 由 [net.mamoe.mirai.console.extensions.SingletonExtensionSelector] 选择后的实例. */ @ConsoleExperimentalApi public open val selectedInstance: T get() = throw UnsupportedOperationException("SingletonExtension has been deprecated.") } ================================================ FILE: mirai-console/backend/mirai-console/src/extension/ExtensionRegistry.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.utils.NotStableForInheritance /** * 一个已经注册的 [Extension]. 可通过 [ComponentStorage.getExtensions] 获得. * * @since 2.11 */ @NotStableForInheritance public interface ExtensionRegistry<E : Extension> { /** * 提供该 [ExtensionRegistry] 的插件. 若为 `null` 则表示由 Mirai Console 内置或者由前端实现. */ public val plugin: Plugin? /** * [Extension] 实例. */ public val extension: E } internal inline val <T> ExtensionRegistry<out InstanceExtension<T>>.instance get() = extension.instance internal class ExtensionRegistryImpl<E : Extension> internal constructor( /** * 提供该 [ExtensionRegistry] 的插件. 若为 `null` 则表示由 Mirai Console 内置或者由前端实现. */ override val plugin: Plugin?, /** * [Extension] 实例. */ extensionInitializer: () -> E, ) : ExtensionRegistry<E> { override val extension: E by lazy(extensionInitializer) } ================================================ FILE: mirai-console/backend/mirai-console/src/extension/PluginComponentStorage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.extensions.* import net.mamoe.mirai.console.internal.extension.AbstractConcurrentComponentStorage import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.reflect.full.companionObjectInstance /** * 添加一些扩展给 [Plugin] 的 [ComponentStorage]. * * 所有扩展都会以 'lazy' 形式注册, 由 Console 在不同的启动阶段分别初始化各类扩展. */ @Suppress("EXPOSED_SUPER_CLASS", "unused", "MemberVisibilityCanBePrivate") public class PluginComponentStorage( @JvmField internal val plugin: Plugin, ) : AbstractConcurrentComponentStorage() { /** * 注册一个扩展 */ public fun <E : Extension> contribute( extensionPoint: ExtensionPoint<E>, lazyInstance: () -> E, ): Unit = contribute(extensionPoint, plugin, lazyInstance) /** * 注册一个扩展. [E] 必须拥有伴生对象为 [ExtensionPoint]. */ public inline fun <reified E : Extension> contribute( noinline lazyInstance: () -> E, ) { @Suppress("UNCHECKED_CAST") (contribute( (E::class.companionObjectInstance as? ExtensionPoint<E> ?: error("Companion object of ${E::class.qualifiedName} is not an ExtensionPoint")), lazyInstance )) } /////////////////////////////////////////////////////////////////////////// // FunctionExtension /////////////////////////////////////////////////////////////////////////// /** 注册一个 [net.mamoe.mirai.console.extensions.SingletonExtensionSelector] */ @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION_ERROR", "DEPRECATION") @Deprecated( "Order of extensions is now determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") public fun contributeSingletonExtensionSelector(lazyInstance: () -> net.mamoe.mirai.console.extensions.SingletonExtensionSelector): Unit = contribute(net.mamoe.mirai.console.extensions.SingletonExtensionSelector.ExtensionPoint, plugin, lazyInstance) /** 注册一个 [BotConfigurationAlterer] */ public fun contributeBotConfigurationAlterer(instance: BotConfigurationAlterer): Unit = contribute(BotConfigurationAlterer, plugin, lazyInstance = { instance }) /** 注册一个 [PostStartupExtension] */ public fun contributePostStartupExtension(instance: PostStartupExtension): Unit = contribute(PostStartupExtension, plugin, lazyInstance = { instance }) /** 注册一个 [PostStartupExtension] */ public fun runAfterStartup(block: () -> Unit): Unit = contributePostStartupExtension(block) /////////////////////////////////////////////////////////////////////////// // InstanceExtensions & SingletonExtensions /////////////////////////////////////////////////////////////////////////// /** 注册一个 [PermissionServiceProvider] */ @JvmName("contributePermissionServiceProvider") @OverloadResolutionByLambdaReturnType public fun contributePermissionService(lazyProvider: () -> PermissionServiceProvider): Unit = contribute(PermissionServiceProvider, plugin, lazyProvider) ///////////////////////////////////// /** 注册一个 [PluginLoaderProvider] */ @JvmName("contributePluginLoaderProvider") @OverloadResolutionByLambdaReturnType public fun contributePluginLoader(lazyProvider: () -> PluginLoaderProvider): Unit = contribute(PluginLoaderProvider, plugin, lazyProvider) // lazy for safety ///////////////////////////////////// /** 注册一个 [CommandCallParserProvider] */ @ExperimentalCommandDescriptors @JvmName("contributeCommandCallParserProvider") @OverloadResolutionByLambdaReturnType public fun contributeCommandCallParser(provider: CommandCallParserProvider): Unit = contribute(CommandCallParserProvider, plugin, provider) ///////////////////////////////////// /** 注册一个 [CommandCallResolverProvider] */ @ExperimentalCommandDescriptors @JvmName("contributeCommandCallResolverProvider") @OverloadResolutionByLambdaReturnType public fun contributeCommandCallParser(provider: CommandCallResolverProvider): Unit = contribute(CommandCallResolverProvider, plugin, provider) ///////////////////////////////////// /** 注册一个 [CommandCallInterceptorProvider] */ @ExperimentalCommandDescriptors @JvmName("contributeCommandCallInterceptorProvider") @OverloadResolutionByLambdaReturnType public fun contributeCommandCallParser(provider: CommandCallInterceptorProvider): Unit = contribute(CommandCallInterceptorProvider, plugin, provider) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/BotConfigurationAlterer.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.FunctionExtension import net.mamoe.mirai.utils.BotConfiguration /** * [MiraiConsole.addBot] 时的 [BotConfiguration] 修改扩展 * * @see MiraiConsole.addBot */ @Suppress("SpellCheckingInspection") // alterer public fun interface BotConfigurationAlterer : FunctionExtension { /** * 修改 [configuration], 返回修改完成的 [BotConfiguration] */ public fun alterConfiguration( botId: Long, configuration: BotConfiguration, ): BotConfiguration public companion object ExtensionPoint : AbstractExtensionPoint<BotConfigurationAlterer>(BotConfigurationAlterer::class) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.util.ConsoleExperimentalApi @ExperimentalCommandDescriptors public interface CommandCallInterceptorProvider : InstanceExtension<CommandCallInterceptor> { @OptIn(ConsoleExperimentalApi::class) @ExperimentalCommandDescriptors public companion object ExtensionPoint : AbstractInstanceExtensionPoint<CommandCallInterceptorProvider, CommandCallInterceptor>( CommandCallInterceptorProvider::class ) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/CommandCallParserProvider.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCallParser import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * The provider of [CommandCallParser] */ @ExperimentalCommandDescriptors public interface CommandCallParserProvider : InstanceExtension<CommandCallParser> { @OptIn(ConsoleExperimentalApi::class) @ExperimentalCommandDescriptors public companion object ExtensionPoint : AbstractInstanceExtensionPoint<CommandCallParserProvider, CommandCallParser>(CommandCallParserProvider::class) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.resolve.CommandCallResolver import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.util.ConsoleExperimentalApi @ExperimentalCommandDescriptors public interface CommandCallResolverProvider : InstanceExtension<CommandCallResolver> { @OptIn(ConsoleExperimentalApi::class) @ExperimentalCommandDescriptors public companion object ExtensionPoint : AbstractInstanceExtensionPoint<CommandCallResolverProvider, CommandCallResolver>(CommandCallResolverProvider::class) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/PermissionServiceProvider.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.extension.AbstractInstanceExtensionPoint import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * [权限服务][PermissionService] 提供器. * * 当插件注册 [PermissionService] 后, 默认会使用插件的 [PermissionService]. */ public interface PermissionServiceProvider : InstanceExtension<PermissionService<*>> { @OptIn(ConsoleExperimentalApi::class) public companion object ExtensionPoint : AbstractInstanceExtensionPoint<PermissionServiceProvider, PermissionService<*>>(PermissionServiceProvider::class) // ! BREAKING CHANGE MADE IN 2.11: supertype changed from AbstractSingletonExtensionPoint to AbstractInstanceExtensionPoint } /** * @see PermissionServiceProvider */ @Deprecated("Please implement your own PermissionServiceProvider.", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") // for hidden. public class PermissionServiceProviderImpl(override val instance: PermissionService<*>) : PermissionServiceProvider /** * @see PermissionServiceProvider */ @Deprecated("Please implement your own PermissionServiceProvider.", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") // for hidden. public class PermissionServiceProviderImplLazy(initializer: () -> PermissionService<*>) : PermissionServiceProvider { override val instance: PermissionService<*> by lazy(initializer) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/PluginLoaderProvider.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.Extension import net.mamoe.mirai.console.extension.InstanceExtension import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * 提供扩展 [PluginLoader] * * @see PluginComponentStorage.contributePluginLoader * * * @see Extension * @see PluginLoader * * @see PluginLoaderProviderImplLazy */ public interface PluginLoaderProvider : InstanceExtension<PluginLoader<*, *>> { public companion object ExtensionPoint : AbstractExtensionPoint<PluginLoaderProvider>(PluginLoaderProvider::class) } @Deprecated("Please implement your own PluginLoaderProvider.", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") // for hidden. public class PluginLoaderProviderImpl(override val instance: PluginLoader<*, *>) : PluginLoaderProvider @Deprecated("Please implement your own PluginLoaderProvider.", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") // for hidden. public class PluginLoaderProviderImplLazy(initializer: () -> PluginLoader<*, *>) : PluginLoaderProvider { override val instance: PluginLoader<*, *> by lazy(initializer) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/PostStartupExtension.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.extension.AbstractExtensionPoint import net.mamoe.mirai.console.extension.ExtensionException import net.mamoe.mirai.console.extension.FunctionExtension import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge /** * 在 Console 启动完成后立即在主线程调用的扩展. 用于进行一些必要的延迟初始化. * * 这些扩展只会, 且一定会被调用正好一次. */ public fun interface PostStartupExtension : FunctionExtension { /** * 将在 Console 主线程执行. * * #### 内部实现细节 * 在 [MiraiConsoleImplementationBridge.doStart] 所有 [MiraiConsoleImplementationBridge.phase] 执行完成后顺序调用. * * @throws Exception 所有抛出的 [Exception] 都会被捕获并包装为 [ExtensionException] 抛出, 并停止 [MiraiConsole] */ @Throws(Exception::class) public operator fun invoke() public companion object ExtensionPoint : AbstractExtensionPoint<PostStartupExtension>(PostStartupExtension::class) } ================================================ FILE: mirai-console/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("DEPRECATION_ERROR") package net.mamoe.mirai.console.extensions import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.extension.* import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.extension.SingletonExtensionSelectorImpl import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.info import kotlin.reflect.KClass /** * 用于同时拥有多个 [SingletonExtension] 时选择一个实例. * * 如有多个 [SingletonExtensionSelector] 注册, 将会停止服务器. */ @Deprecated( "Order of extensions is now determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") @Suppress("DEPRECATION_ERROR") public interface SingletonExtensionSelector : FunctionExtension { /** * 表示一个插件注册的 [Extension] */ @Deprecated( "Order of extensions is now determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") public data class Registry<T : Extension>( val plugin: Plugin?, val extension: T, ) /** * @return `null` 表示使用 Console 内置的 [SingletonExtensionSelector] */ public fun <T : Extension> selectSingleton( extensionType: KClass<T>, candidates: Collection<Registry<T>>, ): T? @Deprecated( "Order of extensions is now determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") public companion object ExtensionPoint : AbstractExtensionPoint<SingletonExtensionSelector>(SingletonExtensionSelector::class) { private var instanceField: SingletonExtensionSelector? = null internal val instance: SingletonExtensionSelector get() = instanceField ?: error("") @OptIn(ConsoleInternalApi::class) internal fun init() { check(instanceField == null) { "Internal error: reinitialize SingletonExtensionSelector" } val instances = GlobalComponentStorage.getExtensions<SingletonExtensionSelector>(ExtensionPoint).toList() instanceField = when { instances.isEmpty() -> SingletonExtensionSelectorImpl instances.size == 1 -> { instances.single().also { registry -> MiraiConsole.mainLogger.info { "Loaded SingletonExtensionSelector: ${registry.extension} from ${registry.plugin?.name ?: "<builtin>"}" } }.extension } else -> { val hint = instances.joinToString { reg -> "'${reg.extension}' from '${reg.plugin?.name ?: "<builtin>"}'" } error( "Found too many SingletonExtensionSelectors: $hint. " + "Check your plugins and ensure there is only one external SingletonExtensionSelectors" ) } } } internal fun <T : Extension> selectSingleton( extensionType: KClass<T>, candidates: Collection<ExtensionRegistry<T>>, ): T? = instance.selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) }) internal fun <T : Extension> SingletonExtensionSelector.selectSingleton( extensionType: KClass<T>, candidates: Collection<ExtensionRegistry<T>>, ): T? = selectSingleton(extensionType, candidates.map { Registry(it.plugin, it.extension) }) } } ================================================ FILE: mirai-console/backend/mirai-console/src/fontend/DefaultLoggingProcessProgress.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.fontend import net.mamoe.mirai.utils.MiraiLogger /** * [ProcessProgress] 的简单实现, 前端应该自行实现 [ProcessProgress] * * 此类为前端未实现 [ProcessProgress] 时的缺省实现 */ internal class DefaultLoggingProcessProgress : ProcessProgress { private var message: String = "" private var lastDisplay = 0L private var changed: Boolean = false private var failed: Boolean = false private companion object { private val logger by lazy { MiraiLogger.Factory.create( DefaultLoggingProcessProgress::class, "ProcessProgress" ) } } override fun updateText(txt: String) { this.message = txt changed = true } override fun setTotalSize(totalSize: Long) { } override fun update(processed: Long) { } override fun update(processed: Long, totalSize: Long) { } override fun markFailed() { failed = true } override fun rerender() { if (!changed) return changed = false val crtTime = System.currentTimeMillis() if (crtTime - lastDisplay < 1000) { return } lastDisplay = crtTime if (failed) { logger.error(message) } else { logger.info(message) } } override fun close() { if (failed) { logger.error(message) } else { logger.info(message) } changed = false message = "" } } ================================================ FILE: mirai-console/backend/mirai-console/src/fontend/ProcessProgress.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.fontend import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.util.ConsoleExperimentalApi import java.io.Closeable /** * 一个下载进度 * * @see MiraiConsole.newProcessProgress */ // @ConsoleFrontEndImplementation public interface ProcessProgress : Closeable { /** * 更新当前下载进度的文本 */ public fun updateText(txt: String) /** * 更新当前下载进度的文本 */ public fun updateText(txt: CharSequence) { updateText(txt.toString()) } /** * 设置此进度的最终大小 */ public fun setTotalSize(totalSize: Long) /** * 更新下载进度的进度 * * 在更新进度后需要[刷新显示][rerender] */ public fun update(processed: Long) /** * 更新下载进度的进度 * * 在更新进度后需要[刷新显示][rerender] */ public fun update(processed: Long, totalSize: Long) /** * 将该进度标记为 已失败 / 出错 * * 注: 即使此进度被标记为失败, 也需要[手动释放][close] */ public fun markFailed() /** * 立即重新渲染此进度 */ public fun rerender() /** * 释放此进度, 相关资源和 UI 将会更新 */ override fun close() } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt.template ================================================ /* * 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 */ /* * Important: This file is generated on build by Gradle task 'generateBuildConstants', on GENERATION_DATE */ package net.mamoe.mirai.console.internal import net.mamoe.mirai.console.util.SemVersion import java.time.Instant internal object MiraiConsoleBuildConstants { @JvmStatic val buildDate: Instant = Instant.ofEpochSecond(BUILD_DATE) const val versionConst: String = "VERSION_CONSTANT" @JvmStatic val version: SemVersion = SemVersion(versionConst) } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn( ConsoleExperimentalApi::class, ConsoleFrontEndImplementation::class, ConsoleInternalApi::class, MiraiInternalApi::class, MiraiExperimentalApi::class ) package net.mamoe.mirai.console.internal import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import me.him188.kotlin.dynamic.delegation.dynamicDelegation import net.mamoe.mirai.Bot import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MalformedMiraiConsoleImplementationError import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.SpaceSeparatedCommandCallParser import net.mamoe.mirai.console.command.resolve.BuiltInCommandCallResolver import net.mamoe.mirai.console.events.AutoLoginEvent import net.mamoe.mirai.console.events.StartupEvent import net.mamoe.mirai.console.extensions.CommandCallParserProvider import net.mamoe.mirai.console.extensions.CommandCallResolverProvider import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.extensions.PostStartupExtension import net.mamoe.mirai.console.internal.auth.ConsoleSecretsCalculator import net.mamoe.mirai.console.internal.command.CommandConfig import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.ConfigurationKey import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.MD5 import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig.Account.PasswordKind.PLAIN import net.mamoe.mirai.console.internal.data.builtins.DataScope import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig import net.mamoe.mirai.console.internal.data.builtins.PluginDependenciesConfig import net.mamoe.mirai.console.internal.enduserreadme.EndUserReadmeProcessor import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.extension.GlobalComponentStorageImpl import net.mamoe.mirai.console.internal.logging.LoggerControllerImpl import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger import net.mamoe.mirai.console.internal.logging.externalbind.slf4j.MiraiConsoleSLF4JAdapter import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.shutdown.ShutdownDaemon import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.permission.RootPermission import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.* import net.mamoe.mirai.console.util.cast import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.utils.* import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.KProperty0 internal val MiraiConsole.pluginManagerImpl: PluginManagerImpl get() = this.pluginManager.cast() /** * [MiraiConsole] 公开 API 与前端实现的连接桥. */ @Suppress("SpellCheckingInspection") internal class MiraiConsoleImplementationBridge( private val externalImplementation: MiraiConsoleImplementation, ) : MiraiConsole, MiraiConsoleImplementation by (dynamicDelegation(MiraiConsoleImplementationBridge::externalImplementation)) { override val origin: MiraiConsoleImplementation get() = externalImplementation // FIXME: 12/12/2021 Workaround for compiler regression, should remove when using Kotlin compiller 1.6.20 private operator fun <V> KProperty0<V>.getValue(thisRef: Any?, property: KProperty<*>): V = this.get() override val buildDate: Instant by MiraiConsoleBuildConstants::buildDate override val version: SemVersion by MiraiConsoleBuildConstants::version override val pluginManager: PluginManagerImpl by lazy { PluginManagerImpl(coroutineContext) } // used internally val globalComponentStorage: GlobalComponentStorageImpl by lazy { GlobalComponentStorageImpl() } val shutdownDaemon = ShutdownDaemon.DaemonStarter(this) // tentative workaround for https://github.com/mamoe/mirai/pull/1889#pullrequestreview-887903183 @Volatile var permissionSeviceLoaded: Boolean = false // For protect account.secrets in console with non-password login lateinit var consoleSecretsCalculator: ConsoleSecretsCalculator // MiraiConsoleImplementation define: get() = LoggerControllerImpl() // Need to cache it or else created every call. // It caused config/Console/Logger.yml ignored. override val loggerController: LoggerController by lazy { externalImplementation.loggerController } override val mainLogger: MiraiLogger by lazy { MiraiLogger.Factory.create(MiraiConsole::class, "main") } /** * Delegates the [platformImplementation] with [loggerController]. */ private inner class ControlledLoggerFactory( private val platformImplementation: MiraiLogger.Factory, ) : MiraiLogger.Factory { override fun create(requester: KClass<*>, identity: String?): MiraiLogger { return MiraiConsoleLogger(loggerController, platformImplementation.create(requester, identity)) } override fun create(requester: Class<*>, identity: String?): MiraiLogger { return MiraiConsoleLogger(loggerController, platformImplementation.create(requester, identity)) } } override fun createLoggerFactory(context: MiraiConsoleImplementation.FrontendLoggingInitContext): MiraiLogger.Factory { error("Duplicated logger factory initalization is not allowed. Use MiraiLogger.Factory instead.") } init { // When writing a log: // 1. ControlledLoggerFactory checks if that log level is enabled // 2. ... if enabled, goto 3 // ... if not, return // 3. [externalImplementation] decides how to log the message // 4. [externalImplementation] outputs by using [platform] val context = object : MiraiConsoleImplementation.FrontendLoggingInitContext { val pendingActions = mutableListOf<() -> Unit>() override fun acquirePlatformImplementation(): MiraiLogger.Factory { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return MiraiLoggerFactoryImplementationBridge.instance } override fun invokeAfterInitialization(action: () -> Unit) { pendingActions.add(action) } } val response = externalImplementation.createLoggerFactory(context) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") MiraiLoggerFactoryImplementationBridge.setInstance(ControlledLoggerFactory(response)) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") MiraiLoggerFactoryImplementationBridge.freeze() // forbid any further overrides context.pendingActions.forEach { it.invoke() } } @Deprecated( "Please use the standard way in mirai-core to create loggers, i.e. MiraiLogger.Factory.INSTANCE.create()", replaceWith = ReplaceWith( "MiraiLogger.Factory.create(yourClass::class, identity)", "net.mamoe.mirai.utils.MiraiLogger" ), level = DeprecationLevel.ERROR ) override fun createLogger(identity: String?): MiraiLogger { return MiraiLogger.Factory.create(MiraiConsole::class, identity) } internal fun doStart() { externalImplementation.preStart() @OptIn(ExperimentalCommandDescriptors::class) phase("register builtin componenets") { GlobalComponentStorage.run { contributeConsole(CommandCallParserProvider, SpaceSeparatedCommandCallParser.Provider) contributeConsole(CommandCallResolverProvider, BuiltInCommandCallResolver.Provider) contributeConsole(PermissionServiceProvider, BuiltInPermissionService.Provider()) } } phase("greeting") { val buildDateFormatted = buildDate.atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) mainLogger.info { "Starting mirai-console..." } val printLogo = true // TODO if (printLogo) { mainLogger.info { AnsiMessageBuilder.create(noAnsi = !isAnsiSupported).apply { /* ___ ____ _ _____ _ | \/ (_) (_) / __ \ | | | . . |_ _ __ __ _ _ | / \/ ___ _ __ ___ ___ | | ___ | |\/| | | '__/ _` | | | | / _ \| '_ \/ __|/ _ \| |/ _ \ | | | | | | | (_| | | | \__/\ (_) | | | \__ \ (_) | | __/ \_| |_/_|_| \__,_|_| \____/\___/|_| |_|___/\___/|_|\___| __ __ __ __ ______ __ | \ / \ \ | \/ \ | \ | ▓▓\ / ▓▓\▓▓ ______ ______ \▓▓ ▓▓▓▓▓▓\ ______ _______ _______ ______ | ▓▓ ______ | ▓▓▓\ / ▓▓▓ \/ \ | \| \ ▓▓ \▓▓/ \| \ / \/ \| ▓▓/ \ | ▓▓▓▓\ ▓▓▓▓ ▓▓ ▓▓▓▓▓▓\ \▓▓▓▓▓▓\ ▓▓ ▓▓ | ▓▓▓▓▓▓\ ▓▓▓▓▓▓▓\ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓\ ▓▓ ▓▓▓▓▓▓\ | ▓▓\▓▓ ▓▓ ▓▓ ▓▓ ▓▓ \▓▓/ ▓▓ ▓▓ ▓▓ __| ▓▓ | ▓▓ ▓▓ | ▓▓\▓▓ \| ▓▓ | ▓▓ ▓▓ ▓▓ ▓▓ | ▓▓ \▓▓▓| ▓▓ ▓▓ ▓▓ | ▓▓▓▓▓▓▓ ▓▓ ▓▓__/ \ ▓▓__/ ▓▓ ▓▓ | ▓▓_\▓▓▓▓▓▓\ ▓▓__/ ▓▓ ▓▓ ▓▓▓▓▓▓▓▓ | ▓▓ \▓ | ▓▓ ▓▓ ▓▓ \▓▓ ▓▓ ▓▓\▓▓ ▓▓\▓▓ ▓▓ ▓▓ | ▓▓ ▓▓\▓▓ ▓▓ ▓▓\▓▓ \ \▓▓ \▓▓\▓▓\▓▓ \▓▓▓▓▓▓▓\▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓ \▓▓\▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓ \▓▓▓▓▓▓▓ */ append("\n\n") val textA = """[ Mirai console $version ]""" val logoLength = 94 lightBlue() val barlength = logoLength - textA.length val leftWidth = barlength / 2 repeat(leftWidth) { append('=') } append(textA) repeat(barlength - leftWidth) { append('=') } append('\n') lightYellow().appendLine(""" __ __ __ __ ______ __""") lightYellow().appendLine("""| \ / \ \ | \/ \ | \""") lightYellow().appendLine("""| ▓▓\ / ▓▓\▓▓ ______ ______ \▓▓ ▓▓▓▓▓▓\ ______ _______ _______ ______ | ▓▓ ______""") lightYellow().appendLine("""| ▓▓▓\ / ▓▓▓ \/ \ | \| \ ▓▓ \▓▓/ \| \ / \/ \| ▓▓/ \""") lightYellow().appendLine("""| ▓▓▓▓\ ▓▓▓▓ ▓▓ ▓▓▓▓▓▓\ \▓▓▓▓▓▓\ ▓▓ ▓▓ | ▓▓▓▓▓▓\ ▓▓▓▓▓▓▓\ ▓▓▓▓▓▓▓ ▓▓▓▓▓▓\ ▓▓ ▓▓▓▓▓▓\""") lightYellow().appendLine("""| ▓▓\▓▓ ▓▓ ▓▓ ▓▓ ▓▓ \▓▓/ ▓▓ ▓▓ ▓▓ __| ▓▓ | ▓▓ ▓▓ | ▓▓\▓▓ \| ▓▓ | ▓▓ ▓▓ ▓▓ ▓▓""") lightYellow().appendLine("""| ▓▓ \▓▓▓| ▓▓ ▓▓ ▓▓ | ▓▓▓▓▓▓▓ ▓▓ ▓▓__/ \ ▓▓__/ ▓▓ ▓▓ | ▓▓_\▓▓▓▓▓▓\ ▓▓__/ ▓▓ ▓▓ ▓▓▓▓▓▓▓▓""") lightYellow().appendLine("""| ▓▓ \▓ | ▓▓ ▓▓ ▓▓ \▓▓ ▓▓ ▓▓\▓▓ ▓▓\▓▓ ▓▓ ▓▓ | ▓▓ ▓▓\▓▓ ▓▓ ▓▓\▓▓ \""") lightYellow().appendLine(""" \▓▓ \▓▓\▓▓\▓▓ \▓▓▓▓▓▓▓\▓▓ \▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓ \▓▓\▓▓▓▓▓▓▓ \▓▓▓▓▓▓ \▓▓ \▓▓▓▓▓▓▓""") append("\n") }.toString() } } mainLogger.info { "Backend: version $version, built on $buildDateFormatted." } mainLogger.info { frontEndDescription.render() } mainLogger.info { "Welcome to visit https://mirai.mamoe.net/" } } phase("check coroutineContext") { if (coroutineContext[Job] == null) { throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a Job in it.") } if (coroutineContext[CoroutineExceptionHandler] == null) { throw MalformedMiraiConsoleImplementationError("The coroutineContext given to MiraiConsole must have a CoroutineExceptionHandler in it.") } MiraiConsole.job.invokeOnCompletion { shutdownDaemon.tryStart() Bot.instances.forEach { kotlin.runCatching { it.close() }.exceptionOrNull()?.let(mainLogger::error) } } } ConsoleInput // start phase("load configurations") { mainLogger.verbose { "Loading configurations..." } consoleDataScope.addAndReloadConfig(AutoLoginConfig()) consoleDataScope.addAndReloadConfig(CommandConfig()) consoleDataScope.addAndReloadConfig(PluginDependenciesConfig()) val loggerController = loggerController if (loggerController is LoggerControllerImpl) { consoleDataScope.addAndReloadConfig(loggerController.loggerConfig) } else { consoleDataScope.addAndReloadConfig(LoggerConfig()) } consoleDataScope.reloadAll() if (loggerController is LoggerControllerImpl) { loggerController.onReload() } } phase("initialize logging bridges") { MiraiConsoleSLF4JAdapter.doSlf4JInit() } phase("initialize all plugins") { pluginManager // init consoleSecretsCalculator = ConsoleSecretsCalculator( pluginManager.pluginsDataPath.resolve("Console/console-secrets.key") ).also { it.consoleKey } mainLogger.verbose { "Loading JVM plugins..." } pluginManager.loadAllPluginsUsingBuiltInLoaders() pluginManager.initExternalPluginLoaders().let { count -> mainLogger.verbose { "$count external PluginLoader(s) found. " } if (count != 0) { mainLogger.verbose { "Loading external plugins..." } } } } phase("load all plugins") { pluginManager.loadPlugins(pluginManager.scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider()) mainLogger.verbose { "${PluginManager.plugins.size} plugin(s) loaded." } } // phase("load SingletonExtensionSelector") { // SingletonExtensionSelector.init() // val instance = SingletonExtensionSelector.instance // if (instance is SingletonExtensionSelectorImpl) { // consoleDataScope.addAndReloadConfig(instance.config) // } // } phase("load PermissionService") { mainLogger.verbose { "Loading PermissionService..." } permissionSeviceLoaded = true PermissionService.INSTANCE.let { ps -> if (ps is BuiltInPermissionService) { consoleDataScope.addAndReloadConfig(ps.config) mainLogger.verbose { "Reloaded PermissionService settings." } } else { mainLogger.info { "Loaded PermissionService from plugin ${ GlobalComponentStorage.getPreferredExtension( PermissionServiceProvider ).plugin?.name }" } } } runIgnoreException<UnsupportedOperationException> { ConsoleCommandSender.permit(RootPermission) } } phase("prepare commands") { mainLogger.verbose { "Loading built-in commands..." } BuiltInCommands.registerAll() mainLogger.info { "Prepared built-in commands: ${BuiltInCommands.all.joinToString { it.primaryName }}" } CommandManager // CommandManagerImpl.commandListener // start } phase("enable plugins") { mainLogger.verbose { "Enabling plugins..." } pluginManager.enableAllLoadedPlugins() for (registeredCommand in CommandManager.allRegisteredCommands) { registeredCommand.permission // init } mainLogger.info { "${pluginManager.plugins.count { it.isEnabled }} plugin(s) enabled." } } phase("end-user-readme") { EndUserReadmeProcessor.process(this) } phase("auto-login bots") { runBlocking { val config = DataScope.get<AutoLoginConfig>() val accounts = config.accounts.toList() for (account in accounts.filter { it.configuration[ConfigurationKey.enable]?.toString()?.equals("true", true) ?: true }) { val id = kotlin.runCatching { account.account.toLong() }.getOrElse { error("Bad auto-login account: '${account.account}'") } if (id == 123456L) continue fun BotConfiguration.configBot() { account.configuration[ConfigurationKey.protocol]?.let { protocol -> this.protocol = runCatching { BotConfiguration.MiraiProtocol.valueOf(protocol.toString()) }.getOrElse { throw IllegalArgumentException( "Bad auto-login config value for `protocol` for account $id", it ) } } account.configuration[ConfigurationKey.heartbeatStrategy]?.let { heartStrate -> this.heartbeatStrategy = runCatching { BotConfiguration.HeartbeatStrategy.valueOf(heartStrate.toString()) }.getOrElse { throw IllegalArgumentException( "Bad auto-login config value for `heartbeatStrategy` for account $id", it ) } } account.configuration[ConfigurationKey.device]?.let { device -> fileBasedDeviceInfo(device.toString()) } mainLogger.info { "Auto-login ${account.account}, protocol: ${this.protocol}, heartbeatStrategy: ${this.heartbeatStrategy}" } } val bot = when (account.password.kind) { PLAIN -> { MiraiConsole.addBot(id, account.password.value, BotConfiguration::configBot) } MD5 -> { val md5 = kotlin.runCatching { account.password.value.hexToBytes() }.getOrElse { error("Bad auto-login md5: '${account.password.value}' for account $id") } MiraiConsole.addBot(id, md5, BotConfiguration::configBot) } } runCatching { bot.login() }.onSuccess { launch { AutoLoginEvent.Success(bot = bot).broadcast() } }.onFailure { mainLogger.error(it) runCatching { bot.close() }.onFailure { err -> mainLogger.error("Error in closing bot", err) } launch { AutoLoginEvent.Failure(bot = bot, cause = it).broadcast() } } } } } val startuped = currentTimeSeconds() phase("finally post") { launch { StartupEvent(timestamp = startuped).broadcast() } globalComponentStorage.useEachExtensions(PostStartupExtension) { it.invoke() } } externalImplementation.postStart() mainLogger.info { "mirai-console started successfully." } } @Suppress("SpellCheckingInspection") @Retention(AnnotationRetention.BINARY) @DslMarker internal annotation class ILoveOmaeKumikoForever /** * 表示一个初始化阶段, 无实际作用. */ @ILoveOmaeKumikoForever private inline fun phase(phase: String, block: () -> Unit) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } prePhase(phase) block() postPhase(phase) } override fun prePhase(phase: String) { externalImplementation.prePhase(phase) } override fun postPhase(phase: String) { externalImplementation.postPhase(phase) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/auth/ConsoleBotAuthorization.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.auth import net.mamoe.mirai.auth.BotAuthInfo import net.mamoe.mirai.auth.BotAuthResult import net.mamoe.mirai.auth.BotAuthSession import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import java.io.ByteArrayOutputStream internal class ConsoleBotAuthorization( private val delegate: suspend (BotAuthSession, BotAuthInfo) -> BotAuthResult, ) : BotAuthorization { override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult { return delegate.invoke(session, info) } @OptIn(ConsoleFrontEndImplementation::class) override fun calculateSecretsKey(bot: BotAuthInfo): ByteArray { val calc = MiraiConsoleImplementation.getBridge().consoleSecretsCalculator val writer = ByteArrayOutputStream() writer += calc.consoleKey.asByteArray writer += bot.deviceInfo.apn writer += bot.deviceInfo.device writer += bot.deviceInfo.bootId writer += bot.deviceInfo.imsiMd5 return writer.toByteArray() } private operator fun ByteArrayOutputStream.plusAssign(data: ByteArray) { write(data) } companion object { fun byQRCode(): ConsoleBotAuthorization = ConsoleBotAuthorization { session, _ -> session.authByQRCode() } } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/auth/ConsoleSecretsCalculator.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.auth import net.mamoe.mirai.utils.SecretsProtection import net.mamoe.mirai.utils.lateinitMutableProperty import java.io.ByteArrayOutputStream import java.io.DataOutputStream import java.nio.file.Path import java.util.* import kotlin.io.path.createDirectories import kotlin.io.path.isRegularFile import kotlin.io.path.readBytes import kotlin.io.path.writeBytes internal class ConsoleSecretsCalculator( private val file: Path, ) { internal val consoleKey: SecretsProtection.EscapedByteBuffer get() = _consoleKey private var _consoleKey: SecretsProtection.EscapedByteBuffer by lateinitMutableProperty { loadOrCreate() } fun loadOrCreate(): SecretsProtection.EscapedByteBuffer { if (file.isRegularFile()) { return SecretsProtection.EscapedByteBuffer(file.readBytes()) } file.parent?.createDirectories() val dataStream = ByteArrayOutputStream() val dataWriter = DataOutputStream(dataStream) repeat(3) { dataWriter.writeUTF(UUID.randomUUID().toString()) } val data = dataStream.toByteArray() file.writeBytes(data) return SecretsProtection.EscapedByteBuffer(data) } fun reloadOrCreate() { _consoleKey = loadOrCreate() } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/command/CommandManagerImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.command import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.Command.Companion.allNames import net.mamoe.mirai.console.command.CommandManager.INSTANCE.findDuplicate import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCallParser.Companion.parseCommandCall import net.mamoe.mirai.console.command.resolve.CommandCallInterceptor.Companion.intercepted import net.mamoe.mirai.console.command.resolve.CommandCallResolver.Companion.resolve import net.mamoe.mirai.console.command.resolve.getOrElse import net.mamoe.mirai.console.internal.data.builtins.DataScope import net.mamoe.mirai.console.internal.util.ifNull import net.mamoe.mirai.console.permission.PermissionService.Companion.testPermission import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.utils.childScope import java.lang.reflect.InvocationTargetException import java.util.concurrent.locks.ReentrantLock import kotlin.coroutines.CoroutineContext @OptIn(ExperimentalCommandDescriptors::class) internal class CommandManagerImpl( parentCoroutineContext: CoroutineContext ) : CommandManager, CoroutineScope by parentCoroutineContext.childScope("CommandManagerImpl") { @Suppress("PropertyName") @JvmField internal val _registeredCommands: MutableList<Command> = mutableListOf() @JvmField internal val requiredPrefixCommandMap: MutableMap<String, Command> = mutableMapOf() @JvmField internal val optionalPrefixCommandMap: MutableMap<String, Command> = mutableMapOf() @JvmField internal val modifyLock = ReentrantLock() /** * 从原始的 command 中解析出 Command 对象 */ override fun matchCommand(commandName: String): Command? { if (commandName.startsWith(commandPrefix)) { return requiredPrefixCommandMap[commandName.substringAfter(commandPrefix).lowercase()] } return optionalPrefixCommandMap[commandName.lowercase()] } ///// IMPL override fun getRegisteredCommands(owner: CommandOwner): List<Command> = _registeredCommands.filter { it.owner == owner } override val allRegisteredCommands: List<Command> get() = _registeredCommands.toList() // copy override val commandPrefix: String get() = DataScope.get<CommandConfig>().commandPrefix override fun unregisterAllCommands(owner: CommandOwner) { for (registeredCommand in getRegisteredCommands(owner)) { unregisterCommand(registeredCommand) } } override fun registerCommand(command: Command, override: Boolean): Boolean { if (command is CompositeCommand) { command.overloads // init lazy } kotlin.runCatching { command.permission // init lazy command.secondaryNames // init lazy command.description // init lazy command.usage // init lazy }.onFailure { throw IllegalStateException("Failed to init command ${command}.", it) } modifyLock.withLock { if (!override) { if (command.findDuplicate() != null) return false } _registeredCommands.add(command) if (command.prefixOptional) { for (name in command.allNames) { val lowerCaseName = name.lowercase() optionalPrefixCommandMap[lowerCaseName] = command requiredPrefixCommandMap[lowerCaseName] = command } } else { for (name in command.allNames) { val lowerCaseName = name.lowercase() optionalPrefixCommandMap.remove(lowerCaseName) // ensure resolution consistency requiredPrefixCommandMap[lowerCaseName] = command } } return true } } override fun findDuplicateCommand(command: Command): Command? = _registeredCommands.firstOrNull { it.allNames intersectsIgnoringCase command.allNames } override fun unregisterCommand(command: Command): Boolean = modifyLock.withLock { if (command.prefixOptional) { command.allNames.forEach { optionalPrefixCommandMap.remove(it.lowercase()) } } command.allNames.forEach { requiredPrefixCommandMap.remove(it.lowercase()) } _registeredCommands.remove(command) } override fun isCommandRegistered(command: Command): Boolean = command in _registeredCommands } // Don't move into CommandManager, compilation error / VerifyError @OptIn(ExperimentalCommandDescriptors::class) internal suspend fun executeCommandImpl( message0: Message, caller: CommandSender, checkPermission: Boolean, ): CommandExecuteResult { val message = message0 .intercepted(caller) .getOrElse { return CommandExecuteResult.Intercepted(null, null, null, it) } val call = message.toMessageChain() .parseCommandCall(caller) .ifNull { return CommandExecuteResult.UnresolvedCommand(null) } .let { raw -> raw.intercepted() .getOrElse { return CommandExecuteResult.Intercepted(raw, null, null, it) } } val resolved = call .resolve() .getOrElse { return it } .let { raw -> raw.intercepted() .getOrElse { return CommandExecuteResult.Intercepted(call, raw, null, it) } } val command = resolved.callee if (checkPermission && !command.permission.testPermission(caller)) { return CommandExecuteResult.PermissionDenied(command, call, resolved) } return try { resolved.calleeSignature.call(resolved) CommandExecuteResult.Success(resolved.callee, call, resolved) } catch (e: CommandArgumentParserException) { CommandExecuteResult.IllegalArgument(e, resolved.callee, call, resolved) } catch (e: InvocationTargetException) { when (val target = e.cause) { is CommandArgumentParserException -> CommandExecuteResult.IllegalArgument(target, resolved.callee, call, resolved) null -> CommandExecuteResult.ExecutionFailed(e, resolved.callee, call, resolved) else -> CommandExecuteResult.ExecutionFailed(target, resolved.callee, call, resolved) } } catch (e: Throwable) { CommandExecuteResult.ExecutionFailed(e, resolved.callee, call, resolved) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/command/CommandReflector.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.descriptor.* import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.console.internal.data.classifierAsKClassOrNull import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.buildMessageChain import net.mamoe.mirai.utils.runBIO import kotlin.reflect.* import kotlin.reflect.full.* internal val ILLEGAL_SUB_NAME_CHARS = "\\/!@#$%^&*()_+-={}[];':\",.<>?`~".toCharArray() internal val WORD_DIVIDER = Regex("""(?<!\\)\s+""") // 分词的空格 internal val ESCAPE_PATTERN = Regex("""\\(.)""") // 转义符和被转义的符号 internal val STOP_PARSE_INDICATOR = Regex("""(?<!\\)\s+--\s+|^--\s+""") // 暂停解析符号,前后都有空格,或前为字符串开始 internal val QUOTE_BEGIN = Regex("""(?<!\\)\s"|^\s"|^"""") // 左双引号,前有一个未转义的空格,或前为字符串开始 internal val QUOTE_END = Regex("""(?<!\\)"\s|(?<!\\)"$""") // 右双引号,前无转义符,后为空格或字符串结束 // 提取引号包围的原始文本,用空格分割剩余部分,并还原被转义的符号 internal fun String.parseParameterTextToList(): List<CharSequence> = if (this.isBlank()) emptyList() else this.split(QUOTE_BEGIN, 2) .flatMapIndexed { index, part -> if (index > 0) part.split(QUOTE_END, 2) else listOf(part) } .let { unquotedTexts -> return if (unquotedTexts.size == 3) unquotedTexts[0].parseParameterTextToList() + listOf(unquotedTexts[1]) + unquotedTexts[2].parseParameterTextToList() else this@parseParameterTextToList.split(WORD_DIVIDER).filterNot { it.isBlank() } .map { it.replace(ESCAPE_PATTERN, "$1") }.toList() } internal fun CharSequence.flattenCommandTextParts(addCallback: (CharSequence) -> Unit) = this.split(STOP_PARSE_INDICATOR, 2).filterNot { it.isBlank() }.let { stopParseParts -> stopParseParts.getOrNull(0)?.parseParameterTextToList()?.forEach(addCallback) stopParseParts.getOrNull(1)?.let(addCallback) } internal fun Any.flattenCommandComponents(): MessageChain = buildMessageChain { when (this@flattenCommandComponents) { is PlainText -> this@flattenCommandComponents.content.flattenCommandTextParts { +PlainText(it) } is CharSequence -> this@flattenCommandComponents.flattenCommandTextParts { +PlainText(it) } is SingleMessage -> add(this@flattenCommandComponents) is Array<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) } is Iterable<*> -> this@flattenCommandComponents.forEach { if (it != null) addAll(it.flattenCommandComponents()) } else -> add(this@flattenCommandComponents.toString()) } } @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") internal object CompositeCommandSubCommandAnnotationResolver : SubCommandAnnotationResolver { override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) = function.hasAnnotation<CompositeCommand.SubCommand>() override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> { val annotated = function.findAnnotation<CompositeCommand.SubCommand>()!!.value return if (annotated.isEmpty()) arrayOf(function.name) else annotated } override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? = parameter.findAnnotation<CompositeCommand.Name>()?.value override fun getDescription(ownerCommand: Command, function: KFunction<*>): String? = function.findAnnotation<CompositeCommand.Description>()?.value } @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") internal object SimpleCommandSubCommandAnnotationResolver : SubCommandAnnotationResolver { override fun hasAnnotation(ownerCommand: Command, function: KFunction<*>) = function.hasAnnotation<SimpleCommand.Handler>() override fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> = emptyArray() override fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? = parameter.findAnnotation<SimpleCommand.Name>()?.value override fun getDescription(ownerCommand: Command, function: KFunction<*>): String = ownerCommand.description } internal interface SubCommandAnnotationResolver { fun hasAnnotation(ownerCommand: Command, function: KFunction<*>): Boolean fun getSubCommandNames(ownerCommand: Command, function: KFunction<*>): Array<out String> fun getAnnotatedName(ownerCommand: Command, parameter: KParameter): String? fun getDescription(ownerCommand: Command, function: KFunction<*>): String? } @ConsoleExperimentalApi public class IllegalCommandDeclarationException : Exception { public override val message: String? public constructor( ownerCommand: Command, correspondingFunction: KFunction<*>, message: String?, ) : super("Illegal command declaration: ${correspondingFunction.name} declared in ${ownerCommand::class.qualifiedName}") { this.message = message } public constructor( ownerCommand: Command, message: String?, ) : super("Illegal command declaration: ${ownerCommand::class.qualifiedName}") { this.message = message } } @OptIn(ExperimentalCommandDescriptors::class) internal class CommandReflector( val command: Command, private val annotationResolver: SubCommandAnnotationResolver, ) { @Suppress("NOTHING_TO_INLINE") private inline fun KFunction<*>.illegalDeclaration( message: String, ): Nothing { throw IllegalCommandDeclarationException(command, this, message) } private fun KFunction<*>.isSubCommandFunction(): Boolean = annotationResolver.hasAnnotation(command, this) private fun KFunction<*>.checkExtensionReceiver() { this.extensionReceiverParameter?.let { receiver -> val classifier = receiver.type.classifierAsKClassOrNull() if (classifier != null) { if (!classifier.isSubclassOf(CommandSender::class) && !classifier.isSubclassOf(CommandContext::class)) { illegalDeclaration("Extension receiver parameter type is not subclass of CommandSender nor CommandContext.") } } } } private fun KFunction<*>.checkNames() { val names = annotationResolver.getSubCommandNames(command, this) for (name in names) { ILLEGAL_SUB_NAME_CHARS.find { it in name }?.let { illegalDeclaration("'$it' is forbidden in command name.") } } } private fun KFunction<*>.checkModifiers() { if (isInline) illegalDeclaration("Command function cannot be inline") if (visibility == KVisibility.PRIVATE) illegalDeclaration("Command function must be accessible from Mirai Console, that is, effectively public.") if (this.hasAnnotation<JvmStatic>()) illegalDeclaration("Command function must not be static.") // should we allow abstract? // if (isAbstract) illegalDeclaration("Command function cannot be abstract") } fun generateUsage(overloads: Iterable<CommandSignatureFromKFunction>): String { return generateUsage(command, annotationResolver, overloads) } companion object { fun generateUsage( command: Command, annotationResolver: SubCommandAnnotationResolver?, overloads: Iterable<CommandSignature> ): String { return overloads.joinToString("\n") { subcommand -> buildString { if (command.prefixOptional) { append("(") append(CommandManager.commandPrefix) append(")") } else { append(CommandManager.commandPrefix) } //if (command is CompositeCommand) { append(command.primaryName) append(" ") //} append(subcommand.valueParameters.joinToString(" ") { it.render() }) if (annotationResolver != null && subcommand is CommandSignatureFromKFunction) { annotationResolver.getDescription(command, subcommand.originFunction)?.let { description -> append(" # ") append(description) } } } } } private fun <T> AbstractCommandValueParameter<T>.render(): String { return when (this) { is AbstractCommandValueParameter.Extended, is AbstractCommandValueParameter.UserDefinedType<*>, -> { val nameToRender = this.name ?: this.type.classifierAsKClass().simpleName if (isOptional) "[$nameToRender]" else "<$nameToRender>" } is AbstractCommandValueParameter.StringConstant -> { this.expectingValue } } } } fun validate(signatures: List<CommandSignatureFromKFunctionImpl>) { data class ErasedParameterInfo( val index: Int, val name: String?, val type: KType, // ignore nullability val additional: String?, ) data class ErasedVariantInfo( val receiver: ErasedParameterInfo?, val valueParameters: List<ErasedParameterInfo>, ) fun CommandParameter<*>.toErasedParameterInfo(index: Int): ErasedParameterInfo { return ErasedParameterInfo( index, this.name, this.type.withNullability(false), if (this is AbstractCommandValueParameter.StringConstant) this.expectingValue else null ) } val candidates = signatures.map { variant -> variant to ErasedVariantInfo( variant.receiverParameter?.toErasedParameterInfo(0), variant.valueParameters.mapIndexed { index, parameter -> parameter.toErasedParameterInfo(index) } ) } val groups = candidates.groupBy { it.second } val clashes = groups.entries.find { (_, value) -> value.size > 1 } ?: return throw CommandDeclarationClashException(command, clashes.value.map { it.first }) } @Throws(IllegalCommandDeclarationException::class) fun findSubCommands(): List<CommandSignatureFromKFunctionImpl> { return command::class.functions // exclude static later .asSequence() .filter { it.isSubCommandFunction() } .onEach { it.checkExtensionReceiver() } .onEach { it.checkModifiers() } .onEach { it.checkNames() } .flatMap { function -> val names = annotationResolver.getSubCommandNames(command, function) if (names.isEmpty()) sequenceOf(createMapEntry(null, function)) else names.associateWith { function }.asSequence() } .map { (name, function) -> val functionNameAsValueParameter = name?.split(' ')?.mapIndexed { index, s -> createStringConstantParameterForName(index, s) } .orEmpty() val valueParameters = function.valueParameters.toMutableList() var receiverParameter = function.extensionReceiverParameter if (receiverParameter == null && valueParameters.isNotEmpty()) { val valueFirstParameter = valueParameters[0] val classifier = valueFirstParameter.type.classifierAsKClassOrNull() if (classifier != null && isAcceptableReceiverType(classifier) ) { receiverParameter = valueFirstParameter valueParameters.removeAt(0) } } val functionValueParameters = valueParameters.associateBy { it.toUserDefinedCommandParameter() } CommandSignatureFromKFunctionImpl( receiverParameter = receiverParameter?.toCommandReceiverParameter(), valueParameters = functionNameAsValueParameter + functionValueParameters.keys, originFunction = function ) { call -> val args = LinkedHashMap<KParameter, Any?>() for ((commandParameter, value) in call.resolvedValueArguments) { if (commandParameter is AbstractCommandValueParameter.StringConstant) { continue } val functionParameter = functionValueParameters[commandParameter] ?: error("Could not find a corresponding function parameter '${commandParameter.name}'") args[functionParameter] = value } val instanceParameter = function.instanceParameter if (instanceParameter != null) { check(instanceParameter.type.classifierAsKClass().isInstance(command)) { "Bad command call resolved. " + "Function expects instance parameter ${instanceParameter.type} whereas actual instance is ${command::class}." } args[instanceParameter] = command } if (receiverParameter != null) { val receiverType = receiverParameter.type.classifierAsKClass() if (receiverType.isSubclassOf(CommandContext::class)) { args[receiverParameter] = CommandContextImpl(call.caller, call.originalMessage) } else { check(receiverType.isInstance(call.caller)) { "Bad command call resolved. " + "Function expects receiver parameter ${receiverParameter.type} whereas actual is ${call.caller::class}." } args[receiverParameter] = call.caller } } // mirai-console#341 if (function.isSuspend) { function.callSuspendBy(args) } else { runBIO { function.callBy(args) } } } }.toList() } private fun isAcceptableReceiverType(classifier: KClass<Any>) = classifier.isSubclassOf(CommandSender::class) || classifier.isSubclassOf(CommandContext::class) @Suppress("SameParameterValue") private fun <K, V> createMapEntry(key: K, value: V) = object : Map.Entry<K, V> { override val key: K get() = key override val value: V get() = value } private fun KParameter.toCommandReceiverParameter(): CommandReceiverParameter<*> { check(!this.isVararg) { "Receiver cannot be vararg." } val classifier = type.classifierAsKClass() return when { classifier.isSubclassOf(CommandSender::class) -> { CommandReceiverParameter.Sender(this.type.isMarkedNullable, this.type) } classifier.isSubclassOf(CommandContext::class) -> { CommandReceiverParameter.Context(this.type.isMarkedNullable, this.type) } else -> { throw IllegalArgumentException("Receiver must be subclass of CommandSender or CommandContext") } } } private fun createStringConstantParameterForName( index: Int, expectingValue: String ): AbstractCommandValueParameter.StringConstant { return AbstractCommandValueParameter.StringConstant("#$index", expectingValue, true) } private fun KParameter.toUserDefinedCommandParameter(): AbstractCommandValueParameter.UserDefinedType<*> { return AbstractCommandValueParameter.UserDefinedType<Any?>( nameForCommandParameter(), this.isOptional, this.isVararg, this.type ) // Any? is erased } private fun KParameter.nameForCommandParameter(): String? = annotationResolver.getAnnotatedName(command, this) ?: this.name } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/command/CommnadConfig.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.ValueName import net.mamoe.mirai.console.data.value @ValueDescription( """ 内置指令系统配置 """ ) internal class CommandConfig : ReadOnlyPluginConfig("Command") { @ValueDescription( """ 指令前缀, 默认 "/" """ ) @ValueName("commandPrefix") val commandPrefix: String by value("/") } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/command/builtin/LoginCommandImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.command.builtin import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.launch import net.mamoe.mirai.Bot import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.internal.data.builtins.DataScope import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.scopeWith import net.mamoe.mirai.message.nextMessageOrNull import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.Either import net.mamoe.mirai.utils.Either.Companion.flatMapNull import net.mamoe.mirai.utils.Either.Companion.fold import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.secondsToMillis @Suppress("RESTRICTED_CONSOLE_COMMAND_OWNER") // IDE plugin internal open class LoginCommandImpl : SimpleCommand( ConsoleCommandOwner, "login", "登录", description = "登录一个账号", ), BuiltInCommandInternal { internal open suspend fun doLogin(bot: Bot) { kotlin.runCatching { bot.login() this }.onFailure { bot.close() }.getOrThrow() } // workaround since LoginCommand is an object @Handler suspend fun CommandSender.handle( id: Long, password: String? = null, protocol: BotConfiguration.MiraiProtocol? = null, ) { fun BotConfiguration.setup(protocol: BotConfiguration.MiraiProtocol?): BotConfiguration { val config = DataScope.get<AutoLoginConfig>() val account = config.accounts.firstOrNull { it.account == id.toString() } if (account != null) { account.configuration[AutoLoginConfig.Account.ConfigurationKey.protocol]?.let { proto -> try { this.protocol = BotConfiguration.MiraiProtocol.valueOf(proto.toString()) } catch (_: Throwable) { // } } account.configuration[AutoLoginConfig.Account.ConfigurationKey.heartbeatStrategy]?.let { heartStrate -> try { this.heartbeatStrategy = BotConfiguration.HeartbeatStrategy.valueOf(heartStrate.toString()) } catch (_: Throwable) { // } } account.configuration[AutoLoginConfig.Account.ConfigurationKey.device]?.let { device -> fileBasedDeviceInfo(device.toString()) } } if (protocol != null) { this.protocol = protocol } return this } fun getPassword(id: Long): Either<ByteArray, String?> { val config = DataScope.get<AutoLoginConfig>() val acc = config.accounts.firstOrNull { it.account == id.toString() } ?: return Either.right(null) val strv = acc.password.value return if (acc.password.kind == AutoLoginConfig.Account.PasswordKind.MD5) Either.left(strv.hexToBytes()) else Either.right( strv ) } val pwd = Either.right<ByteArray, String?>(password).flatMapNull { getPassword(id) } kotlin.runCatching { pwd.fold( onLeft = { pass -> MiraiConsole.addBot(id, pass) { setup(protocol) }.also { doLogin(it) } }, onRight = { pass -> if (pass == null) { sendMessage("Could not find '$id' in AutoLogin config. Please specify password.") return } MiraiConsole.addBot(id, pass) { setup(protocol) }.also { doLogin(it) } } ) }.fold( onSuccess = { scopeWith(ConsoleCommandSender).sendMessage("${it.nick} ($id) Login successful") }, onFailure = { throwable -> scopeWith(ConsoleCommandSender).sendMessage( "Login failed: ${throwable.localizedMessage ?: throwable.message ?: throwable.toString()}" + if (this is CommandSenderOnMessage<*>) { MiraiConsole.launch(CoroutineName("stacktrace delayer from Login")) { fromEvent.nextMessageOrNull(60.secondsToMillis) { it.message.contentEquals("stacktrace") } } "\n 1 分钟内发送 stacktrace 以获取堆栈信息" } else "" ) throw throwable } ) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/command/internal.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.command import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import kotlin.math.max import kotlin.math.min internal infix fun Array<out String>.intersectsIgnoringCase(other: Array<out String>): Boolean { val max = this.size.coerceAtMost(other.size) for (i in 0 until max) { if (this[i].equals(other[i], ignoreCase = true)) return true } return false } internal fun String.fuzzyMatchWith(target: String): Double { if (this == target) { return 1.0 } var match = 0 for (i in 0..(max(this.lastIndex, target.lastIndex))) { val t = target.getOrNull(match) ?: break if (t == this.getOrNull(i)) { match++ } } val longerLength = max(this.length, target.length) val shorterLength = min(this.length, target.length) return match.toDouble() / (longerLength + (shorterLength - match)) } /** * @return candidates */ internal fun Group.fuzzySearchMember( nameCardTarget: String, minRate: Double = 0.2, // 参与判断, 用于提示可能的解 matchRate: Double = 0.6,// 最终选择的最少需要的匹配率, 减少歧义 /** * 如果有多个值超过 [matchRate], 并相互差距小于等于 [disambiguationRate], 则认为有较大歧义风险, 返回可能的解的列表. */ disambiguationRate: Double = 0.1, ): List<Pair<Member, Double>> { val candidates = (this.members.asSequence() + botAsMember) .associateWith { it.nameCard.fuzzyMatchWith(nameCardTarget) } .filter { it.value >= minRate } .toList() .sortedByDescending { it.second } val bestMatches = candidates.filter { it.second >= matchRate } return when { bestMatches.isEmpty() -> candidates bestMatches.size == 1 -> listOf(bestMatches.single().first to 1.0) else -> { if (bestMatches.first().second - bestMatches.last().second <= disambiguationRate) { // resolution ambiguity candidates } else { listOf(bestMatches.first().first to 1.0) } } } } internal fun Command.findOrCreateCommandPermission(parent: Permission): Permission { val id = owner.permissionId("command.$primaryName") return PermissionService.INSTANCE[id] ?: PermissionService.INSTANCE.register(id, description, parent) } //// internal ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/CompositeValueImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.util.ConsoleExperimentalApi // type inference bug internal fun <T> PluginData.createCompositeSetValueImpl(tToValue: (T) -> Value<T>): CompositeSetValueImpl<T> { return object : CompositeSetValueImpl<T>(tToValue) { override fun onChanged() { this@createCompositeSetValueImpl.onValueChanged(this) } } } internal abstract class CompositeSetValueImpl<T>( tToValue: (T) -> Value<T> // should override onChanged ) : CompositeSetValue<T>, AbstractValueImpl<Set<T>>() { private val internalSet: MutableSet<Value<T>> = mutableSetOf() private var _value: Set<T> = internalSet.shadowMap({ it.value }, tToValue).observable { onChanged() } override var value: Set<T> get() = _value set(v) { if (_value != v) { @Suppress("LocalVariableName") val _value = _value as MutableSet<T> _value.clear() _value.addAll(v) onChanged() } } override fun setValueBySerializer(value: Set<T>) { val thisValue = this.value if (!thisValue.tryPatch(value)) { this.value = value // deep set } } protected abstract fun onChanged() override fun toString(): String = _value.toString() override fun equals(other: Any?): Boolean = other is CompositeSetValueImpl<*> && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return value.hashCode() * 31 + super.hashCode() } } // type inference bug internal fun <T> PluginData.createCompositeListValueImpl(tToValue: (T) -> Value<T>): CompositeListValueImpl<T> { return object : CompositeListValueImpl<T>(tToValue) { override fun onChanged() { this@createCompositeListValueImpl.onValueChanged(this) } } } internal abstract class CompositeListValueImpl<T>( tToValue: (T) -> Value<T> // should override onChanged ) : CompositeListValue<T>, AbstractValueImpl<List<T>>() { private val internalList: MutableList<Value<T>> = mutableListOf() private val _value: List<T> = internalList.shadowMap({ it.value }, tToValue).observable { onChanged() } override var value: List<T> get() = _value set(v) { if (_value != v) { @Suppress("LocalVariableName") val _value = _value as MutableList<T> _value.clear() _value.addAll(v) onChanged() } } override fun setValueBySerializer(value: List<T>) { val thisValue = this.value if (!thisValue.tryPatch(value)) { this.value = value // deep set } } protected abstract fun onChanged() override fun toString(): String = _value.toString() override fun equals(other: Any?): Boolean = other is CompositeListValueImpl<*> && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return value.hashCode() * 31 + super.hashCode() } } // workaround to a type inference bug internal fun <K, V> PluginData.createCompositeMapValueImpl( mapInitializer: (() -> MutableMap<Value<K>, Value<V>>?)? = null, kToValue: (K) -> Value<K>, vToValue: (V) -> Value<V>, valueToK: (Value<K>) -> K = Value<K>::value, valueToV: (Value<V>) -> V = Value<V>::value, applyToShadowedMap: ((MutableMap<K, V>) -> (MutableMap<K, V>))? = null ): CompositeMapValueImpl<K, V> { return object : CompositeMapValueImpl<K, V>(mapInitializer, kToValue, vToValue, valueToK, valueToV, applyToShadowedMap) { override fun onChanged() = this@createCompositeMapValueImpl.onValueChanged(this) } } // TODO: 2020/6/24 在一个 Value 被删除后停止追踪其更新. internal abstract class CompositeMapValueImpl<K, V>( mapInitializer: (() -> MutableMap<Value<K>, Value<V>>?)? = null, @JvmField internal val kToValue: (K) -> Value<K>, // should override onChanged @JvmField internal val vToValue: (V) -> Value<V>, // should override onChanged @JvmField internal val valueToK: (Value<K>) -> K = Value<K>::value, @JvmField internal val valueToV: (Value<V>) -> V = Value<V>::value, applyToShadowedMap: ((MutableMap<K, V>) -> (MutableMap<K, V>))? = null ) : CompositeMapValue<K, V>, AbstractValueImpl<Map<K, V>>() { @JvmField internal val internalList: MutableMap<Value<K>, Value<V>> = mapInitializer?.invoke() ?: mutableMapOf() private var _value: MutableMap<K, V> = internalList.shadowMap(valueToK, kToValue, valueToV, vToValue).let { applyToShadowedMap?.invoke(it) ?: it }.observable { onChanged() } override var value: Map<K, V> get() = _value set(v) { if (_value != v) { @Suppress("LocalVariableName") val _value = _value _value.clear() _value.putAll(v) onChanged() } } override fun setValueBySerializer(value: Map<K, V>) { val thisValue = this.value as MutableMap<K, V> if (!thisValue.tryPatch(value)) { this.value = value // deep set } } protected abstract fun onChanged() override fun toString(): String = _value.toString() override fun equals(other: Any?): Boolean = other is CompositeMapValueImpl<*, *> && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return value.hashCode() * 31 + super.hashCode() } } internal fun <K, V> MutableMap<K, V>.patchImpl(_new: Map<K, V>) { val new = _new.toMutableMap() val iterator = this.iterator() for (entry in iterator) { val newValue = new.remove(entry.key) if (newValue != null) { // has replacer if (entry.value?.tryPatch(newValue) != true) { // patch not supported, or old value is null entry.setValue(newValue) } // else: patched, no remove } else { // no replacer iterator.remove() } } putAll(new) } internal fun <C : MutableCollection<E>, E> C.patchImpl(_new: Collection<E>) { this.clear() this.addAll(_new) } /** * True if successfully patched */ @Suppress("UNCHECKED_CAST") internal fun Any.tryPatch(any: Any): Boolean = when { this is MutableCollection<*> && any is Collection<*> -> { (this as MutableCollection<Any?>).patchImpl(any as Collection<Any?>) true } this is MutableMap<*, *> && any is Map<*, *> -> { (this as MutableMap<Any?, Any?>).patchImpl(any as Map<Any?, Any?>) true } this is Value<*> && any is Value<*> -> any.value?.let { otherValue -> this.value?.tryPatch(otherValue) } == true else -> false } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/MemoryPluginDataStorageImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data import net.mamoe.mirai.console.data.MemoryPluginDataStorage import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.PluginDataHolder import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.util.ConsoleExperimentalApi internal class MemoryPluginDataStorageImpl : PluginDataStorage, MemoryPluginDataStorage, MutableMap<Class<out PluginData>, PluginData> by mutableMapOf() { @Suppress("UNCHECKED_CAST") override fun load(holder: PluginDataHolder, instance: PluginData) { instance.onInit(holder, this) } override fun store(holder: PluginDataHolder, instance: PluginData) { // no-op } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data import kotlinx.serialization.json.Json import kotlinx.serialization.modules.plus import net.mamoe.mirai.console.data.MultiFilePluginDataStorage import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.PluginDataHolder import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.logging.lazyInitMiraiLogger import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.yamlkt.Yaml import java.io.File import java.nio.file.Path @Suppress("RedundantVisibilityModifier") // might be public in the future internal open class MultiFilePluginDataStorageImpl( public final override val directoryPath: Path, private val logger: MiraiLogger = lazyInitMiraiLogger { MiraiLogger.Factory.create(MultiFilePluginDataStorageImpl::class) }, ) : PluginDataStorage, MultiFilePluginDataStorage { init { directoryPath.mkdir() } public override fun load(holder: PluginDataHolder, instance: PluginData) { instance.onInit(holder, this) // 0xFEFF is BOM, handle UTF8-BOM val file = getPluginDataFile(holder, instance) val text = file.readText().removePrefix("\uFEFF") if (text.isNotBlank()) { try { when (instance.saveType) { PluginData.SaveType.YAML -> { val yaml = createYaml(instance) yaml.decodeFromString(instance.updaterSerializer, text) } PluginData.SaveType.JSON -> { val json = createJson(instance) json.decodeFromString(instance.updaterSerializer, text) } } } catch (cause: Throwable) { // backup data file file.copyTo(file.resolveSibling("${file.name}.${currentTimeMillis()}.bak")) throw cause } } else { this.store(holder, instance) // save an initial copy } // logger.verbose { "Successfully loaded PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" } } internal fun getPluginDataFileInternal(holder: PluginDataHolder, instance: PluginData): File { return getPluginDataFile(holder, instance) } protected open fun getPluginDataFile(holder: PluginDataHolder, instance: PluginData): File { val name = instance.saveName val dir = directoryPath.resolve(holder.dataHolderName) if (dir.isFile) { error("Target directory $dir for holder $holder is occupied by a file therefore data ${instance::class.qualifiedNameOrTip} can't be saved.") } dir.mkdir() val file = dir.resolve("$name.${instance.saveType.extension}") if (file.isDirectory) { error("Target File $file is occupied by a directory therefore data ${instance::class.qualifiedNameOrTip} can't be saved.") } // logger.verbose { "File allocated for ${instance.saveName}: $file" } return file.toFile().also { it.parentFile?.mkdirs() it.createNewFile() } } @ConsoleExperimentalApi public override fun store(holder: PluginDataHolder, instance: PluginData) { getPluginDataFile(holder, instance).writeText( kotlin.runCatching { when (instance.saveType) { PluginData.SaveType.YAML -> { val yaml = createYaml(instance) yaml.encodeToString(instance.updaterSerializer, Unit).also { yaml.decodeAnyFromString(it) // test yaml } } PluginData.SaveType.JSON -> { val json = createJson(instance) json.encodeToString(instance.updaterSerializer, Unit).also { json.decodeFromString(instance.updaterSerializer, it) // test json } } } }.recoverCatching { logger.warning( "Could not save ${instance.saveName} in ${instance.saveType.name} format due to exception in ${instance.saveType.name} encoder. " + "Please report this exception and relevant configurations to https://github.com/mamoe/mirai/issues/new/choose", it ) if (instance.saveType == PluginData.SaveType.JSON) { throw it } val json = createJson(instance) json.encodeToString(instance.updaterSerializer, Unit).also { string -> json.decodeFromString(instance.updaterSerializer, string) // test json } }.getOrElse { throw IllegalStateException( "Exception while saving $instance, saveName=${instance.saveName} in json format", it ) } ) // logger.verbose { "Successfully saved PluginData: ${instance.saveName} (containing ${instance.castOrNull<AbstractPluginData>()?.valueNodes?.size} properties)" } } private fun createYaml(instance: PluginData): Yaml { return Yaml { this.serializersModule = MessageSerializers.serializersModule + instance.serializersModule // MessageSerializers.serializersModule is dynamic } } private fun createJson(instance: PluginData): Json { return Json { serializersModule = MessageSerializers.serializersModule + instance.serializersModule // MessageSerializers.serializersModule is dynamic prettyPrint = true ignoreUnknownKeys = true isLenient = true allowStructuredMapKeys = true encodeDefaults = true classDiscriminator = "#class" } } } internal fun Path.mkdir(): Boolean = this.toFile().mkdir() internal val Path.isFile: Boolean get() = this.toFile().isFile internal val Path.isDirectory: Boolean get() = this.toFile().isDirectory ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/PluginDataImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_SUPER_CLASS") @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import net.mamoe.mirai.console.data.AbstractPluginData import net.mamoe.mirai.console.data.AbstractPluginData.ValueNode import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.ValueName import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.yamlkt.Comment import net.mamoe.yamlkt.YamlNullableDynamicSerializer import java.lang.reflect.Constructor import kotlin.reflect.KAnnotatedElement /** * Internal implementation for [PluginData] including: * - Reflection on Kotlin properties and Java fields * - Auto-saving */ internal abstract class PluginDataImpl { init { @Suppress("LeakingThis") check(this is AbstractPluginData) } private fun findNodeInstance(name: String): ValueNode<*>? { check(this is AbstractPluginData) return valueNodes.firstOrNull { it.valueName == name } } @OptIn(ExperimentalSerializationApi::class) internal open val updaterSerializer: KSerializer<Unit> = object : KSerializer<Unit> { override val descriptor: SerialDescriptor by lazy { check(this@PluginDataImpl is AbstractPluginData) kotlinx.serialization.descriptors.buildClassSerialDescriptor((this@PluginDataImpl as PluginData).saveName) { for (valueNode in valueNodes) valueNode.run { element(valueName, updaterSerializer.descriptor, annotations = annotations, isOptional = true) } } } override fun deserialize(decoder: Decoder) { val descriptor = descriptor with(decoder.beginStructure(descriptor)) { if (runCatching { decodeSequentially() }.getOrElse { false }) { var index = 0 repeat(decodeCollectionSize(descriptor)) { val valueName = decodeSerializableElement(descriptor, index++, String.serializer()) val node = findNodeInstance(valueName) if (node == null) { decodeSerializableElement(descriptor, index++, YamlNullableDynamicSerializer) } else { decodeSerializableElement(descriptor, index++, node.updaterSerializer) } } } else { outerLoop@ while (true) { innerLoop@ while (true) { val index = decodeElementIndex(descriptor) if (index == CompositeDecoder.DECODE_DONE) { //check(valueName == null) { "name must be null at this moment." } break@outerLoop } val node = findNodeInstance(descriptor.getElementName(index)) if (node == null) { decodeSerializableElement(descriptor, index, YamlNullableDynamicSerializer) } else { decodeSerializableElement(descriptor, index, node.updaterSerializer) } break@innerLoop } } } endStructure(descriptor) } } override fun serialize(encoder: Encoder, value: Unit) { check(this@PluginDataImpl is AbstractPluginData) val descriptor = descriptor with(encoder.beginStructure(descriptor)) { repeat(descriptor.elementsCount) { index -> encodeSerializableElement( descriptor, index, valueNodes.find { it.valueName == descriptor.getElementName(index) }?.updaterSerializer ?: error("Cannot find a serializer for ${descriptor.getElementName(index)}"), Unit ) } endStructure(descriptor) } } } } internal fun KAnnotatedElement.getAnnotationListForValueSerialization(): List<Annotation> { return this.annotations.mapNotNull { when (it) { is SerialName -> error("@SerialName is not supported on Value. Please use @ValueName instead") is ValueName -> null is ValueDescription -> COMMENT_CONSTRUCTOR(it.value) else -> it } } } private val COMMENT_CONSTRUCTOR = findAnnotationImplementationClassConstructor<Comment>()!! @Suppress("NOTHING_TO_INLINE") internal inline operator fun <T : Any?> Constructor<T>.invoke(vararg args: Any?): T = this.newInstance(*args) internal inline fun <reified T : Any> findAnnotationImplementationClassConstructor(): Constructor<out T>? { @Suppress("UNCHECKED_CAST") return T::class.nestedClasses .find { it.simpleName?.endsWith("Impl") == true }?.java?.run { constructors.singleOrNull() } as Constructor<out T>? } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/_PluginData.value.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.ReferenceValue import net.mamoe.mirai.console.data.SerializerAwareValue import net.mamoe.mirai.console.util.ConsoleExperimentalApi import kotlin.reflect.KClass internal object BuiltInSerializerConstants { //// region BuiltInSerializerConstantsPrimitives CODEGEN //// @JvmStatic internal val ByteSerializerDescriptor = Byte.serializer().descriptor @JvmStatic internal val ShortSerializerDescriptor = Short.serializer().descriptor @JvmStatic internal val IntSerializerDescriptor = Int.serializer().descriptor @JvmStatic internal val LongSerializerDescriptor = Long.serializer().descriptor @JvmStatic internal val FloatSerializerDescriptor = Float.serializer().descriptor @JvmStatic internal val DoubleSerializerDescriptor = Double.serializer().descriptor @JvmStatic internal val CharSerializerDescriptor = Char.serializer().descriptor @JvmStatic internal val BooleanSerializerDescriptor = Boolean.serializer().descriptor @JvmStatic internal val StringSerializerDescriptor = String.serializer().descriptor //// endregion BuiltInSerializerConstantsPrimitives CODEGEN //// } @Suppress("UNCHECKED_CAST") internal fun <T : Any> PluginData.valueImplPrimitive(kClass: KClass<T>): SerializerAwareValue<T>? { return when (kClass) { //// region PluginData_valueImplPrimitive CODEGEN //// Byte::class -> byteValueImpl() Short::class -> shortValueImpl() Int::class -> intValueImpl() Long::class -> longValueImpl() Float::class -> floatValueImpl() Double::class -> doubleValueImpl() Char::class -> charValueImpl() Boolean::class -> booleanValueImpl() String::class -> stringValueImpl() //// endregion PluginData_valueImplPrimitive CODEGEN //// else -> error("Internal error: unexpected type passed: ${kClass.qualifiedName}") } as SerializerAwareValue<T>? } //// region PluginData_value_PrimitivesImpl CODEGEN //// internal fun PluginData.valueImpl(default: Byte): SerializerAwareValue<Byte> { return object : ByteValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.byteValueImpl(): SerializerAwareValue<Byte> { return object : ByteValueImpl() { override fun onChanged() = this@byteValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: Short): SerializerAwareValue<Short> { return object : ShortValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.shortValueImpl(): SerializerAwareValue<Short> { return object : ShortValueImpl() { override fun onChanged() = this@shortValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: Int): SerializerAwareValue<Int> { return object : IntValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.intValueImpl(): SerializerAwareValue<Int> { return object : IntValueImpl() { override fun onChanged() = this@intValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: Long): SerializerAwareValue<Long> { return object : LongValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.longValueImpl(): SerializerAwareValue<Long> { return object : LongValueImpl() { override fun onChanged() = this@longValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: Float): SerializerAwareValue<Float> { return object : FloatValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.floatValueImpl(): SerializerAwareValue<Float> { return object : FloatValueImpl() { override fun onChanged() = this@floatValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: Double): SerializerAwareValue<Double> { return object : DoubleValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.doubleValueImpl(): SerializerAwareValue<Double> { return object : DoubleValueImpl() { override fun onChanged() = this@doubleValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: Char): SerializerAwareValue<Char> { return object : CharValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.charValueImpl(): SerializerAwareValue<Char> { return object : CharValueImpl() { override fun onChanged() = this@charValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: Boolean): SerializerAwareValue<Boolean> { return object : BooleanValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.booleanValueImpl(): SerializerAwareValue<Boolean> { return object : BooleanValueImpl() { override fun onChanged() = this@booleanValueImpl.onValueChanged(this) } } internal fun PluginData.valueImpl(default: String): SerializerAwareValue<String> { return object : StringValueImpl(default) { override fun onChanged() = this@valueImpl.onValueChanged(this) } } internal fun PluginData.stringValueImpl(): SerializerAwareValue<String> { return object : StringValueImpl() { override fun onChanged() = this@stringValueImpl.onValueChanged(this) } } //// endregion PluginData_value_PrimitivesImpl CODEGEN //// internal class LazyReferenceValueImpl<T> : ReferenceValue<T>, AbstractValueImpl<T>() { private var initialied: Boolean = false private var valueField: T? = null @Suppress("UNCHECKED_CAST") override var value: T get() { check(initialied) { "Internal error: LazyReferenceValueImpl.valueField isn't initialized" } return valueField as T } set(value) { initialied = true valueField = value } override fun toString(): String { return valueField.toString() } override fun equals(other: Any?): Boolean { if (other === this) return true if (other?.javaClass != this.javaClass) return false other as LazyReferenceValueImpl<*> if (other.valueField != valueField) return false return true } override fun hashCode(): Int { return valueField?.hashCode() ?: 0 } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/_PrimitiveValueDeclarations.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import net.mamoe.mirai.console.data.* import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * The super class to all ValueImpl */ internal abstract class AbstractValueImpl<T> : Value<T> { open fun setValueBySerializer(value: T) { this.value = value } } internal fun <T> Value<T>.setValueBySerializer(value: T) { if (this is SerializableValue<T>) { return this.delegate.setValueBySerializer(value) } this.castOrInternalError<AbstractValueImpl<T>>().setValueBySerializer(value) } //// region PrimitiveValuesImpl CODEGEN //// internal abstract class ByteValueImpl : ByteValue, SerializerAwareValue<Byte>, KSerializer<Unit>, AbstractValueImpl<Byte> { constructor() constructor(default: Byte) { _value = default } private var _value: Byte? = null final override var value: Byte get() = _value ?: error("ByteValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.ByteSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Byte.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Byte.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "ByteValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is ByteValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class ShortValueImpl : ShortValue, SerializerAwareValue<Short>, KSerializer<Unit>, AbstractValueImpl<Short> { constructor() constructor(default: Short) { _value = default } private var _value: Short? = null final override var value: Short get() = _value ?: error("ShortValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.ShortSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Short.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Short.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "ShortValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is ShortValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class IntValueImpl : IntValue, SerializerAwareValue<Int>, KSerializer<Unit>, AbstractValueImpl<Int> { constructor() constructor(default: Int) { _value = default } private var _value: Int? = null final override var value: Int get() = _value ?: error("IntValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.IntSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Int.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Int.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "IntValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is IntValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class LongValueImpl : LongValue, SerializerAwareValue<Long>, KSerializer<Unit>, AbstractValueImpl<Long> { constructor() constructor(default: Long) { _value = default } private var _value: Long? = null final override var value: Long get() = _value ?: error("LongValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.LongSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Long.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Long.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "LongValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is LongValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class FloatValueImpl : FloatValue, SerializerAwareValue<Float>, KSerializer<Unit>, AbstractValueImpl<Float> { constructor() constructor(default: Float) { _value = default } private var _value: Float? = null final override var value: Float get() = _value ?: error("FloatValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.FloatSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Float.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Float.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "FloatValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is FloatValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class DoubleValueImpl : DoubleValue, SerializerAwareValue<Double>, KSerializer<Unit>, AbstractValueImpl<Double> { constructor() constructor(default: Double) { _value = default } private var _value: Double? = null final override var value: Double get() = _value ?: error("DoubleValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.DoubleSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Double.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Double.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "DoubleValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is DoubleValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class CharValueImpl : CharValue, SerializerAwareValue<Char>, KSerializer<Unit>, AbstractValueImpl<Char> { constructor() constructor(default: Char) { _value = default } private var _value: Char? = null final override var value: Char get() = _value ?: error("CharValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.CharSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Char.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Char.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "CharValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is CharValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class BooleanValueImpl : BooleanValue, SerializerAwareValue<Boolean>, KSerializer<Unit>, AbstractValueImpl<Boolean> { constructor() constructor(default: Boolean) { _value = default } private var _value: Boolean? = null final override var value: Boolean get() = _value ?: error("BooleanValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.BooleanSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = Boolean.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(Boolean.serializer().deserialize(decoder)) override fun toString(): String = _value?.toString() ?: "BooleanValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is BooleanValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } internal abstract class StringValueImpl : StringValue, SerializerAwareValue<String>, KSerializer<Unit>, AbstractValueImpl<String> { constructor() constructor(default: String) { _value = default } private var _value: String? = null final override var value: String get() = _value ?: error("StringValue.value should be initialized before get.") set(v) { if (v != this._value) { if (this._value == null) { this._value = v } else { this._value = v onChanged() } } } protected abstract fun onChanged() final override val serializer: KSerializer<Unit> get() = this final override val descriptor: SerialDescriptor get() = BuiltInSerializerConstants.StringSerializerDescriptor final override fun serialize(encoder: Encoder, value: Unit) = String.serializer().serialize(encoder, this.value) final override fun deserialize(decoder: Decoder) = setValueBySerializer(String.serializer().deserialize(decoder)) override fun toString(): String = _value ?: "StringValue.value not yet initialized." override fun equals(other: Any?): Boolean = other is StringValueImpl && other::class.java == this::class.java && other._value == this._value override fun hashCode(): Int { val value = _value return if (value == null) 1 else value.hashCode() * 31 } } //// endregion PrimitiveValuesImpl CODEGEN //// ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPOSED_SUPER_CLASS") package net.mamoe.mirai.console.internal.data.builtins import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser import net.mamoe.mirai.console.command.descriptor.InternalCommandValueArgumentParserExtensions import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.SecretsProtection import net.mamoe.yamlkt.Comment import net.mamoe.yamlkt.YamlDynamicSerializer @ConsoleExperimentalApi @ValueDescription("自动登录配置") public class AutoLoginConfig : AutoSavePluginConfig("AutoLogin") { @Serializable public data class Account( @Comment("账号, 现只支持 QQ 数字账号") val account: String, val password: Password, @Comment( """ 账号配置. 可用配置列表 (注意大小写): protocol: ANDROID_PHONE / ANDROID_PAD / ANDROID_WATCH / MACOS / IPAD device: device.json enable: true heartbeatStrategy: STAT_HB / REGISTER / NONE """ ) val configuration: Map<ConfigurationKey, @Serializable(with = YamlDynamicSerializer::class) Any> = mapOf(), ) { @Serializable public data class Password /** @since 2.10.0 */ public constructor( @Comment("密码种类, 可选 PLAIN 或 MD5") val kind: PasswordKind, /** @since 2.10.0 */ @SerialName("value") @Comment("密码内容, PLAIN 时为密码文本, MD5 时为 16 进制") val value0: SecretsProtection.EscapedString, ) { public constructor(kind: PasswordKind, value: String) : this( kind, SecretsProtection.EscapedString(SecretsProtection.escape(value.toByteArray())), ) internal val value: String get() = value0.asString } @Suppress("EnumEntryName") @Serializable public enum class ConfigurationKey { protocol, device, enable, heartbeatStrategy, ; public object Parser : CommandValueArgumentParser<ConfigurationKey>, InternalCommandValueArgumentParserExtensions<ConfigurationKey>() { override fun parse(raw: String, sender: CommandSender): ConfigurationKey { val key = values().find { it.name.equals(raw, ignoreCase = true) } if (key != null) return key illegalArgument("未知配置项, 可选值: ${values().joinToString()}") } } } @Serializable public enum class PasswordKind { PLAIN, MD5; public object Parser : CommandValueArgumentParser<ConfigurationKey>, InternalCommandValueArgumentParserExtensions<ConfigurationKey>() { override fun parse(raw: String, sender: CommandSender): ConfigurationKey { val key = ConfigurationKey.values().find { it.name.equals(raw, ignoreCase = true) } if (key != null) return key illegalArgument("未知配置项, 可选值: ${ConfigurationKey.values().joinToString()}") } } } } public val accounts: MutableList<Account> by value( mutableListOf( Account( account = "123456", password = Account.Password(Account.PasswordKind.PLAIN, "pwd"), configuration = mapOf( Account.ConfigurationKey.protocol to "ANDROID_PHONE", Account.ConfigurationKey.device to "device.json", Account.ConfigurationKey.enable to true, Account.ConfigurationKey.heartbeatStrategy to "STAT_HB" ) ) ) ) } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/builtins/ConsoleDataScopeImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data.builtins import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.data.AutoSavePluginDataHolder import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.childScope import net.mamoe.mirai.utils.minutesToMillis import java.util.concurrent.ConcurrentLinkedQueue import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass internal val DataScope get() = MiraiConsoleImplementation.getBridge().consoleDataScope internal class ConsoleDataScopeImpl( parentCoroutineContext: CoroutineContext, private val dataStorage: PluginDataStorage, private val configStorage: PluginDataStorage, ) : CoroutineScope by parentCoroutineContext.childScope("ConsoleDataScope"), MiraiConsoleImplementation.ConsoleDataScope { override val dataHolder: AutoSavePluginDataHolder = ConsoleBuiltInPluginDataHolder(this.coroutineContext) override val configHolder: AutoSavePluginDataHolder = ConsoleBuiltInPluginConfigHolder(this.coroutineContext) private val data: MutableCollection<PluginData> = ConcurrentLinkedQueue() // tentatively make it thread-safe private val configs: MutableCollection<PluginConfig> = ConcurrentLinkedQueue() override fun addAndReloadConfig(config: PluginConfig) { configs.add(config) configStorage.load(configHolder, config) } override fun <T : PluginData> find(type: KClass<T>): T? { @Suppress("UNCHECKED_CAST") return (data.find { type.isInstance(it) } ?: configs.find { type.isInstance(it) }) as T? } /** * Set and override, for tests only. */ @TestOnly inline fun <reified T : PluginData> set(value: T) { data.removeIf { value::class.isInstance(it) } data.add(value) } override fun reloadAll() { data.forEach { dt -> dataStorage.load(dataHolder, dt) } configs.forEach { config -> configStorage.load(dataHolder, config) } } } private class ConsoleBuiltInPluginDataHolder( parentCoroutineContext: CoroutineContext ) : AutoSavePluginDataHolder, CoroutineScope by parentCoroutineContext.childScope("ConsoleBuiltInPluginDataHolder") { override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis override val dataHolderName: String get() = "Console" } private class ConsoleBuiltInPluginConfigHolder( parentCoroutineContext: CoroutineContext ) : AutoSavePluginDataHolder, CoroutineScope by parentCoroutineContext.childScope("ConsoleBuiltInPluginConfigHolder") { override val autoSaveIntervalMillis: LongRange = 1.minutesToMillis..10.minutesToMillis override val dataHolderName: String get() = "Console" } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/builtins/EndUserReadmeData.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.data.builtins import net.mamoe.mirai.console.data.PluginDataHolder import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.util.ConsoleExperimentalApi @OptIn(ConsoleExperimentalApi::class) internal class EndUserReadmeData : ReadOnlyPluginConfig("EndUserReadme") { val data: MutableMap<String, String> by value() private lateinit var storage_: PluginDataStorage private lateinit var owner_: PluginDataHolder override fun onInit(owner: PluginDataHolder, storage: PluginDataStorage) { this.storage_ = storage this.owner_ = owner } internal fun saveNow() { storage_.store(owner_, this) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.data.builtins import kotlinx.serialization.Serializable import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.logging.AbstractLoggerController import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi @ConsoleFrontEndImplementation @MiraiExperimentalApi public class LoggerConfig : ReadOnlyPluginConfig("Logger") { @ConsoleExperimentalApi @ValueDescription( """ 默认日志输出等级 可选值: ALL, VERBOSE, DEBUG, INFO, WARNING, ERROR, NONE """ ) public val defaultPriority: AbstractLoggerController.LogPriority by value(AbstractLoggerController.LogPriority.INFO) @ConsoleExperimentalApi @ValueDescription( """ 特定日志记录器输出等级 """ ) public val loggers: Map<String, AbstractLoggerController.LogPriority> by value( mapOf( "example.logger" to AbstractLoggerController.LogPriority.NONE, "console.debug" to AbstractLoggerController.LogPriority.NONE, "Bot" to AbstractLoggerController.LogPriority.ALL, "org.eclipse.aether.internal" to AbstractLoggerController.LogPriority.INFO, "org.apache.http.wire" to AbstractLoggerController.LogPriority.INFO, ) ) @Serializable public class Binding @MiraiExperimentalApi public constructor( public val slf4j: Boolean = true, ) @ValueDescription( """ 是否启动外部日志框架桥接 """ ) public val binding: Binding by value { Binding() } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/builtins/PluginDependenciesConfig.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.data.builtins import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.ValueDescription import net.mamoe.mirai.console.data.value @Suppress("RemoveExplicitTypeArguments") internal class PluginDependenciesConfig : ReadOnlyPluginConfig("PluginDependencies") { @ValueDescription("远程仓库, 如无必要无需修改") val repoLoc: List<String> by value( listOf<String>( "https://maven.aliyun.com/repository/central", "https://repo1.maven.org/maven2/", ) ) } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/collectionUtil.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("DuplicatedCode") package net.mamoe.mirai.console.internal.data import java.util.function.BiConsumer import java.util.function.BiFunction import java.util.function.Function // TODO: 2020/6/24 优化性能: 引入一个 comparator 之类来替代将 Int 包装为 Value<Int> 后进行 containsKey 比较的方法 // java.util.function was used in mirai-core // direct improve apis of java.util.function @Suppress( "MANY_IMPL_MEMBER_NOT_IMPLEMENTED", "MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED", "UNCHECKED_CAST", "USELESS_CAST", "ACCIDENTAL_OVERRIDE", "TYPE_MISMATCH", "NOTHING_TO_OVERRIDE", "EXPLICIT_OVERRIDE_REQUIRED_IN_MIXED_MODE", "CONFLICTING_INHERITED_JVM_DECLARATIONS", ) // for improve java.util.function apis internal open class ShadowMap<K, V, KR, VR>( @JvmField protected val originMapComputer: () -> MutableMap<K, V>, @JvmField protected val kTransform: (K) -> KR, @JvmField protected val kTransformBack: (KR) -> K, @JvmField protected val vTransform: (V) -> VR, @JvmField protected val vTransformBack: (VR) -> V ) : MutableMap<KR, VR> { override val size: Int get() = originMapComputer().size override fun containsKey(key: KR): Boolean = originMapComputer().containsKey(key.let(kTransformBack)) override fun containsValue(value: VR): Boolean = originMapComputer().containsValue(value.let(vTransformBack)) override fun get(key: KR): VR? = originMapComputer()[key.let(kTransformBack)]?.let(vTransform) override fun isEmpty(): Boolean = originMapComputer().isEmpty() override val entries: MutableSet<MutableMap.MutableEntry<KR, VR>> get() = originMapComputer().entries.shadowMap( transform = { entry: MutableMap.MutableEntry<K, V> -> object : MutableMap.MutableEntry<KR, VR> { override val key: KR get() = entry.key.let(kTransform) override val value: VR get() = entry.value.let(vTransform) override fun setValue(newValue: VR): VR = entry.setValue(newValue.let(vTransformBack)).let(vTransform) override fun hashCode(): Int = 17 * 31 + (key?.hashCode() ?: 0) + (value?.hashCode() ?: 0) override fun toString(): String = "$key=$value" override fun equals(other: Any?): Boolean { if (other == null || other !is Map.Entry<*, *>) return false return other.key == key && other.value == value } } } as ((MutableMap.MutableEntry<K, V>) -> MutableMap.MutableEntry<KR, VR>), // type inference bug transformBack = { entry -> object : MutableMap.MutableEntry<K, V> { override val key: K get() = entry.key.let(kTransformBack) override val value: V get() = entry.value.let(vTransformBack) override fun setValue(newValue: V): V = entry.setValue(newValue.let(vTransform)).let(vTransformBack) override fun hashCode(): Int = 17 * 31 + (key?.hashCode() ?: 0) + (value?.hashCode() ?: 0) override fun toString(): String = "$key=$value" override fun equals(other: Any?): Boolean { if (other == null || other !is Map.Entry<*, *>) return false return other.key == key && other.value == value } } } ) override val keys: MutableSet<KR> get() = originMapComputer().keys.shadowMap(kTransform, kTransformBack) override val values: MutableCollection<VR> get() = originMapComputer().values.shadowMap(vTransform, vTransformBack) override fun clear() = originMapComputer().clear() override fun put(key: KR, value: VR): VR? = originMapComputer().put(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) override fun putAll(from: Map<out KR, VR>) { from.forEach { (kr, vr) -> originMapComputer()[kr.let(kTransformBack)] = vr.let(vTransformBack) } } override fun remove(key: KR): VR? = originMapComputer().remove(key.let(kTransformBack))?.let(vTransform) override fun remove(key: KR, value: VR): Boolean = originMapComputer().remove(key.let(kTransformBack), value?.let(vTransformBack)) override fun toString(): String = originMapComputer().toString() override fun hashCode(): Int = originMapComputer().hashCode() override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as ShadowMap<*, *, *, *> if (originMapComputer != other.originMapComputer) return false if (kTransform != other.kTransform) return false if (kTransformBack != other.kTransformBack) return false if (vTransform != other.vTransform) return false if (vTransformBack != other.vTransformBack) return false return true } override fun putIfAbsent(key: KR, value: VR): VR? = originMapComputer().putIfAbsent(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) override fun replace(key: KR, oldValue: VR, newValue: VR): Boolean = originMapComputer().replace(key.let(kTransformBack), oldValue.let(vTransformBack), newValue.let(vTransformBack)) override fun replace(key: KR, value: VR): VR? = originMapComputer().replace(key.let(kTransformBack), value.let(vTransformBack))?.let(vTransform) override fun compute(key: KR, remappingFunction: BiFunction<in KR, in VR?, out VR?>): VR? = originMapComputer().compute(key.let(kTransformBack)) { k1, v1 -> remappingFunction.apply(k1.let(kTransform), v1?.let(vTransform))?.let(vTransformBack) }?.let(vTransform) override fun computeIfAbsent(key: KR, mappingFunction: Function<in KR, out VR>): VR = originMapComputer().computeIfAbsent(key.let(kTransformBack)) { k -> mappingFunction.apply(k.let(kTransform)).let(vTransformBack) }.let(vTransform) @Suppress("WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE") override fun computeIfPresent(key: KR, remappingFunction: BiFunction<in KR, in VR, out VR?>): VR? = originMapComputer().computeIfPresent(key.let(kTransformBack)) { k, v -> remappingFunction.apply(k.let(kTransform), v.let(vTransform))?.let(vTransformBack) }?.let(vTransform) @Suppress("WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE") override fun merge(key: KR, value: VR, remappingFunction: BiFunction<in VR, in VR, out VR?>): VR? { @Suppress("NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER") return originMapComputer().merge(key.let(kTransformBack), value.let(vTransformBack)) { k, v -> remappingFunction.apply(k.let(vTransform), v.let(vTransform))?.let(vTransformBack) }?.let(vTransform) } override fun forEach(action: BiConsumer<in KR, in VR>) { @Suppress("JavaMapForEach") originMapComputer().forEach { t, u -> action.accept(t.let(kTransform), u.let(vTransform)) } } override fun replaceAll(function: BiFunction<in KR, in VR, out VR>) { originMapComputer().replaceAll { t, u -> function.apply(t.let(kTransform), u.let(vTransform))?.let(vTransformBack) } } } internal fun <K, V, KR, VR> MutableMap<K, V>.shadowMap( kTransform: (K) -> KR, kTransformBack: (KR) -> K, vTransform: (V) -> VR, vTransformBack: (VR) -> V ): MutableMap<KR, VR> = ShadowMap({ this }, kTransform, kTransformBack, vTransform, vTransformBack) internal inline fun <E, R> MutableCollection<E>.shadowMap( crossinline transform: (E) -> R, crossinline transformBack: (R) -> E ): MutableCollection<R> { return object : MutableCollection<R> { override val size: Int get() = this@shadowMap.size override fun contains(element: R): Boolean = this@shadowMap.any { it.let(transform) == element } override fun containsAll(elements: Collection<R>): Boolean = elements.all(::contains) override fun isEmpty(): Boolean = this@shadowMap.isEmpty() override fun iterator(): MutableIterator<R> = object : MutableIterator<R> { private val delegate = this@shadowMap.iterator() override fun hasNext(): Boolean = delegate.hasNext() override fun next(): R = delegate.next().let(transform) override fun remove() = delegate.remove() override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun add(element: R): Boolean = this@shadowMap.add(element.let(transformBack)) override fun addAll(elements: Collection<R>): Boolean = this@shadowMap.addAll(elements.map(transformBack)) override fun clear() = this@shadowMap.clear() override fun remove(element: R): Boolean = this@shadowMap.removeIf { it.let(transform) == element } override fun removeAll(elements: Collection<R>): Boolean = elements.all(::remove) override fun retainAll(elements: Collection<R>): Boolean = this@shadowMap.retainAll(elements.mapTo(HashSet(elements.size), transformBack)) override fun toString(): String = this@shadowMap.toString() override fun hashCode(): Int = this@shadowMap.hashCode() } } internal inline fun <E, R> MutableList<E>.shadowMap( crossinline transform: (E) -> R, crossinline transformBack: (R) -> E ): MutableList<R> { return object : MutableList<R> { override val size: Int get() = this@shadowMap.size override fun contains(element: R): Boolean = this@shadowMap.any { it.let(transform) == element } override fun containsAll(elements: Collection<R>): Boolean = elements.all(::contains) override fun get(index: Int): R = this@shadowMap[index].let(transform) override fun indexOf(element: R): Int = this@shadowMap.indexOfFirst { it.let(transform) == element } override fun isEmpty(): Boolean = this@shadowMap.isEmpty() override fun iterator(): MutableIterator<R> = object : MutableIterator<R> { private val delegate = this@shadowMap.iterator() override fun hasNext(): Boolean = delegate.hasNext() override fun next(): R = delegate.next().let(transform) override fun remove() = delegate.remove() override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun lastIndexOf(element: R): Int = this@shadowMap.indexOfLast { it.let(transform) == element } override fun add(element: R): Boolean = this@shadowMap.add(element.let(transformBack)) override fun add(index: Int, element: R) = this@shadowMap.add(index, element.let(transformBack)) override fun addAll(index: Int, elements: Collection<R>): Boolean = this@shadowMap.addAll(index, elements.map(transformBack)) override fun addAll(elements: Collection<R>): Boolean = this@shadowMap.addAll(elements.map(transformBack)) override fun clear() = this@shadowMap.clear() override fun listIterator(): MutableListIterator<R> = object : MutableListIterator<R> { private val delegate = this@shadowMap.listIterator() override fun hasPrevious(): Boolean = delegate.hasPrevious() override fun nextIndex(): Int = delegate.nextIndex() override fun previous(): R = delegate.previous().let(transform) override fun previousIndex(): Int = delegate.previousIndex() override fun add(element: R) = delegate.add(element.let(transformBack)) override fun hasNext(): Boolean = delegate.hasNext() override fun next(): R = delegate.next().let(transform) override fun remove() = delegate.remove() override fun set(element: R) = delegate.set(element.let(transformBack)) override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun listIterator(index: Int): MutableListIterator<R> = object : MutableListIterator<R> { private val delegate = this@shadowMap.listIterator(index) override fun hasPrevious(): Boolean = delegate.hasPrevious() override fun nextIndex(): Int = delegate.nextIndex() override fun previous(): R = delegate.previous().let(transform) override fun previousIndex(): Int = delegate.previousIndex() override fun add(element: R) = delegate.add(element.let(transformBack)) override fun hasNext(): Boolean = delegate.hasNext() override fun next(): R = delegate.next().let(transform) override fun remove() = delegate.remove() override fun set(element: R) = delegate.set(element.let(transformBack)) override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun remove(element: R): Boolean = this@shadowMap.removeIf { it.let(transform) == element } override fun removeAll(elements: Collection<R>): Boolean = elements.all(::remove) override fun removeAt(index: Int): R = this@shadowMap.removeAt(index).let(transform) override fun retainAll(elements: Collection<R>): Boolean = this@shadowMap.retainAll(elements.map(transformBack)) override fun set(index: Int, element: R): R = this@shadowMap.set(index, element.let(transformBack)).let(transform) override fun subList(fromIndex: Int, toIndex: Int): MutableList<R> = this@shadowMap.subList(fromIndex, toIndex).map(transform).toMutableList() override fun toString(): String = this@shadowMap.toString() override fun hashCode(): Int = this@shadowMap.hashCode() } } internal inline fun <E, R> MutableSet<E>.shadowMap( crossinline transform: (E) -> R, crossinline transformBack: (R) -> E ): MutableSet<R> { return object : MutableSet<R> { override val size: Int get() = this@shadowMap.size override fun contains(element: R): Boolean = this@shadowMap.any { it.let(transform) == element } override fun containsAll(elements: Collection<R>): Boolean = elements.all(::contains) override fun isEmpty(): Boolean = this@shadowMap.isEmpty() override fun iterator(): MutableIterator<R> = object : MutableIterator<R> { private val delegate = this@shadowMap.iterator() override fun hasNext(): Boolean = delegate.hasNext() override fun next(): R = delegate.next().let(transform) override fun remove() = delegate.remove() override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun add(element: R): Boolean = this@shadowMap.add(element.let(transformBack)) override fun addAll(elements: Collection<R>): Boolean = this@shadowMap.addAll(elements.map(transformBack)) override fun clear() = this@shadowMap.clear() override fun remove(element: R): Boolean = this@shadowMap.removeIf { it.let(transform) == element } override fun removeAll(elements: Collection<R>): Boolean = elements.all(::remove) override fun retainAll(elements: Collection<R>): Boolean = this@shadowMap.retainAll(elements.mapTo(HashSet(elements.size), transformBack)) override fun toString(): String = this@shadowMap.toString() override fun hashCode(): Int = this@shadowMap.hashCode() } } /* internal inline fun <T> dynamicList(crossinline supplier: () -> List<T>): List<T> { return object : List<T> { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) override fun containsAll(elements: Collection<T>): Boolean = supplier().containsAll(elements) override fun get(index: Int): T = supplier()[index] override fun indexOf(element: T): Int = supplier().indexOf(element) override fun isEmpty(): Boolean = supplier().isEmpty() override fun iterator(): Iterator<T> = supplier().iterator() override fun lastIndexOf(element: T): Int = supplier().lastIndexOf(element) override fun listIterator(): ListIterator<T> = supplier().listIterator() override fun listIterator(index: Int): ListIterator<T> = supplier().listIterator(index) override fun subList(fromIndex: Int, toIndex: Int): List<T> = supplier().subList(fromIndex, toIndex) override fun toString(): String = supplier().toString() override fun hashCode(): Int = supplier().hashCode() } } internal inline fun <T> dynamicSet(crossinline supplier: () -> Set<T>): Set<T> { return object : Set<T> { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) override fun containsAll(elements: Collection<T>): Boolean = supplier().containsAll(elements) override fun isEmpty(): Boolean = supplier().isEmpty() override fun iterator(): Iterator<T> = supplier().iterator() override fun toString(): String = supplier().toString() override fun hashCode(): Int = supplier().hashCode() } } internal inline fun <T> dynamicMutableList(crossinline supplier: () -> MutableList<T>): MutableList<T> { return object : MutableList<T> { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) override fun containsAll(elements: Collection<T>): Boolean = supplier().containsAll(elements) override fun get(index: Int): T = supplier()[index] override fun indexOf(element: T): Int = supplier().indexOf(element) override fun isEmpty(): Boolean = supplier().isEmpty() override fun iterator(): MutableIterator<T> = supplier().iterator() override fun lastIndexOf(element: T): Int = supplier().lastIndexOf(element) override fun add(element: T): Boolean = supplier().add(element) override fun add(index: Int, element: T) = supplier().add(index, element) override fun addAll(index: Int, elements: Collection<T>): Boolean = supplier().addAll(index, elements) override fun addAll(elements: Collection<T>): Boolean = supplier().addAll(elements) override fun clear() = supplier().clear() override fun listIterator(): MutableListIterator<T> = supplier().listIterator() override fun listIterator(index: Int): MutableListIterator<T> = supplier().listIterator(index) override fun remove(element: T): Boolean = supplier().remove(element) override fun removeAll(elements: Collection<T>): Boolean = supplier().removeAll(elements) override fun removeAt(index: Int): T = supplier().removeAt(index) override fun retainAll(elements: Collection<T>): Boolean = supplier().retainAll(elements) override fun set(index: Int, element: T): T = supplier().set(index, element) override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> = supplier().subList(fromIndex, toIndex) override fun toString(): String = supplier().toString() override fun hashCode(): Int = supplier().hashCode() } } internal inline fun <T> dynamicMutableSet(crossinline supplier: () -> MutableSet<T>): MutableSet<T> { return object : MutableSet<T> { override val size: Int get() = supplier().size override fun contains(element: T): Boolean = supplier().contains(element) override fun containsAll(elements: Collection<T>): Boolean = supplier().containsAll(elements) override fun isEmpty(): Boolean = supplier().isEmpty() override fun iterator(): MutableIterator<T> = supplier().iterator() override fun add(element: T): Boolean = supplier().add(element) override fun addAll(elements: Collection<T>): Boolean = supplier().addAll(elements) override fun clear() = supplier().clear() override fun remove(element: T): Boolean = supplier().remove(element) override fun removeAll(elements: Collection<T>): Boolean = supplier().removeAll(elements) override fun retainAll(elements: Collection<T>): Boolean = supplier().retainAll(elements) override fun toString(): String = supplier().toString() override fun hashCode(): Int = supplier().hashCode() } } */ @Suppress( "UNCHECKED_CAST", "USELESS_CAST", "ACCIDENTAL_OVERRIDE", "TYPE_MISMATCH", "NOTHING_TO_OVERRIDE", "MANY_IMPL_MEMBER_NOT_IMPLEMENTED", "MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED", "UNCHECKED_CAST", "USELESS_CAST", "ACCIDENTAL_OVERRIDE", "EXPLICIT_OVERRIDE_REQUIRED_IN_MIXED_MODE", "CONFLICTING_INHERITED_JVM_DECLARATIONS", "WRONG_TYPE_PARAMETER_NULLABILITY_FOR_JAVA_OVERRIDE", "NULLABLE_TYPE_PARAMETER_AGAINST_NOT_NULL_TYPE_PARAMETER" ) // type inference bug internal fun <K, V> MutableMap<K, V>.observable(onChanged: () -> Unit): MutableMap<K, V> { open class ObservableMap : MutableMap<K, V> by (this as MutableMap<K, V>) { override val keys: MutableSet<K> get() = this@observable.keys.observable(onChanged) override val values: MutableCollection<V> get() = this@observable.values.observable(onChanged) override val entries: MutableSet<MutableMap.MutableEntry<K, V>> get() = this@observable.entries.observable(onChanged) override fun clear() = this@observable.clear().also { onChanged() } override fun put(key: K, value: V): V? = this@observable.put(key, value).also { onChanged() } override fun putAll(from: Map<out K, V>) = this@observable.putAll(from).also { onChanged() } override fun remove(key: K): V? = this@observable.remove(key).also { onChanged() } override fun toString(): String = this@observable.toString() override fun hashCode(): Int = this@observable.hashCode() override fun equals(other: Any?): Boolean { if (other == null) return false if (other === this) return true return this@observable == other } override fun remove(key: K, value: V): Boolean = this@observable.remove(key, value).also { onChanged() } override fun putIfAbsent(key: K, value: V): V? = this@observable.putIfAbsent(key, value).also { onChanged() } override fun replace(key: K, oldValue: V, newValue: V): Boolean = this@observable.replace(key, oldValue, newValue).also { onChanged() } override fun replace(key: K, value: V): V? = this@observable.replace(key, value).also { onChanged() } override fun computeIfAbsent(key: K, mappingFunction: Function<in K, out V>): V = this@observable.computeIfAbsent(key, mappingFunction).also { onChanged() } override fun replaceAll(function: BiFunction<in K, in V, out V>) = this@observable.replaceAll(function).also { onChanged() } override fun compute(key: K, remappingFunction: BiFunction<in K, in V?, out V?>): V? = this@observable.compute(key, remappingFunction).also { onChanged() } override fun computeIfPresent(key: K, remappingFunction: BiFunction<in K, in V, out V?>): V? = this@observable.computeIfPresent(key, remappingFunction).also { onChanged() } override fun merge(key: K, value: V, remappingFunction: BiFunction<in V, in V, out V?>): V? = this@observable.merge(key, value, remappingFunction).also { onChanged() } } return ObservableMap() } internal inline fun <T> MutableList<T>.observable(crossinline onChanged: () -> Unit): MutableList<T> { return object : MutableList<T> { override val size: Int get() = this@observable.size override fun contains(element: T): Boolean = this@observable.contains(element) override fun containsAll(elements: Collection<T>): Boolean = this@observable.containsAll(elements) override fun get(index: Int): T = this@observable[index] override fun indexOf(element: T): Int = this@observable.indexOf(element) override fun isEmpty(): Boolean = this@observable.isEmpty() override fun iterator(): MutableIterator<T> = object : MutableIterator<T> { private val delegate = this@observable.iterator() override fun hasNext(): Boolean = delegate.hasNext() override fun next(): T = delegate.next() override fun remove() = delegate.remove().also { onChanged() } override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun lastIndexOf(element: T): Int = this@observable.lastIndexOf(element) override fun add(element: T): Boolean = this@observable.add(element).also { onChanged() } override fun add(index: Int, element: T) = this@observable.add(index, element).also { onChanged() } override fun addAll(index: Int, elements: Collection<T>): Boolean = this@observable.addAll(index, elements).also { onChanged() } override fun addAll(elements: Collection<T>): Boolean = this@observable.addAll(elements).also { onChanged() } override fun clear() = this@observable.clear().also { onChanged() } override fun listIterator(): MutableListIterator<T> = object : MutableListIterator<T> { private val delegate = this@observable.listIterator() override fun hasPrevious(): Boolean = delegate.hasPrevious() override fun nextIndex(): Int = delegate.nextIndex() override fun previous(): T = delegate.previous() override fun previousIndex(): Int = delegate.previousIndex() override fun add(element: T) = delegate.add(element).also { onChanged() } override fun hasNext(): Boolean = delegate.hasNext() override fun next(): T = delegate.next() override fun remove() = delegate.remove().also { onChanged() } override fun set(element: T) = delegate.set(element).also { onChanged() } override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun listIterator(index: Int): MutableListIterator<T> = object : MutableListIterator<T> { private val delegate = this@observable.listIterator(index) override fun hasPrevious(): Boolean = delegate.hasPrevious() override fun nextIndex(): Int = delegate.nextIndex() override fun previous(): T = delegate.previous() override fun previousIndex(): Int = delegate.previousIndex() override fun add(element: T) = delegate.add(element).also { onChanged() } override fun hasNext(): Boolean = delegate.hasNext() override fun next(): T = delegate.next() override fun remove() = delegate.remove().also { onChanged() } override fun set(element: T) = delegate.set(element).also { onChanged() } override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun remove(element: T): Boolean = this@observable.remove(element).also { onChanged() } override fun removeAll(elements: Collection<T>): Boolean = this@observable.removeAll(elements).also { onChanged() } override fun removeAt(index: Int): T = this@observable.removeAt(index).also { onChanged() } override fun retainAll(elements: Collection<T>): Boolean = this@observable.retainAll(elements).also { onChanged() } override fun set(index: Int, element: T): T = this@observable.set(index, element).also { onChanged() } override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> = this@observable.subList(fromIndex, toIndex) override fun toString(): String = this@observable.toString() override fun hashCode(): Int = this@observable.hashCode() } } internal inline fun <T> MutableCollection<T>.observable(crossinline onChanged: () -> Unit): MutableCollection<T> { return object : MutableCollection<T> { override val size: Int get() = this@observable.size override fun contains(element: T): Boolean = this@observable.contains(element) override fun containsAll(elements: Collection<T>): Boolean = this@observable.containsAll(elements) override fun isEmpty(): Boolean = this@observable.isEmpty() override fun iterator(): MutableIterator<T> = object : MutableIterator<T> { private val delegate = this@observable.iterator() override fun hasNext(): Boolean = delegate.hasNext() override fun next(): T = delegate.next() override fun remove() = delegate.remove().also { onChanged() } override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun add(element: T): Boolean = this@observable.add(element).also { onChanged() } override fun addAll(elements: Collection<T>): Boolean = this@observable.addAll(elements).also { onChanged() } override fun clear() = this@observable.clear().also { onChanged() } override fun remove(element: T): Boolean = this@observable.remove(element).also { onChanged() } override fun removeAll(elements: Collection<T>): Boolean = this@observable.removeAll(elements.toSet()).also { onChanged() } override fun retainAll(elements: Collection<T>): Boolean = this@observable.retainAll(elements.toSet()).also { onChanged() } override fun toString(): String = this@observable.toString() override fun hashCode(): Int = this@observable.hashCode() } } internal inline fun <T> MutableSet<T>.observable(crossinline onChanged: () -> Unit): MutableSet<T> { return object : MutableSet<T> { override val size: Int get() = this@observable.size override fun contains(element: T): Boolean = this@observable.contains(element) override fun containsAll(elements: Collection<T>): Boolean = this@observable.containsAll(elements) override fun isEmpty(): Boolean = this@observable.isEmpty() override fun iterator(): MutableIterator<T> = object : MutableIterator<T> { private val delegate = this@observable.iterator() override fun hasNext(): Boolean = delegate.hasNext() override fun next(): T = delegate.next() override fun remove() = delegate.remove().also { onChanged() } override fun toString(): String = delegate.toString() override fun hashCode(): Int = delegate.hashCode() } override fun add(element: T): Boolean = this@observable.add(element).also { onChanged() } override fun addAll(elements: Collection<T>): Boolean = this@observable.addAll(elements).also { onChanged() } override fun clear() = this@observable.clear().also { onChanged() } override fun remove(element: T): Boolean = this@observable.remove(element).also { onChanged() } override fun removeAll(elements: Collection<T>): Boolean = this@observable.removeAll(elements.toSet()).also { onChanged() } override fun retainAll(elements: Collection<T>): Boolean = this@observable.retainAll(elements.toSet()).also { onChanged() } override fun toString(): String = this@observable.toString() override fun hashCode(): Int = this@observable.hashCode() } } /* @OptIn(InternalSerializationApi::class) internal fun <R : Any> Any.smartCastPrimitive(clazz: KClass<R>): R { kotlin.runCatching { return Yaml.default.decodeFromString(clazz.serializer(), this.toString()) }.getOrElse { throw IllegalArgumentException("Cannot cast '$this' to ${clazz.qualifiedName}", it) } } */ ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/reflectionUtils.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.internal.data import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.ValueName import net.mamoe.mirai.utils.createInstanceOrNull import kotlin.reflect.* import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.isSubclassOf internal val KClass<*>.qualifiedNameOrTip: String get() = this.qualifiedName ?: "<anonymous class>" internal inline fun <reified T : Annotation> KAnnotatedElement.hasAnnotation(): Boolean = findAnnotation<T>() != null @Suppress("UNCHECKED_CAST") internal inline fun <reified T : Any> KType.toKClass(): KClass<out T> { val clazz = requireNotNull(classifier as? KClass<T>) { "Unsupported classifier: $classifier" } val fromClass = arguments[0].type?.classifier as? KClass<*> ?: Any::class val toClass = T::class require(toClass.isSubclassOf(fromClass)) { "Cannot cast KClass<${fromClass.qualifiedNameOrTip}> to KClass<${toClass.qualifiedNameOrTip}>" } return clazz } internal inline fun <reified T : PluginData> newPluginDataInstanceUsingReflection(type: KType): T { val classifier = type.toKClass<T>() return with(classifier) { objectInstance ?: createInstanceOrNull() ?: throw IllegalArgumentException( "Cannot create PluginData instance. " + "PluginDataHolder supports PluginData implemented as an object " + "or the ones with a constructor which either has no parameters or all parameters of which are optional, by default newPluginDataInstance implementation." ) } } @Suppress("UNCHECKED_CAST") internal fun KType.classifierAsKClass() = when (val t = classifier) { is KClass<*> -> t else -> error("Only KClass supported as classifier, got $t") } as KClass<Any> @Suppress("UNCHECKED_CAST") internal fun KType.classifierAsKClassOrNull() = when (val t = classifier) { is KClass<*> -> t else -> null } as KClass<Any>? @JvmSynthetic internal fun KClass<*>.findValueName(): String = findAnnotation<ValueName>()?.value ?: qualifiedName ?: throw IllegalArgumentException("Cannot find a serial name for $this") internal fun Int.isOdd() = this and 0b1 != 0 internal val KProperty<*>.valueName: String get() = this.findAnnotation<ValueName>()?.value ?: this.name internal inline val Any.kClassQualifiedName: String? get() = this::class.qualifiedName internal inline val Any.kClassQualifiedNameOrTip: String get() = this::class.qualifiedNameOrTip ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/serializerHelper.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.internal.data import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.* import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.serializer import kotlinx.serialization.serializerOrNull import net.mamoe.mirai.message.MessageSerializers import net.mamoe.yamlkt.YamlDynamicSerializer import net.mamoe.yamlkt.YamlNullableDynamicSerializer import java.lang.reflect.Modifier import kotlin.reflect.KClass import kotlin.reflect.KType @OptIn(ExperimentalSerializationApi::class) @Suppress("UNCHECKED_CAST") internal fun SerializersModule.serializerMirai(type: KType): KSerializer<Any?> { fun serializerByKTypeImpl(type: KType): KSerializer<*> { val rootClass = type.classifierAsKClass() // In Kotlin 1.6.20, `typeOf<Array<Long>>?.classifier` surprisingly gives kotlin.LongArray // https://youtrack.jetbrains.com/issue/KT-52170/ if (type.arguments.size == 1) { // can be typeOf<Array<...>>, so cannot be typeOf<IntArray> val result: KSerializer<Any?>? = when (rootClass) { ByteArray::class -> ArraySerializer(Byte.serializer()).cast() ShortArray::class -> ArraySerializer(Short.serializer()).cast() IntArray::class -> ArraySerializer(Int.serializer()).cast() LongArray::class -> ArraySerializer(Long.serializer()).cast() FloatArray::class -> ArraySerializer(Float.serializer()).cast() DoubleArray::class -> ArraySerializer(Double.serializer()).cast() CharArray::class -> ArraySerializer(Char.serializer()).cast() BooleanArray::class -> ArraySerializer(Boolean.serializer()).cast() else -> null } if (result != null) return result } this.serializerOrNull(type)?.let { return it } // Kotlin builtin and user-defined MessageSerializers.serializersModule.serializerOrNull(type)?.let { return it } // Mirai Messages if (type.classifier == Any::class) return if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer as KSerializer<Any?> val typeArguments = type.arguments .map { requireNotNull(it.type) { "Star projections in type arguments are not allowed, but had $type" } } return when { typeArguments.isEmpty() -> this.serializer(type) else -> { val serializers = typeArguments.map(::serializerMirai) when (rootClass) { Collection::class, List::class, MutableList::class, ArrayList::class -> ListSerializer(serializers[0]) HashSet::class -> SetSerializer(serializers[0]) Set::class, MutableSet::class, LinkedHashSet::class -> SetSerializer(serializers[0]) HashMap::class -> MapSerializer(serializers[0], serializers[1]) Map::class, MutableMap::class, LinkedHashMap::class -> MapSerializer( serializers[0], serializers[1] ) Map.Entry::class -> MapEntrySerializer(serializers[0], serializers[1]) Pair::class -> PairSerializer(serializers[0], serializers[1]) Triple::class -> TripleSerializer(serializers[0], serializers[1], serializers[2]) Any::class -> if (type.isMarkedNullable) YamlNullableDynamicSerializer else YamlDynamicSerializer else -> { if (rootClass.java.isArray) { return ArraySerializer( typeArguments[0].classifier as KClass<Any>, serializers[0] ).cast() } requireNotNull(rootClass.constructSerializerForGivenTypeArgs(*serializers.toTypedArray())) { "Can't find a method to construct serializer for type ${rootClass.simpleName}. " + "Make sure this class is marked as @Serializable or provide serializer explicitly." } } } } } } val result = serializerByKTypeImpl(type) as KSerializer<Any> return if (type.isMarkedNullable) result.nullable else result.cast() } /** * Copied from kotlinx.serialization, modifications are marked with "/* mamoe modify */" * Copyright 2017-2020 JetBrains s.r.o. */ @Suppress( "UNCHECKED_CAST", "UNSUPPORTED", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", ) private fun <T : Any> KClass<T>.constructSerializerForGivenTypeArgs(vararg args: KSerializer<Any?>): KSerializer<T>? { val jClass = this.java // Search for serializer defined on companion object. val companion = jClass.declaredFields.singleOrNull { it.name == "Companion" }?.apply { isAccessible = true }?.get(null) if (companion != null) { val serializer = companion.javaClass.methods .find { method -> method.name == "serializer" && method.parameterTypes.size == args.size && method.parameterTypes.all { it == KSerializer::class.java } } ?.invoke(companion, *args) as? KSerializer<T> if (serializer != null) return serializer } // Check whether it's serializable object findObjectSerializer(jClass)?.let { return it } // Search for default serializer if no serializer is defined in companion object. return try { jClass.declaredClasses.singleOrNull { it.simpleName == ("\$serializer") } ?.getField("INSTANCE")?.get(null) as? KSerializer<T> } catch (e: NoSuchFieldException) { null } } private fun <T : Any> findObjectSerializer(jClass: Class<T>): KSerializer<T>? { // Check it is an object without using kotlin-reflect val field = jClass.declaredFields.singleOrNull { it.name == "INSTANCE" && it.type == jClass && Modifier.isStatic(it.modifiers) } ?: return null // Retrieve its instance and call serializer() val instance = field.get(null) val method = jClass.methods.singleOrNull { it.name == "serializer" && it.parameters.isEmpty() && it.returnType == KSerializer::class.java } ?: return null val result = method.invoke(instance) @Suppress("UNCHECKED_CAST") return result as? KSerializer<T> } internal inline fun <E> KSerializer<E>.bind( crossinline setter: (E) -> Unit, crossinline getter: () -> E ): KSerializer<E> { return object : KSerializer<E> { override val descriptor: SerialDescriptor get() = this@bind.descriptor override fun deserialize(decoder: Decoder): E = this@bind.deserialize(decoder).also { setter(it) } @Suppress("UNCHECKED_CAST") override fun serialize(encoder: Encoder, value: E) = this@bind.serialize(encoder, getter()) } } internal inline fun <E, R> KSerializer<E>.map( crossinline serializer: (R) -> E, crossinline deserializer: (E) -> R ): KSerializer<R> { return object : KSerializer<R> { override val descriptor: SerialDescriptor get() = this@map.descriptor override fun deserialize(decoder: Decoder): R = this@map.deserialize(decoder).let(deserializer) override fun serialize(encoder: Encoder, value: R) = this@map.serialize(encoder, value.let(serializer)) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.data import kotlinx.serialization.KSerializer import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.data.SerializableValue.Companion.serializableValueWith import net.mamoe.mirai.console.data.SerializerAwareValue import net.mamoe.mirai.console.data.valueFromKType import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.createInstanceOrNull import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentMap import kotlin.contracts.contract import kotlin.reflect.KClass import kotlin.reflect.KType import kotlin.reflect.full.isSubclassOf private val primitiveCollectionsImplemented by lazy { false } @Suppress("UnsafeCall", "SMARTCAST_IMPOSSIBLE", "UNCHECKED_CAST") internal fun PluginData.valueFromKTypeImpl(type: KType): SerializerAwareValue<*> { val classifier = type.classifier require(classifier is KClass<*>) if (classifier.isPrimitiveOrBuiltInSerializableValue()) { return valueImplPrimitive(classifier) as SerializerAwareValue<*> } // TODO: 2020/6/24 优化性能: 预先根据类型生成 V -> Value<V> 的 mapper when (classifier) { MutableMap::class, Map::class, LinkedHashMap::class, HashMap::class, ConcurrentMap::class, ConcurrentHashMap::class, -> { val keyClass = type.arguments[0].type?.classifier require(keyClass is KClass<*>) val valueClass = type.arguments[1].type?.classifier require(valueClass is KClass<*>) if (primitiveCollectionsImplemented && keyClass.isPrimitiveOrBuiltInSerializableValue() && valueClass.isPrimitiveOrBuiltInSerializableValue()) { // PrimitiveIntIntMap // ... TODO() } else { return createCompositeMapValueImpl<Any?, Any?>( mapInitializer = { if (classifier.cast<KClass<*>>().isSubclassOf(ConcurrentMap::class)) { ConcurrentHashMap() } else { null } }, kToValue = { k -> valueFromKType(type.arguments[0].type!!, k) }, vToValue = { v -> valueFromKType(type.arguments[1].type!!, v) } ).serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<Map<Any?, Any?>>) // erased } } MutableList::class, List::class, ArrayList::class -> { val elementClass = type.arguments[0].type?.classifier require(elementClass is KClass<*>) if (primitiveCollectionsImplemented && elementClass.isPrimitiveOrBuiltInSerializableValue()) { // PrimitiveIntList // ... TODO() } else { return createCompositeListValueImpl<Any?> { v -> valueFromKType(type.arguments[0].type!!, v) } .serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<List<Any?>>) } } MutableSet::class, Set::class, LinkedHashSet::class, HashSet::class -> { val elementClass = type.arguments[0].type?.classifier require(elementClass is KClass<*>) if (primitiveCollectionsImplemented && elementClass.isPrimitiveOrBuiltInSerializableValue()) { // PrimitiveIntSet // ... TODO() } else { return createCompositeSetValueImpl<Any?> { v -> valueFromKType(type.arguments[0].type!!, v) } .serializableValueWith(serializersModule.serializerMirai(type) as KSerializer<Set<Any?>>) } } else -> { val serializer = serializersModule.serializerMirai(type) return LazyReferenceValueImpl<Any?>().serializableValueWith(serializer) } } } private fun KClass<*>.isReferencingSamePlatformClass(other: KClass<*>): Boolean { return this.qualifiedName == other.qualifiedName // not using .java for } internal fun KClass<*>.createInstanceSmart(): Any { when { isReferencingSamePlatformClass(Array::class) -> return emptyArray<Any?>() } return when (this) { Byte::class -> 0.toByte() Short::class -> 0.toShort() Int::class -> 0 Long::class -> 0L Float::class -> 0.toFloat() Double::class -> 0.0 Boolean::class -> false String::class -> "" MutableMap::class, Map::class, LinkedHashMap::class, HashMap::class -> LinkedHashMap<Any?, Any?>() MutableList::class, List::class, ArrayList::class -> ArrayList<Any?>() MutableSet::class, Set::class, LinkedHashSet::class, HashSet::class -> LinkedHashSet<Any?>() ConcurrentHashMap::class, ConcurrentMap::class, -> ConcurrentHashMap<Any?, Any?>() ByteArray::class -> byteArrayOf() BooleanArray::class -> booleanArrayOf() ShortArray::class -> shortArrayOf() IntArray::class -> intArrayOf() LongArray::class -> longArrayOf() FloatArray::class -> floatArrayOf() DoubleArray::class -> doubleArrayOf() CharArray::class -> charArrayOf() else -> createInstanceOrNull() ?: error("Cannot create instance or find a initial value for ${this.qualifiedNameOrTip}") } } internal fun KClass<*>.isPrimitiveOrBuiltInSerializableValue(): Boolean { when (this) { Byte::class, Short::class, Int::class, Long::class, Boolean::class, Char::class, String::class, //Pair::class, Triple::class // TODO: 2020/6/24 支持 PairValue, TripleValue -> return true } return false } @PublishedApi @Suppress("UNCHECKED_CAST") internal inline fun <reified R> Any.cast(): R { contract { returns() implies (this@cast is R) } return this as R } @PublishedApi @Suppress("UNCHECKED_CAST") internal inline fun <reified R> Any.castOrNull(): R? { contract { returnsNotNull() implies (this@castOrNull is R) } return this as? R } @Suppress("UNCHECKED_CAST") internal inline fun <reified R> Any.castOrInternalError(): R { contract { returns() implies (this@castOrInternalError is R) } return (this as? R) ?: error("Internal error: ${this::class} cannot be casted to ${R::class}") } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/enduserreadme/EndUserReadmeProcessor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.enduserreadme import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withTimeoutOrNull import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.enduserreadme.EndUserReadme import net.mamoe.mirai.console.events.EndUserReadmeInitializeEvent import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.data.builtins.EndUserReadmeData import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.sendAnsiMessage import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.sha256 import net.mamoe.mirai.utils.toUHexString import java.io.File import java.net.InetAddress internal object EndUserReadmeProcessor { private val PADDING = "=".repeat(100) private fun StringBuilder.pad(size: Int) { var size0 = size while (size0 > 0) { val padded = size0.coerceAtMost(PADDING.length) append(PADDING, 0, padded) size0 -= padded } } private fun header(title: String): String { val padding = 100 - title.length val lpadding = padding / 2 val rpadding = padding - lpadding return buildString { pad(lpadding) append(" [ ").append(title).append(" ] ") pad(rpadding) } } private val systemDefaultNames = hashSetOf<String>( "ubuntu", "debian", "arch", "centos", "fedora", "localhost", ) private fun getComputerName(): String { System.getenv("COMPUTERNAME")?.takeUnless(String::isBlank)?.let { return it } System.getenv("HOSTNAME")?.takeUnless(String::isBlank)?.let { return it } runCatching { InetAddress.getLocalHost().hostName ?.takeIf { it.lowercase() !in systemDefaultNames } ?.takeUnless(String::isBlank) ?.let { return it } } runCatching { File("/etc/machine-id").readText().takeUnless(String::isBlank)?.let { return it.trim() } } return "Unknown Computer" } @OptIn(MiraiInternalApi::class, ConsoleFrontEndImplementation::class) fun process(console: MiraiConsoleImplementationBridge) { if (System.getenv("CI") == "true") return if (System.getProperty("mirai.console.skip-end-user-readme") in listOf<String?>("", "true", "yes")) return val pcName = getComputerName() val dataObject = EndUserReadmeData() console.consoleDataScope.addAndReloadConfig(dataObject) runBlocking { val readme = EndUserReadme() runCatching { EndUserReadmeProcessor::class.java.getResourceAsStream("readme.txt")?.bufferedReader()?.use { readme.putAll(it.readText()) } }.onFailure { console.mainLogger.error(it) } EndUserReadmeInitializeEvent(readme).broadcast() // region Remove already read val pcNameBCode = pcName.toByteArray() var changed = false readme.pages.asSequence().map { (key, value) -> return@map key to value.sha256() }.onEach { (_, hash) -> for (i in hash.indices) { hash[i] = hash[i].toInt().xor(pcNameBCode[i % pcNameBCode.size].toInt()).toByte() } }.map { (k, v) -> return@map k to v.toUHexString() }.toList().forEach { (key, hash) -> if (dataObject.data[key] == hash) { readme.pages.remove(key) } else { dataObject.data[key] = hash changed = true } } // endregion suspend fun wait(seconds: Int) { if (seconds < 1) return var printWaiting = true repeat(seconds) { counter -> val suffix = (seconds - counter).toString() + "s" withTimeoutOrNull(1000L) { if (printWaiting) { ConsoleInput.requestInput("Please wait $suffix...") printWaiting = false } while (true) { ConsoleInput.requestInput("Please read before continuing ($suffix)") } } } } suspend fun pause() { ConsoleInput.requestInput("Enter to continue") } if (readme.pages.isNotEmpty()) { listOf( header("End User Readme"), "最终用户须知有更新,在您继续使用前,您必须完整阅读新的用户须知。", ).forEach { ConsoleCommandSender.sendMessage(it) } } readme.pages.forEach { (category, message) -> ConsoleCommandSender.sendMessage(header(category)) message.lines().forEach { command -> val ctrim = command.trim() if (ctrim == EndUserReadme.PAUSE) { pause() } else if (ctrim == EndUserReadme.DELAY) { wait(3) } else if (ctrim.startsWith(EndUserReadme.DELAY)) { wait(ctrim.removePrefix(EndUserReadme.DELAY).trim().toIntOrNull() ?: 3) } else { ConsoleCommandSender.sendAnsiMessage(command) } } wait(3) pause() } if (changed) { dataObject.saveNow() } } } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.extension import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.extension.* import net.mamoe.mirai.console.internal.data.kClassQualifiedNameOrTip import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleExperimentalApi import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.stream.Stream import kotlin.contracts.contract internal class GlobalComponentStorageImpl : AbstractConcurrentComponentStorage() // source compatibility for <2.11 internal val GlobalComponentStorage get() = MiraiConsoleImplementation.getBridge().globalComponentStorage /** * thread-safe. */ internal abstract class AbstractConcurrentComponentStorage : ComponentStorage, ComponentStorageInternal { /////////////////////////////////////////////////////////////////////////// // registry implementation /////////////////////////////////////////////////////////////////////////// /** * For each [ExtensionPoint]. thread-safe. */ internal class Registries<T : Extension> { @Volatile private var data: MutableCollection<ExtensionRegistry<T>> = ArrayList() private val lock = Any() fun register(registry: ExtensionRegistry<T>) { synchronized(lock) { val list = PriorityQueue( data.size + 1, Comparator.comparing<ExtensionRegistry<T>, Int> { it.extension.priority }.reversed() ) list.addAll(data) list.add(registry) data = list } } /** * @return thread-safe Sequence */ fun asSequence(): Sequence<ExtensionRegistry<T>> = data.asSequence() /** * @return thread-safe Sequence */ fun asStream(): Stream<ExtensionRegistry<T>> = data.stream() } private val registries: MutableMap<ExtensionPoint<*>, Registries<*>> = ConcurrentHashMap() private fun <T : Extension> getRegistries(ep: ExtensionPoint<T>): Registries<T> { @Suppress("UNCHECKED_CAST") return registries.getOrPut(ep) { Registries<T>() } as Registries<T> } private fun <T : Extension> registerExtension(ep: ExtensionPoint<T>, registry: ExtensionRegistry<T>) { getRegistries(ep).register(registry) } /////////////////////////////////////////////////////////////////////////// // public API implementation /////////////////////////////////////////////////////////////////////////// internal fun mergeWith(another: AbstractConcurrentComponentStorage) { another.registries.forEach { (ep, registries) -> for (extensionRegistry in registries.asSequence()) { @Suppress("UNCHECKED_CAST") ep as ExtensionPoint<Extension> registerExtension(ep, ExtensionRegistryImpl(extensionRegistry.plugin) { extensionRegistry.extension }) } } } internal inline fun <T : Extension> useEachExtensions( extensionPoint: ExtensionPoint<T>, block: ExtensionRegistry<T>.(instance: T) -> Unit ) { contract { callsInPlace(block) } getExtensions(extensionPoint).forEach { registry -> val plugin = registry.plugin val extension = registry.extension kotlin.runCatching { block.invoke(registry, registry.extension) }.getOrElse { throwable -> extensionPoint.throwExtensionException(extension, plugin, throwable) } } } internal inline fun <T : Extension, E> foldExtensions( extensionPoint: ExtensionPoint<T>, initial: E, block: (acc: E, extension: T) -> E, ): E { contract { callsInPlace(block) } var e: E = initial getExtensions(extensionPoint).forEach { registry -> val plugin = registry.plugin val extension = registry.extension kotlin.runCatching { e = block.invoke(e, extension) }.getOrElse { throwable -> extensionPoint.throwExtensionException(extension, plugin, throwable) } } return e } internal fun <T : Extension> ExtensionPoint<out T>.throwExtensionException( extension: T, plugin: Plugin?, throwable: Throwable, ): Nothing { throw ExtensionException( "Exception while executing extension '${extension.kClassQualifiedNameOrTip}' provided by plugin '${plugin?.name ?: "<builtin>"}', registered for '${this.extensionType.qualifiedName}'", throwable ) } override fun <E : Extension> contribute( extensionPoint: ExtensionPoint<E>, plugin: Plugin, extensionInstance: E, ) { registerExtension(extensionPoint, ExtensionRegistryImpl(plugin) { extensionInstance }) } override fun <E : Extension> contributeConsole(extensionPoint: ExtensionPoint<E>, lazyInstance: () -> E) { registerExtension(extensionPoint, ExtensionRegistryImpl(null, lazyInstance)) } override fun <E : Extension> contribute( extensionPoint: ExtensionPoint<E>, plugin: Plugin, lazyInstance: () -> E, ) { registerExtension(extensionPoint, ExtensionRegistryImpl(plugin, lazyInstance)) } override fun <E : Extension> getExtensions(extensionPoint: ExtensionPoint<E>): Sequence<ExtensionRegistry<E>> { return getRegistries(extensionPoint).asSequence() } override fun <E : Extension> getExtensionsStream(extensionPoint: ExtensionPoint<E>): Stream<ExtensionRegistry<E>> { return getRegistries(extensionPoint).asStream() } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/extension/SingletonExtensionSelectorImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("DEPRECATION_ERROR") @file:OptIn(ConsoleInternalApi::class) package net.mamoe.mirai.console.internal.extension import kotlinx.coroutines.runBlocking import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.extension.Extension import net.mamoe.mirai.console.extensions.SingletonExtensionSelector import net.mamoe.mirai.console.internal.data.kClassQualifiedName import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.info import kotlin.reflect.KClass @Deprecated( "Order of extensions is not determined by its priority property since 2.11. SingletonExtensionSelector is not needed anymore. ", level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.11", errorSince = "2.13", hiddenSince = "2.14") internal object SingletonExtensionSelectorImpl : SingletonExtensionSelector { internal val config: SaveData = SaveData() internal class SaveData : AutoSavePluginConfig("ExtensionSelector") { val value: MutableMap<String, String> by value() } override fun <T : Extension> selectSingleton( extensionType: KClass<T>, candidates: Collection<SingletonExtensionSelector.Registry<T>>, ): T? = when { candidates.isEmpty() -> null candidates.size == 1 -> candidates.single().extension else -> kotlin.run { val target = config.value[extensionType.qualifiedName!!] ?: return promptForSelectionAndSave(extensionType, candidates) val found = candidates.firstOrNull { it.extension::class.qualifiedName == target }?.extension ?: return promptForSelectionAndSave(extensionType, candidates) found } } private fun <T : Extension> promptForSelectionAndSave( extensionType: KClass<T>, candidates: Collection<SingletonExtensionSelector.Registry<T>>, ) = promptForManualSelection(extensionType, candidates) .also { config.value[extensionType.qualifiedName!!] = it.extension.kClassQualifiedName!! }.extension private fun <T : Extension> promptForManualSelection( extensionType: KClass<T>, candidates: Collection<SingletonExtensionSelector.Registry<T>>, ): SingletonExtensionSelector.Registry<T> { MiraiConsole.mainLogger.info { "There are multiple ${extensionType.simpleName}s, please select one:" } val candidatesList = candidates.toList() for ((index, candidate) in candidatesList.withIndex()) { MiraiConsole.mainLogger.info { "${index + 1}. '${candidate.extension}' from '${candidate.plugin?.name ?: "<builtin>"}'" } } MiraiConsole.mainLogger.info { "Please choose a number from 1 to ${candidatesList.count()}" } val choiceStr = runBlocking { ConsoleInput.requestInput("Your choice: ") } val choice = choiceStr.toIntOrNull() ?: error("Bad choice") return candidatesList[choice - 1] } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/logging/LazyInitMiraiLogger.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.logging import me.him188.kotlin.dynamic.delegation.dynamicDelegation import net.mamoe.mirai.utils.MiraiLogger internal fun lazyInitMiraiLogger(block: () -> MiraiLogger): MiraiLogger { return LazyInitMiraiLogger(lazy(block)) } internal fun lazyInitMiraiLogger(block: Lazy<MiraiLogger>): MiraiLogger { return LazyInitMiraiLogger(block) } internal class LazyInitMiraiLogger( private val theLazy: Lazy<MiraiLogger>, ) : MiraiLogger by (dynamicDelegation(LazyInitMiraiLogger::logger)) { private inline val logger: MiraiLogger get() = theLazy.value } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class, MiraiExperimentalApi::class, ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.logging import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig import net.mamoe.mirai.console.logging.AbstractLoggerController import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi internal class LoggerControllerImpl : AbstractLoggerController.PathBased() { // 防止 stack overflow (使用 logger 要加载 LoggerController, LoggerConfig 可能会使用 logger) // 在 console init 阶段 register internal val loggerConfig: LoggerConfig by lazy { LoggerConfig() } override val isLoggerControlStateSupported: Boolean get() = true internal fun onReload() { this.loggerConfigUpdateTime++ } override fun findPriority(identity: String?): LogPriority? { return if (identity == null) { loggerConfig.defaultPriority } else { loggerConfig.loggers[identity] } } override val defaultPriority: LogPriority get() = loggerConfig.defaultPriority } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/logging/MiraiConsoleLogger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.logging import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLoggerPlatformBase import net.mamoe.mirai.utils.SimpleLogger internal class MiraiConsoleLogger( controller: LoggerController, val logger: MiraiLogger ) : MiraiLoggerPlatformBase() { override val identity: String? get() = logger.identity override val isEnabled: Boolean get() = logger.isEnabled private val logState = controller.getLoggerControlState(identity) override val isInfoEnabled: Boolean get() = logState.shouldLog(SimpleLogger.LogPriority.INFO) override val isWarningEnabled: Boolean get() = logState.shouldLog(SimpleLogger.LogPriority.WARNING) override val isDebugEnabled: Boolean get() = logState.shouldLog(SimpleLogger.LogPriority.DEBUG) override val isErrorEnabled: Boolean get() = logState.shouldLog(SimpleLogger.LogPriority.ERROR) override val isVerboseEnabled: Boolean get() = logState.shouldLog(SimpleLogger.LogPriority.VERBOSE) override fun info0(message: String?, e: Throwable?) { if (logState.shouldLog(SimpleLogger.LogPriority.INFO)) logger.info(message, e) } override fun warning0(message: String?, e: Throwable?) { if (logState.shouldLog(SimpleLogger.LogPriority.WARNING)) logger.warning(message, e) } override fun debug0(message: String?, e: Throwable?) { if (logState.shouldLog(SimpleLogger.LogPriority.DEBUG)) logger.debug(message, e) } override fun error0(message: String?, e: Throwable?) { if (logState.shouldLog(SimpleLogger.LogPriority.ERROR)) logger.error(message, e) } override fun verbose0(message: String?, e: Throwable?) { if (logState.shouldLog(SimpleLogger.LogPriority.VERBOSE)) logger.verbose(message, e) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/logging/externalbind/slf4j/MiraiConsoleSLF4JService.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class, MiraiExperimentalApi::class) package net.mamoe.mirai.console.internal.logging.externalbind.slf4j import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get import net.mamoe.mirai.console.internal.data.builtins.DataScope import net.mamoe.mirai.console.internal.data.builtins.LoggerConfig import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.safeCast import org.slf4j.ILoggerFactory import org.slf4j.IMarkerFactory import org.slf4j.event.SubstituteLoggingEvent import org.slf4j.helpers.BasicMarkerFactory import org.slf4j.helpers.NOPLoggerFactory import org.slf4j.helpers.NOPMDCAdapter import org.slf4j.helpers.SubstituteLoggerFactory import org.slf4j.spi.MDCAdapter import org.slf4j.spi.SLF4JServiceProvider @PublishedApi internal class MiraiConsoleSLF4JService : SLF4JServiceProvider { private val basicMarkerFactory = BasicMarkerFactory() private val nopMDCAdapter = NOPMDCAdapter() private val dfactory = ILoggerFactory { MiraiConsoleSLF4JAdapter.getCurrentLogFactory().getLogger(it) } override fun getMarkerFactory(): IMarkerFactory = basicMarkerFactory override fun getMDCAdapter(): MDCAdapter = nopMDCAdapter override fun getRequestedApiVersion(): String = "2.0" override fun getLoggerFactory(): ILoggerFactory = dfactory override fun initialize() {} } internal object MiraiConsoleSLF4JAdapter { /** * Used before mirai-console start */ private val substituteServiceFactory = SubstituteLoggerFactory() @Volatile private var initialized: Boolean = false @Volatile private var currentLoggerFactory: ILoggerFactory = substituteServiceFactory internal fun getCurrentLogFactory(): ILoggerFactory { if (initialized) return currentLoggerFactory synchronized(MiraiConsoleSLF4JAdapter::class.java) { return currentLoggerFactory } } internal fun doSlf4JInit() { synchronized(MiraiConsoleSLF4JAdapter::class.java) { val logConfig = DataScope.get<LoggerConfig>() currentLoggerFactory = if (logConfig.binding.slf4j) { ILoggerFactory { ident -> SLF4JAdapterLogger(MiraiLogger.Factory.create(MiraiConsoleSLF4JAdapter::class.java, ident)) } } else { NOPLoggerFactory() } initialized = true // region replay events substituteServiceFactory.postInitialization() substituteServiceFactory.loggers.forEach { slog -> slog.setDelegate(currentLoggerFactory.getLogger(slog.name)) } substituteServiceFactory.eventQueue.let { queue -> for (event in queue) { replaySingleEvent(event) } } substituteServiceFactory.clear() // endregion } } private fun replaySingleEvent(event: SubstituteLoggingEvent?) { if (event == null) return val substLogger = event.logger substLogger.delegate().safeCast<SLF4JAdapterLogger>()?.process(event) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/logging/externalbind/slf4j/SLF4JAdapterLogger.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.logging.externalbind.slf4j import net.mamoe.mirai.utils.MiraiLogger import org.slf4j.Logger import org.slf4j.Marker import org.slf4j.event.SubstituteLoggingEvent import java.lang.invoke.MethodHandle import java.lang.invoke.MethodHandles import java.lang.invoke.MethodType import java.nio.CharBuffer import java.text.MessageFormat import java.util.regex.Pattern import org.slf4j.event.Level as SLF4JEventLevel @Suppress("RegExpRedundantEscape") internal class SLF4JAdapterLogger( private val logger: MiraiLogger ) : Logger { // Copied from Log4J internal companion object { private const val FORMAT_SPECIFIER = "%(\\d+\\$)?([-#+ 0,(\\<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])" private val MSG_PATTERN = Pattern.compile(FORMAT_SPECIFIER) private const val DELIM_START = '{' private const val DELIM_STOP = '}' private const val ESCAPE_CHAR = '\\' @JvmStatic internal fun String.simpleFormat(args: Array<out Any?>): String { val buffer = StringBuilder() val reader = CharBuffer.wrap(this) var isEscape = false var index = 0 while (reader.hasRemaining()) { when (val next = reader.get()) { ESCAPE_CHAR -> { if (isEscape) { buffer.append(ESCAPE_CHAR) } isEscape = !isEscape } DELIM_START -> { if (isEscape) { buffer.append(next) } else { if (reader.hasRemaining()) { if (reader.get(reader.position()) == DELIM_STOP) { reader.get() buffer.append(args.getOrNull(index)) index++ } else { buffer.append(DELIM_START) } } else { buffer.append(DELIM_START) } } isEscape = false } else -> buffer.append(next).also { isEscape = false } } } return buffer.toString() } internal fun String.format1(vararg arguments: Any?): String = format2(arguments) // (java.lang.String, java.lang.Object[]): java.lang.String @Suppress("LocalVariableName") private val formatWithLog4JMH: MethodHandle? = kotlin.runCatching { val c_ParameterizedMessage = Class.forName("org.apache.logging.log4j.message.ParameterizedMessage") val mhLookup = MethodHandles.lookup() val mh_newParameterizedMessage = mhLookup.findConstructor( c_ParameterizedMessage, MethodType.methodType(Void.TYPE, String::class.java, Array<Any>::class.java) ).asFixedArity() val mh_getFormattedMessage = mhLookup.findVirtual( c_ParameterizedMessage, "getFormattedMessage", MethodType.methodType(String::class.java) ) MethodHandles.filterReturnValue(mh_newParameterizedMessage, mh_getFormattedMessage) }.getOrNull() @JvmStatic internal fun String.format2(args: Array<out Any?>): String { kotlin.runCatching { val formatter = MessageFormat(this) val formats = formatter.formats if (formats.isNotEmpty()) { return formatter.format(args) } } kotlin.runCatching { if (MSG_PATTERN.matcher(this).find()) { return String.format(this, *args) } } kotlin.runCatching { // Try format with Log4J formatWithLog4JMH?.let { formatWithLog4JMH -> return formatWithLog4JMH.invoke(this@format2, args) as String } } return simpleFormat(args) } } ////////////////////////////////////////////////////////////////// override fun isTraceEnabled(): Boolean = logger.isVerboseEnabled override fun isTraceEnabled(marker: Marker?): Boolean = logger.isVerboseEnabled override fun isDebugEnabled(): Boolean = logger.isDebugEnabled override fun isDebugEnabled(marker: Marker?): Boolean = logger.isDebugEnabled override fun isInfoEnabled(): Boolean = logger.isInfoEnabled override fun isInfoEnabled(marker: Marker?): Boolean = logger.isInfoEnabled override fun isWarnEnabled(): Boolean = logger.isWarningEnabled override fun isWarnEnabled(marker: Marker?): Boolean = logger.isWarningEnabled override fun isErrorEnabled(): Boolean = logger.isErrorEnabled override fun isErrorEnabled(marker: Marker?): Boolean = logger.isErrorEnabled ////////////////////////////////////////////////////////////////// override fun getName(): String = logger.identity ?: "<unknown>" @Suppress("DuplicatedCode") internal fun process(event: SubstituteLoggingEvent) { val msg = event.message val argx = event.argumentArray val throwx = event.throwable val evtlv = event.level ?: return val isEnabled = when (evtlv) { SLF4JEventLevel.ERROR -> isErrorEnabled SLF4JEventLevel.WARN -> isWarnEnabled SLF4JEventLevel.INFO -> isInfoEnabled SLF4JEventLevel.DEBUG -> isDebugEnabled SLF4JEventLevel.TRACE -> isTraceEnabled } if (!isEnabled) return if (argx == null) { when (evtlv) { SLF4JEventLevel.ERROR -> error(msg, t = throwx) SLF4JEventLevel.WARN -> warn(msg, t = throwx) SLF4JEventLevel.INFO -> info(msg, t = throwx) SLF4JEventLevel.DEBUG -> debug(msg, t = throwx) SLF4JEventLevel.TRACE -> trace(msg, t = throwx) } return } if (throwx == null) { when (evtlv) { SLF4JEventLevel.ERROR -> error(msg, arguments = argx) SLF4JEventLevel.WARN -> warn(msg, arguments = argx) SLF4JEventLevel.INFO -> info(msg, arguments = argx) SLF4JEventLevel.DEBUG -> debug(msg, arguments = argx) SLF4JEventLevel.TRACE -> trace(msg, arguments = argx) } return } val msg2 = msg.format2(argx) when (evtlv) { SLF4JEventLevel.ERROR -> error(msg2, t = throwx) SLF4JEventLevel.WARN -> warn(msg2, t = throwx) SLF4JEventLevel.INFO -> info(msg2, t = throwx) SLF4JEventLevel.DEBUG -> debug(msg2, t = throwx) SLF4JEventLevel.TRACE -> trace(msg2, t = throwx) } } ////////////////////////////////////////////////////////////////// private inline fun iT(a: () -> Unit) { if (isTraceEnabled) a() } private inline fun iD(a: () -> Unit) { if (isDebugEnabled) a() } private inline fun iI(a: () -> Unit) { if (isInfoEnabled) a() } private inline fun iW(a: () -> Unit) { if (isWarnEnabled) a() } private inline fun iE(a: () -> Unit) { if (isErrorEnabled) a() } ////////////////////////////////////////////////////////////////// override fun trace(msg: String?) { logger.verbose(msg) } override fun trace(msg: String?, t: Throwable?) { logger.verbose(msg, t) } override fun trace(format: String, arg: Any?) = iT { trace(format.format1(arg)) } override fun trace(format: String, arg1: Any?, arg2: Any?) = iT { trace(format.format1(arg1, arg2)) } override fun trace(format: String, arguments: Array<out Any?>) = iT { trace(format.format2(arguments)) } override fun trace(marker: Marker?, msg: String?) = trace(msg) override fun trace(marker: Marker?, format: String, arg: Any?) = trace(format, arg) override fun trace(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = trace(format, arg1, arg2) override fun trace(marker: Marker?, format: String, argArray: Array<out Any?>) = trace(format, argArray) override fun trace(marker: Marker?, msg: String?, t: Throwable?) = trace(msg, t) ////////////////////////////////////////////////////////////////// override fun debug(msg: String?) { logger.debug(msg) } override fun debug(msg: String?, t: Throwable?) { logger.debug(msg, t) } override fun debug(format: String, arg: Any?) = iD { debug(format.format1(arg)) } override fun debug(format: String, arg1: Any?, arg2: Any?) = iD { debug(format.format1(arg1, arg2)) } override fun debug(format: String, arguments: Array<out Any?>) = iD { debug(format.format2(arguments)) } override fun debug(marker: Marker?, msg: String?) = debug(msg) override fun debug(marker: Marker?, format: String, arg: Any?) = debug(format, arg) override fun debug(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = debug(format, arg1, arg2) override fun debug(marker: Marker?, format: String, arguments: Array<out Any?>) = debug(format, arguments) override fun debug(marker: Marker?, msg: String?, t: Throwable?) = debug(msg, t) ////////////////////////////////////////////////////////////////// override fun info(msg: String?) { logger.info(msg) } override fun info(msg: String?, t: Throwable?) { logger.info(msg, t) } override fun info(format: String, arg: Any?) = iI { info(format.format1(arg)) } override fun info(format: String, arg1: Any?, arg2: Any?) = iI { info(format.format1(arg1, arg2)) } override fun info(format: String, arguments: Array<out Any?>) = iI { info(format.format2(arguments)) } override fun info(marker: Marker?, msg: String?) = info(msg) override fun info(marker: Marker?, format: String, arg: Any?) = info(format, arg) override fun info(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = info(format, arg1, arg2) override fun info(marker: Marker?, format: String, arguments: Array<out Any?>) = info(format, arguments) override fun info(marker: Marker?, msg: String?, t: Throwable?) = info(msg, t) ////////////////////////////////////////////////////////////////// override fun warn(msg: String?) { logger.warning(msg) } override fun warn(msg: String?, t: Throwable?) { logger.warning(msg, t) } override fun warn(format: String, arg: Any?) = iW { warn(format.format1(arg)) } override fun warn(format: String, arguments: Array<out Any?>) = iW { warn(format.format2(arguments)) } override fun warn(format: String, arg1: Any?, arg2: Any?) = iW { warn(format.format1(arg1, arg2)) } override fun warn(marker: Marker?, msg: String?) = warn(msg) override fun warn(marker: Marker?, format: String, arg: Any?) = warn(format, arg) override fun warn(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = warn(format, arg1, arg2) override fun warn(marker: Marker?, format: String, arguments: Array<out Any?>) = warn(format, arguments) override fun warn(marker: Marker?, msg: String?, t: Throwable?) = warn(msg, t) ////////////////////////////////////////////////////////////////// override fun error(msg: String?) { logger.error(msg) } override fun error(msg: String?, t: Throwable?) { logger.error(msg, t) } override fun error(format: String, arg: Any?) = iE { error(format.format1(arg)) } override fun error(format: String, arg1: Any?, arg2: Any?) = iE { error(format.format1(arg1, arg2)) } override fun error(format: String, arguments: Array<out Any?>) = iE { error(format.format2(arguments)) } override fun error(marker: Marker?, msg: String?) = error(msg) override fun error(marker: Marker?, format: String, arg: Any?) = error(format, arg) override fun error(marker: Marker?, format: String, arg1: Any?, arg2: Any?) = error(format, arg1, arg2) override fun error(marker: Marker?, format: String, arguments: Array<out Any?>) = error(format, arguments) override fun error(marker: Marker?, msg: String?, t: Throwable?) = error(msg, t) } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/permission/AbstractConcurrentPermissionService.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.permission import net.mamoe.mirai.console.data.PluginDataExtensions import net.mamoe.mirai.console.permission.* import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf import net.mamoe.mirai.console.permission.PermitteeId.Companion.allParentsWithSelf import net.mamoe.mirai.console.permission.PermitteeId.Companion.isChildOf internal abstract class AbstractConcurrentPermissionService<P : Permission> : PermissionService<P> { protected abstract val permissions: MutableMap<PermissionId, P> protected abstract val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>> protected abstract fun createPermission(id: PermissionId, description: String, parent: Permission): P override fun get(id: PermissionId): P? = permissions[id] override fun register(id: PermissionId, description: String, parent: Permission): P { val instance = createPermission(id, description, parent) val old = permissions.putIfAbsent(id, instance) if (old != null) throw PermissionRegistryConflictException(instance, old) return instance } override fun permit(permitteeId: PermitteeId, permission: P) { grantedPermissionsMap[permission.id].add(permitteeId) } override fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) { val success = if (recursive) { getPermittedPermissions(permitteeId).any { permitted -> (permission in permitted.parentsWithSelf) && grantedPermissionsMap[permitted.id].remove(permitteeId) } } else { grantedPermissionsMap[permission.id].remove(permitteeId) } if (!success) { val about = buildList { for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) { val parent = get(permissionIdentifier) ?: continue if (parent !in permission.parentsWithSelf) continue for (permissibleId in permissibleIdentifiers) { if (permissibleId in permitteeId.allParentsWithSelf) { add(parent to permissibleId) } } } } val message = if (about.isEmpty()) { "${permitteeId.asString()} 不拥有权限 ${permission.id}" } else { buildString { appendLine("${permitteeId.asString()} 的 ${permission.id} 权限来自") about.forEach { (parent, permitted) -> appendLine("${permitted.asString()} ${parent.id}") } appendLine("Mirai Console 内置权限系统目前不支持单独禁用继承得到的权限. 可取消继承来源再为其分别分配.") } } throw UnsupportedOperationException(message) } } override fun getRegisteredPermissions(): Sequence<P> = permissions.values.asSequence() override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P> = sequence { for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) { val granted = permissibleIdentifiers.any { permitteeId.isChildOf(it) } if (granted) get(permissionIdentifier)?.let { yield(it) } } } internal fun getPermittedPermissionsAndSource(permitteeId: PermitteeId): Sequence<Pair<PermitteeId, P>> = sequence { for ((permissionIdentifier, permissibleIdentifiers) in grantedPermissionsMap) { permissibleIdentifiers.forEach { pid -> if (permitteeId.isChildOf(pid)) { get(permissionIdentifier)?.let { yield(pid to it) } } } } } } internal fun PermitteeId.getPermittedPermissionsAndSource(): Sequence<Pair<PermitteeId, Permission>> { return when (val ps = PermissionService.INSTANCE) { is AbstractConcurrentPermissionService -> ps.getPermittedPermissionsAndSource(this) else -> ps.getPermittedPermissions(this).map { this to it } } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.permission import kotlinx.serialization.Serializable import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.PluginDataExtensions import net.mamoe.mirai.console.data.PluginDataExtensions.withDefault import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.permission.* import net.mamoe.mirai.console.util.ConsoleExperimentalApi import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.CopyOnWriteArraySet import kotlin.reflect.KClass import kotlin.reflect.full.isSuperclassOf @Suppress("unused") // don't pollute top-level internal fun PermissionService<*>.checkType(permissionType: KClass<out Permission>): PermissionService<Permission> { require(this.permissionType.isSuperclassOf(permissionType)) { "Custom-constructed Permission instance is not allowed (Required ${this.permissionType}, found ${permissionType}. " + "Please obtain Permission from PermissionService.INSTANCE.register or PermissionService.INSTANCE.get" } @Suppress("UNCHECKED_CAST") return this as PermissionService<Permission> } internal class AllPermitPermissionService : PermissionService<PermissionImpl> { private val all = ConcurrentHashMap<PermissionId, PermissionImpl>() override val permissionType: KClass<PermissionImpl> get() = PermissionImpl::class override val rootPermission: PermissionImpl get() = RootPermissionImpl.also { all[it.id] = it } override fun register( id: PermissionId, description: String, parent: Permission, ): PermissionImpl { val new = PermissionImpl(id, description, parent) val old = all.putIfAbsent(id, new) if (old != null) throw PermissionRegistryConflictException(new, old) return new } override fun get(id: PermissionId): PermissionImpl? = all[id] override fun getRegisteredPermissions(): Sequence<PermissionImpl> = all.values.asSequence() override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<PermissionImpl> = all.values.asSequence() override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) { } override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean = true override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) { } } @Suppress("DEPRECATION") private val RootPermissionImpl = PermissionImpl(PermissionId("*", "*"), "The root permission").also { it.parent = it } internal class AllDenyPermissionService : PermissionService<PermissionImpl> { private val all = ConcurrentHashMap<PermissionId, PermissionImpl>() override val permissionType: KClass<PermissionImpl> get() = PermissionImpl::class override val rootPermission: PermissionImpl = RootPermissionImpl.also { all[it.id] = it } override fun register( id: PermissionId, description: String, parent: Permission, ): PermissionImpl { val new = PermissionImpl(id, description, parent) val old = all.putIfAbsent(id, new) if (old != null) throw PermissionRegistryConflictException(new, old) return new } override fun get(id: PermissionId): PermissionImpl? = all[id] override fun getRegisteredPermissions(): Sequence<PermissionImpl> = all.values.asSequence() override fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<PermissionImpl> = emptySequence() override fun permit(permitteeId: PermitteeId, permission: PermissionImpl) { } override fun testPermission(permitteeId: PermitteeId, permission: PermissionImpl): Boolean = false override fun cancel(permitteeId: PermitteeId, permission: PermissionImpl, recursive: Boolean) { } } internal class BuiltInPermissionService : AbstractConcurrentPermissionService<PermissionImpl>(), PermissionService<PermissionImpl> { class Provider : PermissionServiceProvider { override val instance: PermissionService<*> by lazy { BuiltInPermissionService() } override val priority: Int get() = -1 } override val permissionType: KClass<PermissionImpl> get() = PermissionImpl::class override val permissions: ConcurrentHashMap<PermissionId, PermissionImpl> = ConcurrentHashMap() override val rootPermission: PermissionImpl = RootPermissionImpl.also { permissions[it.id] = it } @Suppress("UNCHECKED_CAST") override val grantedPermissionsMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>> get() = config.grantedPermissionMap as PluginDataExtensions.NotNullMutableMap<PermissionId, MutableCollection<PermitteeId>> override fun createPermission(id: PermissionId, description: String, parent: Permission): PermissionImpl = PermissionImpl(id, description, parent) internal val config: ConcurrentSaveData = ConcurrentSaveData("PermissionService") @Suppress("RedundantVisibilityModifier") internal class ConcurrentSaveData private constructor( saveName: String, @Suppress("UNUSED_PARAMETER") primaryConstructorMark: Any?, ) : AutoSavePluginConfig(saveName) { public val grantedPermissionMap: PluginDataExtensions.NotNullMutableMap<PermissionId, MutableSet<AbstractPermitteeId>> by value<MutableMap<PermissionId, MutableSet<AbstractPermitteeId>>>(ConcurrentHashMap()) .withDefault { CopyOnWriteArraySet() } public companion object { @JvmStatic public operator fun invoke( saveName: String, // delegate: PluginConfig, ): ConcurrentSaveData = ConcurrentSaveData(saveName, null) } } } /** * [Permission] 的简单实现 */ @Serializable internal data class PermissionImpl @Deprecated("Only for Root") constructor( override val id: PermissionId, override val description: String, ) : Permission { override lateinit var parent: Permission @Suppress("DEPRECATION") constructor(id: PermissionId, description: String, parent: Permission) : this(id, description) { this.parent = parent } override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as PermissionImpl if (id != other.id) return false if (description != other.description) return false if (parent !== other.parent) return false return true } override fun hashCode(): Int { var result = id.hashCode() result = 31 * result + description.hashCode() result = 31 * result + if (parent == this) 1 else parent.hashCode() return result } override fun toString(): String = "PermissionImpl(id=$id, description='$description', parent=${if (parent === this) "<self>" else parent.toString()})" } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/permission/parseFromStringImpl.kt ================================================ /* * 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("unused") package net.mamoe.mirai.console.internal.permission import net.mamoe.mirai.console.permission.AbstractPermitteeId import net.mamoe.mirai.console.permission.AbstractPermitteeId.* internal fun parseFromStringImpl(string: String): AbstractPermitteeId { val str = string.trim { it.isWhitespace() }.lowercase() if (str == "*") return AnyContact if (str == "console") return Console if (str == "client*") return AnyOtherClient if (str.isNotEmpty()) { when (str[0]) { 'g' -> { val arg = str.substring(1) if (arg == "*") return AnyGroup else arg.toLongOrNull()?.let(::ExactGroup)?.let { return it } } 'f' -> { val arg = str.substring(1) if (arg == "*") return AnyFriend else arg.toLongOrNull()?.let(::ExactFriend)?.let { return it } } 'u' -> { val arg = str.substring(1) if (arg == "*") return AnyUser else arg.toLongOrNull()?.let(::ExactUser)?.let { return it } } 's' -> { val arg = str.substring(1) if (arg == "*") return AnyStranger else arg.toLongOrNull()?.let(::ExactStranger)?.let { return it } } 'c' -> { val arg = str.substring(1) if (arg == "*") return AnyContact } 'm' -> run { val arg = str.substring(1) if (arg == "*") return AnyMemberFromAnyGroup else { val components = arg.split('.') if (components.size == 2) { val groupId = components[0].toLongOrNull() ?: return@run if (components[1] == "*") return AnyMember(groupId) else { val memberId = components[1].toLongOrNull() ?: return@run return ExactMember(groupId, memberId) } } } } 't' -> run { val arg = str.substring(1) if (arg == "*") return AnyTempFromAnyGroup else { val components = arg.split('.') if (components.size == 2) { val groupId = components[0].toLongOrNull() ?: return@run if (components[1] == "*") return AnyGroupTemp(groupId) else { val memberId = components[1].toLongOrNull() ?: return@run return ExactGroupTemp(groupId, memberId) } } } } } } error("Cannot deserialize '$str' as AbstractPermissibleIdentifier") } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/AllDependenciesClassesHolder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.plugin // Same as LazyLoad internal object AllDependenciesClassesHolder { @JvmField internal val allclasses = AllDependenciesClassesHolder::class.java .getResourceAsStream("/META-INF/mirai-console/allclasses.txt")!! .bufferedReader().use { reader -> reader.useLines { lines -> lines.filterNot { it.isBlank() } .toHashSet() } } @JvmField internal val appClassLoader: ClassLoader = AllDependenciesClassesHolder::class.java.classLoader } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class, MiraiInternalApi::class) package net.mamoe.mirai.console.internal.plugin import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ensureActive import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.util.PluginServiceHelper.findServices import net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadAllServices import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.dependencies import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.* import net.mamoe.mirai.console.plugin.loader.AbstractFilePluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoadException import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.* import net.mamoe.yamlkt.Yaml import java.io.File import java.nio.file.Path import java.util.concurrent.ConcurrentHashMap import kotlin.coroutines.CoroutineContext internal val JvmPluginLoader.implOrNull get() = this.castOrNull<BuiltInJvmPluginLoaderImpl>() internal class BuiltInJvmPluginLoaderImpl( parentCoroutineContext: CoroutineContext ) : AbstractFilePluginLoader<JvmPlugin, JvmPluginDescription>(".jar"), CoroutineScope by parentCoroutineContext.childScope("JvmPluginLoader", CoroutineExceptionHandler { _, throwable -> logger.error("Unhandled Jar plugin exception: ${throwable.message}", throwable) }), JvmPluginLoader { companion object { internal val logger: MiraiLogger = MiraiLogger.Factory.create(JvmPluginLoader::class) } private fun pluginsFilesSequence( files: Sequence<File> = PluginManager.pluginsFolder.listFiles().orEmpty().asSequence() ): Sequence<File> { val raw = files .filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) } .toMutableList() val mirai2List = raw.filter { it.name.endsWith(".mirai2.jar", ignoreCase = true) } for (mirai2Plugin in mirai2List) { val name = mirai2Plugin.name.substringBeforeLast('.').substringBeforeLast('.') // without ext. raw.removeAll { it !== mirai2Plugin && it.name.substringBeforeLast('.').substringBeforeLast('.') == name } // remove those with .mirai.jar } return raw.asSequence() } override fun listPlugins(): List<JvmPlugin> { return pluginsFilesSequence().extractPlugins() } override val configStorage: PluginDataStorage get() = MiraiConsoleImplementation.getInstance().configStorageForJvmPluginLoader override val dataStorage: PluginDataStorage get() = MiraiConsoleImplementation.getInstance().dataStorageForJvmPluginLoader private val jvmPluginLoadingCtx: JvmPluginsLoadingCtx by lazy { val legacyCompatibilityLayerClassLoader = LegacyCompatibilityLayerClassLoader.newInstance( BuiltInJvmPluginLoaderImpl::class.java.classLoader, ) val classLoader = DynLibClassLoader.newInstance( legacyCompatibilityLayerClassLoader, "GlobalShared", "global-shared" ) val ctx = JvmPluginsLoadingCtx( legacyCompatibilityLayerClassLoader, classLoader, mutableListOf(), JvmPluginDependencyDownloader(logger), ) logger.debug { "Downloading legacy compatibility modules....." } ctx.downloader.resolveDependencies( sequenceOf( "client-core", "client-core-jvm", "client-okhttp", "utils", "utils-jvm", ).map { "io.ktor:ktor-$it:1.6.8" }.asIterable() ).let { rsp -> rsp.artifactResults.forEach { legacyCompatibilityLayerClassLoader.addLib(it.artifact.file) } if (logger.isVerboseEnabled) { logger.verbose("Legacy compatibility modules:") rsp.artifactResults.forEach { art -> logger.verbose(" `- ${art.artifact} -> ${art.artifact.file}") } } } logger.verbose { "Plugin shared libraries: " + PluginManager.pluginSharedLibrariesFolder } PluginManager.pluginSharedLibrariesFolder.listFiles()?.asSequence().orEmpty() .onEach { logger.debug { "Peek $it in shared libraries" } } .filter { file -> if (file.isDirectory) { return@filter true } if (!file.exists()) { logger.debug { "Skipped $file because file not exists" } return@filter false } if (file.isFile) { if (file.extension == "jar") { return@filter true } logger.debug { "Skipped $file because extension <${file.extension}> != jar" } return@filter false } logger.debug { "Skipped $file because unknown error" } return@filter false } .filter { it.isDirectory || (it.isFile && it.extension == "jar") } .forEach { pt -> classLoader.addLib(pt) logger.debug { "Linked static shared library: $pt" } } val libraries = PluginManager.pluginSharedLibrariesFolder.resolve("libraries.txt") if (libraries.isFile) { logger.verbose { "Linking static shared libraries...." } val libs = libraries.useLines { lines -> lines.filter { it.isNotBlank() } .filterNot { it.startsWith("#") } .onEach { logger.verbose { "static lib queued: $it" } } .toMutableList() } val staticLibs = ctx.downloader.resolveDependencies(libs) staticLibs.artifactResults.forEach { artifactResult -> if (artifactResult.isResolved) { ctx.sharedLibrariesLoader.addLib(artifactResult.artifact.file) ctx.sharedLibrariesDependencies.add(artifactResult.artifact.depId()) logger.debug { "Linked static shared library: ${artifactResult.artifact}" } logger.verbose { "Linked static shared library: ${artifactResult.artifact.file}" } } } } else { libraries.createNewFile() } ctx } override val classLoaders: MutableList<JvmPluginClassLoaderN> get() = jvmPluginLoadingCtx.pluginClassLoaders override fun findLoadedClass(name: String): Class<*>? { return classLoaders.firstNotNullOfOrNull { it.loadedClass(name) } } override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = plugin.description private val pluginFileToInstanceMap: MutableMap<File, JvmPlugin> = ConcurrentHashMap() override fun Sequence<File>.extractPlugins(): List<JvmPlugin> { ensureActive() fun Sequence<Map.Entry<File, JvmPluginClassLoaderN>>.initialize(): Sequence<Map.Entry<File, JvmPluginClassLoaderN>> { return onEach { (_, pluginClassLoader) -> val exportManagers = pluginClassLoader.findServices( ExportManager::class ).loadAllServices() if (exportManagers.isEmpty()) { val rules = pluginClassLoader.getResourceAsStream("export-rules.txt") if (rules == null) pluginClassLoader.declaredFilter = StandardExportManagers.AllExported else rules.bufferedReader(Charsets.UTF_8).useLines { pluginClassLoader.declaredFilter = ExportManagerImpl.parse(it.iterator()) } } else { pluginClassLoader.declaredFilter = exportManagers[0] } } } fun Sequence<Map.Entry<File, JvmPluginClassLoaderN>>.findAllInstances(): Sequence<Map.Entry<File, JvmPlugin>> { return map { (f, pluginClassLoader) -> f to pluginClassLoader.findServices( JvmPlugin::class, KotlinPlugin::class, JavaPlugin::class ).loadAllServices().also { plugins -> plugins.firstOrNull()?.logger?.let { pluginClassLoader.linkedLogger = it } } }.flatMap { (f, list) -> list.associateBy { f }.asSequence() } } fun Map.Entry<File, JvmPluginClassLoaderN>.loadWithoutPluginDescription(): Sequence<Pair<File, JvmPlugin>> { return sequenceOf(this).initialize().findAllInstances().map { (k, v) -> k to v } } fun Map.Entry<File, JvmPluginClassLoaderN>.loadWithPluginDescription(description: JvmPluginDescription): Sequence<Pair<File, JvmPlugin>> { val pluginClassLoader = this.value val pluginFile = this.key pluginClassLoader.pluginDescriptionFromPluginResource = description val pendingPlugin = object : NotYetLoadedJvmPlugin( description = description, classLoaderN = pluginClassLoader, ) { private val plugin by lazy { val services = pluginClassLoader.findServices( JvmPlugin::class, KotlinPlugin::class, JavaPlugin::class ).loadAllServices() if (services.isEmpty()) { error("No plugin instance found in $pluginFile") } if (services.size > 1) { error( "Only one plugin can exist at the same time when using plugin.yml:\n\nPlugins found:\n" + services.joinToString( separator = "\n" ) { it.javaClass.name + " (from " + it.javaClass.classLoader + ")" } ) } return@lazy services[0] } override fun resolve(): JvmPlugin = plugin } pluginClassLoader.linkedLogger = pendingPlugin.logger return sequenceOf(pluginFile to pendingPlugin) } val filePlugins = this.filterNot { pluginFileToInstanceMap.containsKey(it) }.associateWith { JvmPluginClassLoaderN.newLoader(it, jvmPluginLoadingCtx) }.onEach { (_, classLoader) -> classLoaders.add(classLoader) }.asSequence().flatMap { entry -> val (file, pluginClassLoader) = entry val pluginDescriptionDefine = pluginClassLoader.getResourceAsStream("plugin.yml") if (pluginDescriptionDefine == null) { entry.loadWithoutPluginDescription() } else { val desc = kotlin.runCatching { pluginDescriptionDefine.bufferedReader().use { resource -> Yaml.decodeFromString( SimpleJvmPluginDescription.SerialData.serializer(), resource.readText() ).toJvmPluginDescription() } }.onFailure { err -> throw PluginLoadException("Invalid plugin.yml in " + file.absolutePath, err) }.getOrThrow() entry.loadWithPluginDescription(desc) } }.onEach { logger.verbose { "Successfully initialized JvmPlugin ${it.second}." } }.onEach { (file, plugin) -> pluginFileToInstanceMap[file] = plugin } return filePlugins.toSet().map { it.second } } private val loadedPlugins = ConcurrentHashMap<String, JvmPlugin>() private fun Path.moveNameFolder(plugin: JvmPlugin) { val nameFolder = this.resolve(plugin.description.name).toFile() if (plugin.description.name != plugin.description.id && nameFolder.exists()) { // need move val idFolder = this.resolve(plugin.description.id).toFile() val moveDescription = "移动 ${plugin.description.smartToString()} 的数据文件目录(${nameFolder.path})到 ${idFolder.path}" if (idFolder.exists()) { if (idFolder.listFiles()?.size != 0) { logger.error("$moveDescription 失败, 原因:数据文件目录(${idFolder.path})被占用") logger.error("Mirai Console 将自动关闭, 请删除或移动该目录后再启动") MiraiConsole.job.cancel() } else idFolder.delete() } kotlin.runCatching { logger.info(moveDescription) if (!nameFolder.renameTo(idFolder)) { logger.error("$moveDescription 失败") logger.error("Mirai Console 将自动关闭, 请手动移动该文件夹后再启动") MiraiConsole.job.cancel() } }.onFailure { logger.error("$moveDescription 失败, 原因:\n", it) logger.error("Mirai Console 将自动关闭, 请解决该错误后再启动") MiraiConsole.job.cancel() } logger.info("$moveDescription 完成") } } @Throws(PluginLoadException::class) override fun load(plugin: JvmPlugin) { ensureActive() if (loadedPlugins.put(plugin.id, plugin) != null) { error("Plugin '${plugin.id}' is already loaded and cannot be reloaded.") } logger.verbose { "Loading plugin ${plugin.description.smartToString()}" } runCatching { // move nameFolder in config and data to idFolder PluginManager.pluginsDataPath.moveNameFolder(plugin) PluginManager.pluginsConfigPath.moveNameFolder(plugin) check(plugin is JvmPluginInternal || plugin is NotYetLoadedJvmPlugin) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" } // region Link dependencies when (plugin) { is NotYetLoadedJvmPlugin -> plugin.classLoaderN else -> plugin.javaClass.classLoader }.safeCast<JvmPluginClassLoaderN>()?.let { jvmPluginClassLoaderN -> // Link plugin dependencies plugin.description.dependencies.asSequence().mapNotNull { dependency -> plugin.logger.verbose { "Linking dependency: ${dependency.id}" } PluginManager.plugins.firstOrNull { it.id == dependency.id } }.mapNotNull { it.javaClass.classLoader.safeCast<JvmPluginClassLoaderN>() }.forEach { dependency -> plugin.logger.debug { "Linked dependency: $dependency" } jvmPluginClassLoaderN.dependencies.add(dependency) jvmPluginClassLoaderN.pluginSharedCL.dependencies.cast<MutableList<DynLibClassLoader>>().add( dependency.pluginSharedCL ) } jvmPluginClassLoaderN.linkPluginLibraries(plugin.logger) } val realPlugin = when (plugin) { is NotYetLoadedJvmPlugin -> plugin.resolve().also { realPlugin -> check(plugin.description === realPlugin.description) { "A JvmPlugin loaded by plugin.yml must has same description reference" } } else -> plugin } check(realPlugin is JvmPluginInternal) { "A JvmPlugin must extend AbstractJvmPlugin to be loaded by JvmPluginLoader.BuiltIn" } // endregion realPlugin.internalOnLoad() }.getOrElse { throw PluginLoadException("Exception while loading ${plugin.description.smartToString()}", it) } } override fun enable(plugin: JvmPlugin) { if (plugin.isEnabled) error("Plugin '${plugin.name}' is already enabled and cannot be re-enabled.") ensureActive() runCatching { logger.verbose { "Enabling plugin ${plugin.description.smartToString()}" } val loadedPlugins = PluginManager.plugins val failedDependencies = plugin.dependencies.asSequence().mapNotNull { dep -> loadedPlugins.firstOrNull { it.id == dep.id } }.filterNot { it.isEnabled }.toList() if (failedDependencies.isNotEmpty()) { logger.error("Failed to enable '${plugin.name}' because dependencies not enabled: " + failedDependencies.joinToString { "'${it.name}'" }) return } if (plugin is JvmPluginInternal) { plugin.internalOnEnable() } else plugin.onEnable() // Extra space for logging align logger.verbose { "Enabled plugin ${plugin.description.smartToString()}" } }.getOrElse { throw PluginLoadException("Exception while enabling ${plugin.description.name}", it) } } override fun disable(plugin: JvmPlugin) { if (!plugin.isEnabled) error("Plugin '${plugin.name}' is not already disabled and cannot be re-disabled.") if (MiraiConsole.isActive) ensureActive() if (plugin is JvmPluginInternal) { plugin.internalOnDisable() } else plugin.onDisable() } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/Exceptions.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused", "RedundantVisibilityModifier") package net.mamoe.mirai.console.internal.plugin internal class PluginMissingDependencyException : PluginResolutionException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } internal class PluginInfiniteCircularDependencyReferenceException: PluginResolutionException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } internal open class PluginResolutionException : Exception { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/ExportManagerImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.plugin import net.mamoe.mirai.console.plugin.jvm.ExportManager import net.mamoe.mirai.console.util.ConsoleExperimentalApi internal class ExportManagerImpl( private val rules: List<(String) -> Boolean?> ) : ExportManager { override fun isExported(className: String): Boolean { rules.forEach { val result = it(className) if (result != null) return@isExported result } return true } companion object { @JvmStatic fun parse(lines: Iterator<String>): ExportManagerImpl { fun Boolean.without(value: Boolean) = if (this == value) null else this val rules = ArrayList<(String) -> Boolean?>() lines.asSequence().map { it.trim() }.filter { it.isNotBlank() }.filterNot { it[0] == '#' }.forEach { line -> val command = line.substringBefore(' ') val argument = line.substringAfter(' ', missingDelimiterValue = "").trim() val argumentPackage = "$argument." when (command) { "exports" -> rules.add { (it == argument || it.startsWith(argumentPackage)).without(false) } "protects" -> rules.add { if (it == argument || it.startsWith(argumentPackage)) false else null } "export-all", "export-plugin", "export-system" -> rules.add { true } "protect-all", "protect-plugin", "protect-system" -> rules.add { false } } } return ExportManagerImpl(rules) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.plugin import net.mamoe.mirai.console.plugin.jvm.ExportManager import net.mamoe.mirai.console.plugin.jvm.JvmPluginClasspath import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.* import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.graph.DependencyFilter import java.io.File import java.io.InputStream import java.net.URL import java.net.URLClassLoader import java.util.* import java.util.concurrent.atomic.AtomicBoolean import java.util.zip.ZipFile /* Class resolving: | `- Resolve standard classes: hard linked by console (@see AllDependenciesClassesHolder) `- Resolve classes in shared libraries (Shared in all plugins) | |-===== SANDBOX ===== | `- Resolve classes in plugin dependency shared libraries (Shared by depend-ed plugins) `- Resolve classes in independent libraries (Can only be loaded by current plugin) `- Resolve classes in current jar. `- Resolve classes from other plugin jar `- Resolve by AppClassLoader */ internal class JvmPluginsLoadingCtx( val consoleClassLoader: ClassLoader, // plugin system -> mirai-console classloader WRAPPER val sharedLibrariesLoader: DynLibClassLoader, val pluginClassLoaders: MutableList<JvmPluginClassLoaderN>, val downloader: JvmPluginDependencyDownloader, ) { val sharedLibrariesDependencies = HashSet<String>() val sharedLibrariesFilter: DependencyFilter = DependencyFilter { node, _ -> return@DependencyFilter node.artifact.depId() !in sharedLibrariesDependencies } } internal open class DynamicClasspathClassLoader : URLClassLoader { internal constructor(urls: Array<URL>, parent: ClassLoader?) : super(urls, parent) @Suppress("Since15") internal constructor(urls: Array<URL>, parent: ClassLoader?, vmName: String?) : super(vmName, urls, parent) internal fun addLib(url: URL) { addURL(url) } internal fun addLib(file: File) { addURL(file.toURI().toURL()) } companion object { internal val java9: Boolean init { ClassLoader.registerAsParallelCapable() java9 = kotlin.runCatching { Class.forName("java.lang.Module") }.isSuccess } } } internal class LegacyCompatibilityLayerClassLoader : DynamicClasspathClassLoader { private constructor(parent: ClassLoader?) : super(arrayOf(), parent) private constructor(parent: ClassLoader?, vmName: String?) : super(arrayOf(), parent, vmName) override fun toString(): String { return "LegacyCompatibilityLayerClassLoader@" + hashCode() } companion object { init { ClassLoader.registerAsParallelCapable() } fun newInstance(parent: ClassLoader?): LegacyCompatibilityLayerClassLoader { return if (java9) { LegacyCompatibilityLayerClassLoader(parent, "legacy-compatibility-layer") } else { LegacyCompatibilityLayerClassLoader(parent) } } } } internal class DynLibClassLoader : DynamicClasspathClassLoader { private val clName: String? internal var dependencies: List<DynLibClassLoader> = emptyList() private constructor(parent: ClassLoader?, clName: String?) : super(arrayOf(), parent) { this.clName = clName } private constructor(parent: ClassLoader?, clName: String?, vmName: String?) : super(arrayOf(), parent, vmName) { this.clName = clName } companion object { fun newInstance(parent: ClassLoader?, clName: String?, vmName: String?): DynLibClassLoader { return when { java9 -> DynLibClassLoader(parent, clName, vmName) else -> DynLibClassLoader(parent, clName) } } fun tryFastOrStrictResolve(name: String): Class<*>? { if (name.startsWith("java.")) return Class.forName(name, false, JavaSystemPlatformClassLoader) // All mirai-core hard-linked should use same version to avoid errors (ClassCastException). if (name in AllDependenciesClassesHolder.allclasses) { return AllDependenciesClassesHolder.appClassLoader.loadClass(name) } if ( name.startsWith("net.mamoe.mirai.") || name.startsWith("kotlin.") || name.startsWith("kotlinx.") || name.startsWith("org.slf4j.") ) { // Avoid plugin classing cheating try { return AllDependenciesClassesHolder.appClassLoader.loadClass(name) } catch (ignored: ClassNotFoundException) { } } try { return Class.forName(name, false, JavaSystemPlatformClassLoader) } catch (ignored: ClassNotFoundException) { } return null } fun tryFastOrStrictResolveResources(name: String): Enumeration<URL> { if (name.startsWith("java/")) return JavaSystemPlatformClassLoader.getResources(name) // All mirai-core hard-linked should use same version to avoid errors (ClassCastException). val fromDependencies = AllDependenciesClassesHolder.appClassLoader.getResources(name) return if ( name.startsWith("net/mamoe/mirai/") || name.startsWith("kotlin/") || name.startsWith("kotlinx/") || name.startsWith("org/slf4j/") ) { // Avoid plugin classing cheating fromDependencies } else { LinkedHashSet<URL>().apply { addAll(fromDependencies) addAll(JavaSystemPlatformClassLoader.getResources(name)) }.let { Collections.enumeration(it) } } } } internal fun loadClassInThisClassLoader(name: String): Class<*>? { synchronized(getClassLoadingLock(name)) { findLoadedClass(name)?.let { return it } try { return findClass(name) } catch (ignored: ClassNotFoundException) { } } return null } override fun toString(): String { clName?.let { return "DynLibClassLoader{$it}" } return "DynLibClassLoader@" + hashCode() } override fun getResource(name: String?): URL? { if (name == null) return null findResource(name)?.let { return it } if (parent is DynLibClassLoader) { return parent.getResource(name) } return null } override fun getResources(name: String?): Enumeration<URL> { if (name == null) return Collections.emptyEnumeration() val res = findResources(name) return if (parent is DynLibClassLoader) { res + parent.getResources(name) } else { res } } internal fun findButNoSystem(name: String): Class<*>? = findButNoSystem(name, mutableListOf()) private fun findButNoSystem(name: String, track: MutableList<DynLibClassLoader>): Class<*>? { if (name.startsWith("java.")) return null // Skip duplicated searching, for faster speed. if (this in track) return null track.add(this) val pt = this.parent if (pt is DynLibClassLoader) { pt.findButNoSystem(name, track)?.let { return it } } dependencies.forEach { dep -> dep.findButNoSystem(name, track)?.let { return it } } synchronized(getClassLoadingLock(name)) { findLoadedClass(name)?.let { return it } try { findClass(name)?.let { return it } } catch (ignored: ClassNotFoundException) { } } return null } override fun loadClass(name: String, resolve: Boolean): Class<*> { tryFastOrStrictResolve(name)?.let { return it } findButNoSystem(name)?.let { return it } val topParent = generateSequence<ClassLoader>(this) { it.parent }.firstOrNull { it !is DynLibClassLoader } return Class.forName(name, false, topParent) } } internal class JvmPluginClassLoaderN : URLClassLoader { var pluginDescriptionFromPluginResource: JvmPluginDescription? = null val openaccess: JvmPluginClasspath = OpenAccess() val file: File val ctx: JvmPluginsLoadingCtx val sharedLibrariesLogger: DynLibClassLoader val dependencies: MutableCollection<JvmPluginClassLoaderN> = hashSetOf() lateinit var pluginSharedCL: DynLibClassLoader lateinit var pluginIndependentCL: DynLibClassLoader @Suppress("PrivatePropertyName") private val file_: File get() = file var linkedLogger by lateinitMutableProperty { MiraiLogger.Factory.create( JvmPluginClassLoaderN::class, "JvmPlugin[" + file_.name + "]" ) } val undefinedDependencies = mutableSetOf<String>() @Suppress("UNUSED_PARAMETER") private constructor(file: File, ctx: JvmPluginsLoadingCtx, unused: Unit) : super( arrayOf(), ctx.sharedLibrariesLoader ) { this.sharedLibrariesLogger = ctx.sharedLibrariesLoader this.file = file this.ctx = ctx init1() } @Suppress("Since15") private constructor(file: File, ctx: JvmPluginsLoadingCtx) : super( file.name, arrayOf(), ctx.sharedLibrariesLoader ) { this.sharedLibrariesLogger = ctx.sharedLibrariesLoader this.file = file this.ctx = ctx init1() } private fun init1() { try { init0() } catch (e: Throwable) { e.addSuppressed(RuntimeException("Failed to initialize new JvmPluginClassLoader, file=$file")) throw e } } private fun init0() { ZipFile(file).use { zipFile -> zipFile.entries().asSequence() .filter { it.name.endsWith(".class") } .map { it.name.substringBeforeLast('.') } .map { it.removePrefix("/").replace('/', '.') } .map { it.substringBeforeLast('.') } .forEach { pkg -> pluginMainPackages.add(pkg) } zipFile.getEntry("META-INF/mirai-console-plugin/options.properties")?.let { optionsEntry -> runCatching { val options = Properties() zipFile.getInputStream(optionsEntry).bufferedReader().use { reader -> options.load(reader) } openaccess.shouldBeResolvableToIndependent = options.prop( "class.loading.be-resolvable-to-independent", "true" ) { it.toBooleanStrict() } openaccess.shouldResolveIndependent = options.prop( "class.loading.resolve-independent", "true" ) { it.toBooleanStrict() } openaccess.shouldResolveConsoleSystemResource = options.prop( "resources.resolve-console-system-resources", "false" ) { it.toBooleanStrict() } }.onFailure { err -> throw IllegalStateException( "Exception while reading META-INF/mirai-console-plugin/options.properties", err ) } } } pluginSharedCL = DynLibClassLoader.newInstance( ctx.sharedLibrariesLoader, "SharedCL{${file.name}}", "${file.name}[shared]" ) pluginIndependentCL = DynLibClassLoader.newInstance( pluginSharedCL, "IndependentCL{${file.name}}", "${file.name}[private]" ) pluginSharedCL.dependencies = mutableListOf() addURL(file.toURI().toURL()) } private val pluginMainPackages: MutableSet<String> = HashSet() internal var declaredFilter: ExportManager? = null val sharedClLoadedDependencies = mutableSetOf<String>() val privateClLoadedDependencies = mutableSetOf<String>() internal fun containsSharedDependency( dependency: String ): Boolean { if (dependency in sharedClLoadedDependencies) return true return dependencies.any { it.containsSharedDependency(dependency) } } internal fun linkPluginSharedLibraries(logger: MiraiLogger, dependencies: Collection<String>) { linkLibraries(logger, dependencies, true) } internal fun linkPluginPrivateLibraries(logger: MiraiLogger, dependencies: Collection<String>) { linkLibraries(logger, dependencies, false) } private val isPluginLibrariesLinked = AtomicBoolean(false) fun linkPluginLibraries(logger: MiraiLogger) { if (!isPluginLibrariesLinked.compareAndSet(false, true)) return // Link jar dependencies fun InputStream?.readDependencies(): Collection<String> { if (this == null) return emptyList() return bufferedReader().useLines { lines -> lines.filterNot { it.isBlank() } .filterNot { it.startsWith('#') } .map { it.trim() } .toMutableList() } } linkPluginSharedLibraries( logger, getResourceAsStream("META-INF/mirai-console-plugin/dependencies-shared.txt").readDependencies() ) linkPluginPrivateLibraries( logger, getResourceAsStream("META-INF/mirai-console-plugin/dependencies-private.txt").readDependencies() ) } private fun linkLibraries(logger: MiraiLogger, dependencies: Collection<String>, shared: Boolean) { if (dependencies.isEmpty()) return val results = ctx.downloader.resolveDependencies( dependencies, ctx.sharedLibrariesFilter, DependencyFilter filter@{ node, _ -> val depid = node.artifact.depId() if (containsSharedDependency(depid)) return@filter false if (depid in privateClLoadedDependencies) return@filter false return@filter true }) val files = results.artifactResults.mapNotNull { result -> result.artifact?.let { it to it.file } } val linkType = if (shared) "(shared)" else "(private)" files.forEach { (artifact, lib) -> logger.verbose { "Linking $lib $linkType" } if (shared) { pluginSharedCL.addLib(lib) sharedClLoadedDependencies.add(artifact.depId()) } else { pluginIndependentCL.addLib(lib) privateClLoadedDependencies.add(artifact.depId()) } logger.debug { "Linked $artifact $linkType <${if (shared) pluginSharedCL else pluginIndependentCL}>" } } } companion object { init { ClassLoader.registerAsParallelCapable() } fun newLoader(file: File, ctx: JvmPluginsLoadingCtx): JvmPluginClassLoaderN { return when { DynamicClasspathClassLoader.java9 -> JvmPluginClassLoaderN(file, ctx) else -> JvmPluginClassLoaderN(file, ctx, Unit) } } } internal fun resolvePluginSharedLibAndPluginClass(name: String): Class<*>? { return try { pluginSharedCL.findButNoSystem(name) } catch (e: ClassNotFoundException) { null } ?: resolvePluginPublicClass(name) } internal fun resolvePluginPublicClass(name: String): Class<*>? { if (pluginMainPackages.contains(name.pkgName())) { if (declaredFilter?.isExported(name) == false) return null synchronized(getClassLoadingLock(name)) { findLoadedClass(name)?.let { return it } try { return super.findClass(name) } catch (ignored: ClassNotFoundException) { } } } return null } override fun loadClass(name: String, resolve: Boolean): Class<*> = loadClass(name) override fun loadClass(name: String): Class<*> { DynLibClassLoader.tryFastOrStrictResolve(name)?.let { return it } sharedLibrariesLogger.loadClassInThisClassLoader(name)?.let { return it } // Search dependencies first dependencies.forEach { dependency -> dependency.resolvePluginSharedLibAndPluginClass(name)?.let { return it } } // Search in independent class loader // @context: pluginIndependentCL.parent = pluinSharedCL try { pluginIndependentCL.findButNoSystem(name)?.let { return it } } catch (ignored: ClassNotFoundException) { } try { synchronized(getClassLoadingLock(name)) { findLoadedClass(name)?.let { return it } return super.findClass(name) } } catch (error: ClassNotFoundException) { if (!openaccess.shouldResolveIndependent) { return ctx.consoleClassLoader.loadClass(name) } // Finally, try search from other plugins and console system ctx.pluginClassLoaders.forEach { other -> if (other !== this && other !in dependencies) { if (!other.openaccess.shouldBeResolvableToIndependent) return@forEach other.resolvePluginPublicClass(name)?.let { if (undefinedDependencies.add(other.file.name)) { linkedLogger.warning { "Linked class $name in ${other.file.name} but plugin not depend on it." } linkedLogger.warning { "Class loading logic may change in feature." } } return it } } } return ctx.consoleClassLoader.loadClass(name) } } internal fun loadedClass(name: String): Class<*>? = super.findLoadedClass(name) private fun getRes(name: String, shared: Boolean): Enumeration<URL> { val src = mutableListOf<Enumeration<URL>>( findResources(name), ) if (dependencies.isEmpty()) { if (shared) { src.add(sharedLibrariesLogger.getResources(name)) } } else { dependencies.forEach { dep -> src.add(dep.getRes(name, false)) } } src.add(pluginIndependentCL.getResources(name)) val resolved = LinkedHashSet<URL>() if (openaccess.shouldResolveConsoleSystemResource) { DynLibClassLoader.tryFastOrStrictResolveResources(name).let { resolved.addAll(it) } } src.forEach { nested -> resolved.addAll(nested) } return Collections.enumeration(resolved) } override fun getResources(name: String?): Enumeration<URL> { name ?: return Collections.emptyEnumeration() if (name.startsWith("META-INF/mirai-console-plugin/")) return findResources(name) // Avoid loading duplicated mirai-console plugins if (name.startsWith("META-INF/services/net.mamoe.mirai.console.plugin.")) return findResources(name) return getRes(name, true) } override fun getResource(name: String?): URL? { name ?: return null if (name.startsWith("META-INF/mirai-console-plugin/")) return findResource(name) // Avoid loading duplicated mirai-console plugins if (name.startsWith("META-INF/services/net.mamoe.mirai.console.plugin.")) return findResource(name) if (openaccess.shouldResolveConsoleSystemResource) { DynLibClassLoader.tryFastOrStrictResolveResources(name) .takeIf { it.hasMoreElements() } ?.let { return it.nextElement() } } findResource(name)?.let { return it } // parent: ctx.sharedLibrariesLoader sharedLibrariesLogger.getResource(name)?.let { return it } dependencies.forEach { dep -> dep.getResource(name)?.let { return it } } return pluginIndependentCL.getResource(name) } override fun toString(): String { return "JvmPluginClassLoader{${file.name}}" } inner class OpenAccess : JvmPluginClasspath { override val pluginFile: File get() = this@JvmPluginClassLoaderN.file override val pluginClassLoader: ClassLoader get() = this@JvmPluginClassLoaderN override val pluginSharedLibrariesClassLoader: ClassLoader get() = pluginSharedCL override val pluginIndependentLibrariesClassLoader: ClassLoader get() = pluginIndependentCL override var shouldResolveConsoleSystemResource: Boolean = false override var shouldBeResolvableToIndependent: Boolean = true override var shouldResolveIndependent: Boolean = true private val permitted by lazy { arrayOf( this@JvmPluginClassLoaderN, pluginSharedCL, pluginIndependentCL, ) } override fun addToPath(classLoader: ClassLoader, file: File) { if (classLoader !in permitted) { throw IllegalArgumentException("Unsupported classloader or cross plugin accessing: $classLoader") } if (classLoader == this@JvmPluginClassLoaderN) { this@JvmPluginClassLoaderN.addURL(file.toURI().toURL()) return } classLoader as DynLibClassLoader classLoader.addLib(file) } override fun downloadAndAddToPath(classLoader: ClassLoader, dependencies: Collection<String>) { if (classLoader !in permitted) { throw IllegalArgumentException("Unsupported classloader or cross plugin accessing: $classLoader") } if (classLoader === this@JvmPluginClassLoaderN) { throw IllegalArgumentException("Only support download dependencies to `plugin[Shared/Independent]LibrariesClassLoader`") } this@JvmPluginClassLoaderN.linkLibraries( linkedLogger, dependencies, classLoader === pluginSharedCL ) } } } private val JavaSystemPlatformClassLoader: ClassLoader by lazy { kotlin.runCatching { ClassLoader::class.java.methods.asSequence().filter { it.name == "getPlatformClassLoader" }.filter { java.lang.reflect.Modifier.isStatic(it.modifiers) }.firstOrNull()?.invoke(null) as ClassLoader? }.getOrNull() ?: ClassLoader.getSystemClassLoader().parent } private fun String.pkgName(): String = substringBeforeLast('.', "") internal fun Artifact.depId(): String = "$groupId:$artifactId" private operator fun <E> Enumeration<E>.plus(next: Enumeration<E>): Enumeration<E> { return compoundEnumerations(listOf(this, next).iterator()) } private fun <E> compoundEnumerations(iter: Iterator<Enumeration<E>>): Enumeration<E> { return object : Enumeration<E> { private lateinit var crt: Enumeration<E> private var hasMore: Boolean = false private var fetched: Boolean = false override tailrec fun hasMoreElements(): Boolean { if (fetched) return hasMore if (::crt.isInitialized) { hasMore = crt.hasMoreElements() if (hasMore) { fetched = true return true } } if (!iter.hasNext()) { fetched = true hasMore = false return false } crt = iter.next() return hasMoreElements() } override fun nextElement(): E { if (hasMoreElements()) { return crt.nextElement().also { fetched = false } } throw NoSuchElementException() } } } private fun Properties.prop(key: String, def: String): String { try { return getProperty(key, def) } catch (err: Throwable) { throw IllegalStateException("Exception while reading `$key`", err) } } private inline fun <T> Properties.prop(key: String, def: String, dec: (String) -> T): T { try { return getProperty(key, def).let(dec) } catch (err: Throwable) { throw IllegalStateException("Exception while reading `$key`", err) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginDependencyDownload.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class) package net.mamoe.mirai.console.internal.plugin import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.ConsoleDataScope.Companion.get import net.mamoe.mirai.console.fontend.ProcessProgress import net.mamoe.mirai.console.internal.MiraiConsoleBuildDependencies import net.mamoe.mirai.console.internal.data.builtins.DataScope import net.mamoe.mirai.console.internal.data.builtins.PluginDependenciesConfig import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.util.renderMemoryUsageNumber import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.verbose import org.apache.maven.repository.internal.MavenRepositorySystemUtils import org.codehaus.plexus.util.ReaderFactory import org.codehaus.plexus.util.xml.pull.MXParser import org.codehaus.plexus.util.xml.pull.XmlPullParser import org.eclipse.aether.RepositorySystem import org.eclipse.aether.RepositorySystemSession import org.eclipse.aether.artifact.Artifact import org.eclipse.aether.artifact.DefaultArtifact import org.eclipse.aether.collection.CollectRequest import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory import org.eclipse.aether.graph.Dependency import org.eclipse.aether.graph.DependencyFilter import org.eclipse.aether.repository.LocalRepository import org.eclipse.aether.repository.RemoteRepository import org.eclipse.aether.repository.WorkspaceReader import org.eclipse.aether.repository.WorkspaceRepository import org.eclipse.aether.resolution.DependencyRequest import org.eclipse.aether.resolution.DependencyResult import org.eclipse.aether.spi.connector.RepositoryConnectorFactory import org.eclipse.aether.spi.connector.transport.TransporterFactory import org.eclipse.aether.transfer.AbstractTransferListener import org.eclipse.aether.transfer.TransferEvent import org.eclipse.aether.transport.http.HttpTransporterFactory import java.io.File import java.util.concurrent.ConcurrentHashMap @Suppress("DEPRECATION", "MemberVisibilityCanBePrivate") internal class JvmPluginDependencyDownloader( val logger: MiraiLogger, ) { val repositories: MutableList<RemoteRepository> val session: RepositorySystemSession val locator: org.eclipse.aether.spi.locator.ServiceLocator val repository: RepositorySystem val dependencyFilter: DependencyFilter = DependencyFilter { node, _ -> if (node == null || node.artifact == null) return@DependencyFilter true val artGroup = node.artifact.groupId val artId = node.artifact.artifactId if (artGroup == "net.mamoe") { if (artId in listOf( "mirai-core", "mirai-core-jvm", "mirai-core-android", "mirai-core-api", "mirai-core-api-jvm", "mirai-core-api-android", "mirai-core-utils", "mirai-core-utils-jvm", "mirai-core-utils-android", "mirai-console", "mirai-console-terminal", "mirai-console-frontend-base", "mirai-console-compiler-annotations", "mirai-console-compiler-annotations-jvm", ) ) return@DependencyFilter false } // Re-download slf4j-api is unnecessary since slf4j-api was bound by console if (artGroup == "org.slf4j" && artId == "slf4j-api") { return@DependencyFilter false } // Loaded by console system if ("$artGroup:$artId" in MiraiConsoleBuildDependencies.dependencies) return@DependencyFilter false // println(" `- filter: $node") true } init { locator = MavenRepositorySystemUtils.newServiceLocator() locator.addService(RepositoryConnectorFactory::class.java, BasicRepositoryConnectorFactory::class.java) locator.addService(TransporterFactory::class.java, HttpTransporterFactory::class.java) repository = locator.getService(RepositorySystem::class.java) session = MavenRepositorySystemUtils.newSession() session.checksumPolicy = "fail" session.localRepositoryManager = repository.newLocalRepositoryManager( session, LocalRepository(PluginManager.pluginLibrariesFolder) ) session.transferListener = object : AbstractTransferListener() { private val dwnProgresses: MutableMap<File, ProcessProgress> = ConcurrentHashMap() override fun transferStarted(event: TransferEvent) { logger.verbose { "Downloading ${event.resource?.repositoryUrl}${event.resource?.resourceName}" } val nw = MiraiConsoleImplementation.getInstance().createNewProcessProgress() dwnProgresses.put( event.resource.file, nw )?.close() nw.setTotalSize(event.resource.contentLength) nw.updateText("Downloading ${event.resource.resourceName}....") } override fun transferSucceeded(event: TransferEvent) { dwnProgresses.remove(event.resource.file)?.let { dp -> dp.updateText(buildString { append("Downloaded ") append(event.resource.resourceName) append(" (") renderMemoryUsageNumber(this@buildString, event.resource.contentLength) append(")") }) dp.close() } } override fun transferProgressed(event: TransferEvent) { dwnProgresses[event.resource.file]?.let { pg -> pg.update(event.transferredBytes) pg.updateText(buildString bs@{ append("Downloading ") append(event.resource.resourceName) append(" (") val sz = this@bs.length renderMemoryUsageNumber(this@bs, event.transferredBytes) repeat(kotlin.math.max(0, 7 - (this@bs.length - sz))) { append(' ') } append(" / ") renderMemoryUsageNumber(this@bs, event.resource.contentLength) append(")") }) pg.rerender() } } override fun transferFailed(event: TransferEvent) { logger.warning(event.exception) dwnProgresses.remove(event.resource.file)?.let { it.markFailed() it.close() } } } val userHome = System.getProperty("user.home") fun findMavenLocal(): File { val mavenHome = File(userHome, ".m2") fun findFromSettingsXml(): File? { val settings = File(mavenHome, "settings.xml") if (!settings.isFile) return null ReaderFactory.newXmlReader(settings).use { reader -> val parser = MXParser() parser.setInput(reader) var eventType = parser.eventType var joinedSettings = false while (eventType != XmlPullParser.END_DOCUMENT) { when (eventType) { XmlPullParser.START_TAG -> { if (!joinedSettings) { if (parser.name != "settings") { return null } joinedSettings = true } else { if (parser.name == "localRepository") { val loc = File(parser.nextText()) if (loc.isDirectory) return loc return null } else { parser.skipSubTree() } } } // else -> parser.skipSubTree() } eventType = parser.next() } } return null } return kotlin.runCatching { findFromSettingsXml() }.onFailure { error -> logger.warning(error) }.getOrNull() ?: File(mavenHome, "repository") } fun findGradleDepCache(): File { var gradleHome = File(userHome, ".gradle") val gradleEnvHome = System.getenv("GRADLE_USER_HOME").orEmpty() if (gradleEnvHome.isNotBlank()) { gradleHome = File(gradleEnvHome) } return File(gradleHome, "caches/modules-2/files-2.1") } val mavenLocRepo = findMavenLocal() val gradleLocRepo = findGradleDepCache() logger.debug { "Maven local: $mavenLocRepo" } logger.debug { "Gradle cache local: $gradleLocRepo" } session.workspaceReader = object : WorkspaceReader { private val repository: WorkspaceRepository = WorkspaceRepository("default") override fun getRepository(): WorkspaceRepository = repository override fun findArtifact(artifact: Artifact): File? { // logger.debug { "Try resolve $artifact" } val path = session.localRepositoryManager.getPathForLocalArtifact(artifact) File(mavenLocRepo, path).takeIf { it.isFile }?.let { return it } val gradleDep = gradleLocRepo .resolve(artifact.groupId) .resolve(artifact.artifactId) .resolve(artifact.baseVersion) if (gradleDep.isDirectory) { val fileName = buildString { append(artifact.artifactId) append('-') append(artifact.baseVersion) artifact.classifier?.takeIf { it.isNotEmpty() }?.let { c -> append('-').append(c) } append('.').append(artifact.extension) } gradleDep.walk().maxDepth(2) .filter { it.isFile } .firstOrNull { it.name == fileName } ?.let { return it } } return null } override fun findVersions(artifact: Artifact?): MutableList<String> { return mutableListOf() } } session.setReadOnly() val config = DataScope.get<PluginDependenciesConfig>() repositories = repository.newResolutionRepositories( session, config.repoLoc.map { url -> RemoteRepository.Builder(url, "default", url).build() } ) logger.debug { "Remote server: " + config.repoLoc } } fun resolveDependencies(deps: Iterable<String>, vararg filters: DependencyFilter): DependencyResult { val dependencies: MutableList<Dependency> = ArrayList() for (library in deps) { val defaultArtifact = DefaultArtifact(library) val dependency = Dependency(defaultArtifact, null) dependencies.add(dependency) } return repository.resolveDependencies( session, DependencyRequest( CollectRequest( null as Dependency?, dependencies, repositories ), when { filters.isEmpty() -> dependencyFilter else -> DependencyFilter { node, parents -> if (node == null || node.artifact == null) return@DependencyFilter true if (!dependencyFilter.accept(node, parents)) return@DependencyFilter false filters.forEach { filter -> if (!filter.accept(node, parents)) return@DependencyFilter false } return@DependencyFilter true } } ) ) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(MiraiInternalApi::class, ConsoleFrontEndImplementation::class, ConsoleExperimentalApi::class) package net.mamoe.mirai.console.internal.plugin import kotlinx.atomicfu.AtomicLong import kotlinx.atomicfu.atomic import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.* import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.data.runCatchingLog import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.shutdown.ShutdownDaemon import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.AbstractJvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin.Companion.onLoad import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.safeCast import java.io.File import java.io.InputStream import java.nio.file.Path import java.util.* import java.util.concurrent.locks.ReentrantLock import kotlin.coroutines.CoroutineContext internal val <T> T.job: Job where T : CoroutineScope, T : Plugin get() = this.coroutineContext[Job]!! /** * Hides implementations from [JvmPlugin] */ @PublishedApi internal abstract class JvmPluginInternal( parentCoroutineContext: CoroutineContext, ) : JvmPlugin, CoroutineScope { internal enum class PluginStatus { ALLOCATED, CRASHED_LOAD_ERROR(Flags.ALLOW_SWITCH_TO_DISABLE), CRASHED_ENABLE_ERROR(Flags.ALLOW_SWITCH_TO_DISABLE), CRASHED_DISABLE_ERROR, LOAD_PENDING, LOAD_LOADING, LOAD_LOAD_DONE, ENABLE_PENDING, ENABLE_ENABLING, ENABLED(Flags.ALLOW_SWITCH_TO_DISABLE), DISABLE_PENDING, DISABLE_DISABLING, DISABLED, ; private val flags: Int constructor() : this(0) constructor(flags: Int) { this.flags = flags } internal object Flags { // compiler bug: [UNINITIALIZED_VARIABLE] Variable 'FLAG_ALLOW_SWITCH_TO_DISABLE' must be initialized internal const val ALLOW_SWITCH_TO_DISABLE = 1 shl 0 } fun hasFlag(flag: Int): Boolean = flags.and(flag) != 0 } private val pluginStatus = atomic(PluginStatus.ALLOCATED) @get:JvmSynthetic internal val currentPluginStatus: PluginStatus get() = pluginStatus.value final override val isEnabled: Boolean get() = pluginStatus.value === PluginStatus.ENABLED @JvmSynthetic internal fun switchStatusOrFail(expectFlag: Int, update: PluginStatus) { val nowStatus = pluginStatus.value if (nowStatus.hasFlag(expectFlag)) { if (pluginStatus.compareAndSet(expect = nowStatus, update = update)) { return } error("Failed to switch plugin '$id' status from $nowStatus to $update, current status = ${pluginStatus.value}") } error( "Failed to switch plugin '$id' status to $update because current status $nowStatus doesn't contain flag ${ Integer.toBinaryString( expectFlag ) }" ) } @JvmSynthetic internal fun switchStatusOrFail(expect: PluginStatus, update: PluginStatus) { val nowStatus = pluginStatus.value if (nowStatus === expect) { if (pluginStatus.compareAndSet(expect = expect, update = update)) { return } error("Failed to switch plugin '$id' status from $expect to $update, current status=${pluginStatus.value}") } error("Failed to switch plugin '$id' status from $expect to $update, current status = $nowStatus") } final override val parentPermission: Permission by lazy { PermissionService.INSTANCE.register( PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, "*"), "The base permission" ) } private val resourceContainerDelegate by lazy { this::class.java.classLoader.asResourceContainer() } final override fun getResourceAsStream(path: String): InputStream? = resourceContainerDelegate.getResourceAsStream(path) // region JvmPlugin final override val logger: MiraiLogger by lazy { BuiltInJvmPluginLoaderImpl.logger.runCatchingLog { MiraiLogger.Factory.create(this@JvmPluginInternal::class, this.description.name) }.getOrThrow() } private var firstRun = true final override val dataFolderPath: Path by lazy { PluginManager.pluginsDataPath.resolve(description.id).apply { mkdir() } } final override val dataFolder: File by lazy { dataFolderPath.toFile() } final override val configFolderPath: Path by lazy { PluginManager.pluginsConfigPath.resolve(description.id).apply { mkdir() } } final override val configFolder: File by lazy { configFolderPath.toFile() } internal fun internalOnDisable() { switchStatusOrFail( expectFlag = PluginStatus.Flags.ALLOW_SWITCH_TO_DISABLE, update = PluginStatus.DISABLE_PENDING, ) firstRun = false kotlin.runCatching { val crtThread = Thread.currentThread() ShutdownDaemon.pluginDisablingThreads.add(crtThread) try { pluginStatus.value = PluginStatus.DISABLE_DISABLING onDisable() } finally { ShutdownDaemon.pluginDisablingThreads.remove(crtThread) } }.fold( onSuccess = { pluginStatus.value = PluginStatus.DISABLED cancel(CancellationException("plugin disabled")) }, onFailure = { err -> pluginStatus.value = PluginStatus.CRASHED_DISABLE_ERROR cancel(CancellationException("Exception while disabling plugin", err)) // @TestOnly if (err is ConsoleJvmPluginTestFailedError) throw err if (MiraiConsoleImplementation.getInstance().consoleLaunchOptions.crashWhenPluginLoadFailed) { throw err } } ) } @Throws(Throwable::class) internal fun internalOnLoad() { switchStatusOrFail(PluginStatus.ALLOCATED, PluginStatus.LOAD_PENDING) try { pluginStatus.value = PluginStatus.LOAD_LOADING val componentStorage = PluginComponentStorage(this) onLoad(componentStorage) pluginStatus.value = PluginStatus.LOAD_LOAD_DONE GlobalComponentStorage.mergeWith(componentStorage) } catch (e: Throwable) { pluginStatus.value = PluginStatus.CRASHED_LOAD_ERROR cancel(CancellationException("Exception while loading plugin", e)) throw e } } internal fun internalOnEnable(): Boolean { switchStatusOrFail(PluginStatus.LOAD_LOAD_DONE, PluginStatus.ENABLE_PENDING) parentPermission if (!firstRun) refreshCoroutineContext() val except = try { javaClass.getDeclaredAnnotation(ConsoleJvmPluginFuncCallbackStatusExcept.OnEnable::class.java) } catch (e: Throwable) { null } kotlin.runCatching { pluginStatus.value = PluginStatus.ENABLE_ENABLING onEnable() }.fold( onSuccess = { if (except?.excepted == ConsoleJvmPluginFuncCallbackStatus.FAILED) { val msg = "Test point '${javaClass.name}' assets failed but onEnable() invoked successfully" cancel(msg) logger.error(msg) throw AssertionError(msg) } pluginStatus.value = PluginStatus.ENABLED return true }, onFailure = { err -> pluginStatus.value = PluginStatus.CRASHED_ENABLE_ERROR cancel(CancellationException("Exception while enabling plugin", err)) logger.error(err) // @TestOnly if (err is ConsoleJvmPluginTestFailedError) throw err when (except?.excepted) { ConsoleJvmPluginFuncCallbackStatus.SUCCESS -> throw err ConsoleJvmPluginFuncCallbackStatus.FAILED -> return false else -> {} } if (MiraiConsoleImplementation.getInstance().consoleLaunchOptions.crashWhenPluginLoadFailed) { throw err } return false } ) } // region JvmPlugin - Single Module Dependencies @Suppress("FunctionName") @JvmSynthetic internal fun __jpi_try_to_init_dependencies() { val classloader = javaClass.classLoader.safeCast<JvmPluginClassLoaderN>() ?: return val desc = try { Objects.requireNonNull(description) } catch (ignored: NullPointerException) { return } if (desc.dependencies.isEmpty()) { classloader.linkPluginLibraries(logger) } } // endregion // endregion // region CoroutineScope // for future use @Suppress("PropertyName") @get:JvmSynthetic internal val _intrinsicCoroutineContext: CoroutineContext by lazy { this as AbstractJvmPlugin CoroutineName("Plugin $dataHolderName") } private val pluginParentJob: Job = run { val job = parentCoroutineContext[Job] ?: JvmPluginLoader.coroutineContext[Job]!! val pluginManagerJob = MiraiConsole.pluginManager.impl.coroutineContext.job val allJobs = generateSequence(sequenceOf(pluginManagerJob)) { parentSeqs -> parentSeqs.flatMap { it.children } }.flatten() check(allJobs.contains(job)) { "The parent job of plugin `$id' not a child of PluginManager" } job } @JvmField @JvmSynthetic internal val coroutineContextInitializer = { CoroutineExceptionHandler { context, throwable -> if (throwable.rootCauseOrSelf !is CancellationException) logger.error( "Exception in coroutine ${context[CoroutineName]?.name ?: "<unnamed>"} of ${description.name}", throwable ) } .plus(parentCoroutineContext) .plus(SupervisorJob(pluginParentJob)) .plus(_intrinsicCoroutineContext) } private fun refreshCoroutineContext(): CoroutineContext { return coroutineContextInitializer().also { _coroutineContext = it }.also { job.invokeOnCompletion { e -> if (e != null) { if (e !is CancellationException) logger.error(e) if (pluginStatus.value == PluginStatus.ENABLED) { safeLoader.disable(this) } } } } } private val contextUpdateLock: ReentrantLock = ReentrantLock() private var _coroutineContext: CoroutineContext? = null final override val coroutineContext: CoroutineContext get() = _coroutineContext ?: contextUpdateLock.withLock { _coroutineContext ?: refreshCoroutineContext() } // endregion } internal inline fun AtomicLong.updateWhen(condition: (Long) -> Boolean, update: (Long) -> Long): Boolean { while (true) { val current = value if (condition(current)) { if (compareAndSet(current, update(current))) { return true } else continue } return false } } internal val Throwable.rootCauseOrSelf: Throwable get() = generateSequence(this) { it.cause }.lastOrNull() ?: this internal fun Class<out JvmPluginInternal>.loadPluginDescriptionFromClassLoader(): JvmPluginDescription { val classLoader = this.classLoader as? JvmPluginClassLoaderN ?: error("Plugin $this is not loaded by JvmPluginClassLoader") return classLoader.pluginDescriptionFromPluginResource ?: error("Missing `plugin.yml`") } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginTesting.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.plugin import net.mamoe.mirai.utils.MiraiInternalApi /** * 仅用于 Console 测试, 标记期望方法执行结果应该是 success 还是 failed */ @MiraiInternalApi public annotation class ConsoleJvmPluginFuncCallbackStatusExcept { @MiraiInternalApi @Target(AnnotationTarget.CLASS) public annotation class OnEnable( val excepted: ConsoleJvmPluginFuncCallbackStatus, ) } @MiraiInternalApi public enum class ConsoleJvmPluginFuncCallbackStatus { SUCCESS, FAILED } @MiraiInternalApi public class ConsoleJvmPluginTestFailedError : Error { public constructor() : super() public constructor(cause: Throwable?) : super(cause) public constructor(msg: String?, cause: Throwable?) : super(msg, cause) public constructor(msg: String?) : super(msg) } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/MiraiConsoleAsPlugin.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.plugin import net.mamoe.mirai.console.command.ConsoleCommandOwner import net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.SemVersion internal object MiraiConsoleAsPlugin : Plugin { // MiraiConsole always enabled override val isEnabled: Boolean get() = true override val loader: PluginLoader<*, *> get() = TheLoader override val parentPermission: Permission get() = ConsoleCommandOwner.parentPermission override fun permissionId(name: String): PermissionId { return ConsoleCommandOwner.permissionId(name) } internal object TheLoader : PluginLoader<Plugin, PluginDescription> { override fun listPlugins(): List<Plugin> = listOf(MiraiConsoleAsPlugin) override fun disable(plugin: Plugin) { // noop } override fun enable(plugin: Plugin) { // noop } override fun load(plugin: Plugin) { // noop } override fun getPluginDescription(plugin: Plugin): PluginDescription { if (plugin !== MiraiConsoleAsPlugin) { error("loader not match with " + plugin.description.id) } return TheDescription } } internal object TheDescription : PluginDescription { override val id: String get() = "net.mamoe.mirai-console" override val name: String get() = "Console" override val author: String get() = "Mamoe Technologies" override val version: SemVersion get() = MiraiConsoleBuildConstants.version override val info: String get() = "" override val dependencies: Set<PluginDependency> get() = setOf() override fun toString(): String { return "PluginDescription[ mirai-console ]" } } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/NotYetLoadedJvmPlugin.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.plugin import net.mamoe.mirai.console.data.runCatchingLog import net.mamoe.mirai.console.permission.Permission import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.NotYetLoadedPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.utils.MiraiLogger import java.io.File import java.io.InputStream import java.nio.file.Path import kotlin.coroutines.CoroutineContext internal abstract class NotYetLoadedJvmPlugin( override val description: JvmPluginDescription, val classLoaderN: JvmPluginClassLoaderN, ) : JvmPlugin, NotYetLoadedPlugin<JvmPlugin> { abstract override fun resolve(): JvmPlugin override val logger: MiraiLogger by lazy { BuiltInJvmPluginLoaderImpl.logger.runCatchingLog { MiraiLogger.Factory.create(NotYetLoadedJvmPlugin::class, this.description.name) }.getOrThrow() } override val isEnabled: Boolean get() = false override val parentPermission: Permission get() = error("Not yet loaded") override fun permissionId(name: String): PermissionId { return PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, name) } override val coroutineContext: CoroutineContext get() = error("Not yet loaded") override val dataFolderPath: Path get() = error("Not yet loaded") override val dataFolder: File get() = error("Not yet loaded") override val configFolderPath: Path get() = error("Not yet loaded") override val configFolder: File get() = error("Not yet loaded") override fun getResourceAsStream(path: String): InputStream? { return classLoaderN.getResourceAsStream(path) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/PluginDescriptionUtil.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.plugin import net.mamoe.mirai.console.plugin.description.PluginDescription internal fun PluginDescription.smartToString(): String { return "$name v$version" } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.console.internal.plugin import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.job import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.extensions.PluginLoaderProvider import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.plugin.NotYetLoadedPlugin import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.safeLoader import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.loader.PluginLoadException import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.* import java.io.File import java.nio.file.Path import java.util.concurrent.CopyOnWriteArrayList import kotlin.coroutines.CoroutineContext internal val PluginManager.impl: PluginManagerImpl get() = this.cast() internal class PluginManagerImpl( private val parentCoroutineContext: CoroutineContext ) : PluginManager, CoroutineScope by parentCoroutineContext.childScope("PluginManager") { override val pluginsPath: Path = MiraiConsole.rootPath.resolve("plugins").apply { mkdir() } override val pluginsFolder: File = pluginsPath.toFile() override val pluginsDataPath: Path = MiraiConsole.rootPath.resolve("data").apply { mkdir() } override val pluginsDataFolder: File = pluginsDataPath.toFile() override val pluginsConfigPath: Path = MiraiConsole.rootPath.resolve("config").apply { mkdir() } override val pluginsConfigFolder: File = pluginsConfigPath.toFile() override val pluginLibrariesPath: Path = MiraiConsole.rootPath.resolve("plugin-libraries").apply { mkdir() } override val pluginLibrariesFolder: File = pluginLibrariesPath.toFile() override val pluginSharedLibrariesPath: Path = MiraiConsole.rootPath.resolve("plugin-shared-libraries").apply { mkdir() } override val pluginSharedLibrariesFolder: File = pluginSharedLibrariesPath.toFile() @Suppress("ObjectPropertyName") private val _pluginLoaders: MutableList<PluginLoader<*, *>> by lazy { builtInLoaders.toMutableList() } private val logger = MiraiLogger.Factory.create(PluginManager::class, "plugin") @JvmField internal val resolvedPlugins: MutableList<Plugin> = CopyOnWriteArrayList() // write operations are mostly performed on init override val plugins: List<Plugin> get() = resolvedPlugins.toList() override val builtInLoaders: List<PluginLoader<*, *>> by lazy { MiraiConsole.builtInPluginLoaders.map { it.value } } override val pluginLoaders: List<PluginLoader<*, *>> get() = _pluginLoaders.toList() override fun getPluginDescription(plugin: Plugin): PluginDescription = plugin.safeLoader.getPluginDescription(plugin) init { // Kotlin coroutine job cancelling ordering: // - sub job 0 invokeOnCompletion called // - sub job 1 invokeOnCompletion called // - sub job N invokeOnCompletion called // - parent invokeOnCompletion called // So we need register a child job to control plugins' disabling order this.childScopeContext("PluginManager shutdown monitor").job.invokeOnCompletion { plugins.asReversed().forEach { plugin -> if (plugin.isEnabled) { disablePlugin(plugin) } } } resolvedPlugins.add(MiraiConsoleAsPlugin) } // region LOADING private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.loadPluginNoEnable(plugin: P) { kotlin.runCatching { this.load(plugin) resolvedPlugins.add( when (plugin) { is NotYetLoadedPlugin<*> -> plugin.resolve() else -> plugin } ) }.fold( onSuccess = { logger.info { "Successfully loaded plugin ${getPluginDescription(plugin).smartToString()}" } }, onFailure = { logger.info { "Cannot load plugin ${getPluginDescription(plugin).smartToString()}" } throw it } ) } private fun <P : Plugin, D : PluginDescription> PluginLoader<P, D>.enablePlugin(plugin: Plugin) { kotlin.runCatching { @Suppress("UNCHECKED_CAST") this.enable(plugin as P) }.fold( onSuccess = { logger.info { "Successfully enabled plugin ${getPluginDescription(plugin).smartToString()}" } }, onFailure = { logger.info { "Cannot enable plugin ${getPluginDescription(plugin).smartToString()}" } throw it } ) } internal class PluginLoadSession( val allKindsOfPlugins: List<PluginDescriptionWithLoader>, ) /////////////////////////////////////////////////////////////////////////// // Phase #0: // - initialize all plugins using builtin loaders // - sort by dependencies /////////////////////////////////////////////////////////////////////////// /** * 使用 [builtInLoaders] 寻找所有插件, 并初始化其主类. */ @Suppress("UNCHECKED_CAST") @Throws(PluginResolutionException::class) private fun findAndSortAllPluginsUsingBuiltInLoaders(): List<PluginDescriptionWithLoader> { val allDescriptions = builtInLoaders.listAndSortAllPlugins() .asSequence() .onEach { (_, descriptions) -> descriptions.let(::checkPluginDescription) } return allDescriptions.toList().sortByDependencies() } internal fun loadAllPluginsUsingBuiltInLoaders() { for ((l, _, p) in findAndSortAllPluginsUsingBuiltInLoaders()) { l.loadPluginNoEnable(p) } } /////////////////////////////////////////////////////////////////////////// // Phase #1: // - load PluginLoaderProvider /////////////////////////////////////////////////////////////////////////// internal fun initExternalPluginLoaders(): Int { var count = 0 GlobalComponentStorage.useEachExtensions(PluginLoaderProvider) { logger.info { "Loaded PluginLoader ${extension.instance} from ${plugin?.name ?: "<builtin>"}" } _pluginLoaders.add(extension.instance) count++ } return count } // Phase #2 internal fun scanPluginsUsingPluginLoadersIncludingThoseFromPluginLoaderProvider(): PluginLoadSession { return PluginLoadSession(_pluginLoaders.filterNot { builtInLoaders.contains(it) }.listAndSortAllPlugins()) } internal fun loadPlugins(session: PluginLoadSession) { session.allKindsOfPlugins.forEach { it.loader.load(it.plugin) } } internal fun enableAllLoadedPlugins() { resolvedPlugins.forEach { enablePlugin(it) } } @kotlin.jvm.Throws(PluginLoadException::class) internal fun checkPluginDescription(description: PluginDescription) { kotlin.runCatching { PluginDescription.checkPluginDescription(description) }.getOrElse { throw PluginLoadException("PluginDescription check failed.", it) } } private fun List<PluginLoader<*, *>>.listAndSortAllPlugins(): List<PluginDescriptionWithLoader> { return flatMap { loader -> loader.listPlugins().map { plugin -> getPluginDescription(plugin).wrapWith(loader, plugin) } }.sortByDependencies() } @Throws(PluginResolutionException::class) private fun <D : PluginDescription> List<D>.sortByDependencies(): List<D> { val alreadyLoadedPlugins = resolvedPlugins.asSequence().map { it.description }.toList() // snapshot val originPluginDescriptions = this@sortByDependencies val pending2BeResolved = originPluginDescriptions.toMutableList() val resolved = ArrayList<D>(pending2BeResolved.size) fun <T> MutableCollection<T>.filterAndRemove(output: MutableCollection<T>, filter: (T) -> Boolean) { this.removeAll { item -> if (filter(item)) { output.add(item) true } else false } } // Step0. Check non contains death-locked dependencies graph. kotlin.run deathLockDependenciesCheck@{ fun D.checkDependencyLink(list: MutableList<D>) { if (this in list) { list.add(this) throw PluginInfiniteCircularDependencyReferenceException( "Found circular plugin dependency: " + list.joinToString(" -> ") { it.id } ) } list.add(this) this.dependencies.forEach { dependency -> // In this step not care about dependency missing. val dep0 = pending2BeResolved.findDependency(dependency) ?: return@forEach dep0.checkDependencyLink(list) } list.removeLast() } pending2BeResolved.forEach { dependency -> dependency.checkDependencyLink(mutableListOf()) } } // Step1. Fast process no-depended plugins pending2BeResolved.filterAndRemove(resolved) { it.dependencies.isEmpty() } // Step2. Check plugin dependencies graph kotlin.run checkDependenciesMissing@{ val errorMsgs = mutableListOf<String>() pending2BeResolved.forEach { pluginDesc -> val missed = pluginDesc.dependencies.filter { dependency -> val resolvedDep = originPluginDescriptions.findDependency(dependency) ?: alreadyLoadedPlugins.findDependency(dependency) if (resolvedDep != null) { resolvedDep.checkSatisfies(dependency, pluginDesc) false } else !dependency.isOptional } if (missed.isNotEmpty()) { errorMsgs.add( "Cannot load plugin '${pluginDesc.name}', missing dependencies: ${ missed.joinToString( ", " ) { "'$it'" } }" ) } } if (errorMsgs.isNotEmpty()) { throw PluginMissingDependencyException(errorMsgs.joinToString("\n")) } } // Step3. Sort plugins with dependencies var loopStart = 0 // For faster performance sortWithOptionalDependencies@ while (true) { fun searchRemainingDependencies(start: Int, dependency: PluginDependency): Int { for (i in (start + 1) until pending2BeResolved.size) { val dep0 = pending2BeResolved[i] if (dep0.id.equals(dependency.id, ignoreCase = true)) { dep0.checkSatisfies(dependency, pending2BeResolved[i]) return i } } return -1 } for (index in loopStart until pending2BeResolved.size) { // Ensure load after all depended plugins val dep = pending2BeResolved[index] for (pluginDependency in dep.dependencies) { val dependencyIndex = searchRemainingDependencies(index, pluginDependency) if (dependencyIndex != -1) { pending2BeResolved.removeAt(index) pending2BeResolved.add(dependencyIndex, dep) continue@sortWithOptionalDependencies } } loopStart = index + 1 } resolved.addAll(pending2BeResolved) pending2BeResolved.clear() break@sortWithOptionalDependencies } return resolved } @Suppress("FunctionName") @TestOnly internal fun <D : PluginDescription> __sortPluginDescription(list: List<D>): List<D> { return list.sortByDependencies() } } internal data class PluginDescriptionWithLoader( @JvmField val loader: PluginLoader<Plugin, PluginDescription>, // easier type @JvmField val delegate: PluginDescription, @JvmField val plugin: Plugin, ) : PluginDescription by delegate @Suppress("UNCHECKED_CAST") internal fun <D : PluginDescription> PluginDescription.unwrap(): D = if (this is PluginDescriptionWithLoader) this.delegate as D else this as D @Suppress("UNCHECKED_CAST") internal fun PluginDescription.wrapWith(loader: PluginLoader<*, *>, plugin: Plugin): PluginDescriptionWithLoader = PluginDescriptionWithLoader( loader as PluginLoader<Plugin, PluginDescription>, this, plugin ) internal fun <T : PluginDescription> List<T>.findDependency(dependency: PluginDependency): T? { return find { it.id.equals(dependency.id, ignoreCase = true) } } internal fun PluginDescription.checkSatisfies(dependency: PluginDependency, plugin: PluginDescription) { val requirement = dependency.versionRequirement ?: return if (!SemVersion.parseRangeRequirement(requirement).test(this.version)) { throw PluginLoadException("Plugin '${plugin.id}' ('${plugin.id}') requires '${dependency.id}' with version $requirement while the resolved is ${this.version}") } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/shutdown/ShutdownDaemon.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleInternalApi::class) package net.mamoe.mirai.console.internal.shutdown import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.MiraiConsoleImplementationBridge import net.mamoe.mirai.console.internal.pluginManagerImpl import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.utils.debug import java.io.File import java.io.FileDescriptor import java.io.FileOutputStream import java.io.PrintStream import java.lang.management.ManagementFactory import java.lang.reflect.Method import java.nio.file.Paths import java.time.Instant import java.time.ZoneOffset import java.util.* import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import kotlin.io.path.writeText internal object ShutdownDaemon { @Suppress("RemoveRedundantQualifierName") internal class DaemonStarter( private val consoleImplementationBridge: MiraiConsoleImplementationBridge ) { private val started = AtomicBoolean(false) fun tryStart() { if (started.compareAndSet(false, true)) { ShutdownDaemon.start(consoleImplementationBridge) } } } private object ThreadInfoJava9Access { private val isDaemonM: Method? private val getPriorityM: Method? init { var idm: Method? = null var gpm: Method? = null kotlin.runCatching { val klass = Class.forName("java.lang.management.ThreadInfo") val mts = klass.methods.asSequence() idm = mts.firstOrNull { it.name == "isDaemon" } gpm = mts.firstOrNull { it.name == "getPriority" } } isDaemonM = idm getPriorityM = gpm } fun isDaemon(inf: Any): Boolean { isDaemonM?.invoke(inf)?.let { return it as Boolean } return false } fun getPriority(inf: Any): Int { getPriorityM?.invoke(inf)?.let { return it as Int } return -1 } val canGetPri: Boolean get() = getPriorityM != null } val pluginDisablingThreads = ConcurrentLinkedDeque<Thread>() private val Thread.State.isWaiting: Boolean get() = this == Thread.State.WAITING || this == Thread.State.TIMED_WAITING @OptIn(DelicateCoroutinesApi::class) private fun start(bridge: MiraiConsoleImplementationBridge) { val crtThread = Thread.currentThread() val isConsoleRunning = AtomicBoolean(true) // 1 thread to run main daemon // 1 thread to listen console shutdown running // 1 thread reserved val executor = Executors.newFixedThreadPool(3, object : ThreadFactory { private val counter = AtomicInteger(0) override fun newThread(r: Runnable): Thread { return Thread(r, "Mirai Console Shutdown Daemon #" + counter.getAndIncrement()).also { it.isDaemon = true } } }) executor.execute { listen(crtThread, isConsoleRunning) executor.shutdown() } GlobalScope.launch(executor.asCoroutineDispatcher()) { bridge.coroutineContext.job.join() isConsoleRunning.set(false) } bridge.mainLogger.debug { "SHUTDOWN DAEMON STARTED........." } } @Suppress("MemberVisibilityCanBePrivate") fun dumpCrashReport(saveError: Boolean) { val isAndroidSystem = kotlin.runCatching { Class.forName("android.util.Log") }.isSuccess val sb = StringBuilder(1024).append("\n\n") val now = System.currentTimeMillis() sb.append("=============================================================\n") sb.append("MIRAI CONSOLE CRASH REPORT.\n") sb.append("Console has take too long to shutdown.\n\n") sb.append("TIME: ").append(now).append(" <") fun msgAfterTimeDump() { sb.append(">\nSYSTEM: ").append(System.getProperty("os.name")).append(" ") .append(System.getProperty("os.arch")).append(" ").append(System.getProperty("os.version")) sb.append("\nJRT:\n ") sb.append(System.getProperty("java.runtime.name")) sb.append(" ").append(System.getProperty("java.version")) sb.append("\n by ").append(System.getProperty("java.vendor")) sb.append("\nSPEC:\n ").append(System.getProperty("java.specification.name")).append(" ") .append(System.getProperty("java.specification.version")) sb.append("\n by ").append(System.getProperty("java.specification.vendor")) sb.append("\nVM:\n ").append(System.getProperty("java.vm.name")).append(" ") .append(System.getProperty("java.vm.version")) sb.append("\n by ").append(System.getProperty("java.vm.vendor")) sb.append("\n\n") kotlin.runCatching { sb.append("\nPROCESS Working dir: ").append(File("a").absoluteFile.parent ?: File(".").absoluteFile) sb.append("\nConsole Working Dir: ").append(MiraiConsole.rootPath.toAbsolutePath()) } sb.append("\nLoaded plugins:\n") kotlin.runCatching { MiraiConsole.pluginManagerImpl.resolvedPlugins.forEach { plugin -> val desc = plugin.description sb.append("|- ").append(desc.name).append(" v").append(desc.version).append('\n') sb.append("| `- ID: ").append(desc.id).append('\n') desc.author.takeUnless { it.isBlank() }?.let { sb.append("| `- AUTHOR: ").append(it).append('\n') } sb.append("| `- MAIN: ").append(plugin.javaClass).append('\n') plugin.javaClass.protectionDomain?.codeSource?.location?.let { from -> @Suppress("IntroduceWhenSubject") val f: Any = when { from.protocol == "file" -> Paths.get(from.toURI()) else -> from } sb.append("| `- FROM: ").append(f).append('\n') } } } sb.append("\n\n\n") } if (isAndroidSystem) { sb.append(Date(now)) msgAfterTimeDump() sb.append("\n\nTHREADS:\n\n") val threads = Thread.getAllStackTraces() threads.forEach { (thread, stackTrace) -> sb.append("\n\n\n").append(thread).append('\n') stackTrace.forEach { stack -> sb.append('\t').append(stack).append('\n') } } } else { object { // Android doesn't contain management system & classing boxing fun a() { sb.append(Instant.ofEpochMilli(now).atOffset(ZoneOffset.UTC)) msgAfterTimeDump() val rtMxBean = ManagementFactory.getRuntimeMXBean() sb.append("PROCESS: ").append(rtMxBean.name) sb.append("\nVM OPTIONS:\n") rtMxBean.inputArguments.forEach { cmd -> sb.append(" ").append(cmd).append("\n") } sb.append("\n\nTHREADS:\n\n") val threadMxBean = ManagementFactory.getThreadMXBean() val infs = threadMxBean.dumpAllThreads(true, true) infs.forEach { inf -> sb.append("\n\n").append('"') sb.append(inf.threadName) sb.append('"') if (ThreadInfoJava9Access.isDaemon(inf)) { sb.append(" daemon") } if (ThreadInfoJava9Access.canGetPri) { sb.append(" prio=").append(ThreadInfoJava9Access.getPriority(inf)) } sb.append(" Id=").append(inf.threadId) inf.lockName?.let { sb.append(" on ").append(it) } inf.lockOwnerName?.let { lon -> sb.append(" owned by \"").append(lon) sb.append("\" Id=").append(inf.lockOwnerId) } if (inf.isSuspended) sb.append(" (suspended)") if (inf.isInNative) sb.append(" (in native)") sb.append('\n') val lockInf = inf.lockInfo val lockedMonitors = inf.lockedMonitors inf.stackTrace.forEachIndexed { index, stackTraceElement -> sb.append("\tat ").append(stackTraceElement).append('\n') if (index == 0 && lockInf != null) { when (inf.threadState!!) { Thread.State.BLOCKED -> { sb.append("\t- blocked on ").append(lockInf).append('\n') } Thread.State.WAITING, Thread.State.TIMED_WAITING -> { sb.append("\t- waiting on ").append(lockInf).append('\n') } else -> {} } } lockedMonitors.forEach { mi -> if (mi.lockedStackDepth == index) { sb.append("\t- locked ").append(mi).append('\n') } } } sb.append("\n\n") } } }.a() } sb.append("\n\n") val report = sb.toString() if (!isAndroidSystem && saveError) { kotlin.runCatching { PrintStream(FileOutputStream(FileDescriptor.err)).println(report) } } if (saveError) { val fileName = "CONSOLE_CRASH_REPORT_${now}.log" kotlin.runCatching { MiraiConsole.rootPath.resolve(fileName).writeText(report) }.recoverCatching { if (!isAndroidSystem) { File("CONSOLE_CRASH_REPORT_${now}.log").writeText(report) } } } kotlin.runCatching { MiraiConsole.mainLogger.error(report) } } private fun listen(thread: Thread, consoleRunning: AtomicBoolean) { val startTime = System.currentTimeMillis() val timeout = 1000L * 60 while (consoleRunning.get()) { val crtTime = System.currentTimeMillis() if (crtTime - startTime >= timeout) { kotlin.runCatching { dumpCrashReport(saveError = true) } pluginDisablingThreads.forEach { threadKill(it) } threadKill(thread) pluginDisablingThreads.clear() return } Thread.sleep(1000) // Force intercept known death codes pluginDisablingThreads.forEach { pluginCrtThread -> val stackTraces by lazy { pluginCrtThread.stackTrace } if (pluginCrtThread.state.isWaiting) { /// java.desktop: // WToolkit 关闭后执行 java.awt.Window.dispose() 会堵死在 native code for (i in 0 until stackTraces.size.coerceAtMost(10)) { val stack = stackTraces[i] if (stack.className.startsWith("java.awt.") && stack.methodName.contains( "dispose", ignoreCase = true ) ) { pluginCrtThread.interrupt() break } } } } } } private fun threadKill(thread: Thread) { thread.interrupt() Thread.sleep(10) if (!thread.isAlive) return Thread.sleep(100) if (!thread.isAlive) return Thread.sleep(500) @Suppress("DEPRECATION") if (thread.isAlive) thread.stop() } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/util/CommonUtils.kt ================================================ /* * 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:JvmName("CommonUtils") // maintain binary compatibility package net.mamoe.mirai.console.internal.util import io.github.karlatemp.caller.StackFrame import net.mamoe.mirai.console.internal.plugin.implOrNull import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import kotlin.contracts.InvocationKind import kotlin.contracts.contract internal inline fun <reified E : Throwable, R> runIgnoreException(block: () -> R): R? { try { return block() } catch (e: Throwable) { if (e is E) return null throw e } } internal inline fun <reified E : Throwable> runIgnoreException(block: () -> Unit): Unit? { try { return block() } catch (e: Throwable) { if (e is E) return null throw e } } internal fun StackFrame.findLoader(): ClassLoader? { classInstance?.let { return it.classLoader } return runCatching { JvmPluginLoader.implOrNull?.findLoadedClass(className)?.classLoader }.getOrNull() } @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "UnusedParameter") @kotlin.internal.LowPriorityInOverloadResolution internal inline fun <T : Any> T?.ifNull(block: () -> T): T { contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) } return this ?: block() } @Suppress("DeprecatedCallableAddReplaceWith", "UnusedParameter", "UNUSED_PARAMETER") @Deprecated("Useless ifNull on not null value.") // diagnostic deprecation @JvmName("ifNull1") internal inline fun <T : Any> T.ifNull(block: () -> T): T = this @PublishedApi internal inline fun assertionError(message: () -> String = { "Reached an unexpected branch." }): Nothing { contract { callsInPlace(message, InvocationKind.EXACTLY_ONCE) } throw AssertionError(message()) } @PublishedApi internal inline fun assertUnreachable(message: () -> String = { "Reached an unexpected branch." }): Nothing { contract { callsInPlace(message, InvocationKind.EXACTLY_ONCE) } throw AssertionError(message()) } @MarkerUnreachableClause @PublishedApi internal inline val UNREACHABLE_CLAUSE: Nothing get() = assertUnreachable() @DslMarker private annotation class MarkerUnreachableClause ================================================ FILE: mirai-console/backend/mirai-console/src/internal/util/ConsoleInputImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleFrontEndImplementation::class) package net.mamoe.mirai.console.internal.util import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.util.ConsoleInput @Suppress("unused") internal object ConsoleInputImpl : ConsoleInput { private val inputLock = Mutex() override suspend fun requestInput(hint: String): String = inputLock.withLock { MiraiConsoleImplementation.getInstance().consoleInput.requestInput(hint) } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/util/JavaPluginSchedulerImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.util import kotlinx.coroutines.* import kotlinx.coroutines.future.future import net.mamoe.mirai.console.plugin.jvm.JavaPluginScheduler import net.mamoe.mirai.utils.newCoroutineContextWithSupervisorJob import java.util.concurrent.Callable import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import kotlin.coroutines.CoroutineContext internal class JavaPluginSchedulerImpl internal constructor(parentCoroutineContext: CoroutineContext) : CoroutineScope, JavaPluginScheduler { override val coroutineContext: CoroutineContext = parentCoroutineContext.newCoroutineContextWithSupervisorJob(this.toString()) override fun repeating(intervalMs: Long, runnable: Runnable): Future<Void?> { return this.future { while (isActive) { runInterruptible(Dispatchers.IO) { runnable.run() } delay(intervalMs) } null } } override fun delayed(delayMillis: Long, runnable: Runnable): CompletableFuture<Void?> { return future { delay(delayMillis) runInterruptible(Dispatchers.IO) { runnable.run() } null } } override fun <R> delayed(delayMillis: Long, callable: Callable<R>): CompletableFuture<R> { return future { delay(delayMillis) runInterruptible(Dispatchers.IO) { callable.call() } } } override fun <R> async(supplier: Callable<R>): Future<R> { return future { runInterruptible(Dispatchers.IO) { supplier.call() } } } override fun async(runnable: Runnable): Future<Void?> { return future { runInterruptible(Dispatchers.IO) { runnable.run() } null } } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/util/PluginServiceHelper.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.console.internal.util import net.mamoe.mirai.console.internal.data.cast import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.createInstanceOrNull import java.lang.reflect.Modifier import java.util.* import kotlin.reflect.KClass import java.lang.reflect.Member as JReflectionMember internal class ServiceList<T>( internal val classLoader: ClassLoader, internal val delegate: List<String> ) internal object PluginServiceHelper { inline fun <reified T : Any> ClassLoader.findServices(): ServiceList<T> = findServices(T::class) fun <T : Any> ClassLoader.findServices(vararg serviceTypes: KClass<out T>): ServiceList<T> = serviceTypes.flatMap { serviceType -> getResourceAsStream("META-INF/services/" + serviceType.qualifiedName!!)?.let { stream -> stream.bufferedReader().useLines { lines -> lines.filter(String::isNotBlank) .filter { it[0] != '#' } .toList() } }.orEmpty() }.let { ServiceList(this, it) } fun <T : Any> ServiceList<T>.loadAllServices(): List<T> { return delegate.mapNotNull { classLoader.loadService(it) } } private fun <T : Any> ClassLoader.loadService( classname: String, ): T? { @Suppress("UNCHECKED_CAST") return kotlin.runCatching { val clazz = Class.forName(classname, true, this).cast<Class<out T>>() clazz.kotlin.objectInstance ?: kotlin.runCatching { clazz.declaredFields.firstOrNull { it.isStatic() && it.name == "INSTANCE" }?.get(null) }.getOrNull() ?: clazz.kotlin.createInstanceOrNull() ?: clazz.constructors.firstOrNull { it.parameterCount == 0 }?.newInstance() ?: error("Cannot find a no-arg constructor") }.getOrElse { throw ServiceLoadException("Could not load service ${classname}.", it) /* logger.error( { "Could not load PluginLoader ${pluginQualifiedName}." }, PluginLoadException("Could not load PluginLoader ${pluginQualifiedName}.", it) )*/ } as T? } fun <T : Any> loadAllServicesFromMemoryAndPluginClassLoaders(service: KClass<T>): List<T> { val list = ServiceLoader.load(service.java, this::class.java.classLoader).toList() return list + JvmPluginLoader.classLoaders.flatMap { it.findServices(service).loadAllServices() } } } internal fun JReflectionMember.isStatic(): Boolean = this.modifiers.and(Modifier.STATIC) != 0 @Suppress("unused", "RedundantVisibilityModifier") internal open class ServiceLoadException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/util/semver/RequirementInternal.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.util.semver import net.mamoe.mirai.console.util.SemVersion internal interface RequirementInternal { fun test(version: SemVersion): Boolean } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/util/semver/RequirementParser.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.util.semver import kotlin.math.max import kotlin.math.min internal class RequirementParser { sealed class Token { open var line: Int = -1 open var pos: Int = -1 open var sourcePos: Int = -1 open lateinit var content: String sealed class GroupBod : Token() { class Left : GroupBod() { override var content: String get() = "{" set(_) {} } class Right : GroupBod() { override var content: String get() = "}" set(_) {} } } sealed class Logic : Token() { class And : Logic() { override var content: String get() = "&&" set(_) {} } class Or : Logic() { override var content: String get() = "||" set(_) {} } } class Content : Token() class Ending : Token() { override var content: String get() = "" set(_) {} } object Begin : Token() { override var content: String get() = "" set(_) {} override var line: Int get() = 0 set(_) {} override var pos: Int get() = 0 set(_) {} override var sourcePos: Int get() = 0 set(_) {} } override fun toString(): String { return javaClass.canonicalName.substringAfterLast('.') + " - $content [$line, $pos]" } } companion object { const val END = '\u0000' } class TokenReader( @JvmField val content: String ) { @JvmField var pos: Int = 0 @JvmField var line: Int = 0 @JvmField var posi: Int = 0 @JvmField var latestToken: Token = Token.Begin @JvmField var insertToken: Token? = Token.Begin fun peekChar(): Char { if (pos < content.length) return content[pos] return END } fun peekNextChar(): Char { if (pos + 1 < content.length) return content[pos + 1] return END } fun nextChar(): Char { val char = peekChar() pos++ if (char == '\n') { line++ posi = 0 } else { posi++ } return char } fun nextToken(): Token { insertToken?.let { insertToken = null; return it } return nextToken0().also { latestToken = it } } private fun nextToken0(): Token { if (pos < content.length) { while (peekChar().isWhitespace()) { nextChar() } val startIndex = pos if (startIndex >= content.length) { return Token.Ending().also { it.line = line it.pos = posi it.sourcePos = content.length } } val pline = line val ppos = posi nextChar() when (content[startIndex]) { '&' -> { if (peekChar() == '&') { return Token.Logic.And().also { it.pos = ppos it.line = pline it.sourcePos = startIndex nextChar() } } } '|' -> { if (peekChar() == '|') { return Token.Logic.Or().also { nextChar() it.pos = ppos it.line = pline it.sourcePos = startIndex } } } '{' -> { return Token.GroupBod.Left().also { it.pos = ppos it.line = pline it.sourcePos = startIndex } } '}' -> { return Token.GroupBod.Right().also { it.pos = ppos it.line = pline it.sourcePos = startIndex } } } while (true) { when (val c = peekChar()) { '&', '|' -> { if (c == peekNextChar()) { break } nextChar() } '{', '}' -> { break } END -> break else -> nextChar() } } val endIndex = pos return Token.Content().also { it.content = content.substring(startIndex, endIndex) it.pos = ppos it.line = pline it.sourcePos = startIndex } } return Token.Ending().also { it.line = line it.pos = posi it.sourcePos = content.length } } } interface TokensProcessor<R> { fun process(reader: TokenReader): R fun processLine(reader: TokenReader): R fun processLogic(isAnd: Boolean, chunks: Iterable<R>): R } abstract class ProcessorBase<R> : TokensProcessor<R> { fun Token.ia(reader: TokenReader, msg: String, cause: Throwable? = null): Nothing { throw IllegalArgumentException("$msg (at [$line, $pos], ${cutSource(reader, sourcePos)})", cause) } fun cutSource(reader: TokenReader, index: Int): String { val content = reader.content val s = max(0, index - 10) val e = min(content.length, index + 10) return content.substring(s, e) } override fun process(reader: TokenReader): R { return when (val nextToken = reader.nextToken()) { is Token.Begin, is Token.GroupBod.Left -> { val first = when (val next = reader.nextToken()) { is Token.Content -> { processString(reader, next) } is Token.GroupBod.Right -> { nextToken.ia( reader, if (nextToken is Token.Begin) "Invalid token `}`" else "The first token cannot be Group Ending" ) } is Token.Logic -> { nextToken.ia(reader, "The first token cannot be Token.Logic") } is Token.Ending -> { nextToken.ia( reader, if (nextToken is Token.Begin) "Requirement cannot be blank" else "Except more tokens" ) } is Token.GroupBod.Left -> { reader.insertToken = next process(reader) } else -> { next.ia(reader, "Bad token $next") } } // null -> not set // true -> AND mode // false-> OR mode var mode: Boolean? = null val chunks = arrayListOf(first) while (true) { when (val next = reader.nextToken()) { is Token.Ending, is Token.GroupBod.Right -> { val isEndingOfGroup = next is Token.GroupBod.Right val isStartingOfGroup = nextToken is Token.GroupBod.Left if (isStartingOfGroup != isEndingOfGroup) { fun getType(type: Boolean) = if (type) "`}`" else "<EOF>" next.ia( reader, "Except ${getType(isStartingOfGroup)} but got ${getType(isEndingOfGroup)}" ) } else { // reader.insertToken = next break } } is Token.Logic -> { val stx = next is Token.Logic.And if (mode == null) mode = stx else if (mode != stx) { fun getMode(type: Boolean) = if (type) "`&&`" else "`||`" next.ia( reader, "Cannot change logic mode after setting. " + "Except ${getMode(mode)} but got ${getMode(stx)}" ) } chunks.add(process(reader)) } else -> { next.ia( reader, "Except ${ when (mode) { null -> "`&&` or `||`" true -> "`&&`" false -> "`||`" } } but get `${next.content}`" ) } } } if (mode == null) { first } else { processLogic(mode, chunks) } } is Token.Content -> { processString(reader, nextToken) } is Token.Ending -> { nextToken.ia(reader, "Except more values.") } else -> { nextToken.ia(reader, "Assert Error: $nextToken") } } } abstract fun processString(reader: TokenReader, token: Token.Content): R override fun processLine(reader: TokenReader): R { return process(reader).also { val tok = reader.nextToken() if (reader.nextToken() !is Token.Ending) { tok.ia(reader, "Token reader stream not done") } } } } } ================================================ FILE: mirai-console/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.internal.util.semver import net.mamoe.mirai.console.util.SemVersion import kotlin.math.max import kotlin.math.min @Suppress("RegExpRedundantEscape") internal object SemVersionInternal { private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex() private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() private val versionMathRange = """([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex() private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex() private val SEM_VERSION_REGEX = """^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".toRegex() /** 解析核心版本号, eg: `1.0.0` -> IntArray[1, 0, 0] */ @JvmStatic private fun String.parseMainVersion(): IntArray = split('.').map { it.toInt() }.toIntArray() fun parse(version: String): SemVersion { if (!SEM_VERSION_REGEX.matches(version)) { throw IllegalArgumentException("`$version` not a valid version") } var mainVersionEnd = 0 kotlin.run { val iterator = version.iterator() while (iterator.hasNext()) { val next = iterator.next() if (next == '-' || next == '+') { break } mainVersionEnd++ } } var identifier: String? = null var metadata: String? = null if (mainVersionEnd != version.length) { when (version[mainVersionEnd]) { '-' -> { val metadataSplitter = version.indexOf('+', startIndex = mainVersionEnd) if (metadataSplitter == -1) { identifier = version.substring(mainVersionEnd + 1) } else { identifier = version.substring(mainVersionEnd + 1, metadataSplitter) metadata = version.substring(metadataSplitter + 1) } } '+' -> { metadata = version.substring(mainVersionEnd + 1) } } } val mainVersion = version.substring(0, mainVersionEnd).parseMainVersion() return SemVersion( major = mainVersion[0], minor = mainVersion[1], patch = mainVersion.getOrNull(2), identifier = identifier, metadata = metadata ) } @JvmStatic internal fun parseRule(rule: String): RequirementInternal { val trimmed = rule.trim() if (directVersion.matches(trimmed)) { val parsed = SemVersion.invoke(trimmed) return object : RequirementInternal { override fun test(version: SemVersion): Boolean = version.compareTo(parsed) == 0 } } if (versionSelect.matches(trimmed)) { val regex = ("^" + trimmed.replace(".", "\\.") .replace("x", ".+") + "$" ).toRegex() return object : RequirementInternal { override fun test(version: SemVersion): Boolean = regex.matches(version.toString()) } } versionMathRange.matchEntire(trimmed)?.let { range -> // 1 mode // 2 first // 5 sec // 8 type var typeStart = range.groupValues[1][0] var typeEnd = range.groupValues[8][0] var start = SemVersion.invoke(range.groupValues[2]) var end = SemVersion.invoke(range.groupValues[5]) if (start > end) { val c = end end = start start = c val x = typeEnd typeEnd = typeStart typeStart = x } val a: (SemVersion) -> Boolean = when (typeStart) { '[', ']' -> ({ start <= it }) '(', ')' -> ({ start < it }) else -> throw AssertionError() } val b: (SemVersion) -> Boolean = when (typeEnd) { '[', ']' -> ({ it <= end }) '(', ')' -> ({ it < end }) else -> throw AssertionError() } return object : RequirementInternal { override fun test(version: SemVersion): Boolean = a(version) && b(version) } } versionRule.matchEntire(trimmed)?.let { result -> val operator = result.groupValues[1] val version1 = SemVersion.invoke(result.groupValues[8]) return when (operator) { ">=" -> { object : RequirementInternal { override fun test(version: SemVersion): Boolean = version >= version1 } } ">" -> { object : RequirementInternal { override fun test(version: SemVersion): Boolean = version > version1 } } "<=" -> { object : RequirementInternal { override fun test(version: SemVersion): Boolean = version <= version1 } } "<" -> { object : RequirementInternal { override fun test(version: SemVersion): Boolean = version < version1 } } "=" -> { object : RequirementInternal { override fun test(version: SemVersion): Boolean = version.compareTo(version1) == 0 } } "!=" -> { object : RequirementInternal { override fun test(version: SemVersion): Boolean = version.compareTo(version1) != 0 } } else -> error("operator=$operator, version=$version1") } } throw IllegalArgumentException("Cannot parse $rule") } @JvmStatic fun parseRangeRequirement(requirement: String): RequirementInternal = object : RequirementParser.ProcessorBase<RequirementInternal>() { override fun processLogic(isAnd: Boolean, chunks: Iterable<RequirementInternal>): RequirementInternal { return if (isAnd) object : RequirementInternal { override fun test(version: SemVersion): Boolean { return chunks.all { it.test(version) } } } else object : RequirementInternal { override fun test(version: SemVersion): Boolean { return chunks.any { it.test(version) } } } } override fun processString( reader: RequirementParser.TokenReader, token: RequirementParser.Token.Content ): RequirementInternal = kotlin.runCatching { parseRule(token.content) }.getOrElse { token.ia(reader, "Error in parsing rule `${token.content}`", it) } }.processLine(RequirementParser.TokenReader(requirement)) @JvmStatic fun compareInternal(source: SemVersion, other: SemVersion): Int { // ignored metadata in comparing // If $this equals $other (without metadata), // return same. // Compare main-version source.major.compareTo(other.major).takeUnless { it == 0 }?.let { return it } source.minor.compareTo(other.minor).takeUnless { it == 0 }?.let { return it } (source.patch ?: 0).compareTo(other.patch ?: 0).takeUnless { it == 0 }?.let { return it } // If main-versions are same. var identifier0 = source.identifier var identifier1 = other.identifier // If anyone doesn't have the identifier... if (identifier0 == null || identifier1 == null) { return when (identifier0) { identifier1 -> { // null == null // Nobody has identifier 0 } null -> { // $other has identifier, but $this don't have identifier // E.g: // this = 1.0.0 // other = 1.0.0-dev 1 } // It is the opposite of the above. else -> -1 } } fun String.getSafe(index: Int) = getOrElse(index) { ' ' } // ignored same prefix fun getSameSize(s1: String, s2: String): Int { val size = min(s1.length, s2.length) // 1.0-RC19 -> 19 // 1.0-RC107 -> 107 var realSameSize = 0 for (index in 0 until size) { if (s1[index] != s2[index]) { return realSameSize } else { if (!s1[index].isDigit()) { realSameSize = index + 1 } } } return realSameSize } // We ignore the same parts. Because we only care about the differences. // E.g: // 1.0-RC1 -> 1 // 1.0-RC2 -> 2 val ignoredSize = getSameSize(identifier0, identifier1) identifier0 = identifier0.substring(ignoredSize) identifier1 = identifier1.substring(ignoredSize) // Multi-chunk comparing val chunks0 = identifier0.split('-', '.') val chunks1 = identifier1.split('-', '.') chunkLoop@ for (index in 0 until (max(chunks0.size, chunks1.size))) { val value0 = chunks0.getOrNull(index) val value1 = chunks1.getOrNull(index) // Any chunk is null if (value0 == null || value1 == null) { // value0 == null && value1 == null is impossible return if (value0 == null) { // E.g: // value0 = 1.0-RC-dev // value1 = 1.0-RC-dev-1 -1 } else { // E.g: // value0 = 1.0-RC-dev-1 // value1 = 1.0-RC-dev 1 } } try { val result = value0.toInt().compareTo(value1.toInt()) if (result != 0) { return result } continue@chunkLoop } catch (ignored: NumberFormatException) { } // compare chars for (index0 in 0 until (max(value0.length, value1.length))) { val result = value0.getSafe(index0).compareTo(value1.getSafe(index0)) if (result != 0) return result } } return 0 } } ================================================ FILE: mirai-console/backend/mirai-console/src/logging/AbstractLoggerController.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.logging import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.SimpleLogger import java.util.* /** * 日志控制器的基本实现 */ @ConsoleExperimentalApi public abstract class AbstractLoggerController : LoggerController { /** * @param priority 尝试判断的日志等级 * @param settings 配置中的日志等级 (see [getPriority]) */ protected open fun shouldLog( priority: LogPriority, settings: LogPriority, ): Boolean = settings <= priority /** * 获取配置中与 [identity] 对应的 [LogPriority] */ protected abstract fun getPriority(identity: String?): LogPriority override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean = shouldLog(LogPriority.by(priority), getPriority(identity)) // region LoggerControlState support protected open val isLoggerControlStateSupported: Boolean get() = false @Volatile @Transient @JvmField protected var loggerConfigUpdateTime: Long = 0L override fun getLoggerControlState(identity: String?): LoggerController.LoggerControlState { if (isLoggerControlStateSupported) { return object : LoggerController.LoggerControlState { private val status = BitSet(SimpleLogger.LogPriority.values().size) @Volatile @Transient private var lastUpdateTime = -1L override fun shouldLog(priority: SimpleLogger.LogPriority): Boolean { if (lastUpdateTime != loggerConfigUpdateTime) { updateProperties() } return status[priority.ordinal] } @Synchronized private fun updateProperties() { lastUpdateTime = loggerConfigUpdateTime SimpleLogger.LogPriority.values().forEach { prio -> status.set(prio.ordinal, shouldLog(identity, prio)) } } } } return super.getLoggerControlState(identity) } // endregion /** * 便于进行配置存储的 [LogPriority], * 等级优先级与 [SimpleLogger.LogPriority] 对应 */ @Suppress("unused") @ConsoleExperimentalApi public enum class LogPriority { ALL(null), VERBOSE, DEBUG, INFO, WARNING, ERROR, NONE(null); private var mapped: SimpleLogger.LogPriority? = null public companion object { private val mapping = EnumMap<SimpleLogger.LogPriority, LogPriority>(SimpleLogger.LogPriority::class.java) public fun by(priority: SimpleLogger.LogPriority): LogPriority = mapping[priority]!! init { values().forEach { priority -> mapping[priority.mapped ?: return@forEach] = priority } } } @Suppress("UNUSED_PARAMETER") constructor(void: Nothing?) constructor() { mapped = SimpleLogger.LogPriority.valueOf(name) } } /** * 路径形式实现的基本日志控制器 * * Example: * 配置文件: * ``` * defaultPriority: ALL * loggers: * t: NONE * t.sub: VERBOSE * t.sub.1: NONE * ``` * * ``` * "logger.1" * -> "logger.1" << null * -> "logger" << null * -> defaultPriority << ALL * * "t.sub.1" * -> "t.sub.1" << NONE * * "t.sub.2" * -> "t.sub.2" << null * -> "t.sub" << VERBOSE * * ...... * ``` */ @ConsoleExperimentalApi public abstract class PathBased @JvmOverloads public constructor( protected open val spliterator: Char = '.' ) : AbstractLoggerController() { protected abstract val defaultPriority: LogPriority protected abstract fun findPriority(identity: String?): LogPriority? /** * 从 [path] 析出下一次应该进行搜索的二次 path (@see [getPriority]) * * @return 如果返回了 `null`, 会令 [getPriority] 返回 `findPriority(null) ?: defaultPriority`) */ protected open fun nextPath(path: String): String? { val lastIndex = path.lastIndexOf(spliterator) if (lastIndex == -1) return null return path.substring(0, lastIndex) } override fun getPriority(identity: String?): LogPriority { if (identity == null) { return findPriority(null) ?: defaultPriority } else { var path: String = identity while (true) { findPriority(path)?.let { return it } path = nextPath(path) ?: return (findPriority(null) ?: defaultPriority) } } } } } ================================================ FILE: mirai-console/backend/mirai-console/src/logging/LoggerController.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.logging import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.internal.logging.MiraiConsoleLogger import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.SimpleLogger /** * 日志控制系统 * * @see [AbstractLoggerController] * @see [MiraiConsoleImplementation.loggerController] * @see [MiraiConsoleLogger] */ @ConsoleExperimentalApi public interface LoggerController { /** 是否应该记录该等级的日志 */ public fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean public fun getLoggerControlState(identity: String?): LoggerControlState { return object : LoggerControlState { override fun shouldLog(priority: SimpleLogger.LogPriority): Boolean { return shouldLog(identity, priority) } } } /** * 一个 [MiraiLogger] 的日志开关状态 * * 这个类可以缓存 [LoggerController.shouldLog] 的结果, 避免多次查询 * * 在需要的时候会自动更新, 所以不需要手动更换 [LoggerControlState] */ @ConsoleExperimentalApi public interface LoggerControlState { public fun shouldLog(priority: SimpleLogger.LogPriority): Boolean } } ================================================ FILE: mirai-console/backend/mirai-console/src/permission/Permission.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.command.Command /** * 一个抽象的「权限」. 由 [PermissionService] 实现不同, [Permission] 可能会有多种实例. 但一个权限总是拥有确定的 [id]. * * 在匹配权限时, 应使用唯一的 [id] 作为依据. 而不应该使用 [Permission] 实例. 同时, [Permission] 也不适合存储. * * **注意**: 请不要手动实现这个接口. 总是从 [PermissionService.register] 获得实例. * * ### 获取 [Permission] * * #### 根权限 * [RootPermission] 是所有权限的父权限. * * #### 指令的权限 * 每个指令都拥有一个 [Command.permission]. * * [BuiltInCommands.parentPermission] 为所有内建指令的权限. * * #### 手动申请权限 * [PermissionService.register] */ @PermissionImplementation public interface Permission { /** * 唯一识别 ID. 所有权限的 [id] 都互不相同. * * @see PermissionService.get 由 [id] 获取已注册的 [Permission] * @see PermissionId */ public val id: PermissionId /** * 描述信息. 描述信息在注册权限时强制提供. */ public val description: String /** * 父权限. * * 在检查权限时, 若一个 [Permittee] 拥有父 * * [RootPermission] 的 parent 为自身 */ public val parent: Permission public companion object { /** * 根权限. 是所有权限的父权限. * * 供 Java 用户使用. * * @see RootPermission 推荐 Kotlin 用户使用. */ @JvmStatic public fun getRootPermission(): Permission = RootPermission /** * 递归获取 [Permission.parent], `permission.parent.parent`, permission.parent.parent.parent` ... 直到 [Permission.parent] 为它自己. */ @get:JvmStatic public val Permission.parentsWithSelf: Sequence<Permission> get() = generateSequence(this) { p -> p.parent.takeIf { parent -> parent != p } } } } /** * 根权限. 是所有权限的父权限. 权限 ID 为 "*:*" */ @get:JvmSynthetic public inline val RootPermission: Permission // It might be removed in the future, so make it inline to avoid ABI changes. get() = PermissionService.INSTANCE.rootPermission ================================================ FILE: mirai-console/backend/mirai-console/src/permission/PermissionId.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.permission import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.internal.data.map /** * 表示一个 [权限][Permission] 的唯一 ID. * * [PermissionId] 与 [Permission] 唯一对应. * * ### 字符串表示 * `"$namespace:$name"`. 如 "console:command.stop", "*:*" */ @Serializable(with = PermissionId.PermissionIdAsStringSerializer::class) public data class PermissionId( @ResolveContext(PERMISSION_NAMESPACE) public val namespace: String, @ResolveContext(PERMISSION_NAME) public val name: String, ) { init { checkPermissionIdName(name) checkPermissionIdName(namespace) } public object PermissionIdAsStringSerializer : KSerializer<PermissionId> by String.serializer().map( serializer = { it.namespace + ":" + it.name }, deserializer = ::parseFromString ) /** * 返回 `$namespace:$id` */ public override fun toString(): String = "$namespace:$name" public companion object { /** * 由 `$namespace:$id` 解析 [PermissionId]. * * @throws IllegalArgumentException 在解析失败时抛出. */ @JvmStatic public fun parseFromString(@ResolveContext(PERMISSION_ID) string: String): PermissionId { return kotlin.runCatching { string.split(':').let { (namespace, id) -> PermissionId(namespace, id) } }.getOrElse { throw IllegalArgumentException("Could not parse PermissionId from '$string'", it) } } /** * 检查 [PermissionId.name] 的合法性. 在非法时抛出 [IllegalArgumentException] */ @JvmStatic @Throws(IllegalArgumentException::class) public fun checkPermissionIdName(@ResolveContext(PERMISSION_NAME) name: String) { when { name.isBlank() -> throw IllegalArgumentException("PermissionId.name should not be blank.") name.any(Char::isWhitespace) -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.name.") name.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.name.") } } /** * 检查 [PermissionId.namespace] 的合法性. 在非法时抛出 [IllegalArgumentException] */ @JvmStatic @Throws(IllegalArgumentException::class) public fun checkPermissionIdNamespace(@ResolveContext(PERMISSION_NAME) namespace: String) { when { namespace.isBlank() -> throw IllegalArgumentException("PermissionId.namespace should not be blank.") namespace.any(Char::isWhitespace) -> throw IllegalArgumentException("Spaces are not yet allowed in PermissionId.namespace.") namespace.contains(':') -> throw IllegalArgumentException("':' is forbidden in PermissionId.namespace.") } } } } ================================================ FILE: mirai-console/backend/mirai-console/src/permission/PermissionIdNamespace.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PERMISSION_NAME /** * [PermissionId] 的命名空间. 用于提供 [PermissionId.namespace]. */ public interface PermissionIdNamespace { /** * 创建一个此命名空间下的 [PermitteeId]. * * 在指令初始化时, 会申请对应权限. 此时 [name] 为 `command.$primaryName` 其中 [primaryName][Command.primaryName]. */ public fun permissionId(@ResolveContext(PERMISSION_NAME) name: String): PermissionId } ================================================ FILE: mirai-console/backend/mirai-console/src/permission/PermissionImplementation.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.permission import kotlin.annotation.AnnotationTarget.* /** * 表示一个应该由专有的权限插件 (提供 [PermissionService] 的插件) 实现的类. * * * 这样的类不能被用户手动实现或者继承, 也不能使用属性委托或者类委托, 或者其他任意直接或间接实现他们的手段 (否则会导致 [PermissionService] 处理异常). * * 普通插件仅应该使用从 [PermissionService] 或其他途径获取这些对象. */ @Retention(AnnotationRetention.BINARY) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @MustBeDocumented internal annotation class PermissionImplementation ================================================ FILE: mirai-console/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused", "MemberVisibilityCanBePrivate", "CanBeParameter") package net.mamoe.mirai.console.permission /** * @see PermissionService.register */ public class PermissionRegistryConflictException( public val newInstance: Permission, public val existingInstance: Permission, ) : Exception("Conflicting Permission registry. new: $newInstance, existing: $existingInstance") ================================================ FILE: mirai-console/backend/mirai-console/src/permission/PermissionService.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.COMMAND_NAME import net.mamoe.mirai.console.extension.instance import net.mamoe.mirai.console.extensions.PermissionServiceProvider import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.permission.checkType import net.mamoe.mirai.console.permission.Permission.Companion.parentsWithSelf import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiExperimentalApi import kotlin.reflect.KClass /** * 权限服务. 用于承载 Console 的权限系统. * * ### 可扩展 * 权限服务可由插件扩展并覆盖默认实现. * * @see PermissionServiceProvider 相应扩展 */ @PermissionImplementation public interface PermissionService<P : Permission> { /** * [P] 的类型 */ public val permissionType: KClass<P> /** * [RootPermission] 的实现 */ public val rootPermission: P /////////////////////////////////////////////////////////////////////////// /** * 获取一个已经 [注册][register] 了的 [P] */ public operator fun get(id: PermissionId): P? /** * 获取所有已注册的指令列表. 应保证线程安全. * * 备注: Java 实现者使用 `CollectionsKt.asSequence(Collection)` 构造 [Sequence] */ public fun getRegisteredPermissions(): Sequence<P> /** * 获取 [PermitteeId] 和其父标识的所有被授予的所有直接和间接的权限列表 * * 备注: Java 实现者使用 `CollectionsKt.asSequence(Collection)` 构造 [Sequence] */ public fun getPermittedPermissions(permitteeId: PermitteeId): Sequence<P> /** * 判断 [permission] 对 [permission] 的权限. * * 返回 `true` 的意义: * - 通常意义: [permitteeId] 拥有 [permission] 的 '能力' * - 实现意义: [permitteeId] 自身或任意父标识 [PermissionService] 被授予高于或等于 [permission] 的权限 * * @see Companion.testPermission 接收 [Permittee] 参数的扩展 */ public fun testPermission(permitteeId: PermitteeId, permission: P): Boolean { val permissionId = permission.id val all = this[permissionId]?.parentsWithSelf ?: return false return getPermittedPermissions(permitteeId).any { p -> all.any { p.id == it.id } } } /////////////////////////////////////////////////////////////////////////// /** * 申请并注册一个权限 [Permission]. * * @throws PermissionRegistryConflictException 当已存在一个 [PermissionId] 时抛出. * * @param description 描述. 将会展示给用户. * * @return 申请到的 [Permission] 实例 * * @see get 获取一个已注册的权限 * @see getOrFail 获取一个已注册的权限 */ @Throws(PermissionRegistryConflictException::class) public fun register( id: PermissionId, description: String, parent: Permission = RootPermission, ): P /** 为 [Plugin] 分配一个 [PermissionId] */ @ConsoleExperimentalApi public fun allocatePermissionIdForPlugin( plugin: Plugin, @ResolveContext(COMMAND_NAME) permissionName: String, ): PermissionId = PermissionId( plugin.description.id.lowercase(), permissionName.lowercase() ) /////////////////////////////////////////////////////////////////////////// /** * 授予 [permitteeId] 以 [permission] 权限 * * Console 内建的权限服务支持此操作. 但插件扩展的权限服务可能不支持. * * @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出. */ @Throws(UnsupportedOperationException::class) public fun permit(permitteeId: PermitteeId, permission: P) /** * 撤销 [permitteeId] 的 [permission] 授权 * * Console 内建的权限服务支持此操作. 但插件扩展的权限服务可能不支持. * * @param recursive `true` 时递归撤销所有子权限. * 例如, 若 [permission] 为 "*:*", * recursive 为 `true` 时撤销全部权限 (因为所有权限都是 "*:*" 的子权限); * 而为 `false` 时仅撤销 "*:*" 本身, 而不会影响子权限. * * @throws UnsupportedOperationException 当插件扩展的 [PermissionService] 不支持这样的操作时抛出. */ @Throws(UnsupportedOperationException::class) public fun cancel(permitteeId: PermitteeId, permission: P, recursive: Boolean) public companion object { /** * 选用的 [PermissionService] 实例. */ @get:JvmName("getInstance") @JvmStatic @OptIn(ConsoleFrontEndImplementation::class, MiraiExperimentalApi::class) public val INSTANCE: PermissionService<out Permission> get() { if (!MiraiConsoleImplementation.getBridge().permissionSeviceLoaded) { error("PermissionService is not yet ready.") } return GlobalComponentStorage.getPreferredExtension(PermissionServiceProvider).instance } /** * 获取一个权限, 失败时抛出 [NoSuchElementException] * * @see register 申请并注册一个权限 */ @JvmStatic @Throws(NoSuchElementException::class) public fun <P : Permission> PermissionService<P>.getOrFail(id: PermissionId): P = get(id) ?: throw NoSuchElementException("Permission not found: $id") /** * @see findCorrespondingPermission */ @JvmStatic public val PermissionId.correspondingPermission: Permission? get() = findCorrespondingPermission() /** * @see get */ @JvmStatic public fun PermissionId.findCorrespondingPermission(): Permission? = INSTANCE[this] /** * @see getOrFail * @throws NoSuchElementException */ @Throws(NoSuchElementException::class) @JvmStatic public fun PermissionId.findCorrespondingPermissionOrFail(): Permission = INSTANCE.getOrFail(this) /** * @see PermissionService.permit */ @JvmStatic @JvmName("permit0") // clash, not JvmSynthetic to allow possible calls from Java. public fun PermitteeId.permit(permission: Permission) { INSTANCE.checkType(permission::class).permit(this, permission) } /** * @see PermissionService.permit * @throws NoSuchElementException */ @JvmStatic @Throws(NoSuchElementException::class) public fun PermitteeId.permit(permissionId: PermissionId) { permit(permissionId.findCorrespondingPermissionOrFail()) } /** * @see PermissionService.cancel */ @JvmSynthetic @JvmStatic @JvmName("cancel0") // clash, not JvmSynthetic to allow possible calls from Java. public fun PermitteeId.cancel(permission: Permission, recursive: Boolean) { INSTANCE.checkType(permission::class).cancel(this, permission, recursive) } /** * @see PermissionService.cancel * @throws NoSuchElementException */ @JvmStatic @Throws(NoSuchElementException::class) public fun PermitteeId.cancel(permissionId: PermissionId, recursive: Boolean) { cancel(permissionId.findCorrespondingPermissionOrFail(), recursive) } /** * @see PermissionService.testPermission */ @JvmStatic public fun Permittee.hasPermission(permission: Permission): Boolean = permission.testPermission(this@hasPermission) /** * @see PermissionService.testPermission */ @JvmStatic public fun PermitteeId.hasPermission(permission: Permission): Boolean = permission.testPermission(this@hasPermission) /** * @see PermissionService.testPermission * @throws NoSuchElementException */ @JvmStatic @Throws(NoSuchElementException::class) public fun PermitteeId.hasPermission(permissionId: PermissionId): Boolean { val instance = permissionId.findCorrespondingPermissionOrFail() return INSTANCE.checkType(instance::class).testPermission(this@hasPermission, instance) } /** * @see PermissionService.testPermission */ @JvmStatic public fun Permittee.hasPermission(permissionId: PermissionId): Boolean = permissionId.testPermission(this@hasPermission) /** * @see PermissionService.getPermittedPermissions */ @JvmStatic public fun Permittee.getPermittedPermissions(): Sequence<Permission> = INSTANCE.getPermittedPermissions(this@getPermittedPermissions.permitteeId) /** * @see PermissionService.permit */ @JvmStatic public fun Permittee.permit(vararg permissions: Permission) { for (permission in permissions) { INSTANCE.checkType(permission::class).permit(this.permitteeId, permission) } } /** * @see PermissionService.cancel */ @JvmStatic public fun Permittee.cancel(vararg permissions: Permission, recursive: Boolean) { for (permission in permissions) { INSTANCE.checkType(permission::class).cancel(this.permitteeId, permission, recursive) } } /** * @see PermissionService.getPermittedPermissions */ @JvmSynthetic @JvmStatic @JvmName("getPermittedPermissions0") // clash, not JvmSynthetic to allow possible calls from Java. public fun PermitteeId.getPermittedPermissions(): Sequence<Permission> = INSTANCE.getPermittedPermissions(this@getPermittedPermissions) /** * @see PermissionService.testPermission */ @JvmStatic public fun Permission.testPermission(permittee: Permittee): Boolean = INSTANCE.checkType(this::class).testPermission(permittee.permitteeId, this@testPermission) /** * @see PermissionService.testPermission */ @JvmStatic public fun Permission.testPermission(permitteeId: PermitteeId): Boolean = INSTANCE.checkType(this::class).testPermission(permitteeId, this@testPermission) /** * @see PermissionService.testPermission */ @JvmStatic public fun PermissionId.testPermission(permittee: Permittee): Boolean { val p = INSTANCE[this] ?: return false return p.testPermission(permittee) } /** * @see PermissionService.testPermission */ @JvmStatic public fun PermissionId.testPermission(permissible: PermitteeId): Boolean { val p = INSTANCE[this] ?: return false return p.testPermission(permissible) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/permission/Permittee.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "unused", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.command.CommandSender /** * 可被赋予权限的对象, 即 '被许可人'. * * 被许可人自身不持有拥有的权限列表, 而是拥有 [PermitteeId], 标识自己的身份, 供 [权限服务][PermissionService] 处理. * * **注意**: 请不要自主实现 [Permittee] * * @see CommandSender */ @PermissionImplementation public interface Permittee { public val permitteeId: PermitteeId } ================================================ FILE: mirai-console/backend/mirai-console/src/permission/PermitteeId.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.permission import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.command.BuiltInCommands import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.permission.parseFromStringImpl import net.mamoe.mirai.console.permission.AbstractPermitteeId.* import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.contact.* import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiExperimentalApi /** * [被许可人][Permittee] 的标识符 * * 一个这样的标识符即可代表特定的单个 [Permittee], 也可以表示多个同类 [Permittee]. * * ### 获取 [PermitteeId] * 总是通过 [Permittee.permitteeId]. */ @PermissionImplementation public interface PermitteeId { /** * 直接父 [PermitteeId]. 在检查权限时会首先检查自己, 再递归检查父类. * * @see allParentsWithSelf * @see allParents */ public val directParents: Array<out PermitteeId> /** * 转换为字符串表示. 用于权限服务识别和指令的解析. */ public fun asString(): String public companion object { /** * 当 [this] 或 [this] 的任意一个直接或间接父 [PermitteeId.asString] 与 `parent.asString` 相同时返回 `true` * * @since 2.16 */ @JvmStatic public fun PermitteeId.isChildOf(parent: PermitteeId): Boolean { return allParentsWithSelf.any { it.asString() == parent.asString() } // asString is for compatibility issue with external implementations } @JvmStatic @DeprecatedSinceMirai(warningSince = "2.16") public fun PermitteeId.hasChild(child: PermitteeId): Boolean = isChildOf(child) /** * 获取所有直接或间接父类的 [PermitteeId]. */ @get:JvmStatic public val PermitteeId.allParentsWithSelf: Sequence<PermitteeId> get() = sequence { yield(this@allParentsWithSelf) yieldAll(allParents) } /** * 获取所有直接或间接父类的 [PermitteeId], 返回包含 `this` + 这些父类 的 [Sequence] */ @get:JvmStatic public val PermitteeId.allParents: Sequence<PermitteeId> get() = directParents.asSequence().flatMap { it.allParentsWithSelf } /** * 创建 [AbstractPermitteeId.ExactUser] */ @get:JvmSynthetic public val User.permitteeId: ExactUser get() = ExactUser(id) /** * 创建 [AbstractPermitteeId.ExactMember] */ @get:JvmSynthetic public val Member.permitteeId: ExactMember get() = ExactMember(group.id, id) /** * 创建 [AbstractPermitteeId.ExactGroup] */ @get:JvmSynthetic public val Group.permitteeId: ExactGroup get() = ExactGroup(id) /** * 创建 [AbstractPermitteeId.ExactGroupTemp] */ @get:JvmSynthetic public val Member.permitteeIdOnTemp: ExactGroupTemp get() = ExactGroupTemp(group.id, id) /** * 创建 [AbstractPermitteeId.ExactStranger] */ @get:JvmSynthetic public val Stranger.permitteeId: ExactStranger get() = ExactStranger(id) /** * 创建 [AbstractPermitteeId.ExactStranger] */ @MiraiExperimentalApi @get:JvmSynthetic public inline val OtherClient.permitteeId: AnyOtherClient get() = AnyOtherClient } } /** * 内建的 [PermitteeId]. * * - 若指令 A 的权限被授予给 [AnyMember], 那么一个 [ExactMember] 可以执行这个指令. * * #### 字符串表示 * * 当使用 [PermitteeId.asString] 时, 不同的类型的返回值如下表所示. 这些格式也适用于 [BuiltInCommands.PermissionCommand]. * * (不区分大小写. 不区分 Bot). * * [查看字符串表示列表](https://github.com/mamoe/mirai-console/docs/Permissions.md#字符串表示) * * #### 关系图 * * ``` * Console AnyContact * ↑ * | * +---------------------------+------------------------+---------------------+ * | | | * AnyUser AnyGroup AnyOtherClient * ↑ ↑ ↑ * | | | * +--------------+---------------------+ | | * | | | | | * AnyFriend | AnyMemberFromAnyGroup | | * ↑ | ↑ | | * | | | | | * | | +--------+--------------+ | | * | | | | | | * | | | AnyTempFromAnyGroup | | * | | | ↑ | | * | | AnyMember | | | * | | ↑ | | | * | ExactUser | | ExactGroup ExactOtherClient * | ↑ ↑ | | * | | | | | * +------------+ +----------+ | * | | | * ExactFriend ExactMember | * ↑ | * | | * +-----------------------+ * | * | * ExactTemp * ``` */ @OptIn(ConsoleExperimentalApi::class) @Serializable(with = AsStringSerializer::class) public sealed class AbstractPermitteeId( public final override vararg val directParents: PermitteeId, ) : PermitteeId { public final override fun toString(): String = asString() public companion object { /** * 由 [AbstractPermitteeId.asString] 解析 [AbstractPermitteeId] */ @JvmStatic public fun parseFromString(string: String): AbstractPermitteeId = parseFromStringImpl(string) } /** * 使用 [asString] 序列化 [AbstractPermitteeId] */ @ConsoleExperimentalApi public object AsStringSerializer : KSerializer<AbstractPermitteeId> by String.serializer().map( serializer = AbstractPermitteeId::asString, deserializer = ::parseFromString ) /** * 表示任何群对象. (不是指群成员, 而是指这个 '群') * * - **直接父标识符**: [AnyContact] * - **间接父标识符**: 无 * - 字符串表示: "g*" * * @see AnyMember */ public object AnyGroup : AbstractPermitteeId(AnyContact) { override fun asString(): String = "g*" } /** * 表示一个群 * * - **直接父标识符**: [AnyGroup] * - **间接父标识符**: [AnyContact] * - 字符串表示: "g$groupId" */ public data class ExactGroup(public val groupId: Long) : AbstractPermitteeId(AnyGroup) { override fun asString(): String = "g$groupId" } /** * 表示来自一个群的任意一个成员 * * - **直接父标识符**: [AnyMemberFromAnyGroup] * - **间接父标识符**: [AnyUser], [AnyContact] * - 字符串表示: "m$groupId.*" */ public data class AnyMember(public val groupId: Long) : AbstractPermitteeId(AnyMemberFromAnyGroup) { override fun asString(): String = "m$groupId.*" } /** * 表示来自任意群的任意一个成员 * * - **直接父标识符**: [AnyUser] * - **间接父标识符**: [AnyContact] * - 字符串表示: "m*" */ public object AnyMemberFromAnyGroup : AbstractPermitteeId(AnyUser) { override fun asString(): String = "m*" } /** * 表示唯一的一个群成员 * * - **直接父标识符**: [AnyMember], [ExactUser] * - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact] * - 字符串表示: "m$groupId.$memberId" */ public data class ExactMember( public val groupId: Long, public val memberId: Long, ) : AbstractPermitteeId(AnyMember(groupId), ExactUser(memberId)) { override fun asString(): String = "m$groupId.$memberId" } /** * 表示任何好友 * * - **直接父标识符**: [AnyUser] * - **间接父标识符**: [AnyContact] * - 字符串表示: "f*" */ public object AnyFriend : AbstractPermitteeId(AnyUser) { override fun asString(): String = "f*" } /** * 表示唯一的一个好友 * * - **直接父标识符**: [ExactUser], [AnyFriend] * - **间接父标识符**: [AnyUser], [AnyContact] * - 字符串表示: "f$id" */ public data class ExactFriend( public val id: Long, ) : AbstractPermitteeId(ExactUser(id), AnyFriend) { override fun asString(): String = "f$id" } @Deprecated( "use AnyGroupTemp", ReplaceWith("AnyGroupTemp", "net.mamoe.mirai.console.permission.AbstractPermitteeId.AnyGroupTemp"), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.0", hiddenSince = "2.10") public abstract class AnyTemp( groupId: Long, ) : AbstractPermitteeId(AnyMember(groupId), AnyTempFromAnyGroup) /** * 表示任何一个通过一个群 *在临时会话发送消息的* [群成员][Member] * * - **直接父标识符**: [AnyMember], [AnyTempFromAnyGroup] * - **间接父标识符**: [AnyMemberFromAnyGroup], [AnyUser], [AnyContact] * - 字符串表示: "t$groupId.*" */ public data class AnyGroupTemp( public val groupId: Long, ) : @Suppress("DEPRECATION_ERROR") AnyTemp(groupId) { override fun asString(): String = "t$groupId.*" } /** * 表示任何一个 *在临时会话发送消息的* [群成员][Member] * * - **直接父标识符**: [AnyUser] * - **间接父标识符**: [AnyContact] * - 字符串表示: "t*" */ public object AnyTempFromAnyGroup : AbstractPermitteeId(AnyUser) { override fun asString(): String = "t*" } @Deprecated( "use ExactGroupTemp", ReplaceWith("ExactGroupTemp", "net.mamoe.mirai.console.permission.AbstractPermitteeId.ExactGroupTemp"), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.0", hiddenSince = "2.10") public abstract class ExactTemp internal constructor( groupId: Long, memberId: Long, ) : AbstractPermitteeId(ExactMember(groupId, memberId)) /** * 表示唯一的一个 *在临时会话发送消息的* [群成员][Member] * * - **直接父标识符**: [ExactMember] * - **间接父标识符**: [AnyUser], [AnyMember], [ExactUser], [AnyContact], [AnyMemberFromAnyGroup] * - 字符串表示: "t$groupId.$memberId" */ public data class ExactGroupTemp( public val groupId: Long, public val memberId: Long, ) : @Suppress("DEPRECATION_ERROR") ExactTemp(groupId, memberId) { override fun asString(): String = "t$groupId.$memberId" } /** * 表示唯一的一个 [陌生人][Stranger] * * - **直接父标识符**: [ExactUser], [AnyStranger] * - **间接父标识符**: [AnyUser], [AnyContact] * - 字符串表示: "s$id" */ public data class ExactStranger( public val id: Long, ) : AbstractPermitteeId(ExactUser(id), AnyStranger) { override fun asString(): String = "s$id" } /** * 表示任何 [用户][User] * * - **直接父标识符**: [AnyContact] * - **间接父标识符**: 无 * - 字符串表示: "u*" */ public object AnyUser : AbstractPermitteeId(AnyContact) { override fun asString(): String = "u*" } /** * 表示任何 [陌生人][Stranger] * * - **直接父标识符**: [AnyUser] * - **间接父标识符**: [AnyContact] * - 字符串表示: "s*" */ public object AnyStranger : AbstractPermitteeId(AnyUser) { override fun asString(): String = "s*" } /** * 表示精确 [用户][User] * * - **直接父标识符**: [AnyUser] * - **间接父标识符**: [AnyContact] * - 字符串表示: "u$id" */ public data class ExactUser( public val id: Long, ) : AbstractPermitteeId(AnyUser) { override fun asString(): String = "u$id" } /** * 表示任何 [联系对象][Contact] * * - **直接父标识符**: 无 * - **间接父标识符**: 无 * - 字符串表示: "*" */ public object AnyContact : AbstractPermitteeId() { override fun asString(): String = "*" } /** * 表示控制台 * * - **直接父标识符**: 无 * - **间接父标识符**: 无 * - 字符串表示: "console" */ public object Console : AbstractPermitteeId() { override fun asString(): String = "console" } /** * 表示任何其他客户端 * * - **直接父标识符**: [AnyContact] * - **间接父标识符**: 无 * - 字符串表示: "client*" */ public object AnyOtherClient : AbstractPermitteeId(AnyContact) { override fun asString(): String = "client*" } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/NotYetLoadedPlugin.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.plugin.loader.PluginLoader /** * 代表一个 未完成加载/延迟加载 的插件 * * 此实例仅用于插件加载系统, 当 [PluginManager] 加载插件时会自动调用 [resolve] 解析真正的插件实例 * * @see PluginLoader.listPlugins * @see PluginManager * * @since 2.16.0 */ public interface NotYetLoadedPlugin<T : Plugin> : Plugin { public fun resolve(): T } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/Plugin.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.getPluginDescription import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.DeprecationLevel.ERROR import kotlin.DeprecationLevel.HIDDEN /** * 表示一个 mirai-console 插件. * * @see PluginManager.enablePlugin 启用一个插件 * @see PluginManager.disablePlugin 禁用一个插件 * @see PluginManager.description 获取一个插件的 [描述][PluginDescription] * * @see PluginDescription 插件描述, 需由 [PluginLoader] 帮助提供([PluginLoader.getPluginDescription]) * @see JvmPlugin Java, Kotlin 或其他 JVM 平台插件 * @see PluginFileExtensions 支持文件系统存储的扩展 * * @see PluginLoader 插件加载器 */ public interface Plugin : CommandOwner { /** * 当插件已启用时返回 `true`, 否则表示插件未启用. * * @see PluginManager.enablePlugin 启用一个插件 * @see PluginManager.disablePlugin 禁用一个插件 */ public val isEnabled: Boolean /** * 所属插件加载器实例, 此加载器必须能加载这个 [Plugin]. */ public val loader: PluginLoader<*, *> } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution @Deprecated( "Moved to companion for a better Java API. ", ReplaceWith("this.description", "net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.description"), level = HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.0", hiddenSince = "2.10") public inline val Plugin.description: PluginDescription get() = getPluginDescription(this) // resolved to net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.getDescription /** * 获取 [PluginDescription.name] */ public inline val Plugin.name: String get() = getPluginDescription(this).name /** * 获取 [PluginDescription.id] */ public inline val Plugin.id: String get() = getPluginDescription(this).id /** * 获取 [PluginDescription.version] */ public inline val Plugin.version: SemVersion get() = getPluginDescription(this).version /** * 获取 [PluginDescription.info] */ public inline val Plugin.info: String get() = getPluginDescription(this).info /** * 获取 [PluginDescription.author] */ public inline val Plugin.author: String get() = getPluginDescription(this).author /** * 获取 [PluginDescription.dependencies] */ public inline val Plugin.dependencies: Set<PluginDependency> get() = getPluginDescription(this).dependencies ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/PluginFileExtensions.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import java.io.File import java.nio.file.Path /** * 支持文件系统存储的扩展. * * @suppress 此接口只应由 [JvmPlugin] 继承 * * @see JvmPlugin */ public interface PluginFileExtensions { /** * 数据目录路径 * @see PluginData */ public val dataFolderPath: Path /** * 数据目录. `dataFolderPath.toFile()` * @see PluginData */ public val dataFolder: File /** * 从数据目录获取一个文件. * @see dataFolderPath */ public fun resolveDataFile(relativePath: String): File = dataFolderPath.resolve(relativePath).toFile() /** * 从数据目录获取一个文件. * @see dataFolderPath */ public fun resolveDataPath(relativePath: String): Path = dataFolderPath.resolve(relativePath) /** * 从数据目录获取一个文件. * @see dataFolderPath */ public fun resolveDataFile(relativePath: Path): File = dataFolderPath.resolve(relativePath).toFile() /** * 从数据目录获取一个文件路径. * @see dataFolderPath */ public fun resolveDataPath(relativePath: Path): Path = dataFolderPath.resolve(relativePath) /** * 插件配置保存路径 * @see PluginConfig */ public val configFolderPath: Path /** * 插件配置保存路径 * @see PluginConfig */ public val configFolder: File /** * 从配置目录获取一个文件. * @see configFolderPath */ public fun resolveConfigFile(relativePath: String): File = configFolderPath.resolve(relativePath).toFile() /** * 从配置目录获取一个文件. * @see configFolderPath */ public fun resolveConfigPath(relativePath: String): Path = configFolderPath.resolve(relativePath) /** * 从配置目录获取一个文件. * @see configFolderPath */ public fun resolveConfigFile(relativePath: Path): File = configFolderPath.resolve(relativePath).toFile() /** * 从配置目录获取一个文件路径. * @see configFolderPath */ public fun resolveConfigPath(relativePath: Path): Path = configFolderPath.resolve(relativePath) } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/PluginManager.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.console.plugin import me.him188.kotlin.dynamic.delegation.dynamicDelegation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.utils.NotStableForInheritance import java.io.File import java.nio.file.Path /** * 插件管理器. * * [PluginManager] 管理所有 [插件加载器][PluginLoader], 储存对所有插件的引用 ([plugins]), 通过 [PluginLoader] 间接与 [插件实例][Plugin] 交互. * * [插件加载][PluginLoader.load] 和 [插件启用][PluginLoader.enable] 等操作都由 [PluginLoader] 完成. * [PluginManager] 仅作为一个联系所有 [插件加载器][PluginLoader], 使它们互相合作的桥梁. * * 若要主动加载一个插件, 请获取能加载该插件的 [PluginLoader], 然后使用 [PluginLoader.enable] * * ## 获取插件管理器实例 * * 可通过 [MiraiConsole.pluginManager] 或 [PluginManager.INSTANCE] 获取 [PluginManager] 实例. * * @see Plugin 插件 * @see PluginLoader 插件加载器 */ @NotStableForInheritance public interface PluginManager { // region paths /** * 插件自身存放路径 [Path]. 由前端决定具体路径. * * **实现细节**: 在 terminal 前端实现为 `$rootPath/plugins` */ public val pluginsPath: Path /** * 插件自身存放路径 [File]. 由前端决定具体路径. * * **实现细节**: 在 terminal 前端实现为 `$rootPath/plugins` */ public val pluginsFolder: File /** * 插件内部数据存放路径 [Path] * * **实现细节**: 在 terminal 前端实现为 `$rootPath/data` */ public val pluginsDataPath: Path /** * 插件内部数据存放路径 [File] * * **实现细节**: 在 terminal 前端实现为 `$rootPath/data` */ public val pluginsDataFolder: File /** * 插件配置存放路径 [Path] * * **实现细节**: 在 terminal 前端实现为 `$rootPath/config` */ public val pluginsConfigPath: Path /** * 插件配置存放路径 [File] * * **实现细节**: 在 terminal 前端实现为 `$rootPath/config` */ public val pluginsConfigFolder: File /** * 插件运行时依赖存放路径 [Path], 插件自动下载的依赖都会存放于此目录 * * **实现细节**: 在 terminal 前端实现为 `$rootPath/plugin-libraries`, * 依赖 jar 文件由插件共享, 但是运行时插件加载的类是互相隔离的 * * @since 2.11 */ public val pluginLibrariesPath: Path /** * 插件运行时依赖存放路径 [File], 插件自动下载的依赖都会存放于此目录 * * **实现细节**: 在 terminal 前端实现为 `$rootPath/plugin-libraries`, * 依赖 jar 文件由插件共享, 但是运行时插件加载的类是互相隔离的 * * @since 2.11 */ public val pluginLibrariesFolder: File /** * 插件运行时依赖存放路径 [Path], 该路径下的依赖由全部插件共享 * * **实现细节**: 在 terminal 前端实现为 `$rootPath/plugin-shared-libraries` * * @since 2.11 */ public val pluginSharedLibrariesPath: Path /** * 插件运行时依赖存放路径 [File], 该路径下的依赖由全部插件共享 * * **实现细节**: 在 terminal 前端实现为 `$rootPath/plugin-shared-libraries` * * @since 2.11 */ public val pluginSharedLibrariesFolder: File // endregion // region plugins & loaders /** * 已加载的插件列表 * * @return 只读列表 */ public val plugins: List<Plugin> /** * 内建的插件加载器列表. 由 [MiraiConsole] 初始化. * * @return 只读列表 */ public val builtInLoaders: List<PluginLoader<*, *>> /** * 由插件创建的 [PluginLoader] * * @return 只读列表 */ public val pluginLoaders: List<PluginLoader<*, *>> /** * 获取插件的 [描述][PluginDescription], 通过 [PluginLoader.getPluginDescription] */ public fun getPluginDescription(plugin: Plugin): PluginDescription /** * 禁用这个插件 * * @see PluginLoader.disable */ public fun disablePlugin(plugin: Plugin): Unit = plugin.safeLoader.disable(plugin) /** * 加载这个插件 * * @see PluginLoader.load */ public fun loadPlugin(plugin: Plugin): Unit = plugin.safeLoader.load(plugin) /** * 启用这个插件 * * @see PluginLoader.enable */ public fun enablePlugin(plugin: Plugin): Unit = plugin.safeLoader.enable(plugin) // endregion /** * [PluginManager] 实例. 转发所有调用到 [MiraiConsole.pluginManager]. */ public companion object INSTANCE : PluginManager by (dynamicDelegation { MiraiConsole.pluginManager }) { /** * 经过泛型类型转换的 [Plugin.loader] */ @get:JvmSynthetic @Suppress("UNCHECKED_CAST") public inline val <P : Plugin> P.safeLoader: PluginLoader<P, PluginDescription> get() = this.loader as PluginLoader<P, PluginDescription> /** * @see getPluginDescription */ @get:JvmSynthetic public inline val Plugin.description: PluginDescription get() = getPluginDescription(this) /** * @see disablePlugin */ @JvmSynthetic public inline fun Plugin.disable(): Unit = disablePlugin(this) /** * @see enablePlugin */ @JvmSynthetic public inline fun Plugin.enable(): Unit = enablePlugin(this) /** * @see loadPlugin */ @JvmSynthetic public inline fun Plugin.load(): Unit = loadPlugin(this) } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/ResourceContainer.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.plugin.ResourceContainer.Companion.asResourceContainer import net.mamoe.mirai.console.plugin.jvm.JvmPlugin import java.io.InputStream import java.nio.charset.Charset import kotlin.reflect.KClass /** * 资源容器. * * 资源容器可能使用 [Class.getResourceAsStream], [ClassLoader.getResourceAsStream], 也可能使用其他方式, 取决于实现方式. * * @see JvmPlugin [JvmPlugin] 通过 [ClassLoader.getResourceAsStream] 实现 [ResourceContainer], 使用 [ResourceContainer.asResourceContainer] */ public interface ResourceContainer { /** * 获取一个资源文件. * * @return 资源文件内容. 在未找到文件时返回 `null`. */ public fun getResourceAsStream(path: String): InputStream? /** * 读取一个资源文件并以 [Charsets.UTF_8] 解码为 [String]. * * @return 资源文件内容. 在未找到文件时返回 `null`. */ public fun getResource(path: String): String? = getResource(path, Charsets.UTF_8) /** * 读取一个资源文件并以 [charset] 解码为 [String]. * * @return 资源文件内容. 在未找到文件时返回 `null`. */ public fun getResource(path: String, charset: Charset): String? = this.getResourceAsStream(path)?.use(InputStream::readBytes)?.let(::String) public companion object { /** * 使用 [Class.getResourceAsStream] 读取资源文件. * * @see ClassLoader.asResourceContainer */ @JvmStatic @JvmName("create") public fun KClass<*>.asResourceContainer(): ResourceContainer = this.java.asResourceContainer() /** * 使用 [ClassLoader.getResourceAsStream] 读取资源文件. */ @JvmStatic @JvmName("create") public fun ClassLoader.asResourceContainer(): ResourceContainer = ClassLoaderAsResourceContainer(this) /** * 使用 [Class.getResourceAsStream] 读取资源文件. */ @JvmStatic @JvmName("create") public fun Class<*>.asResourceContainer(): ResourceContainer = ClassAsResourceContainer(this) } } private class ClassAsResourceContainer( private val clazz: Class<*> ) : ResourceContainer { override fun getResourceAsStream(path: String): InputStream? = clazz.getResourceAsStream(path) } private class ClassLoaderAsResourceContainer( private val clazz: ClassLoader ) : ResourceContainer { override fun getResourceAsStream(path: String): InputStream? = clazz.getResourceAsStream(path) } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/center/PluginCenter.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin.center import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.console.util.ConsoleExperimentalApi import java.io.File /** * 插件中心, 计划实现中 */ @ConsoleExperimentalApi public interface PluginCenter { @Serializable @ConsoleExperimentalApi public data class PluginInsight( val name: String, val version: String, @SerialName("core") val coreVersion: String, @SerialName("console") val consoleVersion: String, val author: String, val description: String, val tags: List<String>, val commands: List<String>, ) @ConsoleExperimentalApi @Serializable public data class PluginInfo( val name: String, val version: String, @SerialName("core") val coreVersion: String, @SerialName("console") val consoleVersion: String, val tags: List<String>, val author: String, val contact: String, val description: String, val usage: String, val vcs: String, val commands: List<String>, val changeLog: List<String>, ) /** * 获取一些中心的插件基本信息, * 能获取到多少由实际的 [PluginCenter] 决定 * 返回 插件名->Insight */ public suspend fun fetchPlugin(page: Int): Map<String, PluginInsight> /** * 尝试获取到某个插件 by 全名, case sensitive * null 则没有 */ public suspend fun findPlugin(name: String): PluginInfo? public suspend fun <T : Any> T.downloadPlugin(name: String, progressListener: T.(Float) -> Unit): File public suspend fun downloadPlugin(name: String, progressListener: PluginCenter.(Float) -> Unit): File = downloadPlugin<PluginCenter>(name, progressListener) /** * 刷新 */ public suspend fun refresh() public val name: String } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/description/IllegalPluginDescriptionException.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.plugin.description /** * 在检查到非法 [PluginDescription] 时抛出. * * @see PluginDescription.checkPluginDescription */ public class IllegalPluginDescriptionException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/description/PluginDependency.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.plugin.description import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.PLUGIN_ID import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.VERSION_REQUIREMENT import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.util.SemVersion /** * 插件的一个依赖的信息. * * 只有添加依赖后才能使用依赖插件的 API. * * 被依赖的插件将会在依赖方之前加载. 即若 A 依赖 B, 那么 B 总是会在 A 之前加载. * * 注意, 若发现循环依赖, 将会拒绝所有参与循环的插件的加载. * * @see PluginDescription.dependencies */ @Serializable(with = PluginDependency.PluginDependencyAsStringSerializer::class) public data class PluginDependency @JvmOverloads constructor( /** * 依赖插件 ID, [PluginDescription.id] */ @ResolveContext(PLUGIN_ID) public val id: String, /** * 依赖版本号. 为 null 时则为不限制版本. 通常建议至少限制使用同一个主版本号. * 如开发时依赖该插件版本 1.5.0, 则将版本限制设置为 `[1.5.0, 2.0.0)`, 表示大于等于 `1.5.0`, 小于 `2.0.0`. * * 版本遵循 [语义化版本 2.0 规范](https://semver.org/lang/zh-CN/), * * @see SemVersion.Requirement */ @ResolveContext(VERSION_REQUIREMENT) public val versionRequirement: String? = null, /** * 若为 `false`, 在找不到此依赖时将会拒绝插件加载. * 若为 `true`, 在找不到此依赖时也能正常加载. */ public val isOptional: Boolean = false, ) { init { kotlin.runCatching { PluginDescription.checkPluginId(id) if (versionRequirement != null) SemVersion.parseRangeRequirement(versionRequirement) }.getOrElse { throw IllegalArgumentException(it) } } /** * @see PluginDependency */ public constructor( @ResolveContext(PLUGIN_ID) id: String, isOptional: Boolean = false, ) : this( id, null, isOptional ) public override fun toString(): String = buildString { append(id) versionRequirement?.let { append(':') append(it) } if (isOptional) { append('?') } } public companion object { /** * 解析 "$id:$versionRequirement?" */ @JvmStatic @Throws(IllegalArgumentException::class) public fun parseFromString(string: String): PluginDependency { require(string.isNotEmpty()) { "string is empty." } val optional = string.endsWith('?') val (id, version) = string.removeSuffix("?").let { rule -> if (rule.contains(':')) { rule.substringBeforeLast(':') to rule.substringAfterLast(':') } else { rule to null } } return PluginDependency(id, version, optional) } } public object PluginDependencyAsStringSerializer : KSerializer<PluginDependency> by String.serializer().map( serializer = { it.toString() }, deserializer = { parseFromString(it) } ) } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/description/PluginDescription.kt ================================================ /* * 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.console.plugin.description import net.mamoe.mirai.console.compiler.common.CheckerConstants import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.util.SemVersion /** * 插件描述. * * @see Plugin */ public interface PluginDescription { /** * 插件 ID. 用于指令权限等一些内部处理 * * - 仅允许英文字母, '-', '_', '.'. 在内部操作处理时不区分大小写. * - 类似于 Java 包名, 插件 ID 需要 '域名.名称' 格式, 如 `net.mamoe.mirai.example-plugin` * - 域名和名称都是必须的 * - 数字和符号都不允许位于首位 * - '-' 和 '_' 仅允许存在于两个英文字母之间. 不推荐使用 '_'. 请参照 `org.example.mirai.plugin.my-example-plugin` 格式. * * 可使用 [ID_REGEX] 检验格式合法性. * * ID 在插件发布后就应该保持不变, 以便其他插件添加依赖. * * 插件 ID 的域名和名称都不能完全是以下其中一个 ([FORBIDDEN_ID_NAMES]). * - "console" * - "main" * - "plugin" * - "config" * - "data" * * ### 示例 * - 合法 `net.mamoe.mirai.example-plugin` * - 非法 `.example-plugin` * * @see ID_REGEX * @see FORBIDDEN_ID_NAMES */ @ResolveContext(PLUGIN_ID) public val id: String /** * 插件名称用于展示给用户,仅取决于 `PluginDescription` 提供的 `name`,与主类类名等其他信息无关. * * 名称允许中文, 允许各类符号,但不能完全是以下其中一种(忽略大小写)([FORBIDDEN_ID_NAMES]): * - console * - main * - plugin * - config * - data * * @see FORBIDDEN_ID_NAMES */ @ResolveContext(PLUGIN_NAME) public val name: String /** * 插件作者, 允许为空 */ public val author: String /** * 插件版本. * * 语法参考: ([语义化版本 2.0.0](https://semver.org/lang/zh-CN/)). * * 合法的版本号示例: * - `1.0.0` * - `1.0` * - `1.0-M1` * - `1.0.0-M1` * - `1.0.0-M2-1` * - `1` (尽管非常不建议这么做) * * 非法版本号示例: * - `DEBUG-1` * - `-1.0` * - `v1.0` (不允许 "v") * - `V1.0` (不允许 "V") * * @see SemVersion 语义化版本. */ @ResolveContext(SEMANTIC_VERSION) public val version: SemVersion /** * 插件信息, 允许为空 */ public val info: String /** * 此插件依赖的其他插件, 将会在这些插件加载之后加载此插件 * * 特别的, 可以使用 `net.mamoe.mirai-console` 作为插件 id 限制 mirai-console 版本 (自 2.16.0 后) * * @see PluginDependency */ public val dependencies: Set<PluginDependency> public companion object { /** * [PluginDescription.id] 的合法 [Regex]. * * - Group 1: 域名 * - Group 2: 名称 * * ```regex * ([a-zA-Z]\w*(?:\.[a-zA-Z]\w*)*)\.([a-zA-Z]\w*(?:-\w+)*) * ``` * * @see PluginDescription.id */ public val ID_REGEX: Regex = CheckerConstants.PLUGIN_ID_REGEX /** * 在 [PluginDescription.id] 和 [PluginDescription.name] 中禁止用的完全匹配名称列表. * * @see PluginDescription.id */ public val FORBIDDEN_ID_NAMES: Array<String> = CheckerConstants.PLUGIN_FORBIDDEN_NAMES /** * 依次检查 [PluginDescription] 的 [PluginDescription.id], [PluginDescription.name], [PluginDescription.dependencies] 的合法性 * * @throws IllegalPluginDescriptionException 当不合法时抛出. */ @Throws(IllegalPluginDescriptionException::class) public fun checkPluginDescription(instance: PluginDescription) { kotlin.runCatching { checkPluginId(instance.id) checkPluginName(instance.name) checkDependencies(instance.id, instance.dependencies) }.getOrElse { throw IllegalPluginDescriptionException( "Illegal PluginDescription. Plugin ${instance.name} (${instance.id})", it ) } } /** * 检查 [PluginDescription.id] 的合法性. 忽略大小写. * * @throws IllegalPluginDescriptionException 当不合法时抛出. */ @Throws(IllegalPluginDescriptionException::class) public fun checkPluginId(id: String) { if (id.isBlank()) throw IllegalPluginDescriptionException("Plugin id cannot be blank") if (id.none { it == '.' }) throw IllegalPluginDescriptionException("'$id' is illegal. Plugin id must consist of both domain and name. ") val lowercaseId = id.lowercase() if (ID_REGEX.matchEntire(id) == null) { throw IllegalPluginDescriptionException("Plugin does not match regex '${ID_REGEX.pattern}'.") } FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseId }?.let { illegal -> throw IllegalPluginDescriptionException("Plugin id contains illegal word: '$illegal'.") } } /** * 检查 [PluginDescription.name] 的合法性. 忽略大小写. * * @throws IllegalPluginDescriptionException 当不合法时抛出. */ @Throws(IllegalPluginDescriptionException::class) public fun checkPluginName(name: String) { if (name.isBlank()) throw IllegalPluginDescriptionException("Plugin name cannot be blank") val lowercaseName = name.lowercase() FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal -> throw IllegalPluginDescriptionException("Plugin name is illegal: '$illegal'.") } } /** * 检查 [PluginDescription.dependencies] 的合法性. 忽略大小写. * * @throws IllegalPluginDescriptionException 当不合法时抛出. */ @Throws(IllegalPluginDescriptionException::class) public fun checkDependencies(pluginId: String, dependencies: Set<PluginDependency>) { val lowercaseId = pluginId.lowercase() val lowercaseDependencies = dependencies.mapTo(LinkedHashSet(dependencies.size)) { it.id.lowercase() } if (lowercaseDependencies.size != dependencies.size) throw IllegalPluginDescriptionException("Duplicated dependency detected: A plugin cannot depend on different versions of dependencies of the same id") if (lowercaseDependencies.any { it == lowercaseId }) throw IllegalPluginDescriptionException("Recursive dependency detected: A plugin cannot depend on itself") } } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE") package net.mamoe.mirai.console.plugin.jvm import net.mamoe.mirai.console.data.AutoSavePluginDataHolder import net.mamoe.mirai.console.data.PluginConfig import net.mamoe.mirai.console.data.PluginData import net.mamoe.mirai.console.internal.plugin.JvmPluginClassLoaderN import net.mamoe.mirai.console.internal.plugin.JvmPluginInternal import net.mamoe.mirai.console.internal.plugin.loadPluginDescriptionFromClassLoader import net.mamoe.mirai.console.internal.util.PluginServiceHelper import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.minutesToMillis import net.mamoe.mirai.utils.secondsToMillis import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass /** * [JavaPlugin] 和 [KotlinPlugin] 的父类. 所有 [JvmPlugin] 都应该拥有此类作为直接或间接父类. * * @see JavaPlugin * @see KotlinPlugin */ @OptIn(ConsoleExperimentalApi::class) public abstract class AbstractJvmPlugin : JvmPluginInternal, JvmPlugin, AutoSavePluginDataHolder { @JvmOverloads public constructor( description: JvmPluginDescription, parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, ) : super(parentCoroutineContext) { this.description = description } @JvmOverloads public constructor(parentCoroutineContext: CoroutineContext = EmptyCoroutineContext) : super(parentCoroutineContext) { this.description = javaClass.loadPluginDescriptionFromClassLoader() } final override val description: JvmPluginDescription @ConsoleExperimentalApi public final override val dataHolderName: String get() = this.description.id public final override val loader: JvmPluginLoader get() = super<JvmPluginInternal>.loader public final override fun permissionId(name: String): PermissionId = PermissionService.INSTANCE.allocatePermissionIdForPlugin(this, name) /** * 重载 [PluginData] * * @see reloadPluginData */ @JvmName("reloadPluginData") public fun <T : PluginData> T.reload(): Unit = loader.dataStorage.load(this@AbstractJvmPlugin, this) /** * 重载 [PluginConfig] * * @see reloadPluginConfig */ @JvmName("reloadPluginConfig") public fun <T : PluginConfig> T.reload(): Unit = loader.configStorage.load(this@AbstractJvmPlugin, this) /** * 立即保存 [PluginData] * * @see reloadPluginData * @since 2.9 */ @JvmName("savePluginData") public fun <T : PluginData> T.save(): Unit = loader.dataStorage.store(this@AbstractJvmPlugin, this) /** * 立即保存 [PluginConfig] * * @see reloadPluginConfig * @since 2.9 */ @JvmName("savePluginConfig") public fun <T : PluginConfig> T.save(): Unit = loader.configStorage.store(this@AbstractJvmPlugin, this) @ConsoleExperimentalApi public override val autoSaveIntervalMillis: LongRange = 30.secondsToMillis..10.minutesToMillis /** * 获取 [JvmPluginClasspath] * * 注: 仅插件通过 console 内置插件加载器加载时可用 * * @since 2.12 */ protected val jvmPluginClasspath: JvmPluginClasspath by lazy { val classLoader = this@AbstractJvmPlugin.javaClass.classLoader if (classLoader is JvmPluginClassLoaderN) { return@lazy classLoader.openaccess } error("jvmPluginClasspath not available for $classLoader") } /** * 获取 指定类的 SPI Service * * 为了兼容 Kotlin object 单例类,此方法没有直接使用 java 原生的 API, * 而是 手动读取 `META-INF/services/` 的内容, 并尝试构造或获取实例 * * 注: 仅包括当前插件 JAR 的 Service */ @JvmSynthetic protected fun <T : Any> services(kClass: KClass<out T>): Lazy<List<T>> = lazy { val classLoader = try { jvmPluginClasspath.pluginClassLoader } catch (_: IllegalStateException) { this::class.java.classLoader } with(PluginServiceHelper) { classLoader .findServices(kClass) .loadAllServices() } } /** * 获取 指定类的 SPI Service * * 为了兼容 Kotlin object 单例类,此方法没有直接使用 java 原生的 API, * 而是 手动读取 `META-INF/services/` 的内容, 并尝试构造或获取实例 * * 注: 仅包括当前插件 JAR 的 Service */ protected fun <T : Any> services(clazz: Class<out T>): Lazy<List<T>> = services(kClass = clazz.kotlin) /** * 获取 指定类的 SPI Service * * 为了兼容 Kotlin object 单例类,此方法没有直接使用 java 原生的 API, * 而是 手动读取 `META-INF/services/` 的内容, 并尝试构造或获取实例 * * 注: 仅包括当前插件 JAR 的 Service */ protected inline fun <reified T : Any> services(): Lazy<List<T>> = services(kClass = T::class) } /** * 重载一个 [PluginData] * * @see AbstractJvmPlugin.reload */ @JvmSynthetic public inline fun AbstractJvmPlugin.reloadPluginData(instance: PluginData): Unit = this.run { instance.reload() } /** * 重载一个 [PluginConfig] * * @see AbstractJvmPlugin.reload */ @JvmSynthetic public inline fun AbstractJvmPlugin.reloadPluginConfig(instance: PluginConfig): Unit = this.run { instance.reload() } /** * 立即保存 [PluginData] * * @see AbstractJvmPlugin.save * @since 2.9 */ @JvmSynthetic public inline fun AbstractJvmPlugin.savePluginData(instance: PluginData): Unit = this.run { instance.save() } /** * 立即保存 [PluginConfig] * * @see AbstractJvmPlugin.save * @since 2.9 */ @JvmSynthetic public inline fun AbstractJvmPlugin.savePluginConfig(instance: PluginConfig): Unit = this.run { instance.save() } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/ExportManager.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin.jvm import net.mamoe.mirai.console.internal.plugin.ExportManagerImpl import net.mamoe.mirai.console.util.ConsoleExperimentalApi /** * 插件的类导出管理器 * * * 允许插件将一些内部实现保护起来, 避免其他插件调用, 要启动这个特性, * 只需要创建名为 `export-rules.txt` 的规则文件,便可以控制插件的类的公开规则。 * * 如果正在使用 `Gradle` 项目, 该规则文件一般位于 `src/main/resources` 下 * * Example: * ```text * * # #开头的行全部识别为注释 * * # exports, 允许其他插件直接使用某个类 * * # 导出了一个internal包的一个类 * # * exports org.example.miraiconsole.myplugin.internal.OpenInternal * * # 导出了整个 api 包 * # * exports org.example.miraiconsole.myplugin.api * * # 保护 org.example.miraiconsole.myplugin.api2.Internal, 不允许其他插件直接使用 * # * protects org.example.miraiconsole.myplugin.api2.Internal * * # 保护整个包 * # * # 别名: protect-package * protects org.example.miraiconsole.myplugin.internal * * # 此规则不会生效, 因为在此条规则之前, * # org.example.miraiconsole.myplugin.internal 已经被加入到保护域中 * exports org.example.miraiconsole.myplugin.internal.NotOpenInternal * * * # export-plugin, 允许其他插件使用除了已经被保护的全部类 * # 使用此规则会同时让此规则后的所有规则全部失效 * # 别名: export-all, export-system * # export-plugin * * * # 将整个插件放入保护域中 * # 除了此规则之前显式 export 的类, 其他插件将不允许直接使用被保护的插件的任何类 * # 别名: protect-all, protect-system * protect-plugin * * ``` * * 插件也可以通过 Service 来自定义导出控制 * * Example: * ```kotlin * @AutoService(ExportManager::class) * object MyExportManager: ExportManager { * override fun isExported(className: String): Boolean { * println(" <== $className") * return true * } * } * ``` * * @see StandardExportManagers */ @ConsoleExperimentalApi public interface ExportManager { /** * 如果 [className] 能够通过 [ExportManager] 的规则, 返回 true * * @param className [className] 是一个合法的满足 [ClassLoader] 的加载规则 的全限定名. * [className] 不应该是数组的全限定名或者JVM基本类型的名字. * See also: [ClassLoader.loadClass] * [ClassLoader#name](https://docs.oracle.com/javase/8/docs/api/java/lang/ClassLoader.html#name) */ public fun isExported(className: String): Boolean } @ConsoleExperimentalApi public object StandardExportManagers { @ConsoleExperimentalApi public object AllExported : ExportManager { override fun isExported(className: String): Boolean = true } @ConsoleExperimentalApi public object AllDenied : ExportManager { override fun isExported(className: String): Boolean = false } @ConsoleExperimentalApi @JvmStatic public fun parse(lines: Iterator<String>): ExportManager { return ExportManagerImpl.parse(lines) } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/JavaPlugin.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS") package net.mamoe.mirai.console.plugin.jvm import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * Java 插件的父类 */ public abstract class JavaPlugin : JvmPlugin, AbstractJvmPlugin { /** * 通过一个指定的 [JvmPluginDescription] 构造插件示例 * * 当使用 `plugin.yml` 加载插件示例时不能使用此构造器 */ @JvmOverloads public constructor( description: JvmPluginDescription, parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, ) : super(description, parentCoroutineContext) /** * 通过插件内置的 `plugin.yml` 构造插件实例 * * @since 2.16.0 */ @JvmOverloads public constructor( parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, ) : super(parentCoroutineContext) init { __jpi_try_to_init_dependencies() } /** * Java API Scheduler */ public val scheduler: JavaPluginScheduler = JavaPluginScheduler(this.coroutineContext) } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt ================================================ /* * 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("ConsoleUtils") package net.mamoe.mirai.console.plugin.jvm import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Runnable import net.mamoe.mirai.console.internal.util.JavaPluginSchedulerImpl import java.util.concurrent.Callable import java.util.concurrent.CompletableFuture import java.util.concurrent.Future import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * 拥有生命周期管理的简单 Java 线程池. * * 在插件被 [卸载][JavaPlugin.onDisable] 时将会自动停止. * * @see JavaPlugin.scheduler 获取实例 */ public interface JavaPluginScheduler : CoroutineScope { /** * 新增一个 Repeating Task (定时任务) * * 这个 Runnable 会被每 [intervalMs] 调用一次(不包含 [runnable] 执行时间) * * @see Future.cancel 取消这个任务 */ public fun repeating(intervalMs: Long, runnable: Runnable): Future<Void?> /** * 新增一个 Delayed Task (延迟任务) * * 在延迟 [delayMillis] 后执行 [runnable] */ public fun delayed(delayMillis: Long, runnable: Runnable): CompletableFuture<Void?> /** * 新增一个 Delayed Task (延迟任务) * * 在延迟 [delayMillis] 后执行 [callable] */ public fun <R> delayed(delayMillis: Long, callable: Callable<R>): CompletableFuture<R> /** * 异步执行一个任务, 最终返回 [Future], 与 Java 使用方法无异, 但效率更高且可以在插件关闭时停止 */ public fun <R> async(supplier: Callable<R>): Future<R> /** * 异步执行一个任务, 没有返回 */ public fun async(runnable: Runnable): Future<Void?> public companion object { /** * 创建一个 [JavaPluginScheduler] */ @JvmStatic @JvmName("create") @JvmOverloads public operator fun invoke(parentCoroutineContext: CoroutineContext = EmptyCoroutineContext): JavaPluginScheduler = JavaPluginSchedulerImpl(parentCoroutineContext) } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/JvmPlugin.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress( "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS", "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME" ) package net.mamoe.mirai.console.plugin.jvm import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.compiler.common.RestrictedScope import net.mamoe.mirai.console.compiler.common.RestrictedScope.Kind.COMMAND_REGISTER import net.mamoe.mirai.console.compiler.common.RestrictedScope.Kind.PERMISSION_REGISTER import net.mamoe.mirai.console.extension.PluginComponentStorage import net.mamoe.mirai.console.permission.PermissionIdNamespace import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginFileExtensions import net.mamoe.mirai.console.plugin.ResourceContainer import net.mamoe.mirai.utils.MiraiLogger /** * Java, Kotlin 或其他 JVM 平台插件 * * @see AbstractJvmPlugin 默认实现 * * @see JavaPlugin Java 插件 * @see KotlinPlugin Kotlin 插件 * * @see JvmPlugin 支持文件系统扩展 * @see ResourceContainer 支持资源获取 (如 Jar 中的资源文件) */ public interface JvmPlugin : Plugin, CoroutineScope, PluginFileExtensions, ResourceContainer, PermissionIdNamespace { /** 日志 */ public val logger: MiraiLogger /** 插件描述 */ public val description: JvmPluginDescription /** 所属插件加载器实例 */ // `final` in AbstractJvmPlugin public override val loader: JvmPluginLoader get() = JvmPluginLoader /** * 在插件被加载时调用. 只会被调用一次. * * 在 [onLoad] 时可注册扩展 [PluginComponentStorage.contribute] * * @see PluginComponentStorage 查看更多信息 * * @receiver 组件容器 */ @RestrictedScope(COMMAND_REGISTER, PERMISSION_REGISTER) public fun PluginComponentStorage.onLoad() { } /** * 在插件被启用时调用, 可能会被调用多次 */ public fun onEnable() {} /** * 在插件被关闭时调用, 可能会被调用多次 */ public fun onDisable() {} public companion object { @JvmSynthetic public inline fun JvmPlugin.onLoad(storage: PluginComponentStorage): Unit = storage.onLoad() } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginClasspath.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin.jvm import java.io.File /** * [JvmPlugin] 的类路径 * * @since 2.12 * @see AbstractJvmPlugin.jvmPluginClasspath */ public interface JvmPluginClasspath { public val pluginFile: File /** * 插件本体的 [ClassLoader], 负责加载插件本身的类数据 * * JVM NAME: `${pluginFile.name}` */ public val pluginClassLoader: ClassLoader /** * 插件所使用的依赖, 可共享, 当其他插件依赖于当前插件时, 其他插件可以从此 [ClassLoader] 搜索类引用 * * JVM NAME: ${pluginFile.name}&#91;shared] */ public val pluginSharedLibrariesClassLoader: ClassLoader /** * 插件内部使用的依赖, 不公开给其他插件, 其他插件搜索时不会搜索到此插件的依赖 * * JVM NAME: ${pluginFile.name}&#91;private] */ public val pluginIndependentLibrariesClassLoader: ClassLoader /** * [pluginClassLoader] 是否可以通过 [ClassLoader.getResource] 获取 Mirai Console (包括依赖) 的相关资源 * * 默认为 `false` * * @since 2.15.0 */ @SettingProperty("resources.resolve-console-system-resources", defaultValue = "false") public var shouldResolveConsoleSystemResource: Boolean /** * 当前插件是否可以被没有依赖此插件的插件使用 * * 默认为 `true` * * @since 2.15.0 */ @SettingProperty("class.loading.be-resolvable-to-independent", defaultValue = "true") public var shouldBeResolvableToIndependent: Boolean /** * 当前插件是否应该搜索未依赖的插件的类路径 * * 默认为 `true` * * @since 2.15.0 */ @SettingProperty("class.loading.resolve-independent", defaultValue = "true") public var shouldResolveIndependent: Boolean /** * 将 [file] 加入 [classLoader] 的搜索路径内 * * @throws IllegalArgumentException 当 [classLoader] 不是 [pluginClassLoader], * [pluginIndependentLibrariesClassLoader], [pluginSharedLibrariesClassLoader] 时抛出 */ @kotlin.jvm.Throws(IllegalArgumentException::class) public fun addToPath(classLoader: ClassLoader, file: File) /** * 下载依赖, 并注册进 [classLoader] * * 注: 如果需要同时修改 [pluginSharedLibrariesClassLoader] 和 [pluginIndependentLibrariesClassLoader], * 请先对 [pluginSharedLibrariesClassLoader] 以避免出现不和预期的问题 * * @throws IllegalArgumentException 当 [classLoader] 不是 * [pluginSharedLibrariesClassLoader], [pluginIndependentLibrariesClassLoader] * 时抛出 * @throws Exception 网络错误 / 依赖未找到 */ @kotlin.jvm.Throws(IllegalArgumentException::class, Exception::class) public fun downloadAndAddToPath(classLoader: ClassLoader, dependencies: Collection<String>) /** * 此注解仅用于注释 `options.properties` 的键值 * * Note: `META-INF/mirai-console-plugin/options.properties` * * @since 2.15.0 */ @Retention(AnnotationRetention.SOURCE) private annotation class SettingProperty( val name: String, val defaultValue: String = "", ) } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt ================================================ /* * 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("unused", "INVISIBLE_REFERENCE", "INVISIBLE_member", "DeprecatedCallableAddReplaceWith") package net.mamoe.mirai.console.plugin.jvm import io.github.karlatemp.caller.CallerFinder import io.github.karlatemp.caller.StackFrame import kotlinx.serialization.Serializable import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.* import net.mamoe.mirai.console.internal.plugin.JvmPluginClassLoaderN import net.mamoe.mirai.console.internal.util.findLoader import net.mamoe.mirai.console.plugin.description.PluginDependency import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.util.SemVersion import net.mamoe.yamlkt.Yaml /** * JVM 插件的描述. 通常作为 `plugin.yml` * * 请不要自行实现 [JvmPluginDescription] 接口. 它不具有继承稳定性. * * 要查看相关约束, 参考 [PluginDescription] * * @see SimpleJvmPluginDescription * @see JvmPluginDescriptionBuilder */ public interface JvmPluginDescription : PluginDescription { public companion object { /** * 从 [pluginClassloader] 读取资源文件 [filename] 并以 YAML 格式解析为 [SimpleJvmPluginDescription] * * @param filename [ClassLoader.getResourceAsStream] 的参数 `name` * @param pluginClassloader 默认通过 [CallerFinder.getCaller] 获取调用方 [StackFrame] 然后获取其 [Class.getClassLoader]. */ @JvmOverloads @JvmStatic public fun loadFromResource( filename: String = "plugin.yml", pluginClassloader: ClassLoader = CallerFinder.getCaller()?.findLoader() ?: error("Cannot find caller classloader, please specify manually."), ): JvmPluginDescription { if (filename == "plugin.yml") { val cache = (pluginClassloader as? JvmPluginClassLoaderN)?.pluginDescriptionFromPluginResource if (cache != null) return cache } val stream = pluginClassloader.getResourceAsStream(filename) ?: error("Cannot find plugin description resource '$filename'") val bytes = stream.use { it.readBytes() } return Yaml.decodeFromString(SimpleJvmPluginDescription.SerialData.serializer(), String(bytes)) .toJvmPluginDescription() } } } /** * 构建 [JvmPluginDescription] * @see JvmPluginDescriptionBuilder */ @JvmSynthetic public inline fun JvmPluginDescription( /** * @see [PluginDescription.id] */ @ResolveContext(PLUGIN_ID) id: String, /** * @see [PluginDescription.version] */ @ResolveContext(SEMANTIC_VERSION) version: String, /** * @see [PluginDescription.name] */ @ResolveContext(PLUGIN_NAME) name: String = id, block: JvmPluginDescriptionBuilder.() -> Unit = {}, ): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build() /** * 构建 [JvmPluginDescription] * @see JvmPluginDescriptionBuilder */ @JvmSynthetic public inline fun JvmPluginDescription( /** * @see [PluginDescription.id] */ @ResolveContext(PLUGIN_ID) id: String, /** * @see [PluginDescription.version] */ version: SemVersion, /** * @see [PluginDescription.name] */ @ResolveContext(PLUGIN_NAME) name: String = id, block: JvmPluginDescriptionBuilder.() -> Unit = {}, ): JvmPluginDescription = JvmPluginDescriptionBuilder(id, version).apply { name(name) }.apply(block).build() /** * [JvmPluginDescription] 构建器. * * #### Kotlin Example * ``` * val desc = JvmPluginDescription("org.example.example-plugin", "1.0.0") { * info("This is an example plugin") * dependsOn("org.example.another-plugin") * } * ``` * * #### Java Example * ```java * JvmPluginDescription desc = new JvmPluginDescriptionBuilder("org.example.example-plugin", "1.0.0") * .info("This is an example plugin") * .dependsOn("org.example.another-plugin") * .build(); * ``` * * @see [JvmPluginDescription] */ public class JvmPluginDescriptionBuilder( @ResolveContext(PLUGIN_ID) private var id: String, private var version: SemVersion, ) { public constructor( @ResolveContext(PLUGIN_ID) id: String, @ResolveContext(SEMANTIC_VERSION) version: String, ) : this(id, SemVersion(version)) private var name: String = id private var author: String = "" private var info: String = "" private var dependencies: MutableSet<PluginDependency> = mutableSetOf() @ILoveKuriyamaMiraiForever public fun name(@ResolveContext(PLUGIN_NAME) value: String): JvmPluginDescriptionBuilder = apply { this.name = value.trim() } @ILoveKuriyamaMiraiForever public fun version(@ResolveContext(SEMANTIC_VERSION) value: String): JvmPluginDescriptionBuilder = apply { this.version = SemVersion(value) } @ILoveKuriyamaMiraiForever public fun version(value: SemVersion): JvmPluginDescriptionBuilder = apply { this.version = value } @ILoveKuriyamaMiraiForever public fun id(@ResolveContext(PLUGIN_ID) value: String): JvmPluginDescriptionBuilder = apply { this.id = value.trim() } @ILoveKuriyamaMiraiForever public fun author(value: String): JvmPluginDescriptionBuilder = apply { this.author = value.trim() } @ILoveKuriyamaMiraiForever public fun info(value: String): JvmPluginDescriptionBuilder = apply { this.info = value.trimIndent() } @ILoveKuriyamaMiraiForever public fun setDependencies( value: Set<PluginDependency>, ): JvmPluginDescriptionBuilder = apply { this.dependencies = value.toMutableSet() } @ILoveKuriyamaMiraiForever public fun dependsOn( vararg dependencies: PluginDependency, ): JvmPluginDescriptionBuilder = apply { for (dependency in dependencies) { this.dependencies.add(dependency) } } /** * @see PluginDependency */ @ILoveKuriyamaMiraiForever public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, @ResolveContext(VERSION_REQUIREMENT) versionRequirement: String, isOptional: Boolean = false, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, versionRequirement, isOptional)) } /** * 无版本要求 * * @param isOptional [PluginDependency.isOptional] * * @see PluginDependency */ @ILoveKuriyamaMiraiForever public fun dependsOn( @ResolveContext(PLUGIN_ID) pluginId: String, isOptional: Boolean = false, ): JvmPluginDescriptionBuilder = apply { this.dependencies.add(PluginDependency(pluginId, null, isOptional)) } public fun build(): JvmPluginDescription = @Suppress("DEPRECATION_ERROR") SimpleJvmPluginDescription(id, name, version, author, info, dependencies) /** * 标注一个 [JvmPluginDescription] DSL */ @Suppress("SpellCheckingInspection") @Retention(AnnotationRetention.BINARY) @DslMarker internal annotation class ILoveKuriyamaMiraiForever // https://zh.moegirl.org.cn/zh-cn/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5 } /** * @constructor 推荐使用带名称的参数, 而不要按位置摆放. * * @see JvmPluginDescription */ // @Serializable // Keep this file in public API files. Might turn to `public` in the future. internal data class SimpleJvmPluginDescription @JvmOverloads constructor( override val id: String, override val name: String, override val version: SemVersion, override val author: String = "", override val info: String = "", override val dependencies: Set<PluginDependency> = setOf(), ) : JvmPluginDescription { @Suppress("DEPRECATION_ERROR") @JvmOverloads constructor( id: String, name: String = id, version: String, author: String = "", info: String = "", dependencies: Set<PluginDependency> = setOf(), ) : this(id, name, SemVersion(version), author, info, dependencies) init { PluginDescription.checkPluginDescription(this) } @Serializable // Keep this file in public API files. Might turn to `public` in the future. internal data class SerialData @JvmOverloads constructor( val id: String, val name: String? = null, // workaround to ktx-serialization bug val version: SemVersion, val author: String = "", val info: String = "", val dependencies: Set<PluginDependency> = setOf(), ) { fun toJvmPluginDescription(): JvmPluginDescription { return SimpleJvmPluginDescription( id, name ?: id, version, author, info, dependencies ) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin.jvm import kotlinx.coroutines.CoroutineScope import me.him188.kotlin.dynamic.delegation.dynamicDelegation import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.plugin.BuiltInJvmPluginLoaderImpl import net.mamoe.mirai.console.plugin.loader.FilePluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance /** * JVM 插件加载器 */ @NotStableForInheritance public interface JvmPluginLoader : CoroutineScope, FilePluginLoader<JvmPlugin, JvmPluginDescription> { /** * ".jar" */ public override val fileSuffix: String /** * [AbstractJvmPlugin.reloadPluginData] 默认使用的实例 */ @ConsoleExperimentalApi public val dataStorage: PluginDataStorage /** * [AbstractJvmPlugin.reloadPluginData] 默认使用的实例 */ @ConsoleExperimentalApi public val configStorage: PluginDataStorage /** * @since 2.10 */ @MiraiInternalApi public val classLoaders: List<ClassLoader> @MiraiInternalApi public fun findLoadedClass(name: String): Class<*>? public companion object BuiltIn : JvmPluginLoader by (dynamicDelegation { @OptIn(ConsoleFrontEndImplementation::class) MiraiConsoleImplementation.getInstance().jvmPluginLoader }) { override fun getPluginDescription(plugin: JvmPlugin): JvmPluginDescription = BuiltInJvmPluginLoaderImpl.run { plugin.description } } } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt ================================================ /* * 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("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPOSED_SUPER_CLASS", "RedundantVisibilityModifier") package net.mamoe.mirai.console.plugin.jvm import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * Kotlin 插件的父类. */ public abstract class KotlinPlugin : JvmPlugin, AbstractJvmPlugin { /** * 通过一个指定的 [JvmPluginDescription] 构造插件示例 * * 当使用 `plugin.yml` 加载插件示例时不能使用此构造器 */ @JvmOverloads public constructor( description: JvmPluginDescription, parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, ) : super(description, parentCoroutineContext) /** * 通过插件内置的 `plugin.yml` 构造插件实例 * * @since 2.16.0 */ @JvmOverloads public constructor( parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, ) : super(parentCoroutineContext) init { __jpi_try_to_init_dependencies() } } /* public object MyPlugin : KotlinPlugin() public object AccountPluginData : PluginData by MyPlugin.getPluginData() { public val s by value(1) } */ ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/loader/FilePluginLoader.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin.loader import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.utils.NotStableForInheritance import java.io.File /** * ['/plugins'][PluginManager.pluginsPath] 目录中的插件的加载器. 每个加载器需绑定一个后缀. * * @see AbstractFilePluginLoader 默认基础实现 * @see JvmPluginLoader 内建的 Jar (JVM) 插件加载器. */ public interface FilePluginLoader<P : Plugin, D : PluginDescription> : PluginLoader<P, D> { /** * 所支持的插件文件后缀, 含 '.', 不区分大小写. 如 [JvmPluginLoader] 为 ".jar" */ public val fileSuffix: String } /** * [FilePluginLoader] 的默认基础实现. * * @see FilePluginLoader */ public abstract class AbstractFilePluginLoader<P : Plugin, D : PluginDescription>( /** * 所支持的插件文件后缀, 含 '.', 不区分大小写. 如 [JvmPluginLoader] 为 ".jar" */ public override val fileSuffix: String, ) : FilePluginLoader<P, D> { private fun pluginsFilesSequence(): Sequence<File> = PluginManager.pluginsFolder.listFiles().orEmpty().asSequence() .filter { it.isFile && it.name.endsWith(fileSuffix, ignoreCase = true) } /** * 读取扫描到的后缀与 [fileSuffix] 相同的文件中的插件实例, 但不 [加载][PluginLoader.load] */ protected abstract fun Sequence<File>.extractPlugins(): List<P> @NotStableForInheritance // made non-final in 2.11 public override fun listPlugins(): List<P> = pluginsFilesSequence().extractPlugins() } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/loader/PluginLoadException.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.plugin.loader /** * 在加载插件过程中遇到的意料之中的问题. * * @see PluginLoader.load * @see PluginLoader.enable * @see PluginLoader.disable * @see PluginLoader.getPluginDescription */ public open class PluginLoadException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } ================================================ FILE: mirai-console/backend/mirai-console/src/plugin/loader/PluginLoader.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "NOTHING_TO_INLINE") package net.mamoe.mirai.console.plugin.loader import net.mamoe.mirai.console.extensions.PluginLoaderProvider import net.mamoe.mirai.console.plugin.Plugin import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enablePlugin import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader /** * 插件加载器. * * 插件加载器只实现寻找插件列表, 加载插件, 启用插件, 关闭插件这四个功能. * * 一个插件要在何时被加载,依赖如何处理,[PluginLoader] 都无需关心. * * 有关插件的依赖和已加载的插件列表由 [PluginManager] 维护. * * ## 内建加载器 * - [JvmPluginLoader] Jar 插件加载器 * * ## 扩展加载器 * 插件被允许扩展一个加载器. * * ### 实现扩展加载器 * 直接实现接口 [PluginLoader] 或 [FilePluginLoader], 并注册 [PluginLoaderProvider] * * @see JvmPluginLoader Jar 插件加载器 * @see PluginLoaderProvider 扩展 */ public interface PluginLoader<P : Plugin, D : PluginDescription> { /** * 扫描并返回可以被加载的插件的列表. * * 这些插件都应处于还未被加载的状态. * * 在 Console 启动时, [PluginManager] 会获取所有 [PluginDescription], 分析依赖关系, 确认插件加载顺序. * * **实现细节:** 此函数*只应该*在 Console 启动时被调用一次. 但取决于前端实现不同, 或由于被一些插件需要, 此函数也可能会被多次调用. */ public fun listPlugins(): List<P> /** * 获取此插件的描述. * * **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误. * * 若在 Console 启动并加载所有插件的过程中, 本函数抛出异常, 则会放弃此插件的加载, 并影响依赖它的其他插件. * * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如无法读取插件信息等). * * @see PluginDescription 插件描述 */ @Throws(PluginLoadException::class) public fun getPluginDescription(plugin: P): D /** * 主动加载一个插件 (实例), 但不 [启用][enablePlugin] 它. 返回加载成功的主类实例 * * **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enablePlugin], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException]. * * **实现细节**: 此函数只允许抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误. * 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件. * * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况. */ @Throws(IllegalStateException::class, PluginLoadException::class) public fun load(plugin: P) /** * 主动启用这个插件. * * **实现注意**: Console 不会把一个已经启用了的插件再次调用 [load] 或 [enablePlugin], 但不排除意外情况. 实现本函数时应在这种情况时立即抛出异常 [IllegalStateException]. * * **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误. * 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件. * * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * @throws IllegalStateException 在插件已经被加载时抛出. 这属于意料之外的情况. * * @see PluginManager.enablePlugin */ @Throws(IllegalStateException::class, PluginLoadException::class) public fun enable(plugin: P) /** * 主动禁用这个插件. * * **实现细节**: 此函数可抛出 [PluginLoadException] 作为正常失败原因, 其他任意异常都属于意外错误. * 当异常发生时, 插件将会直接被放弃加载, 并影响依赖它的其他插件. * * @throws PluginLoadException 在加载插件遇到意料之中的错误时抛出 (如找不到主类等). * * @see PluginManager.disablePlugin */ @Throws(IllegalStateException::class, PluginLoadException::class) public fun disable(plugin: P) } ================================================ FILE: mirai-console/backend/mirai-console/src/util/Annotations.kt ================================================ /* * 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("ConsoleUtils") package net.mamoe.mirai.console.util import kotlin.annotation.AnnotationTarget.* /** * 表明这个 API 是为了让 Java 使用者调用更方便. Kotlin 使用者不应该使用这些 API. */ @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target(PROPERTY, FUNCTION, CLASS) internal annotation class JavaFriendlyApi /** * 标记为一个仅供 mirai-console 内部使用的 API. * * 这些 API 可能会在任意时刻更改, 且不会发布任何预警. * 非常不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, FUNCTION, PROPERTY) @MustBeDocumented public annotation class ConsoleInternalApi( val message: String = "", ) /** * 标记一个实验性的 API. * * 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @MustBeDocumented public annotation class ConsoleExperimentalApi( val message: String = "", ) ================================================ FILE: mirai-console/backend/mirai-console/src/util/AnsiMessageBuilder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "MemberVisibilityCanBePrivate", "FunctionName") package net.mamoe.mirai.console.util import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.SystemCommandSender import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.asAnsiMessageBuilder import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.dropAnsi import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.MessageReceipt import java.io.Serializable /** * @see buildAnsiMessage * @see sendAnsiMessage * @see AnsiMessageBuilder * @see AnsiMessageBuilder.create * @see asAnsiMessageBuilder * @since 1.1 */ public open class AnsiMessageBuilder public constructor( public val delegate: StringBuilder, ) : Appendable, Serializable { override fun toString(): String = delegate.toString() /** * 同 [append] 方法, 在 `noAnsi=true` 的时候会忽略此函数的调用 * * 参考资料: * - [ANSI转义序列](https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97) * - [ANSI转义序列#颜色](https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E9%A2%9C%E8%89%B2) * * @param code Ansi 操作码 * * @see asAnsiMessageBuilder * @see create */ public open fun ansi(code: String): AnsiMessageBuilder = append(code) public open fun reset(): AnsiMessageBuilder = append(Color.RESET) public open fun white(): AnsiMessageBuilder = append(Color.WHITE) public open fun red(): AnsiMessageBuilder = append(Color.RED) public open fun emeraldGreen(): AnsiMessageBuilder = append(Color.EMERALD_GREEN) public open fun gold(): AnsiMessageBuilder = append(Color.GOLD) public open fun blue(): AnsiMessageBuilder = append(Color.BLUE) public open fun purple(): AnsiMessageBuilder = append(Color.PURPLE) public open fun green(): AnsiMessageBuilder = append(Color.GREEN) public open fun gray(): AnsiMessageBuilder = append(Color.GRAY) public open fun lightRed(): AnsiMessageBuilder = append(Color.LIGHT_RED) public open fun lightGreen(): AnsiMessageBuilder = append(Color.LIGHT_GREEN) public open fun lightYellow(): AnsiMessageBuilder = append(Color.LIGHT_YELLOW) public open fun lightBlue(): AnsiMessageBuilder = append(Color.LIGHT_BLUE) public open fun lightPurple(): AnsiMessageBuilder = append(Color.LIGHT_PURPLE) public open fun lightCyan(): AnsiMessageBuilder = append(Color.LIGHT_CYAN) public companion object { /** * 从 [String] 中剔除 ansi 控制符 */ @JvmStatic public fun String.dropAnsi(): String = this .replace(DROP_CSI_PATTERN, "") // 先进行 CSI 剔除后进行 ANSI 剔除 .replace(DROP_ANSI_PATTERN, "") /** * 使用 [this] 封装一个 [AnsiMessageBuilder] * * @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用 */ @JvmStatic @JvmOverloads @JvmName("from") public fun StringBuilder.asAnsiMessageBuilder(noAnsi: Boolean = false): AnsiMessageBuilder = if (noAnsi) NoAnsiMessageBuilder(this) else AnsiMessageBuilder(this) /** * @param capacity [StringBuilder] 的初始化大小 * * @param noAnsi 为 `true` 时忽略全部与 ansi 有关的方法的调用 * @see AnsiMessageBuilder */ @JvmStatic @JvmOverloads public fun create( capacity: Int = 16, noAnsi: Boolean = false, ): AnsiMessageBuilder = StringBuilder(capacity).asAnsiMessageBuilder(noAnsi) /** * 判断 [sender] 是否支持带 ansi 控制符的正确显示 */ @JvmStatic public fun isAnsiSupported(sender: CommandSender): Boolean = if (sender is SystemCommandSender) { sender.isAnsiSupported } else false /** * 往 [StringBuilder] 追加 ansi 控制符 */ public inline fun StringBuilder.appendAnsi( action: AnsiMessageBuilder.() -> Unit, ): AnsiMessageBuilder = this.asAnsiMessageBuilder().apply(action) } override fun hashCode(): Int = this.delegate.hashCode() override fun equals(other: Any?): Boolean { if (other == null) return false if (other::class.java != this::class.java) return false other as AnsiMessageBuilder return other.delegate == this.delegate } ///////////////////////////////////////////////////////////////////////////////// override fun append(c: Char): AnsiMessageBuilder = apply { delegate.append(c) } override fun append(csq: CharSequence?): AnsiMessageBuilder = apply { delegate.append(csq) } override fun append(csq: CharSequence?, start: Int, end: Int): AnsiMessageBuilder = apply { delegate.append(csq, start, end) } public fun append(any: Any?): AnsiMessageBuilder = apply { delegate.append(any) } public fun append(value: String): AnsiMessageBuilder = apply { delegate.append(value) } public fun append(value: String, start: Int, end: Int): AnsiMessageBuilder = apply { delegate.append(value, start, end) } public fun append(value: Boolean): AnsiMessageBuilder = apply { delegate.append(value) } public fun append(value: Float): AnsiMessageBuilder = apply { delegate.append(value) } public fun append(value: Double): AnsiMessageBuilder = apply { delegate.append(value) } public fun append(value: Int): AnsiMessageBuilder = apply { delegate.append(value) } public fun append(value: Long): AnsiMessageBuilder = apply { delegate.append(value) } public fun append(value: Short): AnsiMessageBuilder = apply { delegate.append(value) } ///////////////////////////////////////////////////////////////////////////////// } /** * @param capacity [StringBuilder] 初始化大小 * @see AnsiMessageBuilder.create * @since 1.1 */ @JvmSynthetic public fun AnsiMessageBuilder(capacity: Int = 16): AnsiMessageBuilder = AnsiMessageBuilder(StringBuilder(capacity)) /** * 构建一条 ANSI 信息 * * @see AnsiMessageBuilder * @since 1.1 */ @JvmSynthetic public inline fun buildAnsiMessage( capacity: Int = 16, action: AnsiMessageBuilder.() -> Unit, ): String = AnsiMessageBuilder.create(capacity, false).apply(action).toString() // 不在 top-level 使用者会得到 Internal error: Couldn't inline sendAnsiMessage /** * 向 [CommandSender] 发送一条带有 ANSI 控制符的信息 * * @see AnsiMessageBuilder * @since 1.1 */ @JvmSynthetic public suspend inline fun CommandSender.sendAnsiMessage( capacity: Int = 16, builder: AnsiMessageBuilder.() -> Unit, ): MessageReceipt<Contact>? { return sendMessage( AnsiMessageBuilder.create(capacity, noAnsi = !AnsiMessageBuilder.isAnsiSupported(this)) .apply(builder) .toString() ) } /** * 向 [CommandSender] 发送一条带有 ANSI 控制符的信息 * * @see AnsiMessageBuilder.Companion.dropAnsi * @since 1.1 */ @JvmSynthetic public suspend inline fun CommandSender.sendAnsiMessage(message: String): MessageReceipt<Contact>? { return sendMessage( if (AnsiMessageBuilder.isAnsiSupported(this)) message else message.dropAnsi() ) } // internals // CSI序列由ESC [、若干个(包括0个)“参数字节”、若干个“中间字节”,以及一个“最终字节”组成。各部分的字符范围如下: // // CSI序列在ESC [之后各个组成部分的字符范围[12]:5.4 // 组成部分 字符范围 ASCII // 参数字节 0x30–0x3F 0–9:;<=>? // 中间字节 0x20–0x2F 空格、!"#$%&'()*+,-./ // 最终字节 0x40–0x7E @A–Z[\]^_`a–z{|}~ // // @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#CSI%E5%BA%8F%E5%88%97 @Suppress("RegExpRedundantEscape") private val DROP_CSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex() // 序列具有不同的长度。所有序列都以ASCII字符ESC(27 / 十六进制 0x1B)开头, // 第二个字节则是0x40–0x5F(ASCII @A–Z[\]^_)范围内的字符。[12]:5.3.a // // 标准规定,在8位环境中,这两个字节的序列可以合并为0x80-0x9F范围内的单个字节(详情请参阅C1控制字符集)。 // 但是,在现代设备上,这些代码通常用于其他目的,例如UTF-8的一部分或CP-1252字符,因此并不使用这种合并的方式。 // // 除ESC之外的其他C0代码(通常是BEL,BS,CR,LF,FF,TAB,VT,SO和SI)在输出时也可能会产生与某些控制序列相似或相同的效果。 // // @see https://zh.wikipedia.org/wiki/ANSI%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97#%E8%BD%AC%E4%B9%89%E5%BA%8F%E5%88%97 // // 注: 缺少详细资料, 只能认定 ansi 长度固定为二字节 (CSI除外) private val DROP_ANSI_PATTERN = """\u001b[\u0040–\u005F]""".toRegex() private object Color { const val RESET = "\u001b[0m" const val WHITE = "\u001b[97m" const val RED = "\u001b[31m" const val EMERALD_GREEN = "\u001b[32m" const val GOLD = "\u001b[33m" const val BLUE = "\u001b[34m" const val PURPLE = "\u001b[35m" const val GREEN = "\u001b[36m" const val GRAY = "\u001b[90m" const val LIGHT_RED = "\u001b[91m" const val LIGHT_GREEN = "\u001b[92m" const val LIGHT_YELLOW = "\u001b[93m" const val LIGHT_BLUE = "\u001b[94m" const val LIGHT_PURPLE = "\u001b[95m" const val LIGHT_CYAN = "\u001b[96m" } private class NoAnsiMessageBuilder(builder: StringBuilder) : AnsiMessageBuilder(builder) { override fun reset(): AnsiMessageBuilder = this override fun white(): AnsiMessageBuilder = this override fun red(): AnsiMessageBuilder = this override fun emeraldGreen(): AnsiMessageBuilder = this override fun gold(): AnsiMessageBuilder = this override fun blue(): AnsiMessageBuilder = this override fun purple(): AnsiMessageBuilder = this override fun green(): AnsiMessageBuilder = this override fun gray(): AnsiMessageBuilder = this override fun lightRed(): AnsiMessageBuilder = this override fun lightGreen(): AnsiMessageBuilder = this override fun lightYellow(): AnsiMessageBuilder = this override fun lightBlue(): AnsiMessageBuilder = this override fun lightPurple(): AnsiMessageBuilder = this override fun lightCyan(): AnsiMessageBuilder = this override fun ansi(code: String): AnsiMessageBuilder = this } ================================================ FILE: mirai-console/backend/mirai-console/src/util/ConsoleInput.kt ================================================ /* * 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("INAPPLICABLE_JVM_NAME", "unused") @file:JvmMultifileClass @file:JvmName("ConsoleUtils") package net.mamoe.mirai.console.util import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.util.ConsoleInputImpl /** * Console 输入. 由于 console 接管了 [标准输入][System. in], [readLine] 等操作需要在这里进行. */ public interface ConsoleInput { /** * 挂起当前协程, 以 [提示][hint] 向用户索要一个输入, 在用户完成输入时返回输入结果. */ @JvmBlockingBridge public suspend fun requestInput(hint: String): String public companion object INSTANCE : ConsoleInput by ConsoleInputImpl } // don't move into INSTANCE, Compilation error @JvmSynthetic public suspend inline fun MiraiConsole.requestInput(hint: String): String = ConsoleInput.requestInput(hint) ================================================ FILE: mirai-console/backend/mirai-console/src/util/ContactUtils.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.util import net.mamoe.mirai.Bot import net.mamoe.mirai.console.internal.data.qualifiedNameOrTip import net.mamoe.mirai.contact.* /** * 为简化操作提供的一些工具 */ @ConsoleExperimentalApi public object ContactUtils { /** * 获取一个 [Bot] 的好友, 群, 或群员. * * 访问顺序为 [Bot.friends] -> [Bot.groups] -> 各群的群员 * * **注意**: 若目标用户存在于多个群, 可能返回任意一个群的 [Member]. * * @see includeMembers 为 `false` 时不查找群成员 */ @JvmOverloads @JvmStatic @ConsoleExperimentalApi public fun Bot.getContact(id: Long, includeMembers: Boolean = true): Contact { return getContactOrNull(id, includeMembers) ?: throw NoSuchElementException("Contact $id not found for bot ${this.id}") } /** * 获取一个 [Bot] 的好友, 群, 或群员. * * 访问顺序为 [Bot.friends] -> [Bot.groups] -> 各群的群员 */ @JvmOverloads @JvmStatic @ConsoleExperimentalApi public fun Bot.getContactOrNull(id: Long, includeMembers: Boolean = true): Contact? { return getFriendOrGroupOrNull(id) ?: kotlin.run { if (includeMembers) { groups.asSequence().flatMap { it.members.asSequence() }.firstOrNull { it.id == id } } else null } } /** * 获取一个 [Bot] 的好友, 或群对象. * * 访问顺序为 [Bot.friends] -> [Bot.groups] */ @JvmStatic @ConsoleExperimentalApi public fun Bot.getFriendOrGroup(id: Long): Contact { return getFriendOrGroupOrNull(id) ?: throw NoSuchElementException("Friend or Group $id not found for bot ${this.id}") } /** * 获取一个 [Bot] 的好友, 或群对象. * * 访问顺序为 [Bot.friends] -> [Bot.groups] */ @JvmStatic @ConsoleExperimentalApi public fun Bot.getFriendOrGroupOrNull(id: Long): Contact? { return this.friends[id] ?: this.groups[id] } /** * 将 [ContactOrBot] 输出为字符串表示. */ @JvmName("renderContactOrName") @JvmStatic public fun ContactOrBot.render(): String { return when (this) { is Bot -> "Bot $nick($id)" is Group -> "Group $name($id)" is Friend -> "Friend $nick($id)" is Member -> "Member $nameCardOrNick(${group.id}.$id)" is Stranger -> "Stranger $nick($id)" else -> error("Illegal type for ContactOrBot: ${this::class.qualifiedNameOrTip}") } } } ================================================ FILE: mirai-console/backend/mirai-console/src/util/CoroutineScopeUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.util import kotlinx.atomicfu.atomic import kotlinx.atomicfu.loop import kotlinx.coroutines.* import net.mamoe.mirai.console.internal.util.runIgnoreException import net.mamoe.mirai.utils.currentTimeMillis import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * Runs `action` every `intervalMillis` since each time [setChanged] is called, ignoring subsequent calls during the interval. */ internal class TimedTask( scope: CoroutineScope, coroutineContext: CoroutineContext = EmptyCoroutineContext, intervalMillis: Long, action: suspend CoroutineScope.() -> Unit, ) { companion object { private const val UNCHANGED = 0L } private val lastChangedTime = atomic(UNCHANGED) fun setChanged() { lastChangedTime.value = currentTimeMillis() } val job: Job = scope.launch(coroutineContext) { // `delay` always checks for cancellation lastChangedTime.loop { last -> val current = currentTimeMillis() if (last == UNCHANGED) { runIgnoreException<CancellationException> { delay(3000) // accuracy not necessary } ?: return@launch } else { if (current - last > intervalMillis) { if (!lastChangedTime.compareAndSet(last, UNCHANGED)) return@loop action() } runIgnoreException<CancellationException> { delay(3000) // accuracy not necessary } ?: return@launch } } } } internal fun CoroutineScope.launchTimedTask( intervalMillis: Long, coroutineContext: CoroutineContext = EmptyCoroutineContext, action: suspend CoroutineScope.() -> Unit, ) = TimedTask(this, coroutineContext, intervalMillis, action) ================================================ FILE: mirai-console/backend/mirai-console/src/util/MemoryFormat.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.util import kotlin.math.floor private const val MEM_B = 1024L private const val MEM_KB = 1024L shl 10 private const val MEM_MB = 1024L shl 20 private const val MEM_GB = 1024L shl 30 @Suppress("NOTHING_TO_INLINE") private inline fun StringBuilder.appendDouble(number: Double): StringBuilder = append(floor(number * 100) / 100) internal fun renderMemoryUsageNumber(num: Long) = buildString { renderMemoryUsageNumber(this, num) } internal fun renderMemoryUsageNumber(builder: StringBuilder, num: Long) { when { num == -1L -> { builder.append(num) } num < MEM_B -> { builder.append(num).append("B") } num < MEM_KB -> { builder.appendDouble(num / 1024.0).append("KB") } num < MEM_MB -> { builder.appendDouble((num ushr 10) / 1024.0).append("MB") } else -> { builder.appendDouble((num ushr 20) / 1024.0).append("GB") } } } private var emptyLine = " ".repeat(10) internal fun Appendable.emptyLine(size: Int) { if (emptyLine.length <= size) { emptyLine = String(CharArray(size) { ' ' }) } append(emptyLine, 0, size) } internal inline fun AnsiMessageBuilder.renderMUNum(size: Int, contentLength: Int, code: () -> Unit) { val s = size - contentLength val left = s / 2 val right = s - left emptyLine(left) code() emptyLine(right) } ================================================ FILE: mirai-console/backend/mirai-console/src/util/MessageScope.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress( "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME", "FunctionName", "SuspendFunctionOnCoroutineScope", "unused", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER" ) package net.mamoe.mirai.console.util import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.fold import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.SystemCommandSender import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.User import net.mamoe.mirai.message.data.Message import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.internal.LowPriorityInOverloadResolution /** * 表示几个消息对象的 '域', 即消息对象的集合. 用于最小化将同一条消息发送给多个类型不同的目标的付出. * * ## 支持的消息对象类型 * [Contact], [CommandSender], [MessageScope] (递归). * * 在下文, `A` 或 `B` 指代这三种类型的其中两种, 允许排列组合. 如 `A.scopeWith(B)` 可能表示 `Contact.scopeWith(MessageScope)`. * * ## 获得 [MessageScope] * - `A.asMessageScope()`. * - `C<A>.toMessageScope()`. 其中 `C` 表示 `Iterable`, `Sequence`, `Flow`, `Array` 其中任一. * * ## 连接 [MessageScope] * - `A?.scopeWith(vararg B?)`. * - `A?.scopeWith(vararg A?)`. * * `null` 项将会被过滤. * * ## 自动去重 * 在连接时, [MessageScope] 会自动根据真实的 [收信对象][CommandSender.subject] 去重. * * 如 `member.asCommandSender().scopeWith(member.group)`, * 返回的 [MessageScope] 实际上只包含 `member.group`. 因为 `member.asCommandSender()` 的最终收信对象就是 `member.group`. * * 因此在使用 [scopeWith] 时, 无需考虑重复性, 只需要把希望发送的目标全部列入. * * ## 使用 [MessageScope] * 在 `scopeWith` 或 `scopeWithNotNull` 后加 `lambda` 参数即可表示使用 [MessageScope]. * 如: * ``` * A.scopeWith(B) { // this: MessageScope * sendMessage(...) * } * ``` * * ## 典例 * 在处理指令时, 目标群对象可能与发件人群对象不同, 如用户在 A 群发指令, 以禁言 B 群的成员. * 此时机器人可能需要同时广播通知到 A 群和 B 群. * * 由于 [CommandSender] 与 [Contact] 无公共接口, 无法使用 [listOfNotNull] 遍历处理. [MessageScope] 就是设计为解决这样的问题. * * *Kotlin* * ``` * // 在一个 SimpleCommand 内 * @Handler * suspend fun CommandSender.handle(target: Member) { * val duration = Random.nextInt(1, 15) * target.mute(duration) * * * // 不使用 MessageScope * val thisGroup = this.getGroupOrNull() * val message = "${this.name} 禁言 ${target.nameCardOrNick} $duration 秒" * if (target.group != thisGroup) { * target.group.sendMessage(message) * } * sendMessage(message) * * * // 使用 MessageScope * // 表示至少发送给 `this`, 当 `this` 的真实发信对象与 `target.group` 不同时, 还额外发送给 `target.group` * this.scopeWith(target.group) { * sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration 秒") * } * * * // 同样地, 可以扩展用法, 同时私聊指令执行者: * // this.scopeWith( * // target, * // target.group * // ) { ... } * } * ``` * * *Java* * ```java * // 在一个 SimpleCommand 内 * @Handler * public void handle(sender: CommandSender, target: Member) { * int duration = Random.nextInt(1, 15); * target.mute(duration); * * * // 不使用 MessageScope * Group thisGroup = this.getGroupOrNull(); * String message = "${this.name} 禁言 ${target.nameCardOrNick} $duration 秒"; * if (!target.group.equals(thisGroup)) { * target.group.sendMessage(message); * } * sender.sendMessage(message); * * * // 使用 MessageScope * // 表示至少发送给 `this`, 当 `this` 的真实发信对象与 `target.group` 不同时, 还额外发送给 `target.group` * MessageScope scope = MessageScopeKt.scopeWith(sender, target); * scope.sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration 秒"); * * // 或是只用一行: * MessageScopeKt.scopeWith(sender, target).sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration 秒"); * } * ``` */ public sealed interface MessageScope { /** * 如果此 [MessageScope] 仅包含一个消息对象, 则 [realTarget] 指向这个对象. 否则 [realTarget] 为 `null`. * * 对于 [CommandSender] 作为 [MessageScope], [realTarget] 总是指令执行者 [User], 即 [CommandSender.user] * * [realTarget] 用于 [MessageScope.invoke] 时的去重. * * @suppress 此 API 不稳定, 可能在任何时间被修改 */ @ConsoleExperimentalApi public val realTarget: Any? /** * 立刻以此发送消息给所有在此 [MessageScope] 下的消息对象 */ @JvmBlockingBridge public suspend fun sendMessage(message: Message) /** * 立刻以此发送消息给所有在此 [MessageScope] 下的消息对象 */ @JvmBlockingBridge public suspend fun sendMessage(message: String) } /** * 使用 [MessageScope] 里的所有消息对象. 与 [kotlin.run] 相同. */ @JvmSynthetic public inline operator fun <R, MS : MessageScope> MS.invoke(action: MS.() -> R): R = this.action() /////////////////////////////////////////////////////////////////////////// // Builders /////////////////////////////////////////////////////////////////////////// /* * 实现提示: 以下所有代码都通过 codegen 模块中 net.mamoe.mirai.console.codegen.MessageScopeCodegen 生成. 请不要手动修改它们. * * 建议阅读 [MessageScope] 的文档. */ //// region MessageScopeBuilders CODEGEN //// public fun Contact.asMessageScope(): MessageScope = createScopeDelegate(this) public fun CommandSender.asMessageScope(): MessageScope = createScopeDelegate(this) @LowPriorityInOverloadResolution public fun Contact?.scopeWith(vararg others: Contact?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun Contact?.scopeWith(vararg others: CommandSender?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun Contact?.scopeWith(vararg others: MessageScope?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun CommandSender?.scopeWith(vararg others: Contact?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun CommandSender?.scopeWith(vararg others: CommandSender?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun CommandSender?.scopeWith(vararg others: MessageScope?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun MessageScope?.scopeWith(vararg others: Contact?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun MessageScope?.scopeWith(vararg others: CommandSender?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } @LowPriorityInOverloadResolution public fun MessageScope?.scopeWith(vararg others: MessageScope?): MessageScope { return others.fold(this.asMessageScopeOrNoop()) { acc, other -> acc.scopeWith(other?.asMessageScope()) } } public fun Contact?.scopeWith(other: Contact?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun Contact?.scopeWith(other: CommandSender?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun Contact?.scopeWith(other: MessageScope?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun CommandSender?.scopeWith(other: Contact?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun CommandSender?.scopeWith(other: CommandSender?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun CommandSender?.scopeWith(other: MessageScope?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun MessageScope?.scopeWith(other: Contact?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun MessageScope?.scopeWith(other: CommandSender?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public fun MessageScope?.scopeWith(other: MessageScope?): MessageScope { @Suppress("DuplicatedCode") return when { this == null && other == null -> NoopMessageScope this == null && other != null -> other.asMessageScope() this != null && other == null -> this.asMessageScope() this != null && other != null -> CombinedScope(asMessageScope(), other.asMessageScope()) else -> null!! } } public inline fun <R> Contact?.scopeWith(vararg others: Contact?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> Contact?.scopeWith(vararg others: CommandSender?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> Contact?.scopeWith(vararg others: MessageScope?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> CommandSender?.scopeWith(vararg others: Contact?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> CommandSender?.scopeWith(vararg others: CommandSender?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> CommandSender?.scopeWith(vararg others: MessageScope?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> MessageScope?.scopeWith(vararg others: Contact?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> MessageScope?.scopeWith(vararg others: CommandSender?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } public inline fun <R> MessageScope?.scopeWith(vararg others: MessageScope?, action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return scopeWith(*others).invoke(action) } @Deprecated( "Senseless scopeWith. Use asMessageScope.", ReplaceWith("this.asMessageScope()", "net.mamoe.mirai.console.util.asMessageScope") ) // diagnostic deprecation public inline fun Contact.scopeWith(): MessageScope = asMessageScope() @Deprecated( "Senseless scopeWith. Use asMessageScope.", ReplaceWith("this.asMessageScope()", "net.mamoe.mirai.console.util.asMessageScope") ) // diagnostic deprecation public inline fun CommandSender.scopeWith(): MessageScope = asMessageScope() @Deprecated( "Senseless scopeWith. Use asMessageScope.", ReplaceWith("this.asMessageScope()", "net.mamoe.mirai.console.util.asMessageScope") ) // diagnostic deprecation public inline fun MessageScope.scopeWith(): MessageScope = asMessageScope() @Deprecated( "Senseless scopeWith. Use .asMessageScope().invoke.", ReplaceWith( "this.asMessageScope()(action)", "net.mamoe.mirai.console.util.asMessageScope", "net.mamoe.mirai.console.util.invoke", ) ) // diagnostic deprecation public inline fun <R> Contact.scopeWith(action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return asMessageScope()(action) } @Deprecated( "Senseless scopeWith. Use .asMessageScope().invoke.", ReplaceWith( "this.asMessageScope()(action)", "net.mamoe.mirai.console.util.asMessageScope", "net.mamoe.mirai.console.util.invoke", ) ) // diagnostic deprecation public inline fun <R> CommandSender.scopeWith(action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return asMessageScope()(action) } @Deprecated( "Senseless scopeWith. Use .asMessageScope().invoke.", ReplaceWith( "this.asMessageScope()(action)", "net.mamoe.mirai.console.util.asMessageScope", "net.mamoe.mirai.console.util.invoke", ) ) // diagnostic deprecation public inline fun <R> MessageScope.scopeWith(action: MessageScope.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return asMessageScope()(action) } //// endregion MessageScopeBuilders CODEGEN //// //// region IterableMessageScopeBuilders CODEGEN //// @JvmName("toMessageScopeContactIterable") public fun Iterable<Contact?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeCommandSenderIterable") public fun Iterable<CommandSender?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeMessageScopeIterable") public fun Iterable<MessageScope?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeContactSequence") public fun Sequence<Contact?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeCommandSenderSequence") public fun Sequence<CommandSender?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeMessageScopeSequence") public fun Sequence<MessageScope?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeContactArray") public fun Array<Contact?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeCommandSenderArray") public fun Array<CommandSender?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmName("toMessageScopeMessageScopeArray") public fun Array<MessageScope?>.toMessageScope(): MessageScope { return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScopeOrNoop() ) } } @JvmSynthetic @JvmName("toMessageScopeContactFlow") public suspend fun Flow<Contact>.toMessageScope(): MessageScope { // Flow<Any?>.firstOrNull isn't yet supported return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScope() ) } } @JvmSynthetic @JvmName("toMessageScopeCommandSenderFlow") public suspend fun Flow<CommandSender>.toMessageScope(): MessageScope { // Flow<Any?>.firstOrNull isn't yet supported return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScope() ) } } @JvmSynthetic @JvmName("toMessageScopeMessageScopeFlow") public suspend fun Flow<MessageScope>.toMessageScope(): MessageScope { // Flow<Any?>.firstOrNull isn't yet supported return this.fold(this.firstOrNull().asMessageScopeOrNoop()) { acc, messageScope -> CombinedScope( acc, messageScope.asMessageScope() ) } } //// endregion IterableMessageScopeBuilders CODEGEN //// /////////////////////////////////////////////////////////////////////////// // Internals /////////////////////////////////////////////////////////////////////////// // [MessageScope] 实现 @PublishedApi internal inline fun MessageScope.asMessageScope(): MessageScope = this private inline fun MessageScope?.asMessageScopeOrNoop(): MessageScope = this?.asMessageScope() ?: NoopMessageScope private inline fun Contact?.asMessageScopeOrNoop(): MessageScope = this?.asMessageScope() ?: NoopMessageScope private inline fun CommandSender?.asMessageScopeOrNoop(): MessageScope = this?.asMessageScope() ?: NoopMessageScope private inline fun createScopeDelegate(o: CommandSender) = CommandSenderAsMessageScope(o) private inline fun createScopeDelegate(o: Contact) = ContactAsMessageScope(o) internal fun MessageScope.asSequence(): Sequence<MessageScope> { return if (this is CombinedScope) { val a = this.first.asSequence() val b = this.second.asSequence() // don't inline. fuck compilers sequenceOf(a, b).flatten() } else sequenceOf(this) } @OptIn(ConsoleExperimentalApi::class) private class CombinedScope( val first: MessageScope, val second: MessageScope, ) : MessageScope { override val realTarget: Any? get() = null private val targets: List<MessageScope> by lazy { this.asSequence().distinctBy { it.realTarget }.toList() } override suspend fun sendMessage(message: Message) { for (target in targets) { target.sendMessage(message) } } override suspend fun sendMessage(message: String) { for (target in targets) { target.sendMessage(message) } } } @OptIn(ConsoleExperimentalApi::class) private class CommandSenderAsMessageScope( private val sender: CommandSender, ) : MessageScope { override val realTarget: Any get() { val sender = this.sender if (sender is SystemCommandSender) return sender return sender.user ?: sender } override suspend fun sendMessage(message: Message) { sender.sendMessage(message) } override suspend fun sendMessage(message: String) { sender.sendMessage(message) } } @OptIn(ConsoleExperimentalApi::class) private class ContactAsMessageScope( private val sender: Contact, ) : MessageScope { override val realTarget: Any get() = sender override suspend fun sendMessage(message: Message) { sender.sendMessage(message) } override suspend fun sendMessage(message: String) { sender.sendMessage(message) } } @OptIn(ConsoleExperimentalApi::class) private object NoopMessageScope : MessageScope { override val realTarget: Any? get() = null override suspend fun sendMessage(message: Message) { } override suspend fun sendMessage(message: String) { } } ================================================ FILE: mirai-console/backend/mirai-console/src/util/MessageUtils.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.util import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageContent @ConsoleExperimentalApi public object MessageUtils { @JvmStatic public fun MessageChain.messageContentsSequence(): Sequence<MessageContent> = asSequence().filterIsInstance<MessageContent>() @JvmStatic public fun MessageChain.firstContent(): MessageContent = messageContentsSequence().first() @JvmStatic public fun MessageChain.firstContentOrNull(): MessageContent? = messageContentsSequence().firstOrNull() } ================================================ FILE: mirai-console/backend/mirai-console/src/util/SemVersion.kt ================================================ /* * 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/dev/LICENSE */ /* * @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp> */ @file:Suppress("unused") package net.mamoe.mirai.console.util import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.SEMANTIC_VERSION import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.VERSION_REQUIREMENT import net.mamoe.mirai.console.internal.data.map import net.mamoe.mirai.console.internal.util.semver.SemVersionInternal import net.mamoe.mirai.console.util.SemVersion.Companion.equals import net.mamoe.mirai.console.util.SemVersion.Requirement import net.mamoe.mirai.console.util.SemVersion.SemVersionAsStringSerializer import kotlin.LazyThreadSafetyMode.PUBLICATION /** * [语义化版本](https://semver.org/lang/zh-CN/) 支持 * * ### 解析示例 * * `1.0.0-M4+c25733b8` 将会解析出下面的内容, * [major] (主本号), [minor] (次版本号), [patch] (修订号), [identifier] (先行版本号) 和 [metadata] (元数据). * ``` * SemVersion( * major = 1, * minor = 0, * patch = 0, * identifier = "M4" * metadata = "c25733b8" * ) * ``` * 其中 identifier 和 metadata 都是可选的. * * 对于核心版本号, 此实现稍微比语义化版本规范宽松一些, 允许 x.y 的存在. * * ### 序列化 * 使用 [SemVersionAsStringSerializer], [SemVersion] 被序列化为 [toString] 的字符串. * * @see Requirement 版本号要修 * @see SemVersion.invoke 由字符串解析 */ @Serializable(with = SemVersionAsStringSerializer::class) public data class SemVersion /** * @see SemVersion.invoke 字符串解析 */ internal constructor( /** 主版本号 */ public val major: Int, /** 次版本号 */ public val minor: Int, /** 修订号 */ public val patch: Int?, /** 先行版本号识别符 */ public val identifier: String? = null, /** 版本号元数据, 不参与版本号对比([compareTo]), 但是参与版本号严格对比([equals]) */ public val metadata: String? = null, ) : Comparable<SemVersion> { init { require(major >= 0) { "major must >= 0" } require(minor >= 0) { "minor must >= 0" } if (patch != null) require(patch >= 0) { "patch must >= 0" } if (identifier != null) require(identifier.none(Char::isWhitespace)) { "identifier must not contain whitespace" } if (metadata != null) require(metadata.none(Char::isWhitespace)) { "metadata must not contain whitespace" } } /** * 一条依赖规则 * @see [parseRangeRequirement] */ @Serializable(Requirement.RequirementAsStringSerializer::class) public data class Requirement internal constructor( /** * 规则的字符串表示方式 * * @see [SemVersion.parseRangeRequirement] */ val rule: String, ) { @Transient internal val impl = kotlin.runCatching { SemVersionInternal.parseRangeRequirement(rule) }.getOrElse { throw java.lang.IllegalArgumentException("Syntax error: $rule", it) } /** 在 [version] 满足此要求时返回 true */ public fun test(version: SemVersion): Boolean = impl.test(version) /** * 序列化为字符串, [rule]. 从字符串反序列化, [parseRangeRequirement]. */ public object RequirementAsStringSerializer : KSerializer<Requirement> by String.serializer().map( serializer = { it.rule }, deserializer = { parseRangeRequirement(it) } ) public companion object { /** * @see parseRangeRequirement */ @JvmSynthetic public operator fun invoke(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Requirement = parseRangeRequirement(requirement) } } /** * 使用 [SemVersion.toString] 序列化, 使用 [SemVersion.invoke] 反序列化. */ public object SemVersionAsStringSerializer : KSerializer<SemVersion> by String.serializer().map( serializer = { it.toString() }, deserializer = { SemVersion(it) } ) public companion object { /** * 解析一个版本号, 将会返回一个 [SemVersion], * 如果发生解析错误将会抛出一个 [IllegalArgumentException] 或者 [NumberFormatException] * * 对于版本号的组成, 有以下规定: * - 必须包含主版本号和次版本号 * - 存在 先行版本号 的时候 先行版本号 不能为空 * - 存在 元数据 的时候 元数据 不能为空 * - 核心版本号只允许 `x.y` 和 `x.y.z` 的存在 * - `1.0-RC` 是合法的 * - `1.0.0-RC` 也是合法的, 与 `1.0-RC` 一样 * - `1.0.0.0-RC` 是不合法的, 将会抛出一个 [IllegalArgumentException] * * 注意情况: * - 第一个 `+` 之后的所有内容全部识别为元数据 * - `1.0+METADATA-M4`, metadata="METADATA-M4" * - 如果不确定版本号是否合法, 可以使用 [regex101.com](https://regex101.com/r/vkijKf/1/) 进行检查 * - 此实现使用的正则表达式为 `^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$` */ @JvmStatic @JvmName("parse") @Throws(IllegalArgumentException::class, NumberFormatException::class) public operator fun invoke(@ResolveContext(SEMANTIC_VERSION) version: String): SemVersion = SemVersionInternal.parse(version) /** * 解析一条依赖需求描述, 在无法解析的时候抛出 [IllegalArgumentException] * * 对于一条规则, 有以下方式可选 * * - `1.0.0-M4` 要求 1.0.0-M4 版本, 且只能是 1.0.0-M4 版本 * - `1.x` 要求 1.x 版本 * - `> 1.0.0-RC` 要求 1.0.0-RC 之后的版本, 不能是 1.0.0-RC * - `>= 1.0.0-RC` 要求 1.0.0-RC 或之后的版本, 可以是 1.0.0-RC * - `< 1.0.0-RC` 要求 1.0.0-RC 之前的版本, 不能是 1.0.0-RC * - `<= 1.0.0-RC` 要求 1.0.0-RC 或之前的版本, 可以是 1.0.0-RC * - `!= 1.0.0-RC` 要求 除了1.0.0-RC 的任何版本 * - `[1.0.0, 1.2.0]` * - `(1.0.0, 1.2.0]` * - `[1.0.0, 1.2.0)` * - `(1.0.0, 1.2.0)` [数学区间](https://baike.baidu.com/item/%E5%8C%BA%E9%97%B4/1273117) * * 对于多个规则, 允许使用逻辑符号 `{}`, `||`, `&&` * 例如: * - `1.x || 2.x || 3.0.0` * - `<= 0.5.3 || >= 1.0.0` * - `{> 1.0 && < 1.5} || {> 1.8}` * - `{> 1.0 && < 1.5} || {> 1.8}` * - `> 1.0.0 && != 1.2.0` * * 特别注意: * - 依赖规则版本号不需要携带版本号元数据, 元数据不参与依赖需求的检查 * - 如果目标版本号携带有先行版本号, 请不要忘记先行版本号 * - 因为 `()` 已经用于数学区间, 使用 `{}` 替代 `()` */ @JvmStatic @Throws(IllegalArgumentException::class) public fun parseRangeRequirement(@ResolveContext(VERSION_REQUIREMENT) rule: String): Requirement = Requirement(rule) /** @see [Requirement.test] */ @JvmStatic @Throws(IllegalArgumentException::class, NumberFormatException::class) public fun Requirement.test(@ResolveContext(SEMANTIC_VERSION) version: String): Boolean = test(invoke(version)) /** * 当满足 [requirement] 时返回 true, 否则返回 false */ @JvmStatic public fun SemVersion.satisfies(requirement: Requirement): Boolean = requirement.test(this) /** * 当满足 [requirement] 时返回 true, 否则返回 false */ @JvmStatic @Throws(IllegalArgumentException::class) public fun SemVersion.satisfies(@ResolveContext(VERSION_REQUIREMENT) requirement: String): Boolean = parseRangeRequirement(requirement).test(this) /** for Kotlin only */ @JvmStatic @JvmSynthetic public operator fun Requirement.contains(version: SemVersion): Boolean = test(version) /** for Kotlin only */ @JvmStatic @JvmSynthetic public operator fun Requirement.contains(@ResolveContext(SEMANTIC_VERSION) version: String): Boolean = test(version) } @Transient private val toString: String by lazy(PUBLICATION) { buildString { append(major) append('.').append(minor) patch?.let { append('.').append(it) } identifier?.let { identifier -> append('-').append(identifier) } metadata?.let { metadata -> append('+').append(metadata) } } } /** * 返回类似 `1.0.0-M4+c25733b8` 的字符串. */ public override fun toString(): String = toString /** * 将 [SemVersion] 转为 Kotlin data class 风格的 [String]. * * ``` * return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)" * ``` */ public fun toStructuredString(): String { return "SemVersion(major=$major, minor=$minor, patch=$patch, identifier=$identifier, metadata=$metadata)" } /** * 比较 `this` 和 [other]. * * @param deep 为 `true` 时进行深度比较, 相当于 [equals]. 为 `false` 时相当于 `compareTo(other) == 0` * @see compareTo */ public fun equals(other: SemVersion, deep: Boolean): Boolean { return if (deep) { (other.major == major && other.minor == minor && other.patch == patch && other.identifier == identifier && other.metadata == metadata) } else { this.compareTo(other) == 0 } } /** * 深度比较 `this` 和 [other], 当且仅当 [major], [patch], [minor], [identifier], [metadata] 完全相同时返回 `true`. * * 如: `1.0.0-RC` != `1.0-RC` * * @see compareTo */ public override fun equals(other: Any?): Boolean { if (other === null) return false if (this === other) return true if (javaClass != other.javaClass) return false return equals(other as SemVersion, deep = true) } public override fun hashCode(): Int { var result = major.hashCode() result = 31 * result + minor.hashCode() result = 31 * result + (patch?.hashCode() ?: 0) result = 31 * result + (identifier?.hashCode() ?: 0) result = 31 * result + (metadata?.hashCode() ?: 0) return result } /** * 比较 `this` 和 [other] 的实际版本大小. * * 如: * - `SemVersion("1.0.0-RC").compareTo(SemVersion("1.0-RC")) == 0` (然而对他们进行 [equals] 判断会返回 `false`) * - `SemVersion("1.3.0") > SemVersion("1.1.0") == true` (因为 1.3.0 比 1.1.0 更高) * * * @return 当 `this` 比 [other] 更高时返回一个正数. * 当 `this` 比 [other] 更低时返回一个负数. * 当 `this` 与 [other] 版本大小相等时返回 0. * * @see equals */ public override operator fun compareTo(other: SemVersion): Int { return SemVersionInternal.run { compareInternal(this@SemVersion, other) } } } ================================================ FILE: mirai-console/backend/mirai-console/src/util/StandardUtils.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.util import kotlin.contracts.contract /** * Perform `this as? T`. */ @JvmSynthetic public inline fun <reified T : Any> Any?.safeCast(): T? { contract { returnsNotNull() implies (this@safeCast is T) } return this as? T } /** * Perform `this as T`. */ @JvmSynthetic public inline fun <reified T : Any> Any?.cast(): T { contract { returns() implies (this@cast is T) } return this as T } ================================================ FILE: mirai-console/backend/mirai-console/src/util/retryCatching.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE", "unused") @file:JvmMultifileClass @file:JvmName("ConsoleUtils") package net.mamoe.mirai.console.util import org.jetbrains.annotations.Range import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.internal.InlineOnly /** * 执行 [n] 次 [block], 在第一次成功时返回执行结果, 在捕获到异常时返回异常. */ @InlineOnly public inline fun <R> retryCatching(n: @Range(from = 1, to = Int.MAX_VALUE.toLong()) Int, block: () -> R): Result<R> { contract { callsInPlace(block, InvocationKind.AT_LEAST_ONCE) } require(n >= 1) { "param n for retryCatching must not be negative" } var exception: Throwable? = null repeat(n) { try { return Result.success(block()) } catch (e: Throwable) { exception?.addSuppressed(e) exception = e } } return Result.failure(exception!!) } ================================================ FILE: mirai-console/backend/mirai-console/test/TestMiraiConosle.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.data.MemoryPluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.PlatformLogger import java.nio.file.Path import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext import kotlin.coroutines.resume import kotlin.io.path.createTempDirectory import kotlin.test.assertNotNull internal object Testing { @Volatile internal var cont: Continuation<Any?>? = null @Suppress("UNCHECKED_CAST") suspend fun <R> withTesting(timeout: Long = 50000L, block: suspend () -> Unit): R { @Suppress("RemoveExplicitTypeArguments") // bug return if (timeout != -1L) { withTimeout<R>(timeout) { suspendCancellableCoroutine<R> { ct -> this@Testing.cont = ct as Continuation<Any?> runBlocking { block() } } } } else { suspendCancellableCoroutine<R> { ct -> this.cont = ct as Continuation<Any?> runBlocking { block() } } } } fun ok(result: Any? = Unit) { val cont = cont assertNotNull(cont) cont.resume(result) } } ================================================ FILE: mirai-console/backend/mirai-console/test/command/AbstractCommandTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.internal.data.builtins.ConsoleDataScopeImpl import net.mamoe.mirai.console.internal.data.builtins.DataScope import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest internal abstract class AbstractCommandTest : AbstractConsoleInstanceTest() { val dataScope get() = DataScope as ConsoleDataScopeImpl val consoleSender get() = ConsoleCommandSender open val sender: CommandSender get() = ConsoleCommandSender open val owner: CommandOwner get() = ConsoleCommandOwner } ================================================ FILE: mirai-console/backend/mirai-console/test/command/CommandContextTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalCommandDescriptors::class) @file:Suppress("unused", "UNUSED_PARAMETER") package net.mamoe.mirai.console.command import kotlinx.coroutines.runBlocking import net.mamoe.mirai.console.Testing import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.java.JCompositeCommand import net.mamoe.mirai.console.command.java.JRawCommand import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.safeCast import org.apache.commons.lang3.ArrayUtils.isSameType import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.TestFactory import kotlin.test.assertEquals internal class CommandContextTest : AbstractCommandTest() { private class MyMetadata : MessageMetadata { override fun hashCode(): Int = javaClass.hashCode() override fun equals(other: Any?): Boolean = isSameType(this, other) override fun toString(): String = "MyMetadata" companion object Key : AbstractMessageKey<MyMetadata>({ it.safeCast() }) } /////////////////////////////////////////////////////////////////////////// // RawCommand /////////////////////////////////////////////////////////////////////////// @TestFactory fun `can execute with sender`(): List<DynamicTest> { return listOf( object : RawCommand(owner, "test") { override suspend fun CommandContext.onCommand(args: MessageChain) { Testing.ok(args) } } to "/test foo", object : SimpleCommand(owner, "test") { @Handler fun CommandContext.foo(arg: MessageChain) { Testing.ok(arg) } } to "/test foo", object : CompositeCommand(owner, "test") { @SubCommand fun CommandContext.sub(arg: MessageChain) { Testing.ok(arg) } } to "/test sub foo", object : JRawCommand(owner, "test") { override fun onCommand(context: CommandContext, args: MessageChain) { Testing.ok(args) } } to "/test foo", object : JSimpleCommand(owner, "test") { @Handler fun foo(context: CommandContext, arg: MessageChain) { Testing.ok(arg) } } to "/test foo", object : JCompositeCommand(owner, "test") { @SubCommand fun sub(context: CommandContext, arg: MessageChain) { Testing.ok(arg) } } to "/test sub foo", ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { instance.withRegistration { assertEquals( messageChainOf(PlainText("foo")), Testing.withTesting { assertSuccess(sender.executeCommand(cmd, checkPermission = false)) } ) } } } } } @TestFactory fun `RawCommand can execute and get original chain`(): List<DynamicTest> { return listOf( object : RawCommand(owner, "test") { override suspend fun CommandContext.onCommand(args: MessageChain) { Testing.ok(originalMessage) } } to "/test foo", object : SimpleCommand(owner, "test") { @Handler fun CommandContext.foo(arg: MessageChain) { Testing.ok(originalMessage) } } to "/test foo", object : CompositeCommand(owner, "test") { @SubCommand fun CommandContext.sub(arg: MessageChain) { Testing.ok(originalMessage) } } to "/test sub foo", object : JRawCommand(owner, "test") { override fun onCommand(context: CommandContext, args: MessageChain) { Testing.ok(context.originalMessage) } } to "/test foo", object : JSimpleCommand(owner, "test") { @Handler fun foo(context: CommandContext, arg: MessageChain) { Testing.ok(context.originalMessage) } } to "/test foo", object : JCompositeCommand(owner, "test") { @SubCommand fun sub(context: CommandContext, arg: MessageChain) { Testing.ok(context.originalMessage) } } to "/test sub foo", ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { instance.withRegistration { assertEquals( cmd, Testing.withTesting<MessageChain> { assertSuccess(sender.executeCommand(cmd, checkPermission = false)) }.contentToString() ) } } } } } @TestFactory fun `can execute and get metadata`(): List<DynamicTest> { val metadata = MyMetadata() return listOf( object : RawCommand(owner, "test") { override suspend fun CommandContext.onCommand(args: MessageChain) { Testing.ok(originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : SimpleCommand(owner, "test") { @Handler fun CommandContext.foo(arg: MessageChain) { Testing.ok(originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : CompositeCommand(owner, "test") { @SubCommand fun CommandContext.sub(arg: MessageChain) { Testing.ok(originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), object : JRawCommand(owner, "test") { override fun onCommand(context: CommandContext, args: MessageChain) { Testing.ok(context.originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : JSimpleCommand(owner, "test") { @Handler fun foo(context: CommandContext, arg: MessageChain) { Testing.ok(context.originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : JCompositeCommand(owner, "test") { @SubCommand fun sub(context: CommandContext, arg: MessageChain) { Testing.ok(context.originalMessage[MyMetadata]) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { instance.withRegistration { assertEquals( metadata, Testing.withTesting { assertSuccess(CommandManager.executeCommand(sender, cmd, checkPermission = false)) } ) } } } } } @TestFactory fun `RawCommand can execute and get chain including metadata`(): List<DynamicTest> { val metadata = MyMetadata() return listOf( object : RawCommand(owner, "test") { override suspend fun CommandContext.onCommand(args: MessageChain) { Testing.ok(originalMessage) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : SimpleCommand(owner, "test") { @Handler fun CommandContext.foo(arg: MessageChain) { Testing.ok(originalMessage) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : CompositeCommand(owner, "test") { @SubCommand fun CommandContext.sub(arg: MessageChain) { Testing.ok(originalMessage) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), object : JRawCommand(owner, "test") { override fun onCommand(context: CommandContext, args: MessageChain) { Testing.ok(context.originalMessage) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : JSimpleCommand(owner, "test") { @Handler fun foo(context: CommandContext, arg: MessageChain) { Testing.ok(context.originalMessage) } } to messageChainOf(PlainText("/test"), metadata, PlainText("foo")), object : JCompositeCommand(owner, "test") { @SubCommand fun sub(context: CommandContext, arg: MessageChain) { Testing.ok(context.originalMessage) } } to messageChainOf(PlainText("/test"), PlainText("sub"), metadata, PlainText("foo")), ).map { (instance, cmd) -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { instance.withRegistration { assertEquals( cmd, Testing.withTesting { assertSuccess(CommandManager.executeCommand(sender, cmd, checkPermission = false)) } ) } } } } } } ================================================ FILE: mirai-console/backend/mirai-console/test/command/CommandValueArgumentContextTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalCommandDescriptors::class) package net.mamoe.mirai.console.command import kotlinx.coroutines.runBlocking import net.mamoe.mirai.console.Testing import net.mamoe.mirai.console.command.descriptor.CommandArgumentParserException import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.command.java.JCompositeCommand import net.mamoe.mirai.console.command.java.JSimpleCommand import net.mamoe.mirai.console.internal.data.classifierAsKClass import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.messageChainOf import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.TestFactory import kotlin.test.assertEquals @Suppress("UNUSED_PARAMETER", "unused") internal class CommandValueArgumentContextTest : AbstractCommandTest() { inner class CustomBooleanParser : CommandValueArgumentParser<Boolean> { @Throws(CommandArgumentParserException::class) override fun parse(raw: String, sender: CommandSender): Boolean { return raw == "TRUE!" } @Throws(CommandArgumentParserException::class) override fun parse(raw: MessageContent, sender: CommandSender): Boolean { // 将一个图片认为是 'true' return if (raw is Image && raw.imageId == "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { true } else super.parse(raw, sender) } } inner class JavaComposite : JCompositeCommand(mockPlugin, "main") { init { addArgumentContext( buildCommandArgumentContext { java.lang.Boolean.TYPE with CustomBooleanParser() } ) } @SubCommand("name") fun foo(context: CommandContext, arg: String, b: Boolean) { Testing.ok(b) } } inner class JavaSimple : JSimpleCommand(mockPlugin, "main") { init { addArgumentContext( buildCommandArgumentContext { java.lang.Boolean.TYPE with CustomBooleanParser() } ) } @Handler fun foo(context: CommandContext, arg: String, b: Boolean) { Testing.ok(b) } } @TestFactory fun test(): List<DynamicTest> { return listOf( JavaComposite() to listOf( messageChainOf(PlainText("/main"), PlainText("name"), PlainText("aaa"), PlainText("TRUE!")), messageChainOf( PlainText("/main"), PlainText("name"), PlainText("aaa"), Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") ) ), JavaSimple() to listOf( messageChainOf(PlainText("/main"), PlainText("aaa"), PlainText("TRUE!")), messageChainOf( PlainText("/main"), PlainText("aaa"), Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") ) ) ).flatMap { (instance, cmds) -> cmds.map { message -> DynamicTest.dynamicTest(instance::class.supertypes.first().classifierAsKClass().simpleName) { runBlocking { instance.withRegistration { assertEquals( true, Testing.withTesting { assertSuccess( CommandManager.executeCommand(sender, message, checkPermission = false) ) } ) } } } } } } } ================================================ FILE: mirai-console/backend/mirai-console/test/command/InstanceTestCommand.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "UNUSED_PARAMETER") package net.mamoe.mirai.console.command import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.Testing import net.mamoe.mirai.console.Testing.withTesting import net.mamoe.mirai.console.command.CommandManager.INSTANCE.getRegisteredCommands import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.CommandManager.INSTANCE.registerCommand import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterAllCommands import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterCommand import net.mamoe.mirai.console.command.descriptor.CommandValueArgumentParser import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.descriptor.buildCommandArgumentContext import net.mamoe.mirai.console.internal.command.CommandManagerImpl import net.mamoe.mirai.console.internal.command.flattenCommandComponents import net.mamoe.mirai.console.permission.PermissionService.Companion.permit import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import net.mamoe.mirai.message.data.* import java.time.* import java.time.temporal.TemporalAccessor import kotlin.reflect.KClass import kotlin.test.* class TestCompositeCommand : CompositeCommand( owner, "testComposite", "tsC" ) { @SubCommand fun mute(seconds: Int = 60) { Testing.ok(seconds) } @SubCommand fun mute(target: Long, seconds: Int) { Testing.ok(seconds) } } class TestRawCommand : RawCommand( owner, "testRaw" ) { override suspend fun CommandSender.onCommand(args: MessageChain) { Testing.ok(args) } } class TestSimpleCommand : RawCommand(owner, "testSimple", "tsS") { override suspend fun CommandSender.onCommand(args: MessageChain) { Testing.ok(args) } } @Suppress("EnumEntryName") class TestEnumArgCommand : CompositeCommand(owner, "testenum") { enum class TestEnum { V1, V2, V3 } enum class TestCase { A, a } enum class TestCamelCase { A, B, A_B } @SubCommand("tcc") fun CommandSender.testCamelCase(enum: TestCamelCase) { Testing.ok(enum) } @SubCommand("tc") fun CommandSender.testCase(enum: TestCase) { Testing.ok(enum) } @SubCommand fun CommandSender.e1(enum: TestEnum) { Testing.ok(enum) } } class TestTemporalArgCommand : CompositeCommand(owner, "testtemporal") { @SubCommand fun CommandSender.instant(temporal: Instant) { Testing.ok(temporal) } @SubCommand fun CommandSender.year(temporal: Year) { Testing.ok(temporal) } @SubCommand fun CommandSender.yearmonth(temporal: YearMonth) { Testing.ok(temporal) } @SubCommand fun CommandSender.localdate(temporal: LocalDate) { Testing.ok(temporal) } @SubCommand fun CommandSender.localtime(temporal: LocalTime) { Testing.ok(temporal) } @SubCommand fun CommandSender.localdatetime(temporal: LocalDateTime) { Testing.ok(temporal) } @SubCommand fun CommandSender.offsettime(temporal: OffsetTime) { Testing.ok(temporal) } @SubCommand fun CommandSender.offsetdatetime(temporal: OffsetDateTime) { Testing.ok(temporal) } @SubCommand fun CommandSender.zoneddatetime(temporal: ZonedDateTime) { Testing.ok(temporal) } @SubCommand fun CommandSender.monthday(temporal: MonthDay) { Testing.ok(temporal) } @SubCommand fun CommandSender.zoneoffset(temporal: ZoneOffset) { Testing.ok(temporal) } } private val sender get() = ConsoleCommandSender private val owner get() = ConsoleCommandOwner @OptIn(ExperimentalCommandDescriptors::class) internal class InstanceTestCommand : AbstractConsoleInstanceTest() { private val manager by lazy { MiraiConsoleImplementation.getBridge().commandManager as CommandManagerImpl } private val simpleCommand by lazy { TestSimpleCommand() } private val rawCommand by lazy { TestRawCommand() } private val compositeCommand by lazy { TestCompositeCommand() } @BeforeTest fun grantPermission() { ConsoleCommandSender.permit(simpleCommand.permission) ConsoleCommandSender.permit(compositeCommand.permission) } @Test fun testRegister() { try { unregisterAllCommands(ConsoleCommandOwner) // builtins unregisterAllCommands(owner) // testing unit unregisterCommand(simpleCommand) assertTrue(compositeCommand.register()) assertFalse(compositeCommand.register()) assertEquals(1, getRegisteredCommands(owner).size) assertEquals(1, manager._registeredCommands.size) assertEquals(2, manager.requiredPrefixCommandMap.size, manager.requiredPrefixCommandMap.entries.joinToString { it.toString() }) } finally { unregisterCommand(compositeCommand) } } @Test fun testSimpleExecute() = runTest { simpleCommand.withRegistration { assertEquals("test", withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, "test")) }.contentToString()) } } @Test fun `test raw command`() = runTest { rawCommand.withRegistration { val result = withTesting<MessageChain> { assertSuccess(rawCommand.execute(sender, PlainText("a1"), PlainText("a2"), PlainText("a3"))) } assertEquals(3, result.size) assertEquals("a1, a2, a3", result.joinToString()) } } @Test fun `test flattenCommandArgs`() { val result = arrayOf("test", image).flattenCommandComponents().toTypedArray() assertEquals("test", result[0].content) assertSame(image, result[1]) assertEquals(2, result.size) } @Test fun `test enum argument`() = runTest { val enum = TestEnumArgCommand() enum.withRegistration { assertEquals(TestEnumArgCommand.TestEnum.V1, withTesting { assertSuccess(enum.execute(sender, PlainText("e1"), PlainText("V1"))) }) assertEquals(TestEnumArgCommand.TestEnum.V2, withTesting { assertSuccess(enum.execute(sender, PlainText("e1"), PlainText("V2"))) }) assertEquals(TestEnumArgCommand.TestEnum.V3, withTesting { assertSuccess(enum.execute(sender, PlainText("e1"), PlainText("V3"))) }) withTesting<Unit> { assertFailure(enum.execute(sender, PlainText("e1"), PlainText("ENUM_NOT_FOUND"))) Testing.ok(Unit) } assertEquals(TestEnumArgCommand.TestEnum.V1, withTesting { assertSuccess(enum.execute(sender, PlainText("e1"), PlainText("v1"))) }) assertEquals(TestEnumArgCommand.TestEnum.V2, withTesting { assertSuccess(enum.execute(sender, PlainText("e1"), PlainText("v2"))) }) assertEquals(TestEnumArgCommand.TestEnum.V3, withTesting { assertSuccess(enum.execute(sender, PlainText("e1"), PlainText("v3"))) }) assertEquals(TestEnumArgCommand.TestCase.A, withTesting { assertSuccess(enum.execute(sender, PlainText("tc"), PlainText("A"))) }) assertEquals(TestEnumArgCommand.TestCase.a, withTesting { assertSuccess(enum.execute(sender, PlainText("tc"), PlainText("a"))) }) withTesting<Unit> { assertFailure(enum.execute(sender, PlainText("tc"), PlainText("ENUM_NOT_FOUND"))) Testing.ok(Unit) } assertEquals(TestEnumArgCommand.TestCamelCase.A, withTesting { assertSuccess(enum.execute(sender, PlainText("tcc"), PlainText("A"))) }) assertEquals(TestEnumArgCommand.TestCamelCase.A, withTesting { assertSuccess(enum.execute(sender, PlainText("tcc"), PlainText("a"))) }) assertEquals(TestEnumArgCommand.TestCamelCase.B, withTesting { assertSuccess(enum.execute(sender, PlainText("tcc"), PlainText("B"))) }) assertEquals(TestEnumArgCommand.TestCamelCase.B, withTesting { assertSuccess(enum.execute(sender, PlainText("tcc"), PlainText("b"))) }) assertEquals(TestEnumArgCommand.TestCamelCase.A_B, withTesting { assertSuccess(enum.execute(sender, PlainText("tcc"), PlainText("A_B"))) }) assertEquals(TestEnumArgCommand.TestCamelCase.A_B, withTesting { assertSuccess(enum.execute(sender, PlainText("tcc"), PlainText("a_b"))) }) assertEquals(TestEnumArgCommand.TestCamelCase.A_B, withTesting { assertSuccess(enum.execute(sender, PlainText("tcc"), PlainText("aB"))) }) withTesting<Unit> { assertFailure(enum.execute(sender, PlainText("tc"), PlainText("ENUM_NOT_FOUND"))) Testing.ok(Unit) } } } @Test fun `test temporal argument`() = runTest { val command = TestTemporalArgCommand() command.withRegistration { val temporal: List<KClass<out TemporalAccessor>> = listOf( Instant::class, Year::class, YearMonth::class, LocalDate::class, LocalTime::class, LocalDateTime::class, OffsetTime::class, OffsetDateTime::class, ZonedDateTime::class, MonthDay::class, ZoneOffset::class ) for (kClass in temporal) { val subCommand = kClass.simpleName!! val implement: TemporalAccessor = withTesting { assertSuccess(execute(sender, PlainText(subCommand), PlainText("now"))) } assertTrue { kClass.isInstance(implement) } assertEquals(implement, withTesting { assertSuccess(execute(sender, PlainText(subCommand), PlainText("$implement"))) }) } } } @Test fun testSimpleArgsSplitting() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "ttt", "tt").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test ttt tt"))) }.joinToString()) } } @Test fun testSimpleArgsEscape() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "esc ape").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test esc\\ ape"))) }.joinToString()) } } @Test fun testSimpleArgsQuote() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "esc ape").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test \"esc ape\""))) }.joinToString()) } } @Test fun testSimpleArgsQuoteReject() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "es\"c", "ape\"").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test es\"c ape\""))) }.joinToString()) } } @Test fun testSimpleArgsQuoteEscape() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "\"esc", "ape\"").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test \\\"esc ape\""))) }.joinToString()) } } @Test fun testSimpleArgsMultipleQuotes() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "esc ape", "1 2").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test \"esc ape\" \"1 2\""))) }.joinToString()) } } @Test fun testSimpleArgsMisplacedQuote() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "esc ape", "1\"", "\"2").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test \"esc ape\" 1\" \"2 "))) }.joinToString()) } } @Test fun testSimpleArgsQuoteSpaceEscape() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test \"esc", "ape\"").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test\\ \"esc ape\""))) }.joinToString()) } } @Test fun testSimpleArgsStopParse() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "esc ape ").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test -- esc ape "))) }.joinToString()) } } @Test fun testSimpleArgsStopParse2() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "esc ape test\\12\"\"3").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test -- esc ape test\\12\"\"3"))) }.joinToString()) } } @Test fun testSimpleArgsStopParseReject() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test--", "esc", "ape").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test-- esc ape "))) }.joinToString()) } } @Test fun testSimpleArgsStopParseEscape() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "--", "esc", "ape").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test \\-- esc ape"))) }.joinToString()) } } @Test fun testSimpleArgsStopParseEscape2() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", " --", "esc", "ape").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test \\ -- esc ape"))) }.joinToString()) } } @Test fun testSimpleArgsStopParseQuote() = runTest { simpleCommand.withRegistration { assertEquals(arrayOf("test", "--", "esc", "ape").joinToString(), withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, PlainText("test \"--\" esc ape"))) }.joinToString()) } } val image = Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") @Test fun `PlainText and Image args splitting`() = runTest { simpleCommand.withRegistration { val result = withTesting<MessageChain> { assertSuccess(simpleCommand.execute(sender, buildMessageChain { +"test" +image +"tt" })) } assertEquals<Any>(arrayOf("test", image, "tt").joinToString(), result.toTypedArray().joinToString()) assertSame(image, result[1]) } } @Test fun `test throw Exception`() { runBlocking { assertTrue(sender.executeCommand("").isFailure()) } } @Test fun `executing command by string command`() = runTest { compositeCommand.withRegistration { val result = withTesting<Int> { assertSuccess(sender.executeCommand("/testComposite mute 1")) } assertEquals(1, result) } } @Test fun `composite command descriptors`() { val overloads = compositeCommand.overloads assertEquals("CommandSignature(<mute>, seconds: Int = ...)", overloads[0].toString()) assertEquals("CommandSignature(<mute>, target: Long, seconds: Int)", overloads[1].toString()) } @Test fun `composite command executing`() = runTest { compositeCommand.withRegistration { assertEquals(1, withTesting { assertSuccess(compositeCommand.execute(sender, "mute 1")) }) } } @Test fun `test first param command sender`() = runTest { object : CompositeCommand(owner, "cmd") { @SubCommand fun handle(sender: CommandSender, arg: String) { Testing.ok(arg) } }.withRegistration { assertEquals("test", withTesting { assertSuccess(execute(sender, "handle test")) }) } object : SimpleCommand(owner, "cmd") { @Handler fun handle(sender: CommandSender, arg: String) { Testing.ok(arg) } }.withRegistration { assertEquals("hello", withTesting { assertSuccess(execute(sender, "hello")) }) } object : SimpleCommand(owner, "cmd") { @Handler fun handle(arg: String, sender: CommandSender) { Testing.ok(arg) } }.withRegistration { assertFailure(execute(sender, "hello")) } } @Test fun `composite sub command resolution conflict`() { runBlocking { val composite = object : CompositeCommand( owner, "tr" ) { @Suppress("UNUSED_PARAMETER") @SubCommand fun mute(seconds: Int) { Testing.ok(1) } @Suppress("UNUSED_PARAMETER") @SubCommand fun mute(seconds: Int, arg2: Int = 1) { Testing.ok(2) } } registerCommand(composite) println(composite.overloads.joinToString()) composite.withRegistration { assertEquals( 1, withTesting { assertSuccess( composite.execute( sender, "mute 123" ) ) }) // one arg, resolves to mute(Int) assertEquals( 2, withTesting { assertSuccess( composite.execute( sender, "mute 123 1" ) ) }) // two arg, resolved to mute(Int, Int) } } } @Test fun `composite sub command parsing`() { runBlocking { class MyClass( val value: Int, ) val composite = object : CompositeCommand( owner, "test22", overrideContext = buildCommandArgumentContext { add(object : CommandValueArgumentParser<MyClass> { override fun parse(raw: String, sender: CommandSender): MyClass { return MyClass(raw.toInt()) } override fun parse(raw: MessageContent, sender: CommandSender): MyClass { if (raw is PlainText) return parse(raw.content, sender) assertSame(image, raw) return MyClass(2) } }) } ) { @SubCommand fun mute(seconds: MyClass) { Testing.ok(seconds) } } composite.withRegistration { assertEquals(333, withTesting<MyClass> { assertSuccess(execute(sender, "mute 333")) }.value) assertEquals(2, withTesting<MyClass> { assertSuccess( execute(sender, buildMessageChain { +"mute" +image }) ) }.value) } } } @Test fun `test simple command`() { runBlocking { val simple = object : SimpleCommand(owner, "test") { @Handler fun onCommand(string: String) { Testing.ok(string) } } simple.withRegistration { // assertEquals("xxx", withTesting { simple.execute(sender, "xxx") }) assertEquals("xxx", withTesting { assertSuccess(sender.executeCommand("/test xxx")) }) } } } @Test fun `test optional argument command`() { runBlocking { val optionCommand = object : CompositeCommand( owner, "testOptional" ) { @SubCommand fun optional(arg1: String, arg2: String = "Here is optional", arg3: String? = null) { println(arg1) println(arg2) println(arg3) // println(arg3) Testing.ok(Unit) } } optionCommand.withRegistration { withTesting<Unit> { assertSuccess(sender.executeCommand("/testOptional optional 1")) } } } } @Test fun `test vararg`() { runBlocking { val optionCommand = object : CompositeCommand( owner, "test" ) { @SubCommand fun vararg(arg1: Int, vararg x: String) { assertEquals(1, arg1) Testing.ok(x) } @SubCommand fun enum(arg1: Int, vararg y: TestEnumArgCommand.TestEnum) { assertEquals(1, arg1) Testing.ok(y) } @SubCommand fun long(arg1: String, vararg z: Long) { assertEquals("arg1", arg1) Testing.ok(z) } @SubCommand fun int(arg1: String, vararg z: Int) { assertEquals("arg1", arg1) Testing.ok(z) } @SubCommand fun byte(arg1: String, vararg z: Byte) { assertEquals("arg1", arg1) Testing.ok(z) } @SubCommand fun short(arg1: String, vararg z: Short) { assertEquals("arg1", arg1) Testing.ok(z) } @SubCommand fun float(arg1: String, vararg z: Float) { assertEquals("arg1", arg1) Testing.ok(z) } @SubCommand fun double(arg1: String, vararg z: Double) { assertEquals("arg1", arg1) Testing.ok(z) } @SubCommand fun char(arg1: String, vararg z: Char) { assertEquals("arg1", arg1) Testing.ok(z) } } optionCommand.withRegistration { // Array<String> assertContentEquals( emptyArray<String>(), withTesting { assertSuccess(sender.executeCommand("/test vararg 1")) } ) assertContentEquals( arrayOf("s"), withTesting<Array<String>> { assertSuccess(sender.executeCommand("/test vararg 1 s")) } ) assertContentEquals( arrayOf("s", "s", "s"), withTesting { assertSuccess(sender.executeCommand("/test vararg 1 s s s")) } ) // Array<TestEnum> assertContentEquals( emptyArray<TestEnumArgCommand.TestEnum>(), withTesting { assertSuccess(sender.executeCommand("/test enum 1")) } ) assertContentEquals( arrayOf(TestEnumArgCommand.TestEnum.V1), withTesting { assertSuccess(sender.executeCommand("/test enum 1 ${TestEnumArgCommand.TestEnum.V1}")) } ) assertContentEquals( TestEnumArgCommand.TestEnum.values(), withTesting { assertSuccess(sender.executeCommand("/test enum 1 ${TestEnumArgCommand.TestEnum.values().joinToString(" ")}")) } ) // LongArray assertContentEquals( longArrayOf(), withTesting { assertSuccess(sender.executeCommand("/test long arg1")) } ) assertContentEquals( longArrayOf(1), withTesting { assertSuccess(sender.executeCommand("/test long arg1 1")) } ) assertContentEquals( longArrayOf(1, 2, 3), withTesting { assertSuccess(sender.executeCommand("/test long arg1 1 2 3")) } ) // IntArray assertContentEquals( intArrayOf(), withTesting { assertSuccess(sender.executeCommand("/test int arg1")) } ) assertContentEquals( intArrayOf(1), withTesting { assertSuccess(sender.executeCommand("/test int arg1 1")) } ) assertContentEquals( intArrayOf(1, 2, 3), withTesting { assertSuccess(sender.executeCommand("/test int arg1 1 2 3")) } ) // ByteArray assertContentEquals( byteArrayOf(), withTesting { assertSuccess(sender.executeCommand("/test byte arg1")) } ) assertContentEquals( byteArrayOf(1), withTesting { assertSuccess(sender.executeCommand("/test byte arg1 1")) } ) assertContentEquals( byteArrayOf(1, 2, 3), withTesting { assertSuccess(sender.executeCommand("/test byte arg1 1 2 3")) } ) // ShortArray assertContentEquals( shortArrayOf(), withTesting { assertSuccess(sender.executeCommand("/test short arg1")) } ) assertContentEquals( shortArrayOf(1), withTesting { assertSuccess(sender.executeCommand("/test short arg1 1")) } ) assertContentEquals( shortArrayOf(1, 2, 3), withTesting { assertSuccess(sender.executeCommand("/test short arg1 1 2 3")) } ) // FloatArray assertContentEquals( floatArrayOf(), withTesting { assertSuccess(sender.executeCommand("/test float arg1")) } ) assertContentEquals( floatArrayOf(1.0F), withTesting { assertSuccess(sender.executeCommand("/test float arg1 1")) } ) assertContentEquals( floatArrayOf(1.0F, 1.5F, 2.0F), withTesting { assertSuccess(sender.executeCommand("/test float arg1 1 1.5 2")) } ) // DoubleArray assertContentEquals( doubleArrayOf(), withTesting { assertSuccess(sender.executeCommand("/test double arg1")) } ) assertContentEquals( doubleArrayOf(1.0), withTesting { assertSuccess(sender.executeCommand("/test double arg1 1")) } ) assertContentEquals( doubleArrayOf(1.0, 1.5, 2.0), withTesting { assertSuccess(sender.executeCommand("/test double arg1 1 1.5 2")) } ) } } } } @OptIn(ExperimentalCommandDescriptors::class) internal fun assertSuccess(result: CommandExecuteResult) { if (result.isFailure()) { throw result.exception ?: AssertionError(result.toString()) } } @OptIn(ExperimentalCommandDescriptors::class) internal fun assertFailure(result: CommandExecuteResult) { if (!result.isFailure()) { throw AssertionError("$result not a failure") } } ================================================ FILE: mirai-console/backend/mirai-console/test/command/JSimpleTest.java ================================================ /* * 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 command; import net.mamoe.mirai.console.command.java.JSimpleCommand; import net.mamoe.mirai.console.plugin.jvm.JavaPlugin; import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription; public class JSimpleTest { @SuppressWarnings("PluginMainServiceNotConfiguredJava") private static class Main extends JavaPlugin { public Main(JvmPluginDescription description) { super(description); } } static class T extends JSimpleCommand { public T() { super(new Main(null), "name"); } } } ================================================ FILE: mirai-console/backend/mirai-console/test/command/commanTestingUtil.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.command import net.mamoe.mirai.console.command.CommandManager.INSTANCE.register import net.mamoe.mirai.console.command.CommandManager.INSTANCE.unregisterCommand inline fun <T : Command, R> T.withRegistration(block: T.() -> R): R { this.register() try { return block() } finally { unregisterCommand(this) } } ================================================ FILE: mirai-console/backend/mirai-console/test/configuration/AutoLoginTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.configuration import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.internal.data.builtins.AutoLoginConfig import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import net.mamoe.mirai.console.testFramework.MockConsoleImplementation import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.globalEventChannel import net.mamoe.mirai.utils.BotConfiguration import org.junit.jupiter.api.Disabled import kotlin.test.Test import kotlin.test.assertEquals class AutoLoginTest : AbstractConsoleInstanceTest() { @Disabled // no mock login @Test fun testHeartbeatStrategy() { stopConsole() // stop previous console val console = MockConsoleImplementation() val config = AutoLoginConfig() config.accounts.clear() config.accounts.add( AutoLoginConfig.Account( "111", AutoLoginConfig.Account.Password(AutoLoginConfig.Account.PasswordKind.PLAIN, "pwd"), configuration = mapOf(AutoLoginConfig.Account.ConfigurationKey.heartbeatStrategy to "REGISTER") ) ) console.consoleDataScope.addAndReloadConfig(config) console.globalEventChannel().subscribeAlways<BotOnlineEvent> { assertEquals(BotConfiguration.HeartbeatStrategy.REGISTER, this.bot.configuration.heartbeatStrategy) } console.start() } } ================================================ FILE: mirai-console/backend/mirai-console/test/data/JAutoSavePluginDataTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.data import net.mamoe.mirai.console.data.java.JavaAutoSavePluginData import net.mamoe.mirai.console.plugin.jvm.reloadPluginData import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import net.mamoe.mirai.console.util.JavaFriendlyApi import kotlin.test.Test import kotlin.test.assertEquals class JAutoSavePluginDataTest : AbstractConsoleInstanceTest() { @OptIn(JavaFriendlyApi::class) class MyData : JavaAutoSavePluginData("testSaveName") { val strMember: Value<String> = value("strMember", "str") val intMember: Value<Int> = value("intMember", 1) val typed: Value<List<String>> = typedValue( "typed", createKType( List::class.java, false, createKType(String::class.java, false) ), listOf("aa", "bb") ) } @Test fun `can reload`() { val instance = MyData() mockPlugin.reloadPluginData(instance) assertEquals("str", instance.strMember.value) assertEquals(Integer.valueOf(1), instance.intMember.value) assertEquals(listOf("aa", "bb"), instance.typed.value) assertEquals( """ strMember: str intMember: 1 typed: - aa - bb """.trimIndent(), mockPlugin.dataFolder.resolve("testSaveName.yml").readText() ) } } ================================================ FILE: mirai-console/backend/mirai-console/test/data/JavaPluginDescriptionTests.kt ================================================ /* * 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.console.data import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest @Suppress("unused") class JavaPluginDescriptionTests : AbstractConsoleInstanceTest() { // @Test // fun testSimpleValue() { // class MyJavaData : JAutoSavePluginData("data") { // val list: Value<String> = value("str") // } // // val d = MyJavaData() // mockPlugin.reloadPluginData(d) // assertEquals("str", d.list.value) // } // // @Test // fun testValueSet() { // class MyJavaData : JAutoSavePluginData("data") { // val list: Value<Set<String>> = typedValue(createKType(MutableSet::class.java, createKType(String::class.java))) // } // // mockPlugin.reloadPluginData(MyJavaData()) // } } ================================================ FILE: mirai-console/backend/mirai-console/test/data/MultiFilePluginDataStorageImplTests.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonClassDiscriminator import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import org.junit.jupiter.api.Test import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.test.assertEquals internal class MultiFilePluginDataStorageImplTests : AbstractConsoleInstanceTest() { @TempDir internal lateinit var storePath: Path @Serializable @JsonClassDiscriminator("base_type") internal sealed class Base // not using interface, see https://github.com/Kotlin/kotlinx.serialization/issues/2181 @Serializable @SerialName("DerivedA") internal data class DerivedA(val valueA: Double) : Base() @Serializable @SerialName("DerivedB") internal data class DerivedB(val valueB: String) : Base() @Serializable @SerialName("DerivedC") internal object DerivedC : Base() { @Suppress("unused") const val valueC: Int = 42 } private class YamlPluginData : AutoSavePluginData("test_yaml") { var int by value(1) val map: MutableMap<String, String> by value() val map2: MutableMap<String, MutableMap<String, String>> by value() companion object { val string = """ int: 2 map: key1: value1 key2: value2 map2: key1: key1: value1 key2: value2 key2: key1: value1 key2: value2 """.trimIndent() } } private class JsonPluginData : AutoSavePluginData("test_json") { override val saveType = PluginData.SaveType.JSON val baseMap: MutableMap<String, Base> by value() companion object { val string = """ { "baseMap": { "A": { "base_type": "DerivedA", "valueA": 11.4514 }, "B": { "base_type": "DerivedB", "valueB": "mamoe.mirai" }, "C": { "base_type": "DerivedC" } } } """.trimIndent() } } private val dataStorage by lazy { MultiFilePluginDataStorageImpl(storePath) } @Test fun testYamlLoad() { val data = YamlPluginData() dataStorage.load(mockPlugin, data) dataStorage.getPluginDataFileInternal(mockPlugin, data).writeText(YamlPluginData.string) dataStorage.load(mockPlugin, data) assertEquals(2, data.int) assertEquals(mapOf("key1" to "value1", "key2" to "value2"), data.map) assertEquals( mapOf( "key1" to mapOf("key1" to "value1", "key2" to "value2"), "key2" to mapOf("key1" to "value1", "key2" to "value2") ), data.map2 ) } @Test fun testYamlStore() { val data = YamlPluginData() dataStorage.load(mockPlugin, data) data.int = 2 data.map["key1"] = "value1" data.map["key2"] = "value2" data.map2["key1"] = mutableMapOf("key1" to "value1", "key2" to "value2") data.map2["key2"] = mutableMapOf("key1" to "value1", "key2" to "value2") dataStorage.store(mockPlugin, data) val file = dataStorage.getPluginDataFileInternal(mockPlugin, data) assertEquals(YamlPluginData.string, file.readText()) } @Test fun testJsonLoad() { val data = JsonPluginData() dataStorage.load(mockPlugin, data) dataStorage.getPluginDataFileInternal(mockPlugin, data).writeText(JsonPluginData.string) dataStorage.load(mockPlugin, data) assertEquals( mapOf( "A" to DerivedA(11.4514), "B" to DerivedB("mamoe.mirai"), "C" to DerivedC ), data.baseMap ) } @Test fun testJsonStore() { val data = JsonPluginData() dataStorage.load(mockPlugin, data) data.baseMap["A"] = DerivedA(11.4514) data.baseMap["B"] = DerivedB("mamoe.mirai") data.baseMap["C"] = DerivedC dataStorage.store(mockPlugin, data) val file = dataStorage.getPluginDataFileInternal(mockPlugin, data) assertEquals(JsonPluginData.string, file.readText()) } } ================================================ FILE: mirai-console/backend/mirai-console/test/data/PluginDataTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.data import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.Json import kotlinx.serialization.modules.SerializersModule import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.utils.mapPrimitive import org.junit.jupiter.api.io.TempDir import java.nio.file.Path import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertSame internal class PluginDataTest : AbstractConsoleInstanceTest() { @TempDir lateinit var tempDir: Path class MyPluginData : AutoSavePluginData("test") { var int by value(1) val map: MutableMap<String, String> by value() val map2: MutableMap<String, MutableMap<String, String>> by value() } @Suppress("unused") private val jsonPrettyPrint = Json { prettyPrint = true encodeDefaults = true } private val json = Json { encodeDefaults = true } @Test fun testStringify() { val data = MyPluginData() var string = json.encodeToString(data.updaterSerializer, Unit) assertEquals("""{"int":1,"map":{},"map2":{}}""", string) data.int = 2 string = json.encodeToString(data.updaterSerializer, Unit) assertEquals("""{"int":2,"map":{},"map2":{}}""", string) } @Test fun testParseUpdate() { val data = MyPluginData() assertEquals(1, data.int) json.decodeFromString( data.updaterSerializer, """ {"int":3,"map":{},"map2":{}} """.trimIndent() ) assertEquals(3, data.int) } @Test fun testNestedParseUpdate() { val data = MyPluginData() fun delegation() = data.map val refBefore = data.map fun reference() = refBefore assertEquals(mutableMapOf(), delegation()) // delegation json.decodeFromString( data.updaterSerializer, """ {"int":1,"map":{"t":"test"},"map2":{}} """.trimIndent() ) assertEquals(mapOf("t" to "test").toString(), delegation().toString()) assertEquals(mapOf("t" to "test").toString(), reference().toString()) assertSame(reference(), delegation()) // check shadowing } @Test fun testDeepNestedParseUpdate() { val data = MyPluginData() fun delegation() = data.map2 val refBefore = data.map2 fun reference() = refBefore assertEquals(mutableMapOf(), delegation()) // delegation json.decodeFromString( data.updaterSerializer, """ {"int":1,"map":{},"map2":{"t":{"f":"test"}}} """.trimIndent() ) assertEquals(mapOf("t" to mapOf("f" to "test")).toString(), delegation().toString()) assertEquals(mapOf("t" to mapOf("f" to "test")).toString(), reference().toString()) assertSame(reference(), delegation()) // check shadowing } @Test fun testDeepNestedTrackingParseUpdate() { val data = MyPluginData() data.map2["t"] = mutableMapOf() fun delegation() = data.map2["t"]!! val refBefore = data.map2["t"]!! fun reference() = refBefore assertEquals(mutableMapOf(), delegation()) // delegation json.decodeFromString( data.updaterSerializer, """ {"int":1,"map":{},"map2":{"t":{"f":"test"}}} """.trimIndent() ) assertEquals(mapOf("f" to "test").toString(), delegation().toString()) assertEquals(mapOf("f" to "test").toString(), reference().toString()) assertSame(reference(), delegation()) // check shadowing } class SupportsMessageChain : AutoSavePluginData("test") { val chain: MessageChain by value(messageChainOf(PlainText("str"))) } @Test fun `supports message chain`() { assertEquals( """ chain: - type: PlainText value: content: str """.trimIndent(), serializePluginData(SupportsMessageChain()) ) serializeAndRereadPluginData(SupportsMessageChain()) } class SupportsPolymorphicCorrectly : AutoSavePluginData("test") { val singleMessage: SingleMessage by value(PlainText("str")) val plainText: PlainText by value(PlainText("str")) } @Test fun `supports polymorphic correctly`() { assertEquals( """ singleMessage: type: PlainText value: content: str plainText: content: str """.trimIndent(), serializePluginData(SupportsPolymorphicCorrectly()) ) serializeAndRereadPluginData(SupportsPolymorphicCorrectly()) } class SupportsSerializersModule : AutoSavePluginData("test") { override val serializersModule: SerializersModule = SerializersModule { contextual(MyClass::class, myClassSerializer) } val v: MyClass by value(MyClass("test")) data class MyClass( val str: String ) companion object { private val myClassSerializer = String.serializer().mapPrimitive("MyClass", { MyClass(it) }, { it.str } ) } } @Test fun `supports serializers module`() { assertEquals( """ v: test """.trimIndent(), serializePluginData(SupportsSerializersModule()) ) serializeAndRereadPluginData(SupportsSerializersModule()) } private fun serializePluginData(data: PluginData): String { val storage = MultiFilePluginDataStorageImpl(tempDir) storage.store(mockPlugin, data) return storage.getPluginDataFileInternal(mockPlugin, data).readText() } private fun serializeAndRereadPluginData(data: PluginData) { val storage = MultiFilePluginDataStorageImpl(tempDir) storage.store(mockPlugin, data) val serialized = storage.getPluginDataFileInternal(mockPlugin, data).readText() storage.load(mockPlugin, data) assertEquals(serialized, storage.getPluginDataFileInternal(mockPlugin, data).readText()) } class DefaultValueForArray : AutoSavePluginData("save") { val byteArray: ByteArray by value() val booleanArray: BooleanArray by value() var shortArray: ShortArray by value() val intArray: IntArray by value() val longArray: LongArray by value() val floatArray: FloatArray by value() val doubleArray: DoubleArray by value() val charArray: CharArray by value() var stringArray: Array<String> by value() var longObjectArray: Array<Long> by value() } @Test fun `default value for array`() { val instance = DefaultValueForArray() assertEquals( """ byteArray: [] booleanArray: [] shortArray: [] intArray: [] longArray: [] floatArray: [] doubleArray: [] charArray: [] stringArray: [] longObjectArray: [] """.trimIndent(), serializePluginData(instance) ) instance.shortArray = shortArrayOf(1) instance.stringArray = arrayOf("1234") println(instance.findBackingFieldValueNode(instance::longObjectArray)) instance.longObjectArray = arrayOf(1234) assertEquals( """ byteArray: [] booleanArray: [] shortArray: - 1 intArray: [] longArray: [] floatArray: [] doubleArray: [] charArray: [] stringArray: - 1234 longObjectArray: - 1234 """.trimIndent(), serializePluginData(instance) ) serializeAndRereadPluginData(instance) } class DefaultValueForCollections : AutoSavePluginData("save") { val map: Map<String, String> by value() val mapAny: Map<String, Any> by value() val hashMapAny: HashMap<String, Any> by value() val linkedHashMapAny: LinkedHashMap<String, Any> by value() val list: List<String> by value() val listAny: List<Any> by value() val set: Set<String> by value() val setAny: Set<Any> by value() } @Test fun `default value for collections`() { val instance = DefaultValueForCollections() assertEquals( """ map: {} mapAny: {} hashMapAny: {} linkedHashMapAny: {} list: [] listAny: [] set: [] setAny: [] """.trimIndent(), serializePluginData(instance) ) serializeAndRereadPluginData(instance) } } ================================================ FILE: mirai-console/backend/mirai-console/test/data/PluginMovingTests.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.data import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.data.mkdir import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.load import net.mamoe.mirai.console.plugin.id import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.console.plugin.name import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import kotlin.test.Test class PluginMovingTests : AbstractConsoleInstanceTest() { private val mockPluginWithName by lazy { object : KotlinPlugin(JvmPluginDescription("org.test1.test1", "1.0.0", "test1")) {} } private val mockPluginWithName2 by lazy { object : KotlinPlugin(JvmPluginDescription("org.test2.test2", "1.0.0", "test2")) {} } private val mockPluginWithName3 by lazy { object : KotlinPlugin(JvmPluginDescription("org.test2.test3", "1.0.0", "test3")) {} } private fun mkdir(abstractPath: String) = PluginManager.pluginsDataPath.resolve(abstractPath).mkdir() //@Disabled // disabled since test framework fails @Test fun movingPluginPath() { // Normal move mkdir(mockPlugin.name) assert(!MiraiConsole.job.isCancelled) // when id == name mkdir(mockPluginWithName.name) mockPluginWithName.load() assert(!MiraiConsole.job.isCancelled) // move to empty folder mkdir(mockPluginWithName2.name) mkdir(mockPluginWithName2.id) mockPluginWithName2.load() assert(!MiraiConsole.job.isCancelled) // fail move mkdir(mockPluginWithName3.name) mkdir(mockPluginWithName3.id) PluginManager.pluginsDataPath.resolve(mockPluginWithName3.id).toFile().resolve("x").createNewFile() mockPluginWithName3.load() assert(MiraiConsole.job.isCancelled) } } ================================================ FILE: mirai-console/backend/mirai-console/test/extension/GlobalComponentStorageTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.extension import net.mamoe.mirai.console.internal.extension.GlobalComponentStorage import net.mamoe.mirai.console.internal.extension.GlobalComponentStorageImpl import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import kotlin.test.Test import kotlin.test.assertEquals internal class GlobalComponentStorageTest : AbstractConsoleInstanceTest() { class MyInstance class MyExtension(override val instance: MyInstance, override val priority: Int) : InstanceExtension<MyInstance> { companion object EP : AbstractInstanceExtensionPoint<MyExtension, MyInstance>(MyExtension::class) } @Test fun `can register`() { GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) GlobalComponentStorage.getExtensionsList(MyExtension).run { assertEquals(2, size) } } @Test fun `can contribute`() { GlobalComponentStorage.contribute(MyExtension, mockPlugin, MyExtension(MyInstance(), 1)) GlobalComponentStorage.contribute(MyExtension, mockPlugin, MyExtension(MyInstance(), 2)) GlobalComponentStorage.getExtensionsList(MyExtension).run { assertEquals(2, size) } } @Test fun `can sort by priority`() { GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) GlobalComponentStorage.getExtensionsList(MyExtension).run { assertEquals(2, size) assertEquals(2, first().extension.priority) assertEquals(1, get(1).extension.priority) } } @Test fun `can sort by priority 2`() { GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) GlobalComponentStorage.getExtensionsList(MyExtension).run { assertEquals(2, size) assertEquals(2, first().extension.priority) assertEquals(1, get(1).extension.priority) } } @Test fun `can fold`() { GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 2)) GlobalComponentStorage.contributeConsole(MyExtension, MyExtension(MyInstance(), 1)) val list = GlobalComponentStorage.foldExtensions(MyExtension, listOf<MyExtension>()) { acc, extension -> acc + extension } assertEquals(2, list.size) assertEquals(2, list.first().priority) assertEquals(1, list[1].priority) } } private fun <T : Extension> GlobalComponentStorageImpl.getExtensionsList(ep: ExtensionPoint<T>): List<ExtensionRegistry<T>> { return getExtensions(ep).toList() } ================================================ FILE: mirai-console/backend/mirai-console/test/logging/TestAbstractLoggerController.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.logging import net.mamoe.mirai.utils.SimpleLogger import org.junit.jupiter.api.Test import kotlin.test.assertEquals internal class TestAbstractLoggerController { private class TestController( override val isLoggerControlStateSupported: Boolean, ) : AbstractLoggerController() { var callcount = 0 override fun getPriority(identity: String?): LogPriority { return LogPriority.ALL } override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean { if (priority == SimpleLogger.LogPriority.INFO) { callcount++ } return super.shouldLog(identity, priority) } } @Test fun `test logger control state caching`() { val controller = TestController(true) assertEquals(0, controller.callcount) val state = controller.getLoggerControlState("Test") assertEquals(0, controller.callcount) // lazy load state.shouldLog(SimpleLogger.LogPriority.DEBUG) assertEquals(1, controller.callcount) state.shouldLog(SimpleLogger.LogPriority.INFO) assertEquals(1, controller.callcount) state.shouldLog(SimpleLogger.LogPriority.DEBUG) state.shouldLog(SimpleLogger.LogPriority.INFO) state.shouldLog(SimpleLogger.LogPriority.INFO) assertEquals(1, controller.callcount) } @Test fun `test keep binary compatibility`() { val controller = TestController(false) assertEquals(0, controller.callcount) val state = controller.getLoggerControlState("Test") assertEquals(0, controller.callcount) repeat(50) { count -> state.shouldLog(SimpleLogger.LogPriority.INFO) assertEquals(count + 1, controller.callcount) } } } ================================================ FILE: mirai-console/backend/mirai-console/test/logging/TestAbstractLoggerController_PathBased.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.logging import kotlin.test.Test @Suppress("ClassName") internal class TestAbstractLoggerController_PathBased { @Test fun `test AbstractLoggerController$PathBased`() { val config = mapOf( "test" to "ALL", "test.test" to "VERBOSE", "test.test.test" to "NONE", ).mapValues { AbstractLoggerController.LogPriority.valueOf(it.value) } val c = object : AbstractLoggerController.PathBased() { override val defaultPriority: LogPriority get() = LogPriority.NONE override fun findPriority(identity: String?): LogPriority? { if (identity == null) return defaultPriority return config[identity] } fun priority(i: String?): LogPriority = getPriority(i) } fun assertSame(path: String?, p: String) { kotlin.test.assertSame(c.priority(path), AbstractLoggerController.LogPriority.valueOf(p)) } assertSame("test.test.test", "NONE") assertSame("test.test.test.more.test", "NONE") assertSame("test.test.t1", "VERBOSE") assertSame("test.test.t15w", "VERBOSE") assertSame("test.test", "VERBOSE") assertSame("test", "ALL") assertSame("test.tes1ww", "ALL") assertSame("test.asldjawe.awej2oi3", "ALL") assertSame("AWawex", "NONE") assertSame("awpejaszx.aljewkz", "NONE") assertSame("test0.awekjo23xxxxx", "NONE") } } ================================================ FILE: mirai-console/backend/mirai-console/test/permission/PermissionServiceTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.permission import net.mamoe.mirai.console.internal.permission.BuiltInPermissionService import net.mamoe.mirai.console.internal.permission.PermissionImpl import kotlin.test.* internal class PermissionServiceTest { @Test fun `test built in`() { val builtIn: PermissionService<PermissionImpl> = BuiltInPermissionService() assertEquals(PermissionId.parseFromString("*:*"), builtIn.rootPermission.id) val plugin = builtIn.register(PermissionId.parseFromString("plugin:*"), "", builtIn.rootPermission) assertEquals(builtIn[PermissionId.parseFromString("plugin:*")], plugin) val command = builtIn.register(PermissionId.parseFromString("plugin:command"), "", plugin) assertEquals(builtIn[PermissionId.parseFromString("plugin:command")], command) val any = AbstractPermitteeId.parseFromString("m12345.*") val member = AbstractPermitteeId.parseFromString("m12345.6789") assertFalse { builtIn.testPermission(any, plugin) } // test permit builtIn.permit(any, plugin) assertTrue { builtIn.testPermission(any, plugin) } assertTrue { builtIn.testPermission(member, plugin) } assertTrue { builtIn.testPermission(any, command) } assertTrue { builtIn.testPermission(member, command) } // test cancel fail (by parent) val cause1 = assertFails { builtIn.cancel(member, command, false) } assertTrue { cause1 is UnsupportedOperationException } assertEquals(""" m12345.6789 的 plugin:command 权限来自 m12345.* plugin:* Mirai Console 内置权限系统目前不支持单独禁用继承得到的权限. 可取消继承来源再为其分别分配. """.trimIndent(), cause1.message) // test recursive cancel builtIn.cancel(any, builtIn.rootPermission, true) assertFalse { builtIn.testPermission(any, plugin) } assertFalse { builtIn.testPermission(member, plugin) } assertFalse { builtIn.testPermission(any, command) } assertFalse { builtIn.testPermission(member, command) } // test cancel (no permit) val cause2 = assertFails { builtIn.cancel(member, command, false) } assertTrue { cause2 is UnsupportedOperationException } assertEquals("${member.asString()} 不拥有权限 ${command.id}", cause2.message) // test not recursive cancel builtIn.permit(any, plugin) builtIn.permit(any, command) builtIn.cancel(any, plugin, false) assertFalse { builtIn.testPermission(any, plugin) } assertFalse { builtIn.testPermission(member, plugin) } assertTrue { builtIn.testPermission(any, command) } assertTrue { builtIn.testPermission(member, command) } } } ================================================ FILE: mirai-console/backend/mirai-console/test/permission/PermissionsBasicsTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.permission import kotlin.test.Test import kotlin.test.assertFails internal class PermissionsBasicsTest { @Suppress("ILLEGAL_PERMISSION_NAMESPACE", "ILLEGAL_PERMISSION_NAME") // inspection by mirai-console-intellij @Test fun testInvalidPermissionId() { assertFails { PermissionId("space namespace", "name") } assertFails { PermissionId("namespace", "space name") } // assertFails { PermissionId("", "name") } // assertFails { PermissionId("namespace", "") } assertFails { PermissionId("namespace:name", "name") } assertFails { PermissionId("namespace", "namespace:name") } } } ================================================ FILE: mirai-console/backend/mirai-console/test/plugin/BuiltInJvmPluginLoaderImplTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest internal class BuiltInJvmPluginLoaderImplTest : AbstractConsoleInstanceTest() { } ================================================ FILE: mirai-console/backend/mirai-console/test/plugin/PluginLoadingOrderTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.plugin import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.internal.plugin.PluginInfiniteCircularDependencyReferenceException import net.mamoe.mirai.console.internal.plugin.PluginManagerImpl import net.mamoe.mirai.console.internal.plugin.PluginMissingDependencyException import net.mamoe.mirai.console.internal.plugin.impl import net.mamoe.mirai.console.plugin.description.PluginDescription import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.loader.PluginLoadException import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal class PluginLoadingOrderTest : AbstractConsoleInstanceTest() { private val pm: PluginManagerImpl get() = MiraiConsole.pluginManager.impl private class Reorder<T>(val l: List<T>) { operator fun get(vararg indexes: Int): List<T> { return MutableList(indexes.size) { l[indexes[it]] } } } private val <T> List<T>.reorder: Reorder<T> get() = Reorder(this) @Test fun singlePlugin() { val descriptions = listOf<PluginDescription>( JvmPluginDescription("a.a.a", "1.0.0"), ) assertEquals( descriptions, pm.__sortPluginDescription(descriptions) ) } @Test fun successIfOptional() { val descriptions = listOf<PluginDescription>( JvmPluginDescription("a.a.a", "1.0.0") { dependsOn("a.a.a.opt", isOptional = true) }, JvmPluginDescription("c.c.c", "1.0.0") { dependsOn("c.c.c.opt", isOptional = true) }, JvmPluginDescription("b.b.b", "1.0.0") { dependsOn("b.b.b.opt", isOptional = true) }, ) assertEquals( descriptions, pm.__sortPluginDescription(descriptions) ) } @Test fun failedIfDeathLock() { assertFailsWith<PluginInfiniteCircularDependencyReferenceException> { pm.__sortPluginDescription( listOf( JvmPluginDescription("a.a.a", "1.0.0") { dependsOn("b.b.b") }, JvmPluginDescription("b.b.b", "1.0.0") { dependsOn("a.a.a") }, ) ) }.let { assertEquals("Found circular plugin dependency: a.a.a -> b.b.b -> a.a.a", it.message) } } @Test fun failedIfMissing() { assertFailsWith<PluginMissingDependencyException> { pm.__sortPluginDescription( listOf( JvmPluginDescription("a.a.a", "1.0.0") { dependsOn("b.b.b") } ) ) }.let { assertEquals("Cannot load plugin 'a.a.a', missing dependencies: 'b.b.b'", it.message) } } @Test fun failedIfVersionNotMatch() { assertFailsWith<PluginLoadException> { pm.__sortPluginDescription( listOf( JvmPluginDescription("a.a.a", "1.0.0"), JvmPluginDescription("b.b.b", "1.0.0") { dependsOn("a.a.a", "<0.9.9") }, ) ) }.let { assertEquals( "Plugin 'b.b.b' ('b.b.b') requires 'a.a.a' with version <0.9.9 while the resolved is 1.0.0", it.message ) } assertFailsWith<PluginLoadException> { pm.__sortPluginDescription( listOf( JvmPluginDescription("a.a.a", "1.0.0"), JvmPluginDescription("b.b.b", "1.0.0") { dependsOn("a.a.a", "<0.9.9", isOptional = true) }, ) ) }.let { assertEquals( "Plugin 'b.b.b' ('b.b.b') requires 'a.a.a' with version <0.9.9 while the resolved is 1.0.0", it.message ) } } @Test fun allNonDependencyPlugin() { val descriptions = listOf<PluginDescription>( JvmPluginDescription("a.a.a", "1.0.0"), JvmPluginDescription("a.a.b", "1.0.0"), JvmPluginDescription("a.c.w", "1.0.0"), JvmPluginDescription("a.z.x", "1.0.0"), JvmPluginDescription("a.w.q", "1.0.0"), JvmPluginDescription("w.z.a", "1.0.0"), ) assertEquals( descriptions, pm.__sortPluginDescription(descriptions) ) } @Test fun pluginWithDependencies() { val descriptions = listOf<PluginDescription>( JvmPluginDescription("a.a.a", "1.0.0") { dependsOn("b.b.b") }, JvmPluginDescription("b.b.b", "1.0.0"), ) assertEquals( descriptions.reorder[1, 0], pm.__sortPluginDescription(descriptions) ) } @Test fun pluginWithOptionalDependency() { val desc = listOf<PluginDescription>( JvmPluginDescription("a.a.a.opt", "1.0.0") { dependsOn("a.a.a", isOptional = true) }, JvmPluginDescription("a.a.a", "1.0.0"), JvmPluginDescription("b.b.b", "1.0.0"), JvmPluginDescription("b.b.b.opt", "1.0.0") { dependsOn("b.b.b", isOptional = true) }, JvmPluginDescription("c.c.c.opt", "1.0.0") { dependsOn("a.a.a") }, ) assertEquals( desc.reorder[1, 2, 0, 3, 4], pm.__sortPluginDescription(desc) ) } @Test fun `2nd direct depend`() { val descs = listOf( JvmPluginDescription("c.c.c", "1.0.0") { dependsOn("b.b.b", "1.0.0") }, JvmPluginDescription("a.a.a", "1.0.0"), JvmPluginDescription("b.b.b", "1.0.0") { dependsOn("a.a.a", "1.0.0") }, ) assertEquals( descs.reorder[1, 2, 0], pm.__sortPluginDescription(descs) ) } @Test fun `3nd optional depend`() { val desc = listOf( JvmPluginDescription("b.b.b", "1.0.0") { dependsOn("a.a.a", isOptional = true) }, JvmPluginDescription("a.a.a", "1.0.0"), JvmPluginDescription("d.d.d", "1.0.0") { dependsOn("a.a.a") dependsOn("c.c.c") }, JvmPluginDescription("c.c.c", "1.0.0") { dependsOn("b.b.b", isOptional = true) }, JvmPluginDescription("e.e.e", "1.0.0") { dependsOn("c.c.c", isOptional = true) dependsOn("d.d.d") }, ) assertEquals( desc.reorder[1, 0, 3, 2, 4], pm.__sortPluginDescription(desc) ) } } ================================================ FILE: mirai-console/backend/mirai-console/test/testFramework/AbstractConsoleInstanceTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.testFramework import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.runBlocking import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import org.junit.jupiter.api.AfterEach import kotlin.test.BeforeTest abstract class AbstractConsoleInstanceTest { init { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge.reinit() } val mockPlugin by lazy { mockKotlinPlugin() } private lateinit var implementation: MiraiConsoleImplementation val consoleImplementation: MiraiConsoleImplementation by ::implementation @BeforeTest protected open fun initializeConsole() { this.implementation = MockConsoleImplementation().apply { start() } CommandManager consoleImplementation.jvmPluginLoader.load(mockPlugin) consoleImplementation.jvmPluginLoader.enable(mockPlugin) } @AfterEach protected open fun stopConsole() { if (MiraiConsoleImplementation.instanceInitialized) { try { consoleImplementation.jvmPluginLoader.disable(mockPlugin) } catch (e: Exception) { e.printStackTrace() } try { runBlocking { MiraiConsole.job.cancelAndJoin() } } catch (e: CancellationException) { // ignored } catch (e: Exception) { e.printStackTrace() } finally { MiraiConsoleImplementation.currentBridge = null } } println("=========".repeat(4) + "CONSOLE STOPPED" + "=========".repeat(4)) } companion object { fun mockKotlinPlugin(id: String = "org.test.test"): KotlinPlugin { return object : KotlinPlugin(JvmPluginDescription(id, "1.0.0")) {} } } } ================================================ FILE: mirai-console/backend/mirai-console/test/testFramework/MockConsoleImplementation.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.testFramework import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.internal.data.MultiFilePluginDataStorageImpl import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.PlatformLogger import java.nio.file.Path import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext import kotlin.io.path.createTempDirectory open class MockConsoleImplementation : MiraiConsoleImplementation { final override val rootPath: Path = createTempDirectory() override val frontEndDescription: MiraiConsoleFrontEndDescription get() = object : MiraiConsoleFrontEndDescription { override val name: String get() = "Test" override val vendor: String get() = "Test" override val version: SemVersion get() = SemVersion("1.0.0") } override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }) override val jvmPluginLoader: JvmPluginLoader by lazy { backendAccess.createDefaultJvmPluginLoader(coroutineContext) } override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = object : MiraiConsoleImplementation.ConsoleCommandSenderImpl { override suspend fun sendMessage(message: Message) { println(message) } override suspend fun sendMessage(message: String) { println(message) } } override val commandManager: CommandManager by lazy { backendAccess.createDefaultCommandManager(coroutineContext) } override val dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorageImpl(rootPath.resolve("data")) override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorageImpl(rootPath.resolve("config")) override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorageImpl(rootPath.resolve("data")) override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorageImpl(rootPath.resolve("config")) override val consoleInput: ConsoleInput = object : ConsoleInput { override suspend fun requestInput(hint: String): String { println(hint) return readLine() ?: error("No stdin") } } override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver = LoginSolver.Default!! override fun createLoggerFactory(context: MiraiConsoleImplementation.FrontendLoggingInitContext): MiraiLogger.Factory { return object : MiraiLogger.Factory { override fun create(requester: Class<*>, identity: String?): MiraiLogger { return PlatformLogger(identity) } } } override val consoleDataScope: MiraiConsoleImplementation.ConsoleDataScope by lazy { MiraiConsoleImplementation.ConsoleDataScope.createDefault( coroutineContext, dataStorageForBuiltIns, configStorageForBuiltIns ) } override val coroutineContext: CoroutineContext = CoroutineName("Console Main") + SupervisorJob() + CoroutineExceptionHandler { _, throwable -> throwable.printStackTrace() } } inline fun <R> withConsoleImplementation(crossinline block: MockConsoleImplementation.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } MockConsoleImplementation().run { start() try { return block() } finally { cancel() } } } ================================================ FILE: mirai-console/backend/mirai-console/test/testFramework/test/FrameworkInstanceTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.testFramework.test import net.mamoe.mirai.console.plugin.PluginManager import net.mamoe.mirai.console.testFramework.AbstractConsoleInstanceTest import kotlin.test.Test import kotlin.test.assertEquals class FrameworkInstanceTest : AbstractConsoleInstanceTest() { @Test fun testConsole1() { assertEquals(1, PluginManager.plugins.size) } @Test fun testConsole2() { assertEquals(1, PluginManager.plugins.size) } } ================================================ FILE: mirai-console/backend/mirai-console/test/util/TestCoroutineUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.util //import kotlinx.coroutines.* //import kotlin.test.Test //import java.util.concurrent.atomic.AtomicInteger //import kotlin.coroutines.resume //import kotlin.test.assertEquals //import kotlin.time.milliseconds //import kotlin.time.seconds internal class TestCoroutineUtils { // TODO TestCoroutineUtils disabled manually: on CI real time measurement is not precise causing tests to fail. // @Test // fun `test launchTimedTask 0 time`() = runTest { // val scope = CoroutineScope(SupervisorJob()) // // val result = withTimeoutOrNull(6000) { // suspendCancellableCoroutine<Unit> { cont -> // scope.launchTimedTask(5.seconds.toLongMilliseconds()) { // cont.resume(Unit) // } // } // } // // assertEquals(null, result) // scope.cancel() // } // // @Test // fun `test launchTimedTask finishes 1 time`() = runTest { // val scope = CoroutineScope(SupervisorJob()) // // withTimeout(4000) { // suspendCancellableCoroutine<Unit> { cont -> // val task = scope.launchTimedTask(3.seconds.toLongMilliseconds()) { // cont.resume(Unit) // } // task.setChanged() // } // } // // scope.cancel() // } // // @Test // fun `test launchTimedTask finishes multiple times`() = runTest { // val scope = CoroutineScope(SupervisorJob()) // // val resumedTimes = AtomicInteger(0) // val task = scope.launchTimedTask(3000.milliseconds.toLongMilliseconds()) { // resumedTimes.incrementAndGet() // } // task.setChanged() // launch { // delay(4000) // task.setChanged() // } // // delay(6000) // assertEquals(1, resumedTimes.get()) // delay(15000) // assertEquals(2, resumedTimes.get()) // // scope.cancel() // } // // @Test // fun `test launchTimedTask interval less than delay`() = runTest { // val scope = CoroutineScope(SupervisorJob()) // // withTimeout(5000) { // suspendCancellableCoroutine<Unit> { cont -> // val task = scope.launchTimedTask(1.seconds.toLongMilliseconds()) { // cont.resume(Unit) // } // task.setChanged() // } // } // // scope.cancel() // } } ================================================ FILE: mirai-console/backend/mirai-console/test/util/TestSemVersion.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ /* * @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp> */ package net.mamoe.mirai.console.util import net.mamoe.mirai.console.util.SemVersion.Companion.test import kotlin.test.Test import kotlin.test.assertFails internal class TestSemVersion { @Test internal fun testCompare() { fun String.sem(): SemVersion = SemVersion.invoke(this) assert("1.0".sem() < "1.0.1".sem()) assert("1.0.0".sem() != "1.0".sem()) assert("1.0.0".sem().compareTo("1.0".sem()) == 0) assert("1.1".sem() > "1.0.0".sem()) assert("1.0-M4".sem() < "1.0-M5".sem()) assert("1.0-M5-dev-7".sem() < "1.0-M5-dev-15".sem()) assert("1.0-M5-dev-79".sem() < "1.0-M5-dev-7001".sem()) assert("1.0-M6".sem() > "1.0-M5-dev-15".sem()) assert("1.0-RC".sem() > "1.0-M5-dev-15".sem()) assert("1.0-RC2".sem() > "1.0-RC".sem()) // example on semver // 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0 assert("1.0.0-alpha".sem() < "1.0.0-alpha.1".sem()) assert("1.0.0-alpha.1".sem() < "1.0.0-alpha.beta".sem()) assert("1.0.0-alpha.beta".sem() < "1.0.0-beta".sem()) assert("1.0.0-beta".sem() < "1.0.0-beta.2".sem()) assert("1.0.0-beta.2".sem() < "1.0.0-beta.11".sem()) assert("1.0.0-beta.11".sem() < "1.0.0-rc.1".sem()) assert("1.0.0-rc.1".sem() < "1.0.0".sem()) } @Test internal fun testRequirementCopy() { fun SemVersion.Requirement.check(a: SemVersion.Requirement.() -> SemVersion.Requirement) { assert(a().impl !== this.impl) } SemVersion.parseRangeRequirement("1.0").check { copy() } SemVersion.parseRangeRequirement("1.0").check { copy("2.0") } SemVersion.parseRangeRequirement("1.0").check { copy("1.0") } } @Test internal fun testRequirement() { fun SemVersion.Requirement.assert(version: String): SemVersion.Requirement { assert(test(version)) { version } return this } fun assertInvalid(requirement: String) { assertFails(requirement) { SemVersion.parseRangeRequirement(requirement) } } fun SemVersion.Requirement.assertFalse(version: String): SemVersion.Requirement { assert(!test(version)) { version } return this } SemVersion.parseRangeRequirement("1.0") .assert("1.0").assert("1.0.0") .assertFalse("1.1.0").assertFalse("2.0.0") SemVersion.parseRangeRequirement("1.x") .assert("1.0").assert("1.1") .assert("1.5").assert("1.14514") .assertFalse("2.33") SemVersion.parseRangeRequirement("2.0||1.2.x") SemVersion.parseRangeRequirement("{2.0||1.2.x} && 1.1.0 &&1.2.3") SemVersion.parseRangeRequirement("2.0 || 1.2.x") .assert("2.0").assert("2.0.0") .assertFalse("2.1") .assert("1.2.5").assert("1.2.0").assertFalse("1.2") .assertFalse("1.0.0") SemVersion.parseRangeRequirement("[1.0.0, 19190.0]") .assert("1.0.0").assertFalse("0.1.0") .assert("19190.0").assertFalse("19198.10") SemVersion.parseRangeRequirement("[1.0.0, 2.0.0)") .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") SemVersion.parseRangeRequirement("(2.0.0, 1.0.0]") .assert("1.0.0").assert("1.2.3").assertFalse("2.0.0") SemVersion.parseRangeRequirement("(2.0.0, 1.0.0)") .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") SemVersion.parseRangeRequirement("(1.0.0, 2.0.0)") .assertFalse("1.0.0").assert("1.2.3").assertFalse("2.0.0") SemVersion.parseRangeRequirement(" >= 1.0.0") .assert("1.0.0") .assert("114.514.1919") .assertFalse("0.0.0") .assertFalse("0.98774587") SemVersion.parseRangeRequirement("> 1.0.0") .assertFalse("1.0.0") SemVersion.parseRangeRequirement("!= 1.0.0 && != 2.0.0") .assert("1.2.3").assert("2.1.1") .assertFalse("1.0").assertFalse("1.0.0") .assertFalse("2.0").assertFalse("2.0.0") .assert("2.0.1").assert("1.0.1") SemVersion.parseRangeRequirement("> 1.0.0 || < 0.9.0") .assertFalse("1.0.0") .assert("0.8.0") .assertFalse("0.9.0") SemVersion.parseRangeRequirement("{>= 1.0.0 && <= 1.2.3} || {>= 2.0.0 && <= 2.2.3}") .assertFalse("1.3.0") .assert("1.0.0").assert("1.2.3") .assertFalse("0.9.0") .assert("2.0.0").assert("2.2.3").assertFalse("2.3.4") assertInvalid("WPOXAXW") assertInvalid("1.0.0 || 1.0.0 && 1.0.0") assertInvalid("{") assertInvalid("}") assertInvalid("") assertInvalid("1.2.3 - 3.2.1") assertInvalid("1.5.78 &&") assertInvalid("|| 1.0.0") } private fun String.check() { val sem = SemVersion.invoke(this) assert(this == sem.toString()) { "$this != $sem" } } private fun String.checkInvalid() { kotlin.runCatching { SemVersion.invoke(this) } .onSuccess { assert(false) { "$this not a invalid sem-version" } } } @Test internal fun testSemVersionParsing() { "0.0".check() "1.0.0".check() "1.2.3.4.5.6.7.8".checkInvalid() "5555.0-A".check() "5555.0-A+METADATA".check() "5555.0+METADATA".check() "987.0+wwwxx-wk".check() "NOT.NUMBER".checkInvalid() "0".checkInvalid() "".checkInvalid() "1.".checkInvalid() "0.1-".checkInvalid() "1.9+".checkInvalid() "5.1+68-7".check() "5.1+68-".check() } @Test internal fun testSemVersionOfficial() { """ 1.0-RC 0.0.4 1.2.3 10.20.30 1.1.2-prerelease+meta 1.1.2+meta 1.1.2+meta-valid 1.0.0-alpha 1.0.0-beta 1.0.0-alpha.beta 1.0.0-alpha.beta.1 1.0.0-alpha.1 1.0.0-alpha0.valid 1.0.0-alpha.0valid 1.0.0-alpha-a.b-c-somethinglong+build.1-aef.1-its-okay 1.0.0-rc.1+build.1 2.0.0-rc.1+build.123 1.2.3-beta 10.2.3-DEV-SNAPSHOT 1.2.3-SNAPSHOT-123 1.0.0 2.0.0 1.1.7 2.0.0+build.1848 2.0.1-alpha.1227 1.0.0-alpha+beta 1.2.3----RC-SNAPSHOT.12.9.1--.12+788 1.2.3----R-S.12.9.1--.12+meta 1.2.3----RC-SNAPSHOT.12.9.1--.12 1.0.0+0.build.1-rc.10000aaa-kk-0.1 1.0.0-0A.is.legal """.trimIndent().split('\n').asSequence() .filter { it.isNotBlank() }.map { it.trim() }.forEach { it.check() } """ 1 1.2.3-0123 1.2.3-0123.0123 1.1.2+.123 +invalid -invalid -invalid+invalid -invalid.01 alpha alpha.beta alpha.beta.1 alpha.1 alpha+beta alpha_beta alpha. alpha.. beta 1.0.0-alpha_beta -alpha. 1.0.0-alpha.. 1.0.0-alpha..1 1.0.0-alpha...1 1.0.0-alpha....1 1.0.0-alpha.....1 1.0.0-alpha......1 1.0.0-alpha.......1 01.1.1 1.01.1 1.1.01 1.2.3.DEV 1.2.31.2.3----RC-SNAPSHOT.12.09.1--..12+788 1.2.31.2.3-RC -1.0.3-gamma+b7718 +justmeta 9.8.7+meta+meta 9.8.7-whatever+meta+meta 99999999999999999999999.999999999999999999.99999999999999999----RC-SNAPSHOT.12.09.1--------------------------------..12 """.trimIndent().split('\n').asSequence() .filter { it.isNotBlank() }.map { it.trim() }.forEach { it.checkInvalid() } } } ================================================ FILE: mirai-console/backend/mirai-console/testResources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider ================================================ net.mamoe.mirai.console.internal.logging.externalbind.slf4j.MiraiConsoleSLF4JService ================================================ FILE: mirai-console/docs/.conf/nav.js ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ module.exports = { text: "mirai-console", link: "/", items: [ {text: "Index", link: "/"}, {text: "配置项目", link: "/ConfiguringProjects.html"}, {text: "启动 Console", link: "/Run.html"}, { text: "后端插件开发基础", items: [ {text: "插件 - Plugin 模块", link: "/plugin/Plugins.html"}, {text: "插件 - JVM Plugin", link: "/plugin/JVMPlugin.html"}, {text: "指令 - Command 模块", link: "/Commands.html"}, {text: "存储 - PluginData 模块", link: "/PluginData.html"}, {text: "权限 - Permission 模块", link: "/Permissions.html"}, ] }, { text: "后端插件开发进阶", items: [ {text: "扩展 - Extension 模块和扩展点", link: "/Extensions.html"}, ] }, { text: "实现前端", items: [ {text: "FrontEnd", link: "/FrontEnd.html"}, ] }, { text: "Misc", items: [ {text: 'Q & A', link: '/QA.html'}, {text: '日志配置', link: '/Logging.html'}, {text: '内置命令', link: '/BuiltInCommands.html'}, {text: 'Mirai Console 附录', link: '/Appendix.html'}, {text: "插件 - JVM Plugin - 附录", link: "/plugin/JVMPlugin-Appendix.html"}, {text: "插件 - JVM Plugin - 多插件数据交换", link: "/plugin/JVMPlugin-DataExchange.html"}, {text: "插件 - JVM Plugin - 排错", link: "/plugin/JVMPlugin-Debug.html"}, ] }, ] } ================================================ FILE: mirai-console/docs/Appendix.md ================================================ # Mirai Console - Appendix ### Mirai Console 演进 Mirai Console 是不断前进的框架,将来必定会发生 API 弃用和重构。 维护者会严谨地推进每一项修改,并提供迁移周期(至少 2 个次版本)。 #### 版本规范 Mirai Console 的版本号遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/#spec-item-9) 规范。 在日常开发中, Mirai Console 会以 `-dev-1`,`-dev-2` 等版本后缀发布开发预览版本。这些版本仅用于兼容性测试等目的,无稳定性保证。 在大版本开发过程中,Mirai Console 会以 `-M1`, `-M2` 等版本后缀发布里程碑预览版本。代表一系列功能的完成,但还不稳定。 这些版本里新增的 API 仍可能还会在下一个 Milestone 版本变化,因此请按需使用。 在大版本即将发布前,Mirai Console 会以 `-RC` 版本后缀发布最终的预览版本。 `RC` 表示新版本 API 已经确定,离稳定版发布只差最后的一些内部优化或 bug 修复。 #### 版本选择 **稳定性**:稳定 (`x.y.z`) > 发布预览 (`-RC`) > 里程碑预览 (`-M`) > 开发 (`-dev`)。 | 目的 | 推荐至少更新到版本 | |:--------------------------:|:--------------:| | 生产环境 | `x.y.z` | | 希望尽早体验稳定新特性的插件作者 | `-RC` | | 无论如何都想体验新特性的插件作者 | `-M` | | 前端实现者, 底层插件作者 | `-M` | | 为 Mirai Console 提交 PR | `-dev` | 其中,‘底层插件’ 表示提供扩展等的插件。如权限系统,其他语言插件加载器等。 #### 更新兼容性 对于 `x.y.z` 版本号: - 当 `z` 增加时,只会有 bug 修复,和必要的新函数添加(为了解决某一个问题),不会有破坏性变化。 - 当 `y` 增加时,可能有新 API 的引入,和旧 API 的弃用。但这些弃用会经过一个弃用周期后才被删除(隐藏)。向下兼容得到保证。 - 当 `x` 增加时,任何 API 都可能会有变化。无兼容性保证。 #### 弃用周期 一个计划被删除的 API,将会在下一个次版本开始经历弃用周期。 如一个 API 在 `1.1.0` 起被弃用,它首先会是 `WARNING` (使用时会得到一个编译警告)弃用级别。 在 `1.2.0` 上升为 `ERROR`(使用时会得到一个编译错误); 在 `1.3.0` 上升为 `HIDDEN`(使用者无法看到这些 API)。 `HIDDEN` 的 API 仍然会保留在代码中并正常编译,以提供二进制兼容性,直到下一个主版本更新。 ================================================ FILE: mirai-console/docs/BuiltInCommands.md ================================================ # Mirai Console - BuiltIn Commands Mirai Console 内置一些指令。 下文中 `<A|B>` 表示可以是 A 或 B。`[ ]` 表示一个必须的参数, `[ ]?` 表示一个可选的参数。 ## HelpCommand `/help` 查看指令帮助 ## StopCommand `/<stop|shutdown|exit>` 关闭 Mirai Console ## LoginCommand `/<login|登录> [qq] [password] [protocol]?` | 参数 | 可选值 | 描述 | |:---- | :---- | :------------------------------------------------------------------- | | protocol | ANDROID_PHONE | Android 手机. 所有功能都支持. | | protocol | ANDROID_PAD | Android 平板. | | protocol | ANDROID_WATCH | Android 手表. 注意: 不支持戳一戳事件解析 | 临时登录一个账号 ## PermissionCommand 主指令: `/<permission|perm|权限>` 次指令: | 指令 | 描述 | |:--------------------------------------------------------------------------------------------|:----------------------| | `/<permission\|perm\|权限> <permit\|grant\|add> [target] [permission]` | 授权一个权限 | | `/<permission\|perm\|权限> <cancel\|deny\|remove> [target] [permission]` | 撤销一个权限 | | `/<permission\|perm\|权限> <cancelAll\|denyAll\|removeAll> [target] [permission]` | 撤销一个权限及其所有子权限 | | `/<permission\|perm\|权限> <permittedPermissions\|pp\|grantedPermissions\|gp> [target] [all]` | 查看被授权权限列表 | | `/<permission\|perm\|权限> <listPermissions\|lp>` | 查看所有权限列表 | ### `[target]` 和 `[permission]` 示例 `[target]` 是 [*被许可人 ID*](Permissions.md#被许可人-id),可以查看[表示方法](Permissions.md#字符串表示) 。 `[permission]` 是 [*权限 ID*](Permissions.md#权限-id)。每个指令都拥有一个权限 ID。请使用 `/perm list` 查看权限 ID 列表。 示例:`/perm permit u123456 console:command.stop` ### 授予一个用户执行所有指令的权限 示例: - `/perm permit u123456 *:*` 允许用户 123456 执行任意指令 - `/perm permit u123456 console:*` 允许用户 123456 执行任意 Console 内置指令 - `/perm permit u123456 org.example.my-plugin:*` 允许用户 123456 执行插件 `org.example.my-plugin` 的任意指令 ### 授予所有用户执行某个指令的权限 示例: - `/perm permit u* console:command.help` 允许所有用户执行 `console:command.help`(即 `/help`) - `/perm permit u* console:*` 允许所有用户执行任意 Console 内置指令 - `/perm permit u* org.example.my-plugin:*` 允许所有用户执行插件 `org.example.my-plugin` 的任意指令 ## AutoLoginCommand 主指令: `/autoLogin` 次指令: | 指令 | 描述 | |:--------------------------------------------------------------|:---------------------| | `/<autoLogin\|自动登录> list` | 查看自动登录账号列表 | | `/<autoLogin\|自动登录> add [account] [password] [passwordKind]` | 添加自动登录 | | `/<autoLogin\|自动登录> clear` | 清除所有配置 | | `/<autoLogin\|自动登录> remove [account]` | 删除一个账号 | | `/<autoLogin\|自动登录> setConfig [account] [configKey] [value]` | 设置一个账号的一个配置项 | | `/<autoLogin\|自动登录> removeConfig [account] [configKey]` | 删除一个账号的一个配置项 | | `configKey` | 可选值 | |:-----------:|:-------------------------------------------------:| | `protocol` | `ANDROID_PHONE` / `ANDROID_PAD` / `ANDROID_WATCH` | 示例:`/autoLogin setConfig 123456 protocol ANDROID_PHONE` ## StatusCommand `/status` 获取 Mirai Console 运行状态 ================================================ FILE: mirai-console/docs/Commands.md ================================================ # Mirai Console Backend - Commands [`Plugin`]: ../backend/mirai-console/src/plugin/Plugin.kt [`PluginDescription`]: ../backend/mirai-console/src/plugin/description/PluginDescription.kt [`PluginLoader`]: ../backend/mirai-console/src/plugin/loader/PluginLoader.kt [`PluginManager`]: ../backend/mirai-console/src/plugin/PluginManager.kt [`JvmPluginLoader`]: ../backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt [`JvmPlugin`]: ../backend/mirai-console/src/plugin/jvm/JvmPlugin.kt [`JvmPluginDescription`]: ../backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt [`AbstractJvmPlugin`]: ../backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt [`KotlinPlugin`]: ../backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt [`JavaPlugin`]: ../backend/mirai-console/src/plugin/jvm/JavaPlugin.kt [`Value`]: ../backend/mirai-console/src/data/Value.kt [`PluginData`]: ../backend/mirai-console/src/data/PluginData.kt [`AbstractPluginData`]: ../backend/mirai-console/src/data/AbstractPluginData.kt [`AutoSavePluginData`]: ../backend/mirai-console/src/data/AutoSavePluginData.kt [`AutoSavePluginConfig`]: ../backend/mirai-console/src/data/AutoSavePluginConfig.kt [`PluginConfig`]: ../backend/mirai-console/src/data/PluginConfig.kt [`PluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt [`MultiFilePluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L116 [`MemoryPluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L100 [`AutoSavePluginDataHolder`]: ../backend/mirai-console/src/data/PluginDataHolder.kt#L45 [`PluginDataHolder`]: ../backend/mirai-console/src/data/PluginDataHolder.kt [`PluginDataExtensions`]: ../backend/mirai-console/src/data/PluginDataExtensions.kt [`MiraiConsole`]: ../backend/mirai-console/src/MiraiConsole.kt [`MiraiConsoleImplementation`]: ../backend/mirai-console/src/MiraiConsoleImplementation.kt <!--[MiraiConsoleFrontEnd]: ../backend/mirai-console/src/MiraiConsoleFrontEnd.kt--> [`Command`]: ../backend/mirai-console/src/command/Command.kt [`Register`]: ../backend/mirai-console/src/command/CommandManager.kt#L77 [`AbstractCommand`]: ../backend/mirai-console/src/command/Command.kt#L90 [`CompositeCommand`]: ../backend/mirai-console/src/command/CompositeCommand.kt [`SimpleCommand`]: ../backend/mirai-console/src/command/SimpleCommand.kt [`RawCommand`]: ../backend/mirai-console/src/command/RawCommand.kt [`CommandManager`]: ../backend/mirai-console/src/command/CommandManager.kt [`CommandSender`]: ../backend/mirai-console/src/command/CommandSender.kt [`CommandValueArgumentParser`]: ../backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt [`CommandArgumentContext`]: ../backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt [`CommandArgumentContext.BuiltIns`]: ../backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt#L66 [`MessageScope`]: ../backend/mirai-console/src/util/MessageScope.kt "指令" 目前通常是 "/commandName arg1 arg2 arg3" 格式的消息。在将来可能会被扩展。 指令拥有一个主要名称和任意个次要名称。使用任意名称都可以执行指令。 定义指令 ------ 每个指令都是一个 `Command` 类型的对象。`Command` 是一个接口,定义了基本的指令的属性: ```kotlin interface Command { val names: Array<out String> // 名称 val usage: String // 用法 val description: String // 描述 val permission: Permission // 权限 val prefixOptional: Boolean // 前缀可选 val owner: CommandOwner // 拥有者 val overloads: List<CommandSignature> // 指令的签名列表 } ``` `AbstractCommand` 提供了对 `Command` 的基础实现。而要在插件定义指令,建议继承如下三种指令实现: (注意:所有指令都需要注册到指令管理器才能生效,详见 [注册指令](#注册指令)) ### 原生指令 原生指令即 [`RawCommand`],它直接处理触发指令的原消息链。 `RawCommand` 提供了两个抽象函数,在指令被执行时将会调用它们: *Kotlin* ```kotlin open override suspend fun CommandContext.onCommand(args: MessageChain) open override suspend fun CommandSender.onCommand(args: MessageChain) ``` *Java* ```java public abstract class JRawCommand { // ... public void onCommand(CommandContext context, MessageChain args) { } public void onCommand(CommandSender sender, MessageChain args) { } } ``` 例如在聊天环境通过消息链 `/test 123 [图片]` 触发指令(`[图片]` 表示一个图片),`onCommand` 接收的 `args` 为包含 2 个元素的 `MessageChain`。第一个元素为 `PlainText("123")`,第二个元素为 `Image`。 注意,当 `onCommand(CommandSender, MessageChain)` 和 `onCommand(CommandContext, MessageChain)` 被同时覆盖时, 只有 `onCommand(CommandContext, MessageChain)` 会生效。 `CommandContext` 是当前指令的执行环境,定义如下: ```kotlin interface CommandContext { val sender: CommandSender val originalMessage: MessageChain } ``` 其中 `sender` 为指令执行者。它可能是控制台(`ConsoleCmomandSender` ),也可能是用户(`UserCommandSender`)等; `originalMessage` 为触发指令的原消息链,包含元数据,也包含指令名。 若在聊天环境触发指令,`originalMessage` 将会包含 `MessageSource`。 注意,`MessageSource` 等 `MessageMetadata` 的位置是不确定的。取决于 mirai-core 的版本,它可能会存在于消息链中的任意位置。因此请不要依赖于 `originalMessage` 的元素顺序。 `args` 参数的顺序是稳定的,因为它只包含消息内容(`MessageContent`)。 #### 使用 `RawCommand` 只需要按需继承 `onCommand` 其中一个即可。如果需要使用原消息链,则继承 `CommandContext` 的,否则继承 `CommandSender` 的可以使实现更简单。 通常可以以单例形式实现指令,当然非单例模式也是支持的。 下面分别为在 Kotlin 和 Java 的示例实现: *Kotlin* ```kotlin object MyCommand : RawCommand( MyPluginMain, "name", // 使用插件主类对象作为指令拥有者;设置主指令名为 "name" // 可选: "name2", "name3", // 增加两个次要名称 usage = "/name arg1 arg2", // 设置用法,将会在 /help 展示 description = "这是一个测试指令", // 设置描述,将会在 /help 展示 prefixOptional = true, // 设置指令前缀是可选的,即使用 `test` 也能执行指令而不需要 `/test` ) { override suspend fun CommandContext.onCommand(args: MessageChain) { } } ``` *Java* ```java public final class MyCommand extends JRawCommand { public static final MyCommand INSTANCE = new MyCommand(); private MyCommand() { super(MyPluginMain.INSTANCE, "test"); // 使用插件主类对象作为指令拥有者;设置主指令名为 "test" // 可选设置如下属性 setUsage("/test"); // 设置用法,这将会在 /help 中展示 setDescription("这是一个测试指令"); // 设置描述,也会在 /help 中展示 setPrefixOptional(true); // 设置指令前缀是可选的,即使用 `test` 也能执行指令而不需要 `/test` } @Override public void onCommand(@NotNull CommandSender sender, @NotNull MessageChain args) { // 处理指令 } } ``` ### 参数智能解析 Console 提供参数智能解析功能,可以阅读用户手册的 [指令参数智能解析](../../docs/ConsoleTerminal.md#指令参数智能解析) 了解这一功能。 有两种指令实现支持这个功能,它们分别是简单指令 `SimpleCommand` 和复合指令 `CompositeCommand`。 ### 复合指令 复合指令即 `CompositeCommand`,支持参数智能解析。 Console 通过反射实现参数类型识别。标注 `@SubCommand` 的函数(方法)都会被看作是子指令。 一个简单的子指令定义如下: *Kotlin* ```kotlin object MyComposite : CompositeCommand() { // ... @SubCommand("name") suspend fun foo(context: CommandContext, arg: String) { println(arg) } } ``` *Java* ```java public final class MyComposite extends JCompositeCommand { // ... @SubCommand("name") public void foo(CommandContext context, String arg) { System.out.println(arg); } } ``` Java 使用者请了解 Kotlin 的 `fun foo(context: CommandContext, arg: String)` 相当于 Java 的 `public void foo(CommandContext context, String arg)`。下面部分简单示例将只用 Kotlin 展示。 #### 子指令 用 `@SubCommand` 标注的函数就是子指令。子指令将隶属于其主指令。定义于主指令 `main` 的名称为 `child` 的子指令在执行时需要使用 `/main child`,其中 `/` 表示指令前缀(如果需要)。`/main child arg1 arg2` 中的 `arg1` 和 `arg2` 则分别表示传递给子指令的第一个和第二个参数。 子指令可以拥有多个名称,即 `@SubCommand("child1", "child2")` 可以由 `/main child1` 或 `/main child2` 执行。 #### 子指令名称 `@SubCommand` 的参数为子指令的名称,可以有多个名称,即 `@SubCommand("name1", "name2")` 。若子指令名称与函数名称相同,可以省略 `@SubCommand` 的参数。例如 `@SubCommand("foo") suspend fun foo()` 可以简写为 `@SubCommand suspend fun foo()`。 #### 子指令参数 子指令的第一个参数(在 Kotlin 也可以是接收者(`receiver`))可以是 `CommandContext` 或 `CommandSender`,分别用来获取指令执行环境或发送人。与 `RawCommand` 相同,如果需要使用原消息链,则使用 `CommandContext`,否则使用 `CommandSender` 的可以让实现更简单。 在这个参数以外的就是是子指令的值参数。 值参数将会对应消息链。例如定义于名称为 `comp` 的 `CompositeCommand` 中的子指令 `@SubCommand fun foo(context: CommandContext, arg1: String, arg2: Int)` 在由 `/comp foo str 1` 执行时,`str` 将会传递给 `arg1`;`1` 将会传递给 `arg2`。将会在下文详细解释此内容。 在 Kotlin,子指令既可以是 `suspend` 也可以不是。子指令在不是挂起函数时可以使用阻塞 IO(如 `File.readText`),因为子指令会在 IO 线程执行。 #### 定义参数 子指令函数(方法)定义的参数将按顺序成为指令的参数。如下示例中 `arg1` 将成为第一个参数,`arg2` 为第二个: *Kotlin* ```kotlin object MyComposite : CompositeCommand(MyPluginMain, "main") { // ... @SubCommand("name") suspend fun foo(context: CommandContext, arg: String, b: Boolean) { println(arg) } } ``` *Java* ```java public final class MyComposite extends JCompositeCommand { public MyComposite() { super(MyPluginMain.INSTANCE, "main"); // ... } // ... @SubCommand("name") public void foo(CommandContext context, String arg, boolean b) { System.out.println(arg); } } ``` 在执行时,`/main name 1 true` 中 `1` 将会被解析为 `String` 类型的参数 `arg`、`true` 将会被解析为 `boolean` 参数的 `b`。 #### 内置智能解析 可参考 `CommandValueArgumentParser`,Console 内置支持以下类型的参数: - `Message` - `SingleMessage` - `MessageContent` - 原生数据类型 - `PlainText` - `Image` - `String` - `Bot` - `Contact` - `User` - `Friend` - `Member` - `Group` - `PermissionId` - `PermitteeId` - `Enum` - `TemporalAccessor` #### 自定义智能解析 可在 `CmopositeCommand` 继承 `context` 属性增加自定义解析器。下面示例中为 `Boolean` 指定了自定义的解析器,子指令的 `b` 参数将会用此解析器解析。 *Kotlin* ```kotlin object CustomBooleanParser : CommandValueArgumentParser<Boolean> { override fun parse(raw: String, sender: CommandSender): Boolean { return raw == "TRUE!" } override fun parse( raw: MessageContent, sender: CommandSender ): Boolean { // 将一个图片认为是 'true' if (raw is Image && raw.imageId == "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { return true } return super.parse(raw, sender) } } object MyComposite : CompositeCommand( MyPluginMain, "main", overrideContext = buildCommandArgumentContext { Boolean::class with CustomBooleanParser } ) { // ... @SubCommand("name") suspend fun foo(context: CommandContext, arg: String, b: Boolean) { println(b) } } ``` *Java* ```java // CustomBooleanParser.java public final class CustomBooleanParser implements CommandValueArgumentParser<Boolean> { @NotNull @Override public Boolean parse(@NotNull String raw, @NotNull CommandSender sender) throws CommandArgumentParserException { return raw.equals("TRUE!"); } @NotNull @Override public Boolean parse(@NotNull MessageContent raw, @NotNull CommandSender sender) throws CommandArgumentParserException { // 将一个图片认为是 'true' if (raw instanceof Image && ((Image) raw).getImageId().equals("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg")) { return true; } return CommandValueArgumentParser.super.parse(raw, sender); } } // MyComposite.java public final class MyComposite extends JCompositeCommand { public MyComposite() { super(MyPluginMain.INSTANCE, "main"); // ... addArgumentContext(new CommandArgumentContextBuilder() .add(Boolean.TYPE, new CustomBooleanParser()) // 注册解析器 .build()); } // ... @SubCommand("name") public void foo(CommandContext context, String arg, boolean b) { System.out.println(b); } } ``` 在 `parse` 时抛出 `CommandArgumentParserException` 会被看作是正常退出,异常的内容会返回给指令调用人。在 `parse` 时抛出其他异常则会认为是插件错误。 ### 简单指令 简单指令与复合指令拥有一样的智能参数解析功能。简单指令没有子指令,使用 `@Handler` 标注一个函数可以让它处理指令: *Kotlin* ```kotlin object MySimple : SimpleCommand(MyPluginMain, "main") { // ... @Handler suspend fun foo(context: CommandContext, arg: String, b: Boolean) { println(b) } } ``` *Java* ```java // MySimple.java public final class MySimple extends JSimpleCommand { public MySimple() { super(MyPluginMain.INSTANCE, "main"); // ... } // ... @Handler public void foo(CommandContext context, String arg, boolean b) { System.out.println(b); } } ``` 在执行时,`/main aaaa false` 将会调用 `foo` 函数(方法)。`aaaa` 匹配 `String` 类型的参数 `arg` ,`false` 匹配 `boolean` 类型的参数 `b`。 简单指令也可以使用自定义参数解析器,用法与复合指令一样。 *Kotlin* ```kotlin object MySimple : SimpleCommand( MyPluginMain, "main", overrideContext = buildCommandArgumentContext { Boolean::class with CustomBooleanParser } ) { // ... @Handler suspend fun foo(context: CommandContext, arg: String, b: Boolean) { println(b) } } ``` *Java* ```java // MySimple.java public final class MySimple extends JSimpleCommand { public MySimple() { super(MyPluginMain.INSTANCE, "main"); // ... addArgumentContext(new CommandArgumentContextBuilder() .add(Boolean.TYPE, new CustomBooleanParser()) // 注册解析器 .build()); } // ... @Handler public void foo(CommandContext context, String arg, boolean b) { System.out.println(b); } } ``` ### 选择 [`RawCommand`], [`SimpleCommand`] 或 [`CompositeCommand`] 若需要不限长度的,自由的参数列表,使用 [`RawCommand`]。 若需要子指令,使用 [`CompositeCommand`]。否则使用 [`SimpleCommand`]。 ### 自行实现指令 Console 允许插件自行实现指令(不使用上述 `RawCommand`、`SimpleCommand` 和 `CompositeCommand`)。但注意,在实现时难免会需要使用到抽象指令描述器(如 `CommandArgument` ),而这些描述器是不稳定的。因此插件自行实现指令可能会导致不兼容未来的 Console 版本。 注册指令 ------- 所有指令都需要注册到指令管理器才能生效。要注册指令,在 `onEnable` 使用 `CommandManager.registerCommand(command)`。 ### 查看已注册的所有指令 使用 `PluginManager.INSTANCE.getAllRegisteredCommands()` 。可以获得当前已经注册的所有 `Command` 实例列表。 执行指令 ------- 指令既可以由插件执行,也可以在消息环境中由用户执行(需要 [chat-command](https://github.com/project-mirai/chat-command) )。 ### 在插件执行指令 若要通过字符串解析目标指令并执行,使用 `CommandManager.INSTANCE.executeCommand(CommandSender, Message)` ,其中 `Message` 为包含前缀(如果有必要)、指令名称、以及指令参数列表的完整消息。 若要通过字符串解析目标指令并执行,使用 `CommandManager.INSTANCE.executeCommand(CommandSender, Command, Message)` ,其中 `Message` 传递给指令的参数列表,不包含前缀或指令名称。注意,若要执行复合指令,需要包含子指令名称。 ### 指令语法解析 一条消息可以被解析为指令,如果它满足: ```text <指令前缀><任一指令名> <指令参数列表> ``` 指令参数列表由空格分隔。 ### 指令解析流程 > 注意:该流程可能会变化,请不要依赖这个流程。 对于 ```text @Handler suspend fun handle(context: CommandContext, target: User, message: String) ``` 指令 `/tell 123456 Hello` 的解析流程: 1. 被分割为 `/`, `"tell"`, `"123456"`, `"Hello"` 2. 根据 `/` 和 `"test"`,确定 `MySimpleCommand` 作为目标指令。`"123456"`, `"Hello"` 作为指令的原生参数。 3. 由于 `MySimpleCommand` 定义的 `handle` 需要两个参数, 即 `User` 和 `String` ,`"123456"` 需要转换成 `User`,`"Hello"` 需要转换成 `String`。 4. 指令寻找合适的解析器(`CommandValueArgumentParser`) 5. `"123456"` 通过 `ExistingUserValueArgumentParser` 变为 `User` 类型的参数 6. `"Hello"` 通过 `StringValueArgumentParser` 变为 `String` 类型的参数 7. 解析完成的参数传入 `handle` 文本参数转义 ----- 不同的参数默认用空格分隔。有时用户希望在文字参数中包含空格本身,参数解析器可以接受三种表示方法。 以上文中定义的 `MySimpleCommand` 为例: ### 英文双引号 表示将其中内容作为一个参数,可以包括空格。 例如:用户输入 `/tell 123456 "Hello world!"` ,`message` 会收到 `Hello world!`。 注意:双引号仅在参数的首尾部生效。例如,用户输入 `/tell 123456 He"llo world!"`,`message` 只会得到 `He"llo`。 ### 转义符 即英文反斜杠 `\`。表示忽略之后一个字符的特殊含义,仅看作字符本身。 例如: - 用户输入 `/tell 123456 Hello\ world!`,`message` 得到 `Hello world!`; - 用户输入 `/tell 123456 \"Hello world!\"`,`message` 得到 `"Hello`。 ### 暂停解析标志 即连续两个英文短横线 `--`。表示从此处开始,到**这段文字内容**结束为止,都作为一个完整参数。 例如: - 用户输入 `/tell 123456 -- Hello:::test\12""3`,`message` 得到 `Hello:::test\12""3`(`:` 表示空格); - 用户输入 `/tell 123456 -- Hello @全体成员 test1 test2`,那么暂停解析的作用范围到 `@` 为止,之后的 `test1` 和 `test2` 是不同的参数。 - 用户输入 `/tell 123456 \-- Hello` 或 `/tell 123456 "--" Hello` ,这不是暂停解析标志,`message` 得到 `--` 本身。 注意: `--` 的前后都应与其他参数有间隔,否则不认为这是暂停解析标志。 例如,用户输入 `/tell 123456--Hello world!`,`123456--Hello` 会被试图转换为 `User` 并出错。即使转换成功,`message` 也只会得到 `world!`。 ### 非文本参数的转义 有时可能需要只用一个参数来接受各种消息内容,例如用户可以在 `/tell 123456` 后接图片、表情等,它们都是 `message` 的一部分。 对于这种定义方式,Mirai Console 的支持尚待实现,目前可以使用 [`RawCommand`] 替代。 ## 指令发送者 指令可能在聊天环境执行,也可能在控制台执行。 指令发送者即 `CommandSender`,是执行指令时的必须品之一。 ### 类型 ```text CommandSender <---------+---------------+-------------------------------+ ↑ | | | | | | | | UserCommandSender GroupAwareCommandSender CommandSenderOnMessage | ↑ ↑ ↑ | | | | AbstractCommandSender | | | ↑ | | | | sealed | | | +-------------+-------------+ | | | | | | | | | | | | | } ConsoleCommandSender AbstractUserCommandSender | | } 一级子类 ↑ | | } | sealed | | | | | +----------------------+ | | | | | | | +------+------------+---------------+ | | | | | | | | | } FriendCommandSender MemberCommandSender TempCommandSender | } 二级子类 ↑ ↑ ↑ | } | | | | | | | | } FriendCommandSenderOnMessage MemberCommandSenderOnMessage TempCommandSenderOnMessage | } 三级子类 | | | | } | | | | +-----------------------------+----------------------------+---------------+ ``` 有关类型的详细信息,请查看 [CommandSender.kt](../backend/mirai-console/src/command/CommandSender.kt#L48-L135) ### 获取控制台指令发送者 `ConsoleCommandSender` 表示以控制台身份执行指令。它是一个单例对象,在 Kotlin 可以直接通过类型获得类名获得实例,在 Java 可通过 `ConsoleCommandSender.INSTANCE` 获得。 ### 获取其他指令发送者 在 Kotlin 可使用扩展函数:`Contact.asCommandSender()` 或 `MessageEvent.toCommandSender()` 。 在 Java 可使用 `CommandSender.from` 和 `CommandSender.of`。 [`MessageScope`] ----- 表示几个消息对象的'域',即消息对象的集合。用于最小化将同一条消息发送给多个类型不同的目标的付出。示例: *Kotlin* ```kotlin // 在一个 CompositeCommand 内 @Handler suspend fun CommandSender.handle(target: Member) { val duration = Random.nextInt(1, 15) target.mute(duration) // 不使用 MessageScope, 无用的样板代码 val thisGroup = this.getGroupOrNull() val message = "${this.name} 禁言 ${target.nameCardOrNick} $duration 秒" if (target.group != thisGroup) { target.group.sendMessage(message) } sendMessage(message) // 使用 MessageScope, 清晰逻辑 // 表示至少发送给 `this`, 当 `this` 的真实发信对象与 `target.group` 不同时, 还额外发送给 `target.group` this.scopeWith(target.group) { sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration 秒") } // 同样地, 可以扩展用法, 同时私聊指令执行者: // this.scopeWith( // target, // target.group // ) { ... } } ``` *Java* ```java public class MyCommand extends SimpleCommand { @Handler public void handle(sender: CommandSender, target: Member) { int duration = Random.nextInt(1, 15); target.mute(duration); // 不使用 MessageScope Group thisGroup = CommandSenderKt.getGroupOrNull(sender); String message = "${this.name} 禁言 ${target.nameCardOrNick} $duration 秒"; if (!target.group.equals(thisGroup)) { target.group.sendMessage(message); } sender.sendMessage(message); // 使用 MessageScope // 表示至少发送给 `this`, 当 `this` 的真实发信对象与 `target.group` 不同时, 还额外发送给 `target.group` MessageScope scope = MessageScopeKt.scopeWith(sender, target); scope.sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration 秒"); // 或是只用一行: MessageScopeKt.scopeWith(sender, target).sendMessage("${name} 禁言了 ${target.nameCardOrNick} $duration 秒"); } } ``` ---- > 下一步,[PluginData](PluginData.md#mirai-console-backend---plugindata) > > 返回 [开发文档索引](README.md#mirai-console) ================================================ FILE: mirai-console/docs/ConfiguringProjects.md ================================================ # Mirai Console - Configuring Projects 配置 Mirai Console 项目。 ## 模块说明 Mirai Console 分前后端模块实现。开发插件**只需要针对同一个后端开发**,便可以运行在所有前端。 现有的前端如下所示。你不需要下载它们,因为插件的开发都是通用的。 - `mirai-console-terminal`: JVM 终端前端,适合在测试环境或服务器运行。 - [`MiraiAndroid`](https://github.com/mzdluo123/MiraiAndroid): Android 应用前端,可兼容大部分 Mirai Console 插件。 - [`mirai-compose`](https://github.com/sonder-joker/mirai-compose): 跨平台桌面图形前端,拥有可视化管理。正处于测试阶段。 ## 选择版本 `mirai-console` 与 `mirai-core` **同步版本** 发布。版本号见 [mirai](/docs/ConfiguringProjects.md#%E9%80%89%E6%8B%A9%E7%89%88%E6%9C%AC) 。 通常使用最新版本的稳定版本即可。 ## 配置项目 请选择以下四种方法之一。不推荐使用 Maven 构建 Mirai Console 插件。 ### A.使用项目创建工具 Mirai 为 IntelliJ IDEA 或 Android Studio 提供插件: [安装方法](/docs/Preparations.md#%E5%AE%89%E8%A3%85-ide-%E6%8F%92%E4%BB%B6) 之后便可在新建项目时选择 `Mirai` ,将会自动套用 [模板项目](https://github.com/project-mirai/mirai-console-plugin-template)。 ![](.ConfiguringProjects_images/6d010b1a.png) ![](.ConfiguringProjects_images/a6a3b24b.png) > 现在你已经配置好了项目,开始阅读 [Core 开发文档](/docs/CoreAPI.md) 或 [Console 开发文档](README.md#mirai-console) ### B.使用模板项目 Mirai 鼓励插件开发者将自己的作品开源,并为此提供了模板项目。 注意,模板项目依赖的版本号不一定是最新的。请自行替换。 1. 访问 [mirai-console-plugin-template](https://github.com/project-mirai/mirai-console-plugin-template) 2. 点击绿色按钮 "Use this template",创建项目 3. 克隆项目,检查并修改生成的属性 > 现在你已经配置好了项目,开始阅读 [Core 开发文档](/docs/CoreAPI.md) 或 [Console 开发文档](README.md#mirai-console) ### C.使用 Gradle 插件配置项目 `VERSION`: [选择版本](#选择版本) 若使用 `build.gradle.kts`: ```kotlin plugins { kotlin("jvm") version "1.5.10" kotlin("plugin.serialization") version "1.5.10" id("net.mamoe.mirai-console") version "VERSION" } ``` 若使用 `build.gradle`: ```groovy plugins { id 'org.jetbrains.kotlin.jvm' version '1.5.10' id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.10' id 'net.mamoe.mirai-console' version 'VERSION' } ``` 完成。Mirai Console Gradle 插件会为你配置依赖等所有编译环境。 可以在 [README](../tools/gradle-plugin/README.md#mirai-console-gradle-plugin) 获取详细的 Gradle 插件使用方法。 > 现在你已经配置好了项目,开始阅读 [Core 开发文档](/docs/CoreAPI.md) 或 [Console 开发文档](README.md#mirai-console) ### D.手动配置项目 不推荐这种方式,因为通常还需要配置一些不容易配置的编译器参数。 添加依赖: `build.gradle.kts` 或 `build.gradle`: ```kotlin dependencies { compileOnly("net.mamoe:mirai-core:$CORE_VERSION") // mirai-core 的 API compileOnly("net.mamoe:mirai-console:$CONSOLE_VERSION") // 后端 testImplementation("net.mamoe:mirai-console-terminal:$CONSOLE_VERSION") // 前端, 用于启动测试 } ``` 注意,在打包插件时必须将依赖一并打包进插件 JAR,且排除 `mirai-core`,`mirai-console` 和[它们的间接依赖](https://mvnrepository.com/artifact/net.mamoe/mirai-core-jvm/2.4.0),否则可能导致兼容性问题。 > 现在你已经配置好了项目,开始阅读 [Core 开发文档](/docs/CoreAPI.md) 或 [Console 开发文档](README.md#mirai-console) ================================================ FILE: mirai-console/docs/Contributing.md ================================================ # Mirai Console - Contributing 感谢你来到这里,感谢你对 Mirai Console 做的一切贡献。 ## 开发 Mirai Console ### 模块 Mirai Console 项目由四个模块组成:后端,前端,Gradle 插件,Intellij 插件。 ``` / |--- backend 后端 | |--- codegen 后端代码生成工具 | `--- mirai-console 后端主模块, 发布为 net.mamoe:mirai-console |--- buildSrc 项目构建 |--- frontend 前端 | `--- mirai-console-terminal 终端前端,发布为 net.mamoe:mirai-console-terminal `--- tools 开发工具 |--- compiler-common 编译器通用模块 |--- gradle-plugin Gradle 插件,发布为 net.mamoe.mirai-console `--- intellij-plugin IntelliJ 平台 IDE 插件,发布为 Mirai Console ``` 请前往各模块内的 README.md 查看详细说明。 ### 构建 ```shell script ./gradlew build ``` 首次加载和构建 mirai-console 项目可能要花费数小时时间。 ## 贡献代码 ### 代码风格 - 请优先使用 Kotlin - 请遵守 [Kotlin 官方代码风格](https://www.kotlincn.net/docs/reference/coding-conventions.html) <!-- ## 发布版本 (以下内容针对拥有 Mirai Console write 权限的项目成员) 若你要发布一个 Mirai Console dev release: 1. 添加 Git 版本号 tag,格式为 `v1.0.1-dev-1`; 2. `git push --tags` 推送 tag 更新,GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。 若你要发布一个 Mirai Console 稳定版 release,请按顺序进行如下检查: 1. 在 GitHub [milestones](https://github.com/mamoe/mirai-console/milestones) 确认目标版本的工作已经处理完毕; 2. Close milestone; 3. 更新 buildSrc/Versions.kt 中 `project` 版本号为目标版本; 4. 在 [ConfiguringProjects](ConfiguringProjects.md#选择版本) 更新稳定版本号; 5. 本地执行 `./gradlew fillBuildConstants`; 6. Push 前几步的修改为同一个 commit,commit 备注为版本号名称,如 `1.1.0`; 7. 在 GitHub release 根据 Git commit 记录编写更新记录: - 描述所有来自社区的 PR 记录; - 完整列举公开 API 的任何变动,简要描述或省略内部变动; - 为更改按 “后端”,“前端”,“IDE 插件”,“Gradle 插件” 分类; 8. 点击 `Publish`。GitHub Actions 将会检测到 tag 更新并执行 JCenter 发布。 --> ================================================ FILE: mirai-console/docs/Extensions.md ================================================ # Mirai Console Backend - Extensions Mirai Console 拥有灵活的 Extensions API,支持扩展 Console 的一些服务。 Extensions 属于插件开发的进阶内容。 [`Extension`]: ../backend/mirai-console/src/extension/Extension.kt [`ExtensionPoint`]: ../backend/mirai-console/src/extension/ExtensionPoint.kt [`PluginComponentStorage`]: ../backend/mirai-console/src/extension/PluginComponentStorage.kt [`ComponentStorage`]: ../backend/mirai-console/src/extension/ComponentStorage.kt ## [扩展][`Extension`] ### [组件容器][`ComponentStorage`] 容纳插件注册的 [扩展][`Extension`]。 ### 注册扩展 插件仅能在 [`onLoad`](Plugins.md#加载) 阶段注册扩展。 示例: ```kotlin object MyPlugin : KotlinPlugin( /* ... */ ) { fun PluginComponentStorage.onLoad() { contributePermissionService { /* ... */ } contributePluginLoader { /* ... */ } contribute(ExtensionPoint) { /* ... */ } } } ``` ```java // java public class MyPlugin extends JavaPlugin { public MyPlugin() { // ... } @Override public void onLoad(PluginComponentStorage pcs) { pcs.contributePermissionService(() -> { /* ... */ }); pcs.contributePluginLoader(() -> { /* ... */ }); pcs.contribute(ExtensionPoint, () -> { /* ... */ }); } } ``` ### 可用扩展 查看 [extensions](../backend/mirai-console/src/extensions)。每个文件对应一个扩展。 ================================================ FILE: mirai-console/docs/FrontEnd.md ================================================ # Mirai Console Frontend Mirai Console 前端开发文档。 [`MiraiConsole`]: ../backend/mirai-console/src/MiraiConsole.kt ## 实现前端 ### 添加编译器设置 在 `build.gradle` 或 `build.gradle.kts` 添加: ```kotlin kotlin.sourceSets.all { languageSettings.optIn("net.mamoe.mirai.console.ConsoleFrontEndImplementation") } ``` 此后就可以使用 `net.mamoe.mirai.console.ConsoleFrontEndImplementation` 标记的所有 API。 ### 实现 Mirai Console [`MiraiConsole`] 是后端的公开对象,由 [MiraiConsoleImplementationBridge](../backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt) 代理,与前端链接。 前端需要实现 [MiraiConsoleImplementation.kt](../backend/mirai-console/src/MiraiConsoleImplementation.kt)。 由于实现前端需要一定的技术能力,相信实现者都能理解源码内注释。 ### 启动 Mirai Console 通过 `public fun MiraiConsoleImplementation.start()`。 [MiraiConsoleImplementation.kt: Line 161](../backend/mirai-console/src/MiraiConsoleImplementation.kt#L161) ================================================ FILE: mirai-console/docs/Logging.md ================================================ # Mirai Console Backend - Logging Console 的日志一共有五个级别: | 级别(由高到低) | 用途 | 默认启用 | |:----------:|--------------|:------:| | ERROR | 记录影响程序运行的错误 | 是 | | WARNING | 记录不影响程序运行的警告 | 是 | | INFO | 记录一条普通信息 | 是 | | DEBUG | 记录普通调试信息 | 否 | | VERBOSE | 记录详细调试信息 | 否 | `DEBUG` 和 `VERBOSE` 作为调试信息,默认关闭。插件开发者可能会使用这两个级别来输出调试信息。如果你在使用中遇到问题,启用这个两个级别获得更多日志后再报告开发者可能更有帮助。 特别地,`ALL` 表示启用全部日志,`NONE` 表示禁用全部日志。 在终端前端(或 [MCL](https://github.com/iTXTech/mirai-console-loader) ),日志配置文件默认路径为 `config/Console/Logger.yml`。示例内容为如下。 ```yaml # 默认日志输出等级 可选值: ALL, VERBOSE, DEBUG, INFO, WARNING, ERROR, NONE defaultPriority: INFO # 特定日志记录器输出等级 loggers: example.logger: NONE console.debug: NONE Bot: ALL ``` ## 调整全局默认日志等级 修改 `defaultPriority` 即可修改全局默认日志等级。 例如设置为 DEBUG,则启用上表中 DEBUG 及更高级别的日志,即 DEBUG、INFO、WARNING、ERROR。 ## 调整特定日志等级 每个插件被分配的日志的 ID 为插件的显示名称。 提示:该 ID 也可以在日志中找到。如下面的日志中,`Bot 12345678` 就是其所属日志的 ID。(`V` 代表等级为 VERBOSE,以首字母识别) ```text 2022-05-02 11:09:28 V/Bot 12345678: Event: BotOnlineEvent(bot=Bot(12345678)) ``` 如果在日志配置这样修改: ```yaml loggers: "Bot 12345678": NONE ``` 那么将禁用来自该 Bot 的所有日志。 假设要启用名为 `Chat Command` 的插件的 DEBUG 及更高级别的日志: ```yaml loggers: "Chat Command": DEBUG ``` ================================================ FILE: mirai-console/docs/Permissions.md ================================================ # Mirai Console Backend - Permissions Mirai Console 权限系统。 > 优先使用 Mirai Console 权限系统管理权限是最好的选择 [`PermissionService`]: ../backend/mirai-console/src/permission/PermissionService.kt [`Permission`]: ../backend/mirai-console/src/permission/Permission.kt [`RootPermission`]: ../backend/mirai-console/src/permission/Permission.kt#L82 [`PermissionId`]: ../backend/mirai-console/src/permission/PermissionId.kt [`PermissionIdNamespace`]: ../backend/mirai-console/src/permission/PermissionIdNamespace.kt [`Permittee`]: ../backend/mirai-console/src/permission/Permittee.kt [`PermitteeId`]: ../backend/mirai-console/src/permission/PermitteeId.kt [`AbstractPermitteeId`]: ../backend/mirai-console/src/permission/PermitteeId.kt#L77 [`CommandSender`]: ../backend/mirai-console/src/command/CommandSender.kt ## 权限 每个权限都由 [`Permission`] 对象表示。 一个 [`Permission`] 拥有这些信息: ```kotlin interface Permission { val id: PermissionId // 唯一识别 ID val description: String // 描述信息 val parent: Permission // 父权限 } ``` 「权限」表示的意义是 “做一项工作的能力”。如 “执行指令 /stop”,“操作数据库” 都能叫作权限。 [`Permission`] 对象由 Console 内置或者由特定权限插件实现。其他插件不能实现 [`Permission`] 接口,只能从 `PermissionService` 注册并获取。 ### 权限 ID ```kotlin data class PermissionId( val namespace: String, // 命名空间 val name: String, // 名称 ) ``` [`PermissionId`] 是 [`Permission`] 的唯一标识符。知道 [`PermissionId`] 就可以获取到对应的 [`Permission`]。 字符串表示为 `$namespace:$name`,如 `console:command.stop`, `*:*` > 一般情况下使用位于插件对象(`JvmPlugin`) 的 `permissionId` 为插件分配一个 [`PermissionId`] #### 命名空间 命名空间(`namespace`)用于限定权限的创建者,避免冲突。 一些常见命名空间: | 用途 | 命名空间 | |:-------------|:------------| | 根权限 | `"*"` | | Console 内置 | `"console"` | | ID 为 A 的插件 | `"A"` | #### 名称 名称则表示特定的含义。如一个指令,某一项操作等。 一些常见名称: | 用途 | 名称 | |:--------------------------|:--------------| | 根权限 | `"*"` | | Console 内置的名为 A 的指令 | `"command.A"` | | ID 为 A 的插件的名为 B 的指令 | `"command.B"` | #### 根权限 [`RootPermission`] 是所有权限的父权限。其 ID 为 `*:*` > 如果 [`Permittee`] (见下文) 拥有根权限, 相当于 [`Permittee`] 拥有全部权限 (内置实现) ## 被许可人 ```kotlin interface Permittee { val permitteeId: PermitteeId } ``` [`Permittee`] 表示一个可被赋予权限的对象,即 '被许可人'。 [`CommandSender`] 实现 [`Permittee`]。 被许可人自身不持有拥有的权限列表,而是拥有 [`PermitteeId`],标识自己的身份,供 [权限服务][`PermissionService`] 处理。 **注意**:请不要自主实现 [`Permittee`]。 ### 被许可人 ID ```kotlin interface PermitteeId { val directParents: Array<out PermitteeId> // 直接父对象 fun asString(): String // 转换为字符串表示 } ```` [`PermitteeId`] 是被许可人的标识符。 一个这样的标识符既可代表特定的单个 [`Permittee`], 也可以表示多个同类 [`Permittee`]. #### `directParents` [`PermitteeId`] 允许拥有多个父对象。在检查权限时会首先检查自己, 再递归检查父类。 #### 衍生类型 [`PermitteeId`] 的实现通常是 [`AbstractPermitteeId`] 在 [`AbstractPermitteeId`] 查看其子类。 **注意**: 对应 [权限服务][`PermissionService`] 没明确说明可以自行实现时, 不要轻易实现 [`PermitteeId`] #### 字符串表示 当使用 `PermitteeId.asString` 时, 不同的类型的返回值如下表所示. 这些格式也适用于 [权限指令](#使用内置权限服务指令). (不区分大小写. 不区分 Bot). | 被许可人类型 | 字符串表示示例 | 备注 | |:----------------:|:-----------:|:------------------------------------| | 控制台 | console | | | 任意其他客户端 | client* | 即 Bot 自己发消息给自己 | | 精确群 | g123456 | 表示群, **而不表示群成员, 不表示群成员** | | 精确好友 | f123456 | 必须通过好友消息 | | 精确群临时会话 | t123456.789 | 群 123456 内的成员 789. 必须通过临时会话 | | 精确群成员 | m123456.789 | 群 123456 内的成员 789. 同时包含临时会话 | | 精确用户 | u123456 | 同时包含群成员, 好友, 临时会话 | | 任意群 | g\* | g 意为 group, **不表示群成员** | | 任意群的任意群员 | m\* | m 意为 member | | 精确群的任意群员 | m123456.\* | 群 123456 内的任意成员. 同时包含临时会话 | | 任意临时会话 | t\* | t 意为 temp. 必须通过临时会话 | | 精确群的任意临时会话 | t123456.\* | 群 123456 内的任意成员. 必须通过临时会话 | | 任意好友 | f\* | f 意为 friend | | 任意用户 | u\* | u 意为 user. 任何人在任何环境 | | 任意陌生人 | s\* | s 意为 stranger. 任何人在任何环境 | | 任意联系对象 | \* | 即任何人, 任何群. 不包括控制台 | ### 获取被许可人 在 Kotlin 通常使用 `CommandSender.permitteeId`,在 Java 使用 `CommandSender.getPermitteeId( )`。 也可以直接构造 [`AbstractPermitteeId`] 的子类。或者在 Kotlin 使用扩展 `User.permitteeId`。 ## 权限服务 [`PermissionService`] 承载权限的授权和管理。 Console 的权限系统完全由 [`PermissionService`] 提供支持。 权限服务可以由插件提供(见 [扩展](Extensions.md))。 在没有任何提供权限服务的插件时会使用 Console 内置实现。 在整个运行时 Console 只会使用同一个权限服务,如果安装多个提供权限服务的插件很有可能导致崩溃。 > 如果运行于 JVM 平台, > 可以使用 [Karlatemp/LuckPerms-Mirai](https://github.com/Karlatemp/LuckPerms-Mirai) > 以得到更好的使用体验 (支持权限组, 权限检查状态详细输出等) ### 判断权限 在 Kotlin,在该有扩展的对象上 Console 都为它们实现了扩展。可以使用: ```kotlin fun Permittee.hasPermission(Permission): Boolean fun Permission.testPermission(Permittee): Boolean fun PermitteeId.hasPermission(Permission): Boolean fun PermissionId.testPermission(Permittee): Boolean fun Permittee.hasPermission(PermissionId): Boolean fun Permission.testPermission(PermitteeId): Boolean // ... ``` 在 Java,请查看 [`PermissionService`] 中的伴生对象。 > 查看使用示例: > > - [Him188/mirai-console-example-plugin](https://github.com/Him188/mirai-console-example-plugin/blob/master/src/main/kotlin/org/example/my/plugin/MyPluginMain.kt#L116) > - [Karlatemp/mirai-console-permission-service-example (gist)](https://gist.github.com/Karlatemp/38b1491dc033854755c0ec7367ba081b) ### 注册权限 每一条指令都会默认自动创建一个权限。 如果希望手动注册一个其他用途的权限,使用 `PermissionService.register`。 **注意**: - 权限只能在插件 [启用](Plugins.md#启用) 之后才能注册。否则会得到一个异常。 - 使用 `PermissionService.register` 时对于同一个 [`PermissionId`] 只能注册一次, 如果多次注册会得到一个异常 ### 使用内置权限服务指令 **指令**: "/permission", "/perm", "/权限" 使用指令而不带参数可以获取如下用法: ``` /permission cancel <被许可人 ID> <权限 ID> 取消授权一个权限 /permission cancelall <被许可人 ID> <权限 ID> 取消授权一个权限及其所有子权限 /permission listpermissions 查看所有权限列表 /permission permit <被许可人 ID> <权限 ID> 授权一个权限 /permission permittedpermissions <被许可人 ID> 查看被授权权限列表 ``` 其中, 被许可人 ID 使用 [字符串表示](#字符串表示), 权限 ID 参见 [权限 ID](#权限-id) ---- > 这是文档的最后一个章节。 > > 返回 [开发文档索引](README.md#mirai-console) ================================================ FILE: mirai-console/docs/PluginData.md ================================================ # Mirai Console Backend - PluginData [`Plugin`]: ../backend/mirai-console/src/plugin/Plugin.kt [`PluginDescription`]: ../backend/mirai-console/src/plugin/description/PluginDescription.kt [`PluginLoader`]: ../backend/mirai-console/src/plugin/loader/PluginLoader.kt [`PluginManager`]: ../backend/mirai-console/src/plugin/PluginManager.kt [`JvmPluginLoader`]: ../backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt [`JvmPlugin`]: ../backend/mirai-console/src/plugin/jvm/JvmPlugin.kt [`JvmPluginDescription`]: ../backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt [`AbstractJvmPlugin`]: ../backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt [`KotlinPlugin`]: ../backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt [`JavaPlugin`]: ../backend/mirai-console/src/plugin/jvm/JavaPlugin.kt [`Value`]: ../backend/mirai-console/src/data/Value.kt [`PluginData`]: ../backend/mirai-console/src/data/PluginData.kt [`AbstractPluginData`]: ../backend/mirai-console/src/data/AbstractPluginData.kt [`AutoSavePluginData`]: ../backend/mirai-console/src/data/AutoSavePluginData.kt [`AutoSavePluginConfig`]: ../backend/mirai-console/src/data/AutoSavePluginConfig.kt [`PluginConfig`]: ../backend/mirai-console/src/data/PluginConfig.kt [`PluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt [`MultiFilePluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L116 [`MemoryPluginDataStorage`]: ../backend/mirai-console/src/data/PluginDataStorage.kt#L100 [`AutoSavePluginDataHolder`]: ../backend/mirai-console/src/data/PluginDataHolder.kt#L45 [`PluginDataHolder`]: ../backend/mirai-console/src/data/PluginDataHolder.kt [`PluginDataExtensions`]: ../backend/mirai-console/src/data/PluginDataExtensions.kt [`MiraiConsole`]: ../backend/mirai-console/src/MiraiConsole.kt [`MiraiConsoleImplementation`]: ../backend/mirai-console/src/MiraiConsoleImplementation.kt <!--[MiraiConsoleFrontEnd]: ../backend/mirai-console/src/MiraiConsoleFrontEnd.kt--> [`Command`]: ../backend/mirai-console/src/command/Command.kt [`CompositeCommand`]: ../backend/mirai-console/src/command/CompositeCommand.kt [`SimpleCommand`]: ../backend/mirai-console/src/command/SimpleCommand.kt [`RawCommand`]: ../backend/mirai-console/src/command/RawCommand.kt [`CommandManager`]: ../backend/mirai-console/src/command/CommandManager.kt [`Annotations`]: ../backend/mirai-console/src/util/Annotations.kt [`ConsoleInput`]: ../backend/mirai-console/src/util/ConsoleInput.kt [`JavaPluginScheduler`]: ../backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt [`ResourceContainer`]: ../backend/mirai-console/src/plugin/ResourceContainer.kt [`PluginFileExtensions`]: ../backend/mirai-console/src/plugin/PluginFileExtensions.kt [Kotlin]: https://www.kotlincn.net/ [Java]: https://www.java.com/zh_CN/ [JVM]: https://zh.wikipedia.org/zh-cn/Java%E8%99%9A%E6%8B%9F%E6%9C%BA [JAR]: https://zh.wikipedia.org/zh-cn/JAR_(%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F) [为什么不支持热加载和卸载插件?]: QA.md#为什么不支持热加载和卸载插件 [使用 AutoService]: QA.md#使用-autoservice Mirai Console 提供支持自动保存的,静态类型插件数据模型。 ### 设计目标 - 源码级静态强类型:避免 `getString()`, `getList()`... - 全自动加载保存:插件仅需在启动时通过一行代码链接自动保存 - 与前端同步修改:在 Android 等图形化前端实现中可以在内存动态同步修改 - 存储扩展性:可使用多种方式存储,无论是文件还是数据库,插件层都使用同一种实现方式 综上,**最小化插件作者在处理数据和配置做的付出**。 *暂无数据库保存支持,但这已经被提上日程。* ## [`Value`] ```kotlin interface Value<T> : ReadWriteProperty<Any?, T> { @get:JvmName("get") @set:JvmName("set") var value: T } ``` 表示一个值代理。在 [`PluginData`] 中,除简单数据类型外,值都经过 [`Value`] 包装。 ## [`PluginData`] 一个插件内部的, 对用户隐藏的数据对象。类似于属性名作为键,对应 [`Value`] 作为值的 `Map`。 [`PluginData`] 接口拥有一个基础实现类,[`AbstractPluginData`],默认不支持自动保存,仅存储键值关系及其序列化器。 插件可继承 [`AbstractPluginData`],拥有高自由的实现细节访问权限,并扩展数据结构。 但通常,插件使用 [`AutoSavePluginData`]。 [`AutoSavePluginData`] 监听保存在它之中的值的修改,并在合适的时机在提供的 [`AutoSavePluginDataHolder`] 协程作用域下启动协程保存数据。 ### 使用 `PluginData` 示例在此时比理论更高效。 1. 定义一个单例,继承 `AutoSavePluginData` ```kotlin object MyData : AutoSavePluginData("MyData") ``` 2. 使用委托添加属性。所有类型都可以使用同样的‘语法’。 ```kotlin object MyData : AutoSavePluginData("MyData") { // 文件名为 MyData, 会被保存为 MyData.yml val value1 by value<Int>() // 推断为 Int val value2 by value(0) // 默认值为 0, 推断为 Int var value3 by value(0) // 支持 var,修改会自动保存 val value4: Int by value() // 显式类型和推断类型,你喜欢哪种? val value5: List<String> by value() // 支持 List,Set val value6: MutableList<String> by value() // 可按需使用 Mutable 类型 val value7: List<List<String>> by value() // 支持嵌套 val value8: Map<String, List<List<String>>> by value() // 支持 Map var value9: List<String> by value() // List、Set 或 Map 同样支持 var。但请注意这是非引用赋值(详见下文)。 } ``` 3. 建立自动保存链接 使用 `PluginDataStorage.load(PluginDataHolder, PluginData)` 即可完成自动保存链接,并读取数据。 对于 [JVM 插件][`JvmPlugin`],可简便地在 `onEnable()` 中使用 `MyData.reload()`(对于上例)。详见 [读取 `PluginData` 或 `PluginConfig`](plugin/JVMPlugin-Appendix.md#读取-plugindata-或-pluginconfig) ### 定义数据模型(Java) *由于 Java 语法局限,为 Kotlin 而设计的 PluginData 在 Java 使用很复杂。* *即使 Mirai Console 为 Java 提供适配器,也强烈推荐 Java 用户在项目中混用 Kotlin 代码来完成数据模型定义。* 参考 [JAutoSavePluginData](../backend/mirai-console/src/data/java/JAutoSavePluginData.kt#L69) ### 非引用赋值 由于实现特殊, 赋值时不会写其引用. 即: ```kotlin val list = ArrayList<String>("A") MyPluginData.list = list // 赋值给 PluginData 的委托属性是非引用的 println(MyPluginData.list) // "[A]" list.add("B") println(list) // "[A, B]" println(MyPluginData.list) // "[A]" // !! 由于 `list` 的引用并未赋值给 `MyPluginData.list`. ``` 另一个更容易出错的示例: ```kotlin // MyPluginData.nestedMap: MutableMap<Long, List<Long>> by value() val newList = MyPluginData.map.getOrPut(1, ::mutableListOf) newList.add(1) // 不会添加到 MyPluginData.nestedMap 中, 因为 `mutableListOf` 创建的 MutableList 被非引用地添加进了 MyPluginData.nestedMap ``` 要解决这种无法自动初始化空集合的问题,请查看 [实验性扩展方法](#实验性扩展方法) 方法 ### 使用自定义可序列化数据类型 在 Kotlin,支持使用 [kotlinx.serialization](https://github.com/kotlin/kotlinx.serialization) 序列化的自定义数据类型。 **Console 使用反射构造自定义数据类型示例**。当自定义数据类型拥有公开无参构造器,或者一个构造器的所有参数都可选时,在使用委托 `by value()` 时可无需提供默认值。 否则,需要提供默认值。(见如下示例) 自定义数据类型定义: ```kotlin @Serializable // kotlinx.serialization.Serializable class CustomA(val str: String) @Serializable class CustomB(val str: String = "") // 参数可选,CustomB 就可以直接被反射构造。 ``` 使用时: ```kotlin object MyData : AutoSavePluginData("MyData") { val value1 by value(CustomA("")) // CustomA 不可以通过反射直接构造实例,因为必须提供参数 str。因此要在创建 value 时提供默认值。 val value2: CustomB by value() // CustomB 可以通过反射直接构造实例 } ``` ### (实验性)[扩展方法][`PluginDataExtensions`] 由于非引用赋值特性,在 `PluginData` 中定义的 `Map` 无法使用 `map.getOrPut(..., ::mutableListOf)` 等方法。 为此,Console 提供一些 *映射方法*。 (下文示例省略 `Value` 所在的 `PluginData` 定义) #### (实验性)`Map.withEmptyDefault` ```kotlin fun <K, InnerE, InnerV> SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>>.withEmptyDefault(): SerializerAwareValue<MutableMap<K, Map<InnerE, InnerV>>> ``` 创建一个代理对象, 当 `Map.get` 返回 `null` 时先放入一个 `LinkedHashMap`, 再返回这个 `LinkedHashMap`。 示例: ```kotlin val value1 by value<MutableMap<Long, List<Int>>>().withEmptyDefault() ``` 使用时 ```kotlin val v: MutableMap<Long, List<Int>> = MyData.value1[123456] // 此时 Map.get 返回非 null。因为若 MyData 中不存在 123456 对应的值,就先放入一个空 List。 ``` **但是,这种方法不支持多层嵌套**:例如 `Map<Long, Map<Long, List<Int>>>` 内层的 Map 不会被这样处理。 因此此方法仍处于实验性状态。如果你有任何建议,请在 issues 中发起讨论。 #### (实验性)`Map.withDefault` ```kotlin fun <K, V> SerializerAwareValue<MutableMap<K, V>>.withDefault(defaultValueComputer: (K) -> V): SerializerAwareValue<MutableMap<K, V>> ``` 与上述 `Map.withEmptyDefault` 类似。只是把默认值从 `mutableListOf` 换成了 `defaultValueComputer()` **但是,方法命名仍有待确认**:`withDefault` 可能不是最好的命名,因为可能与标准库的 `map.withDefault` 产生歧义(他们行为不同) #### (实验性)`Map.mapKeys` 映射 `Map` 的键。 ```kotlin fun <OldK, NewK, V> SerializerAwareValue<MutableMap<OldK, V>>.mapKeys( oldToNew: (OldK) -> NewK, newToOld: (NewK) -> OldK, ): SerializerAwareValue<MutableMap<NewK, V>> ``` 可进一步简化配置的操作。 示例: ```kotlin val value by value<MutableMap<Long, List<Int>>>().withEmptyDefault().mapKeys(Bot::id, Bot::getInstance) ``` 使用时: ```kotlin val bot: Bot = getBot() val list: List<Int> = value[bot] value[bot] = listOf() ``` ## [`PluginConfig`] ### [`PluginData`] 与 [`PluginConfig`] 的区别 - [`PluginData`] 表示插件内部的数据,不应该被用户看到。 - [`PluginConfig`] 表示插件的配置,用户可以修改这些配置。 ### 使用 [`PluginConfig`] [`PluginConfig`] 与 [`PluginData`] 用法完全相同。 在上述 [使用 `PluginData`](#使用-plugindata) 的示例中, 将 [`AutoSavePluginData`] 换为 [`AutoSavePluginConfig`] 即可创建一个配置,而不是数据。 在加载时使用 `configInstance.reload()` 或 `JvmPlugin.reloadPluginConfig(configInstance)`。 ## [`PluginDataHolder`] ***注意:这是实验性 API。*** [`PluginData`] 的拥有者。一般用于区分不同插件的不同 [`PluginData`],避免命名冲突。 [`JvmPlugin`] 实现 [`PluginDataHolder`],使用插件名作为保存时的名称。 ## [`PluginDataStorage`] ***注意:这是实验性 API。*** [`PluginData`] 的存储仓库,将 [`PluginData`] 从内存序列化到文件或到数据库,或反之。 内置的实现包含:[`MultiFilePluginDataStorage`], [`MemoryPluginDataStorage`] ---- > 下一步,[Permissions](Permissions.md#mirai-console-backend---permissions) > > 返回 [开发文档索引](README.md#mirai-console) ================================================ FILE: mirai-console/docs/QA.md ================================================ # Q&A ## 插件 ### 使用 AutoService 在 `build.gradle.kts` 添加: ```kotlin plugins { kotlin("kapt") } dependencies { val autoService = "1.0-rc7" kapt("com.google.auto.service", "auto-service", autoService) compileOnly("com.google.auto.service", "auto-service-annotations", autoService) } ``` *对于 `build.gradle` 用户, 请自行按照 Groovy DSL 语法翻译* ### 为什么不支持热加载和卸载插件? 在热加载过程容易产生冲突情况; 卸载时不容易完全卸载所有静态对象。 为了避免这些麻烦,Mirai Console 认为没有支持热加载和热卸载的必要。 ================================================ FILE: mirai-console/docs/README.md ================================================ # Mirai Console 欢迎来到 mirai-console 开发文档! **Mirai Console 基于 mirai-core,因此建议先阅读 [Mirai 文档](/docs/CoreAPI.md) 。** > 其他链接: > - **[如何配置 Mirai Console 项目](ConfiguringProjects.md)** > - **[如何启动 Mirai Console](Run.md)** ### 后端插件开发基础 - 插件 - [Plugin 模块](plugin/Plugins.md) - 指令 - [Command 模块](Commands.md) - 存储 - [PluginData 模块](PluginData.md) - 权限 - [Permission 模块](Permissions.md) **示例插件**: - [mirai-console-example-plugin (Kotlin DSL)](https://github.com/Him188/mirai-console-example-plugin) - [mirai-console-example-plugin (Groovy DSL)](https://github.com/Karlatemp/mirai-console-example-plugin) - [MAPluginTemplate (Groovy DSL)](https://github.com/project-mirai/MAPluginTemplate) Jvm和Android双平台通用模板 ### 后端插件开发进阶 - 扩展 - [Extension 模块和扩展点](Extensions.md) ### 实现前端 - [FrontEnd](FrontEnd.md) ================================================ FILE: mirai-console/docs/Run.md ================================================ # Mirai Console - Run Mirai Console 可以独立启动,也可以被嵌入到某个应用中。 ## 使用工具自动独立启动 该部分文档已经转移到 [用户手册](/docs/UserManual.md)。 ## 嵌入应用启动(实验性) 将 Mirai Console 作为一个依赖嵌入一个 JVM 应用启动,开发者主动加载插件。尤其适合在开发时启动真实环境的测试。 ### 环境 - JDK 1.8+ / Android SDK 26+ (Android 8+) - Kotlin 1.4+ ### 添加依赖 [选择版本](ConfiguringProjects.md#选择版本) `build.gradle.kts`: ```kotlin dependencies { api("net.mamoe:mirai-console-terminal:2.0.0") // 自行替换版本 api("net.mamoe:mirai-core:2.0.0") } ``` ### 启动 Terminal 前端 一行启动: ```kotlin MiraiConsoleTerminalLoader.startAsDaemon() ``` 注意, Mirai Console 将会以 '守护进程' 形式启动,不会阻止主线程退出。 ### 从内存加载 JVM 插件(实验性) 在嵌入使用时,插件可以直接加载: ```kotlin MiraiConsoleTerminalLoader.startAsDaemon() // 先启动 Mirai Console // Kotlin Plugin.load() // 扩展函数 Plugin.enable() // 扩展函数 // Java PluginManager.INSTANCE.loadPlugin(Plugin.INSTANCE) PluginManager.INSTANCE.enablePlugin(Plugin.INSTANCE) ``` 但注意:这种方法目前是实验性的——一些特定的功能如注册扩展可能不会正常工作。 ## 手动配置独立启动 强烈建议使用自动启动工具,若无法使用,可以参考如下手动启动方式。 ### 环境 - JRE 8+ / JDK 8+ ### 准备文件 要启动 Mirai Console,你需要: - mirai-core - mirai-console 后端 - mirai-console 任一前端 - 相关依赖 只有 mirai-console 前端才有入口点 `main` 方法。目前只有一个 terminal 前端可用。 #### 从 JCenter 下载模块 - mirai-core 会提供 [mirai-core-all] - mirai-console 与其各个模块都会提供 `-all` 的 Shadowed 构建 ```shell script # 注: 自行更换对应版本号 # Download mirai-core-all curl -L https://maven.aliyun.com/repository/central/net/mamoe/mirai-core-all/1.3.3/mirai-core-all-1.3.3-all.jar -o mirai-core-all-1.3.3.jar # Download mirai-console curl -L https://maven.aliyun.com/repository/central/net/mamoe/mirai-console/1.0.0/mirai-console-1.0.0-all.jar -o mirai-console-1.0.0.jar # Download mirai-console-terminal curl -L https://maven.aliyun.com/repository/central/net/mamoe/mirai-console-terminal/1.0.0/mirai-console-terminal-1.0.0-all.jar -o mirai-console-terminal-1.0.0.jar ``` ### 启动 mirai-console-terminal 前端 1. 下载如下三个模块的最新版本文件并放到一个文件夹内 (如 `libs`)(详见 [下载模块](#从-jcenter-下载模块)): - mirai-core-all - mirai-console - mirai-console-terminal 2. 创建一个新的文件, 名为 `start-mirai-console.bat`/`start-mirai-console.ps1`/`start-mirai-console.sh` Windows CMD: ```shell script @echo off title Mirai Console java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader %* pause ``` Windows PowerShell: ```shell script $Host.UI.RawUI.WindowTitle = "Mirai Console" java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $args pause ``` Linux: ```shell script #!/usr/bin/env bash echo -e '\033]2;Mirai Console\007' java -cp "./libs/*" net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader $* ``` 然后就可以开始使用 mirai-console 了。 #### mirai-console-terminal 前端参数 使用 `./start-mirai-console --help` 查看 mirai-console-terminal 支持的启动参数。 [mirai-repo]: https://github.com/project-mirai/mirai-repo/tree/master/shadow [mirai-core-all]: https://repo.maven.apache.org/maven2/net/mamoe/mirai-core-all/ ================================================ FILE: mirai-console/docs/plugin/JVMPlugin-Appendix.md ================================================ # Mirai Console Backend - JVM Plugins - Appendix 本页包含一些 JVM 插件的附录。 ## 依赖管理 ### API 导出管理 插件可能被其他插件依赖,插件可以将一些内部实现保护起来,避免其他插件调用。 要启动这个特性, 只需要在资源目录创建名为 `export-rules.txt` 的规则文件,便可以控制插件的类的公开规则。 如果正在使用 `Gradle` 项目, 该规则文件一般需要放置于 `src/main/resources` 下。 示例: ```text # #开头的行全部识别为注释 # exports, 允许其他插件直接使用某个类 # 导出了一个internal包的一个类 # exports org.example.miraiconsole.myplugin.internal.OpenInternal # 导出了整个 api 包 # exports org.example.miraiconsole.myplugin.api # 保护 org.example.miraiconsole.myplugin.api2.Internal, 不允许其他插件直接使用 # protects org.example.miraiconsole.myplugin.api2.Internal # 保护整个包 # # 别名: protect-package protects org.example.miraiconsole.myplugin.internal # 此规则不会生效, 因为在此条规则之前, # org.example.miraiconsole.myplugin.internal 已经被加入到保护域中 exports org.example.miraiconsole.myplugin.internal.NotOpenInternal # export-plugin, 允许其他插件使用除了已经被保护的全部类 # 使用此规则会同时让此规则后的所有规则全部失效 # 别名: export-all, export-system # export-plugin # 将整个插件放入保护域中 # 除了此规则之前显式 export 的类, 其他插件将不允许直接使用被保护的插件的任何类 # 别名: protect-all, protect-system protect-plugin ``` 插件也可以通过 Service 来自定义导出控制 Example: ```kotlin @AutoService(ExportManager::class) object MyExportManager : ExportManager { override fun isExported(className: String): Boolean { println(" <== $className") return true } } ``` ## 数据操作 ### 读取 [`PluginData`] 或 [`PluginConfig`] > 本节基于章节 [PluginData](../PluginData.md) 的内容。 > 在阅读本节前建议先阅读上述基础章节。也可以先跳过本节。 [`JvmPlugin`] 实现接口 [`AutoSavePluginDataHolder`],提供: Kotlin: - `public fun <T : PluginData> T.reload()` - `public fun <T : PluginConfig> T.reload()` Java: - `public fun reloadPluginData(PluginData)` - `public fun reloadPluginData(PluginConfig)` **仅可在插件 onEnable() 时及其之后才能使用这些方法。** **在插件 onDisable() 之后不能使用这些方法。** #### 使用示例 ```kotlin object SchedulePlugin : KotlinPlugin( JvmPluginDescription( id = "org.example.my-schedule-plugin", version = "1.0.0", ) { name("Schedule") // author("...") // dependsOn("...") } ) { // ... override fun onEnable() { MyData.reload() // 仅需此行,保证启动时更新数据,在之后自动存储数据。 } } object MyData : AutoSavePluginData() { val value: Map<String, String> by value() } ``` ================================================ FILE: mirai-console/docs/plugin/JVMPlugin-DataExchange.md ================================================ # Mirai Console Backend - JVM Plugins - Data Exchange > 本章主要介绍如何在多个插件内直接交换数据 > > 注: 多插件之间广播事件也是直接交换数据的一种方式 -------------------------- 如果需要在插件之间直接交换数据, 则插件之间必须存在直接或间接的依赖关系。 在 [Console - JvmPlugin](JVMPlugin.md#类加载隔离) 中有提到类加载隔离,没有依赖关系的插件之间是不能直接交换数据的。 > 关于如何建立依赖关系, 见 [JVMPlugin - 有关插件依赖的说明](JVMPlugin.md#有关插件依赖的说明) ## 在有依赖关系的插件中广播事件 现在系统中存在有两个插件 `com.example.guide.baseplugin` 和 `com.example.guide.extplugin`, 其中 `extplugin` 依赖 `baseplugin`。 在 `baseplugin` 中定义的类都可以在 `extplugin` 中访问 ## 在无依赖关系的两个插件中广播事件 在两个没有依赖关系的插件中, 是不能直接交换数据的, 即使使用了相同的类名也是不能进行数据交换的。 如果需要在两个没有任何关系的插件中交换数据, 需要最少三个模块: `data-typedef`, `plugin-a`, `plugin-b`, `plugin-....` ### 多插件数据交换核心思路 在多个插件间交换数据, 必须存在直接或者间接的关系, 只有建立了关系才能解析到相同的类。 两个没有关系的插件 `A` 和 `B` 之间, 必须通过另外的模块 `data-typedef` 建立起间接的关系。 比如在 `data-typedef` 中定义的事件 `MyEvent`, 即使该事件是在 A 广播的也可以在 B 监听到。 ### 通过修改插件全局类路径链接插件 > 不推荐此方法, 仅适合于高度自定义 > > - 使用此方法很可能会干扰到其他插件的执行导致其他插件报错。 > - 使用此方法很可能会遇到库冲突的情况。 将 `data-typedef.jar` 放入 `plugin-shared-libraries` 即可。 模块间的关系如下图 ```text plugin-a plugin-b others.... ...... | | | ...... | | | ...... | | | ...... ================================================================= ## data-typedef ## ........ ================================================================= | ================================================================= ## ## MIRAI CONSOLE PLUGIN SYSTEM ## ``` ### 将 data-typedef 打包成中转插件 将 `data-typedef` 也编写为一个 mirai-console 插件, 并在 `plugin-a`, `plugin-b` 中定义对 `data-typedef` 的依赖定义即可。 模块间的关系如下图 ```text plugin-a plugin-b others.... ...... | | | ...... +--- data-typedef ---+ | ...... | | ...... ================================================================= ## MIRAI CONSOLE PLUGIN SYSTEM ## ``` ## 排错 见 [JVMPlugin Debug](JVMPlugin-Debug.md) ---------------------- > 返回 [JVMPlugin](JVMPlugin.md) > > 返回 [开发文档索引](../README.md#mirai-console) ================================================ FILE: mirai-console/docs/plugin/JVMPlugin-Debug.md ================================================ # Mirai Console Backend - JVM Plugins - Debug > 建议在 Java 9+ 的环境中进行排错, mirai-console 在 java 9+ 中的错误堆栈中报告了错误类来自哪个类加载器 ## 错误堆栈基本解析 ```log java.lang.Exception: Thread stack dump at java.base/java.lang.Thread.dumpStack(Thread.java) at example-plugin.mirai2.jar[shared]//com.example.exmapleplugin.sharedlib.SharedLib.handle(shared.kt:6) at example-plugin.mirai2.jar[private]//com.example.exmapleplugin.privatelib.PrivLib.cmd(priv.kt:5) at example-plugin.mirai2.jar//com.example.exmapleplugin.MyCommand.cmd(MyCommand.kt:63) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at ...... at net.mamoe.mirai.console.command.CommandManager.executeCommand$default(CommandManager.kt:125) at chat-command-0.5.1.jar//net.mamoe.mirai.console.plugins.chat.command.PluginMain.handleCommand(PluginMain.kt:86) at chat-command-0.5.1.jar//net.mamoe.mirai.console.plugins.chat.command.PluginMain$onEnable$2$1.invokeSuspend(PluginMain.kt:69) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at ...... ``` 来自 plugin 本身的类加载器的堆栈会以 插件文件名 开头, 其中 `...[private]` `....[shared]` 都是该插件使用的类库. - `[shared]` 代表是共享库, 其中的类可以被依赖此插件的其他插件解析到 - `[private]` 代表是私有库, 仅该插件自己内部使用, 依赖此插件的其他插件将不能解析到此类加载器的类 ## 多插件间数据交换结果和预期不符合 多插件间数据结果不一致 90% 是因为缺少依赖关系导致的未解析到相同的类导致结果不一致 关于如何建立关系, 见 [JVMPlugin - Data Exchange](./JVMPlugin-DataExchange.md) 可以使用以下代码确定是否是因为类链接错误导致的数据不一致 ```kotlin fun MiraiLogger.dumpClass(klass: Class<*>) { info { "Class name: $klass" } info { " |- loader: ${klass.classLoader}" } info { " |- module: ${klass.module}" } } ``` ```java public static void dumpClass(MiraiLogger logger, Class<?> klass) { logger.info("Class name: " + klass); logger.info(" |- loader: " + klass.getClassLoader()); logger.info(" |- module: " + klass.getModule()); } ``` ## 使用的第三方库报错没有模块实现 在插件初始化的时候, 线程上下文类加载器依然还是 console 的系统类加载器 (`AppClassLoader`), 需要手动将其切换到插件的类加载器 详见 [Issue Comment](https://github.com/mamoe/mirai/issues/2138#issuecomment-1179673302) ```kotlin fun onEnable() { val oThreadCtxLoader = Thread.currentThread().contextClassLoader try { Thread.currentThread().contextClassLoader = javaClass.classLoader // ....... } finally { Thread.currentThread().contextClassLoader = oThreadCtxLoader } } ``` ## 使用依赖库后无法加载插件 / clinit 无法使用依赖库 错误类似 ```log 2023-12-08 00:23:42 E/main: Failed to init MiraiConsole. net.mamoe.mirai.console.internal.util.ServiceLoadException: Could not load service com.example.exmapleplugin.MyPlugin at .... Caused by: java.lang.NoClassDefFoundError: com/example/somelibrary/ClassFromLibrary at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Class.java:467) at net.mamoe.mirai.console.internal.util.PluginServiceHelper.loadService(PluginServiceHelper.kt:51) Caused by: java.lang.NoClassDefFoundError: org/quartz/SchedulerException ... 23 more Caused by: java.lang.ClassNotFoundException: com/example/somelibrary/ClassFromLibrary at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188) Caused by: java.lang.ClassNotFoundException: com.example.somelibrary.ClassFromLibrary at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520) at net.mamoe.mirai.console.internal.plugin.JvmPluginClassLoaderN.loadClass(JvmPluginClassLoader.kt:389) ... 26 more ``` 此原因是因为 clinit 阶段时 mirai-console 还未加载依赖库至插件类搜索路径中。 如果您使用 mirai-console 2.16.0+ 请创建 `plugin.yml`, mirai-console 才能将依赖库在 clinit 阶段前加载到插件类搜索路径,见 [JVMPlugin - 通过资源文件提供静态信息](./JVMPlugin.md#%E9%80%9A%E8%BF%87%E8%B5%84%E6%BA%90%E6%96%87%E4%BB%B6%E6%8F%90%E4%BE%9B%E9%9D%99%E6%80%81%E4%BF%A1%E6%81%AF) 如果您使用 mirai-console 2.160 之前的版本, 请创建一个新的类,此类不要包含依赖库的代码,然后将此类的代码转移到您的真正的逻辑代码 示例 ```kotlin object OuterPlugin: KotlinPlugin(...) { override fun onEnable() { ActuallyPluginClassLoader.onEnable() } } object ActuallyPluginClassLoader { fun onEnable() { // ..... } } ``` > 底层分析 > > 为了实现插件之间的相互依赖,mirai-console 必须获取到插件的信息 (PluginDescription) 才能进行插件类路径链接操作 > > 在 mirai-console 2.16.0 之前,插件加载顺序为 > > - 加载插件主类 (即 clinit 阶段) > - 加载插件实例 (传递 PluginDescription 给 mirai-console) > - mirai-console 构建插件依赖关系,链接插件类路径 > - 执行插件的 onEnable > > 在 2.16.0+, 存在 plugin.yml 时, 插件加载的顺序为 > > - 加载 plugin.yml > - 构建插件依赖关系,链接插件类路径 > - 加载插件主类 > - 加载插件实例 > - 执行 onEnable ## java.lang.LinkageError: loader constraint violation ```log java.lang.LinkageError: loader constraint violation: when resolving method 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)' the class loader 'test-ktor-dev.mirai2.jar' @61dde151 of the current class, com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2, and the class loader 'app' for the method's defining class, io/ktor/client/request/HttpRequestBuilder, have different Class objects for the type io/ktor/http/HttpMethod used in the signature (com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 is in unnamed module of loader 'test-ktor-dev.mirai2.jar' @61dde151, parent loader 'global-shared' @32b9bd12; io.ktor.client.request.HttpRequestBuilder is in unnamed module of loader 'app') at ................. java.lang.LinkageError: loader constraint violation: when resolving method 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)' the class loader 'test-ktor-dev.mirai2.jar' @61dde151 of the current class, com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2, and the class loader 'app' for the method's defining class, io/ktor/client/request/HttpRequestBuilder, have different Class objects for the type io/ktor/http/HttpMethod used in the signature ( com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 is in unnamed module of loader 'test-ktor-dev.mirai2.jar' @61dde151, parent loader 'global-shared' @32b9bd12; io.ktor.client.request.HttpRequestBuilder is in unnamed module of loader 'app' ) ``` 翻译 ```log JVM 无法解析 com/kasukusakura/testktor/TestKtor$getTailrec$$inlined$get$2 中的方法引用 'void io.ktor.client.request.HttpRequestBuilder.setMethod(io.ktor.http.HttpMethod)' 搜索到的 'io.ktor.client.request.HttpRequestBuilder' 位于系统类加载器 'app' 中 HttpRequestBuilder 中的 HttpMethod 引用和 TestKtor$getTailrec$$inlined$get$2 得到的 HttpMethod 引用 不一致, 无法连接 ``` 结论: 插件没有附带完整的 ktor 依赖导致部分类解析至插件库加载器, 部分类解析至系统类加载器 ================================================ FILE: mirai-console/docs/plugin/JVMPlugin.md ================================================ # Mirai Console Backend - JVM Plugins [`Plugin`]: ../../backend/mirai-console/src/plugin/Plugin.kt [`PluginDescription`]: ../../backend/mirai-console/src/plugin/description/PluginDescription.kt [`PluginLoader`]: ../../backend/mirai-console/src/plugin/loader/PluginLoader.kt [`PluginManager`]: ../../backend/mirai-console/src/plugin/PluginManager.kt [`JvmPluginLoader`]: ../../backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt [`JvmPlugin`]: ../../backend/mirai-console/src/plugin/jvm/JvmPlugin.kt [`JvmPluginDescription`]: ../../backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt [`AbstractJvmPlugin`]: ../../backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt [`KotlinPlugin`]: ../../backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt [`JavaPlugin`]: ../../backend/mirai-console/src/plugin/jvm/JavaPlugin.kt [`PluginData`]: ../../backend/mirai-console/src/data/PluginData.kt [`PluginConfig`]: ../../backend/mirai-console/src/data/PluginConfig.kt [`PluginDataStorage`]: ../../backend/mirai-console/src/data/PluginDataStorage.kt [`ExportManager`]: ../../backend/mirai-console/src/plugin/jvm/ExportManager.kt [`MiraiConsole`]: ../../backend/mirai-console/src/MiraiConsole.kt [`MiraiConsoleImplementation`]: ../../backend/mirai-console/src/MiraiConsoleImplementation.kt <!--[MiraiConsoleFrontEnd]: ../../backend/mirai-console/src/MiraiConsoleFrontEnd.kt--> [`Command`]: ../../backend/mirai-console/src/command/Command.kt [`CompositeCommand`]: ../../backend/mirai-console/src/command/CompositeCommand.kt [`SimpleCommand`]: ../../backend/mirai-console/src/command/SimpleCommand.kt [`RawCommand`]: ../../backend/mirai-console/src/command/RawCommand.kt [`CommandManager`]: ../../backend/mirai-console/src/command/CommandManager.kt [`Annotations`]: ../../backend/mirai-console/src/util/Annotations.kt [`ConsoleInput`]: ../../backend/mirai-console/src/util/ConsoleInput.kt [`JavaPluginScheduler`]: ../../backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt [`ResourceContainer`]: ../../backend/mirai-console/src/plugin/ResourceContainer.kt [`PluginFileExtensions`]: ../../backend/mirai-console/src/plugin/PluginFileExtensions.kt [`AutoSavePluginDataHolder`]: ../../backend/mirai-console/src/data/PluginDataHolder.kt [MiraiLogger]: ../../../mirai-core-api/src/commonMain/kotlin/utils/MiraiLogger.kt [Kotlin]: https://www.kotlincn.net/ [Java]: https://www.java.com/zh_CN/ [JVM]: https://zh.wikipedia.org/zh-cn/Java%E8%99%9A%E6%8B%9F%E6%9C%BA [JAR]: https://zh.wikipedia.org/zh-cn/JAR_(%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F) [为什么不支持热加载和卸载插件?]: ../QA.md#为什么不支持热加载和卸载插件 [使用 AutoService]: ../QA.md#使用-autoservice [MCI]: ../../tools/intellij-plugin/ [MiraiPixel]: ../../tools/intellij-plugin/resources/icons/pluginMainDeclaration.png 本章节介绍 JVM 平台的插件。 ## JVM 平台插件接口 - [`JvmPlugin`] 所有的 JVM 插件(特别地,打包为 `JAR` 的)都必须实现 [`JvmPlugin`],否则不会被 [`JvmPluginLoader`] 识别和加载。 [`JvmPlugin`] 派生为 [`KotlinPlugin`] 和 [`JavaPlugin`],其关系图如下所示。 ![](JVMPlugin_images/75227ef5.png) 其中 `AbstractJvmPlugin` 是 Console 提供的基础实现,如数据目录等。 ## 主类 JVM 平台插件的主类应被实现为一个单例(Kotlin `object`,Java 静态初始化的类,详见下文示例)。 **Kotlin 使用者的插件主类应继承 [`KotlinPlugin`]。** **其他 JVM 语言(如 Java)使用者的插件主类应继承 [`JavaPlugin`]。** ### 定义主类 Mirai Console 使用类似 Java `ServiceLoader` 但更灵活的机制加载插件。 一个正确的主类定义可以是以下三种任一。注意 "描述" 将会在[下文](#描述)解释。 1. Kotlin `object` ```kotlin object A : KotlinPlugin( /* 描述 */) ``` 2. Java 静态初始化单例 `class` ```java public final class A extends JavaPlugin { public static final A INSTANCE = new A(); // 必须 public static, 必须名为 INSTANCE private A() { super( /* 描述 */); } } ``` 3. Java `class` 使用这种方法时,插件实例由 Console 在合适的时机创建。 ```java public final class A extends JavaPlugin { public A() { // 必须公开且无参 super( /* 描述 */); } } ``` ### 确认主类正确定义 在 [IDE 插件][MCI] 的帮助下,一个正确的插件主类定义的行号处会显示 Mirai 像素风格形象图:![MiraiPixel] ![PluginMainDeclaration](images/PluginMainDeclaration.png) ### 配置主类服务 #### 自动配置主类服务 [IDE 插件][MCI] 会自动检查主类服务的配置。在没有正确配置时,IDE 将会警告并为你自动配置: ![PluginMainServiceNotConfigured](images/PluginMainServiceNotConfigured.png) #### 手动配置主类服务 若无法使用 IntelliJ 插件,可在资源目录 `META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件内存放插件主类全名(以纯文本 UTF-8 存储,文件内容只包含一行插件主类全名或以 `#` 起始的注释)。 也可以使用各种自动化工具配置 service 信息,如使用 Google 的 [AutoService](https://github.com/google/auto/tree/master/service) 。附[在 Kotlin 使用的方法][使用 AutoService]。 > 备注:Console 虽然使用这种常用的 service 配置方式,但不使用 ServiceLoader 加载插件实例。Console > 的加载方式更灵活。 ## 描述 插件主类需要提供一份描述信息。 插件可以通过资源文件提供静态的信息,也可以通过构造器动态传递。 描述拥有如下属性: [语义化版本 2.0.0]: https://semver.org/lang/zh-CN/ | 属性 | 可空 | 备注 | |--------------|-----|-----------------------------| | id | 否 | 唯一识别标识符,仅能包含英文字母、数字、`.`、`-` | | version | 否 | 版本号,见补充说明 | | name | 是 | 供用户阅读的名称,可包含任意字符 | | author | 是 | 作者信息 | | dependencies | 是 | 依赖其他插件的 ID 及版本范围 | | info | 是 | 供用户阅读的描述信息 | ### 有关插件版本号的补充说明 - 插件自身的版本要求遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范,合格的版本例如:`1.0.0`、`1.0`、`1.0-M1`、`1.0-pre-1`; ### 有关插件依赖的说明 - 插件依赖的版本遵循 [语义化版本 2.0.0](https://semver.org/lang/zh-CN/) 规范,同时支持 [Apache Ivy 风格表示方法](http://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html) 。 在定义时需使用 ID,因为 ID 是唯一的。不支持使用名称(name)。 定义必须依赖 net.mamoe.chat-command: ```yaml dependencies: - "net.mamoe.chat-command" ``` 定义可选依赖 net.mamoe.chat-command: ```yaml dependencies: - "net.mamoe.chat-command?" ``` 定义必须依赖 1.0 及以上, 2.0 (不包含)以下的 net.mamoe.chat-command 插件: ```yaml dependencies: - "net.mamoe.chat-command:[1.0, 2.0)" ``` 定义可选依赖 1.0 及以上, 2.0 (不包含)以下的 net.mamoe.chat-command 插件: ```yaml dependencies: - "net.mamoe.chat-command:[1.0, 2.0)?" ``` 插件的依赖将在下文 [依赖其他插件][#依赖其他插件] 详细说明。 ### 通过资源文件提供静态信息 在资源文件目录(如 `src/main/resources`)创建 `plugin.yml`,并填写上述属性,示例: ```yaml id: net.mamoe.chat-command version: 0.3.0 name: Chat Command author: Mamoe Technologies dependencies: [ ] # 或者不写这行 info: 允许在聊天环境执行指令。 ``` 然后在插件主类指定从资源加载: *Kotlin* ```kotlin object A : KotlinPlugin(JvmPluginDescription.loadFromResource()) ``` *Java* ```java public final class JExample extends JavaPlugin { public static final JExample INSTANCE = new JExample(); private JExample() { super(JvmPluginDescription.loadFromResource()); } } ``` 注意,尽管 `loadFromResource` 支持读取任意路径的文件,但也建议使用默认的 `plugin.yml` 。因为目前有计划修改插件信息的读取方式,这种 `plugin.yml` 的读取方式很有可能会继续得到支持。 ### 在构造器动态提供 *注意:由于目前有计划修改插件信息的读取方式,这种构造器动态提供的方法已不再推荐。* 在构造器动态构造 `JvmPluginDescription`: *Kotlin* ```kotlin object MyPlugin : KotlinPlugin( JvmPluginDescription( // 必要属性 id = "net.mamoe.chat-command", version = "1.0.0", ) { // 非必须属性 name("Chat Command") // author("...") // dependsOn("...") // 与 YAML 方式类似,如 "net.mamoe.chat-command:[1.0, 2.0)?" } ) ``` *Java* ```java public final class JExample extends JavaPlugin { public static final JExample INSTANCE = new JExample(); private JExample() { super(new JvmPluginDescriptionBuilder( // 必要属性 "org.example.test-plugin", // id "1.0.0" // version ).author("...") // 可选属性,可以不提供, 直接 build .info("...") .build() ); } } ``` ## 使用日志 Console 为插件提供一个 [`MiraiLogger`][MiraiLogger]: *Kotlin* ```kotlin object MyPluginMain : KotlinPlugin( /* ... */) { override fun onEnable() { logger.info { "一条 INFO 级别的日志" } // 当日志被用户启用时才会执行 lambda 内代码并记录日志 logger.debug { "一条 DEBUG 级别的日志" } // DEBUG 级别 } } ``` *Java* ```java public final class JExample extends JavaPlugin { public static final JExample INSTANCE = new JExample(); private JExample() { // ... } @Override public void onEnable() { if (getLogger().isInfoEnabled()) { getLogger().info("一条 INFO 级别的日志"); // 当日志被用户启用时才会执行 } } } ``` 有关日志的配置方式可以参考 [Logging](../Logging.md) ## 插件生命周期与依赖管理 每个 JVM 插件有如下状态:初始化、加载、启用、禁用。 [//]: # (预加载、) [//]: # (- 预加载:Console 识别插件的静态依赖信息。) - 初始化:插件主类由插件加载器识别并创建实例(如有必要)。插件的依赖将完成链接(**因此在初始化之后才允许调用依赖**); - 加载:插件加载器已经识别了所有的插件并根据依赖关系确定了加载顺序。这时插件的 `onLoad()` 回调将会被调用,插件做一次性初始化工作; - 启用:插件加载器已经加载了所有的插件并调用它们的 `onLoad()`。这时插件的 `onEnable()` 回调将会被调用,插件开始正常工作; - 禁用:当用户要求关闭某个插件,或者 Console 正在关闭时插件会被禁用,`onDisable()` 回调将会被调用,插件停止一切在启用状态时创建的工作。 每个插件一定会经历初始化和加载,但可能会经历零次到任意次启用和禁用。根据不同 Console 前端的行为和用户的设置,插件可能只会加载不会启用,也有可能会在禁用后被重新启用。因此插件必须只在 `onEnable` 时创建事件监听等任务,且在禁用时停止这些任务。 多个插件的加载是*顺序的*,意味着若一个插件的 `onLoad` 等回调处理缓慢,后续插件的加载也会被延后,即使它们可能没有依赖关系。因此需要让 `onLoad`,`onEnable`,`onDisable` 快速返回。 ### 有依赖时的加载顺序 若插件 A 需要调用另一个插件 B,那么就认为 A 依赖 B。当存在依赖关系时,被依赖的插件(B)将总是会早于(A)加载。这适用于所有上述回调,即 B 的 `onLoad`、`onEnable` 会早于 A 调用,而 `onDisable` 会晚于 A 调用。 ### 不支持热加载和热卸载 Mirai Console 不提供热加载和热卸载功能,所有插件只能在服务器启动前加载,在 Console 停止时卸载。([为什么不支持热加载和卸载插件?]) 只有当插件 A 在其描述中定义了依赖时,才会被允许调用 B 的接口。要了解如何定义,可参考上文 [有关插件依赖的说明]。 ### Kotlin 协程生命周期管理 [`JvmPlugin`] 实现 `CoroutineScope`,并由 Console 内部实现提供其 `coroutineContext`。 **所有插件启动的协程都应该受 `JvmPlugin` 作用域的管理**,如要启动一个协程,正确的做法是: ```kotlin object MyPluginMain : KotlinPlugin( /* ... */) { override fun onEnable() { // 即 MyPluginMain.launch,在当前协程作用域创建任务。 launch { delay(1000) println("一秒钟过去了。") } // globalEventChannel 在当前协程作用域创建事件通道,会在 onDisable 自动关闭。 globalEventChannel().subscribeAlways<MemberLeaveEvent> { println("有人离开了群。") } } } ``` ### Java 线程生命周期管理 插件创建的所有线程或异步任务都需要在 `onDisable()` 时关闭。 [JavaPluginScheduler]: ../../backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt 若要执行简单的异步、延迟、重复任务,可使用 `getScheduler()` 获取到简单任务调度器。示例: ```java public final class JExample extends JavaPlugin { public static final JExample INSTANCE = new JExample(); private JExample() { // ... } @Override public void onEnable() { getScheduler().delayed(1000L, () -> System.out.println("一秒钟过去了。")); } } ``` 详细查看 [JavaPluginScheduler]。 ### 控制插件类路径 [JvmPluginClasspath]: ../../backend/mirai-console/src/plugin/jvm/JvmPluginClasspath.kt Mirai Console 支持动态按需下载依赖和按需链接依赖 (通过 `JvmPluginClasspath.addToPath` 和 `JvmPluginClasspath.downloadAndAddToPath`) `JvmPluginClasspath` 还支持控制是否应该引用其他插件的类路径 & 是否允许其他非依赖此插件的插件使用此插件的类路径 *Java* (Kotlin 类似) ```java public final class JExample extends JavaPlugin { //...... @Override public void onLoad(PluginComponentStorage storage) { getLogger().info(String.valueOf(getJvmPluginClasspath().getShouldResolveIndependent())); getJvmPluginClasspath().addToPath( getJvmPluginClasspath().getPluginIndependentLibrariesClassLoader(), resolveDataFile("mylib.jar") ); } } ``` 详细查看 [JvmPluginClasspath] #### 通过配置文件控制类路径选项 [JvmPluginClasspath] 中的部分选项可以通过配置文件指定, 虽然在代码中也可以修改, 但是通过配置文件指定是最好的。 > 因为如果在代码中修改, 类链接会在选项修改之前完成,从而导致一些不正常的逻辑 要使用配置文件控制 JvmPluginClasspath 中的选项, 需要创建名为 `META-INF/mirai-console-plugin/options.properties` 的资源文件 > 通常情况这个文件的位置是 `src/main/resources/META-INF/mirai-console-plugin/options.properties` > > 如果没有资源文件夹, Intellij IDEA 在创建文件夹时会提示 resources 补全 > > ![CreateResourcesDir](./images/CreateResourcesDir.png) 选项的键值已经在 [JvmPluginClasspath] 源文件中使用 `@SettingProperty` 注明 示例: ```properties # suppress inspection "UnusedProperty" for whole file resources.resolve-console-system-resources=false class.loading.be-resolvable-to-independent=false class.loading.resolve-independent=false ``` ## 访问数据目录和配置目录 [`JvmPlugin`] 实现接口 [`PluginFileExtensions`]。插件可通过 `resolveDataFile` ,`resolveConfigFile` 等方法取得数据目录或配置目录下的文件。 - 数据目录(dataFolder)用来存放只供插件内部读取的数据; - 配置目录(configFolder)用来存放可由用户修改的配置。 可以在任何时刻使用这些方法。 详见 [`PluginFileExtensions`]。 *Java* (Kotlin 类似) ```java public final class JExample extends JavaPlugin { public static final JExample INSTANCE = new JExample(); private JExample() { // ... } @Override public void onEnable() { File dataFile = resolveDataFile("myDataFile.txt"); File configFile = resolveConfigFile("myConfigFile.txt"); } } ``` ### 物理目录路径 用 `$root` 表示 Mirai Console 运行路径,`$name` 表示插件名, 在终端前端(Terminal),插件数据目录一般在 `$root/data/$name` ,插件配置目录一般在 `$root/config/$name`。**但是插件不应该依赖这些物理路径,因为在其他前端上没有保证。** ### 访问 [JAR] 包内资源文件 [`JvmPlugin`] 实现接口 [`ResourceContainer`]。插件可通过 `getResource` ,`getResourceAsStream` 等取得插件 [JAR] 包内资源文件。 可以在任何时刻使用这些方法。 详见 [`ResourceContainer`]。 ## 构建 构建 mirai-console 插件推荐使用 [mirai-console-gradle](../../tools/gradle-plugin/README.md) (若使用推荐方式创建项目则默认使用)。 > 要执行一个名为 `taskName` 的 Gradle 任务,可在项目目录使用 `./gradlew taskName`。 ### 打包和分发插件 执行 Gradle 任务 [`buildPlugin`](../../tools/gradle-plugin/README.md#打包依赖) 即可打包后缀为 `.mirai2.jar` 的插件 JAR。打包结果输出在 `build/mirai/`。 这个文件就是插件的可分发文件。可以放入 Console `plugins` 目录中使用。 #### 自 2.11 的变更 在 2.11 以前,插件文件后缀为 `.mirai.jar`,而在 2.11 时插件文件后缀修改为了 `.mirai2.jar` 。这是因为依赖打包机制有修改。2.11 起不再打包全部的依赖,而是只打包必要和开发者强制指定的(详见 [插件依赖打包机制](#插件依赖打包机制) )。 - Console 2.11 及以上在扫描插件时若同时发现 `.mirai.jar` 和 `.mirai2.jar` ,只会加载 `.mirai2.jar`。 - Console 2.11 以前则会都加载。 由于 2.10 及以前版本编译的插件也能在 2.11 运行,用户可以顺利地从 2.10 升级到 2.11。但无法直接从 2.11 降级到 2.10,降级时需要删除 `.mirai2.jar`。 ### 插件依赖打包机制 在打包插件时, 所有使用的*外部依赖*<sup>(1)</sup> 都将会存放于 JAR 内的一个*依赖列表*<sup>(2)</sup>中。 依赖将会在用户侧 Console 启动时从[远程仓库](#默认的远程仓库列表)下载并加载。 特别地,直接依赖的本地 JAR 和子项目依赖将会直接打包进插件 JAR。 > 注释 > - (1): 外部依赖, 即来自 Maven Central 的依赖, 如 `net.mamoe:mirai-core-api` > - (2): 具体文件路径 `META-INF/mirai-console-plugin/dependencies-private.txt` > - (3): 包括直接依赖的本地 JAR (如 `fileTree('libs')`), 子项目 (`project(':subproject')`) 和其他显式声明直接打包的依赖, 更多见 [mirai-console-gradle](../../tools/gradle-plugin/README.md) #### 默认的远程仓库列表 - Maven Central:[repo.maven.apache.org](https://repo.maven.apache.org/maven2/) - 阿里云 Maven Central 中国代理:[maven.aliyun.com](https://maven.aliyun.com/repository/central) 默认优先使用阿里云代理仓库,在失败时使用 Maven Central。注意,使用阿里云仓库是不受保证的 – 将来可能会换用其他仓库,但 Maven Central 官方仓库不会删除(除非用户在配置中删除,当然这不属于开发插件时的考虑范畴)。 用户也可以在配置中自行定义代理仓库,但插件开发者不应该期望于用户自行添加包含插件使用的依赖的仓库。**所以插件只应该使用在 Maven Central 的依赖,且将使用的其他所有依赖[打包](#调整依赖打包策略)到插件 JAR。** ### 类加载隔离 插件仅能访问自身项目、依赖的库、Console、以及在描述中定义了的依赖的插件。每个插件依赖的库都会被隔离,不同插件可以使用不同版本的同一个库,互不冲突。** 但不应该在依赖另一个插件时使用不同版本的依赖,这可能导致不确定的后果。** #### 优先加载插件自身及依赖 在有潜在类冲突时,将优先选择插件内部实现以及定义的依赖。每个类的搜索顺序是: - 插件自身以及[强制打包](#调整依赖打包策略)的依赖 - 插件定义的仓库依赖 - 插件定义的其他插件依赖及它们的依赖(间接) - mirai-console 及 mirai-core 使用的公开依赖,包含: - kotlin-reflect - kotlinx-coroutines-jdk8 - kotlinx-serialization-json - ktor-client-okhttp > 这些依赖的版本可在 [buildSrc](../../../buildSrc/src/main/kotlin/Versions.kt) 查看 ### 调整依赖打包策略 要强制打包或忽略某个依赖,请参阅 [Gradle 插件文档](../../tools/gradle-plugin/README.md#打包依赖) 。 要了解什么情况下需要强制打包,请参阅 [插件依赖打包机制](#插件依赖打包机制)。 ## 调试 [mirai-console-gradle](../../tools/gradle-plugin/README.md) 提供了在 IDEA 等 IDE 中调试插件的功能, 运行 Gradle 任务 `runConsole` 即可启动完整的 mirai-console。 > mirai-console 测试时默认在 `/debug-sandbox` 运行。 > > 可在 `.gitignore` 中添加 `/debug-sandbox` 规则以避免测试环境被提交至 Git。 使用 IDEA 创建的项目可在 `Run Configurations` 找到 `Run Mirai Console`。 ![PluginDebugRunConfiguration.png](images/PluginDebugRunConfiguration.png) 可在 IDEA 右侧 `Gradle` 中找到 Gradle 任务 `runConsole`。 ![PluginDebugGradleTask.png](images/PluginDebugGradleTask.png) 运行后即可看见如下图所示的 mirai-console: ![PluginDebugWindowPreview](images/PluginDebugWindowPreview.webp) > 如需进行调试, 请使用运行 (绿色三角形) 旁边的 `Debug` 按钮. > 也可以在 Gradle 工具栏找到 `runConsole` 并右键选择 `Debug`。 > > 如果没法输入命令, 请确认 Gradle 任务视图没有被聚焦至 `:runConsole`, > 必须选择整个 Gradle 任务视图才可执行命令。 ### 排错 详见 [JVMPlugin Debug](JVMPlugin-Debug.md) ## 发布插件到 mirai-console-loader 插件中心仍在开发中。 ## 多插件间数据交换 见 [JVMPlugin - Data Exchange](JVMPlugin-DataExchange.md) > 下一步,[Commands](../Commands.md#mirai-console-backend---commands) > > 返回 [开发文档索引](../README.md#mirai-console) ================================================ FILE: mirai-console/docs/plugin/Plugins.md ================================================ # Mirai Console Backend - Plugins [`Plugin`]: ../../backend/mirai-console/src/plugin/Plugin.kt [`PluginDescription`]: ../../backend/mirai-console/src/plugin/description/PluginDescription.kt [`PluginLoader`]: ../../backend/mirai-console/src/plugin/loader/PluginLoader.kt [`PluginManager`]: ../../backend/mirai-console/src/plugin/PluginManager.kt [`JvmPluginLoader`]: ../../backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt [`JvmPlugin`]: ../../backend/mirai-console/src/plugin/jvm/JvmPlugin.kt [`JvmPluginDescription`]: ../../backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt [`AbstractJvmPlugin`]: ../../backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt [`KotlinPlugin`]: ../../backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt [`JavaPlugin`]: ../../backend/mirai-console/src/plugin/jvm/JavaPlugin.kt [`PluginData`]: ../../backend/mirai-console/src/data/PluginData.kt [`PluginConfig`]: ../../backend/mirai-console/src/data/PluginConfig.kt [`PluginDataStorage`]: ../../backend/mirai-console/src/data/PluginDataStorage.kt [`ExportManager`]: ../../backend/mirai-console/src/plugin/jvm/ExportManager.kt [`MiraiConsole`]: ../../backend/mirai-console/src/MiraiConsole.kt [`MiraiConsoleImplementation`]: ../../backend/mirai-console/src/MiraiConsoleImplementation.kt [`Command`]: ../../backend/mirai-console/src/command/Command.kt [`CompositeCommand`]: ../../backend/mirai-console/src/command/CompositeCommand.kt [`SimpleCommand`]: ../../backend/mirai-console/src/command/SimpleCommand.kt [`RawCommand`]: ../../backend/mirai-console/src/command/RawCommand.kt [`CommandManager`]: ../../backend/mirai-console/src/command/CommandManager.kt [`Annotations`]: ../../backend/mirai-console/src/util/Annotations.kt [`ConsoleInput`]: ../../backend/mirai-console/src/util/ConsoleInput.kt [`JavaPluginScheduler`]: ../../backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt [`ResourceContainer`]: ../../backend/mirai-console/src/plugin/ResourceContainer.kt [`PluginFileExtensions`]: ../../backend/mirai-console/src/plugin/PluginFileExtensions.kt [`AutoSavePluginDataHolder`]: ../../backend/mirai-console/src/data/PluginDataHolder.kt#L45 [Kotlin]: https://www.kotlincn.net/ [Java]: https://www.java.com/zh_CN/ [JVM]: https://zh.wikipedia.org/zh-cn/Java%E8%99%9A%E6%8B%9F%E6%9C%BA [JAR]: https://zh.wikipedia.org/zh-cn/JAR_(%E6%96%87%E4%BB%B6%E6%A0%BC%E5%BC%8F) [使用 AutoService]: ../QA.md#使用-autoservice [JVMPlugin]: ./JVMPlugin.md Mirai Console (简称 Console) 运行在 [JVM],支持使用 [Kotlin] 或 [Java] 等 JVM 语言编写的插件。 本章节简要介绍 Console 插件架构(与平台无关的基础架构)。 ## 通用的插件接口 - [`Plugin`] 所有 Console 插件都必须直接或间接地实现 [`Plugin`] 接口。 > **解释 *插件***:只要实现了 [`Plugin`] 接口的对象都可以叫做「Mirai Console 插件」,简称 「插件」。 > 为了便捷,内含 [`Plugin`] 实现的一个 [JAR] 文件也可以被称为「插件」。 基础的 [`Plugin`] 很通用,它只拥有很少的成员: ```kotlin interface Plugin : CommandOwner { // CommandOwner 表示该对象可以创建指令 val isEnabled: Boolean // 当插件已开启时返回 true val loader: PluginLoader<*, *> // 能处理这个 Plugin 实例的 PluginLoader } ``` [`Plugin`] 接口拥有强扩展性,以支持 Mirai Console 统一管理使用其他编程语言编写的插件 (详见进阶章节 [扩展 - PluginLoader](../Extensions.md))。 > 除非你是在实现新种类插件,否则不要直接实现 `Plugin` 接口。 ## 插件加载器 - [`PluginLoader`] Mirai Console 支持使用多个插件加载器来加载多种类型插件。每个插件加载器都支持一种类型的插件。 Mirai Console 内置 [`JvmPluginLoader`] 以加载 JVM 平台插件(见下文),并允许这些插件注册扩展的插件加载器(见章节 [扩展](../Extensions.md)) ,以支持读取其他语言编写的插件并接入 Console 插件管理系统。 ## 总结 Mirai Console 提供抽象的插件及其加载器接口,支持扩展。各类插件行为由其加载器确定。插件作者需要基于特定的插件平台实现,如 Console 内置的 [JVM 平台][JVMPlugin]。 ## 继续阅读 - [JVM 平台插件详情][JVMPlugin] - [编写插件加载器](../Extensions.md) ================================================ FILE: mirai-console/frontend/mirai-android/.github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: 报告一个bug title: "[BUG]" labels: bug assignees: '' --- <!-- 请确认你已经做了下面这些事情,若问题还是未解决,那么请尽可详细地描述你的问题。 - 我已经对照 CHANGELOG ,相关问题未在近期更新中解决 - 我已经搜索了已有的 Issues 列表中有没相关的信息 - 我已经阅读了 MiraiAndroid 的相关文档 - 我使用的是最新release版本 MiraiAndroid - 我确定这个问题不是Mirai协议支持库的问题 --> <!--在下面空白处简略描述你遇到的问题--> <!--如果有控制台报错,请尽量在下面空白处附加全面的日志. (请使用右上角的分享日志功能)--> <!--如果程序崩溃,请使用上传日志功能--> ``` ``` #### 复现 <!--在这里简略说明如何让这个问题再次发生--> <!--可使用 1. 2. 3. 的列表格式,或其他任意恰当的格式--> <!--如有必要,你可以在下文继续添加其他信息--> ================================================ FILE: mirai-console/frontend/mirai-android/.github/workflows/android.yml ================================================ name: Android Build on: pull_request: branches: - 'master' push: branches: - 'master' jobs: build: name: Run Build runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v1 - name: set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - name: Unit tests run: bash ./gradlew build #jobs: # test: # runs-on: macos-latest # steps: # - name: checkout # uses: actions/checkout@v3 # # - name: run tests # uses: reactivecircus/android-emulator-runner@v2 # with: # api-level: 29 # script: bash ./gradlew connectedCheck # jobs: # # test: # # name: Run Unit Tests # # runs-on: ubuntu-18.04 # # steps: # # - uses: actions/checkout@v1 # # - name: set up JDK 1.8 # # uses: actions/setup-java@v1 # # with: # # java-version: 1.8 # # - name: Unit tests # # run: bash ./gradlew test --stacktrace # apk: # name: Generate APK # runs-on: ubuntu-18.04 # steps: # - uses: actions/checkout@v1 # - name: set up JDK 1.8 # uses: actions/setup-java@v1 # with: # java-version: 1.8 # - name: Build debug APK # run: bash ./gradlew assembleDebug --stacktrace # - name: Upload APK # uses: actions/upload-artifact@v1 # with: # name: MiraiAndroid # path: app/build/outputs/apk/debug/app-debug.apk ================================================ FILE: mirai-console/frontend/mirai-android/.gitignore ================================================ *.iml .gradle /local.properties /.idea/caches /.idea/libraries /.idea/modules.xml /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store /build /captures .externalNativeBuild .cxx app/release ================================================ FILE: mirai-console/frontend/mirai-android/LICENSE ================================================ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/> Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. <one line to give the program's name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <https://www.gnu.org/licenses/>. Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>. ================================================ FILE: mirai-console/frontend/mirai-android/README.md ================================================ <!-- * @Descripttion: * @version: * @Author: sueRimn * @Date: 2020-05-08 16:45:00 * @LastEditors: sueRimn * @LastEditTime: 2020-05-09 12:22:15 --> <div align="center"> <img width="160" src="https://cdn.jsdelivr.net/gh/mzdluo123/blog_imgs/img/20200531205703.png" alt="logo"></br> <img width="95" src="https://cdn.jsdelivr.net/gh/mzdluo123/blog_imgs/img/20200531205726.png" alt="title"> </div> # MiraiAndroid <img alt="GitHub Workflow Status" src="https://img.shields.io/github/workflow/status/mzdluo123/MiraiAndroid/Android Build?style=flat-square"> <img alt="GitHub issues" src="https://img.shields.io/github/issues/mzdluo123/MiraiAndroid?style=flat-square"> <img alt="GitHub pull requests" src="https://img.shields.io/github/issues-pr/mzdluo123/MiraiAndroid?style=flat-square"> mirai-console的Android前端程序,可作为qq机器人使用,支持多种脚本接口 关于mirai项目以及mirai-console的一切请点击[这里](https://github.com/mamoe/mirai) 相比使用`Termux`或者是`Linux Deploy`等应用运行mirai的方案,该项目提供的方案具有更好的性能以及更少的资源占用,但可能存在兼容性问题 最新的构建版本你可以到release或QQ群内找到 MiraiAndroid交流群:`1131127734`但是请注意,如果您违反了群内相关规定或是有其他不当行为你可能会被无理由移出本群 图标以及形象由画师<a href = "https://github.com/DazeCake">DazeCake</a>绘制 ## 声明 ### 一切开发旨在学习,请勿用于非法用途 - MiraiAndroid 是完全免费且开放源代码的软件,仅供学习和娱乐用途使用 - MiraiAndroid 不会通过任何方式强制收取费用,或对使用者提出物质条件 - MiraiAndroid 由整个开源社区维护,并不是属于某个个体的作品,所有贡献者都享有其作品的著作权。 ### 许可证 Copyright (C) 2019-2020 Mamoe Technologies and contributors. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. `MiraiAndroid` 采用 `AGPLv3` 协议开源。为了整个社区的良性发展,我们**强烈建议**您做到以下几点: - **间接接触(包括但不限于使用 `httpapi` 或 跨进程技术)到 `mirai` 的软件使用 `AGPLv3` 开源** - **不鼓励,不支持一切商业使用** # 已实现的功能 * 兼容mirai-console插件(实验性) * 带验证码的登录处理 * 内置Google d8 dex编译器,可直接编译JVM的console插件在Android运行(实验性) * lua脚本接口(测试版) * 网络掉线提醒 # 安装脚本 目前MiraiAndroid已支持lua和JavaScript脚本,感谢[lua-mirai](https://github.com/only52607/lua-mirai)和[mirai-js](https://github.com/iTXTech/mirai-js)项目 ## lua脚本 以下是一个简单的示例 ```lua Event.onLoad = function (bot) bot:subscribeGroupMsg( function(bot, msg, group, sender) group:sendMsg( msg ) end ) end ``` 这个脚本实现了最简单的"复读机"功能,更多API请看[lua-mirai android api](https://github.com/only52607/lua-mirai/blob/master/docs/miraiandroid.md) ## JavaScript脚本 以下是一个~~简单~~复杂的示例 ```JavaScript // 插件信息 pluginInfo = { name: "JsPluginExample", version: "1.0.0", author: "PeratX", website: "https://github.com/iTXTech/mirai-js/blob/master/examples/reply.js" }; let verbose = true; // onLoad 事件 plugin.ev.onLoad = () => { logger.info("插件已加载:" + plugin.dataDir); // 插件数据读写 let file = plugin.getDataFile("test.txt") // 第三个编码参数默认为 UTF-8,可空,同readText第二个参数 stor.writeText(file, "真的很强。", Charset.forName("GBK")); logger.info("读取文件:" + file + " 内容:" + stor.readText(file, Charset.forName("GBK"))); let config = new JsonConfig(plugin.getDataFile("test.json")); config.put("wow", "Hello World!"); config.save(); let v = 0; // 启动协程 core.launch(() => { v++; logger.info("正在等待:" + v); if (verbose) { // 100ms执行一次 return 100; } // 停止协程,返回 -1 return -1; }); // 延时1000ms执行一次 core.launch(() => { verbose = false return -1; }, 1000); // 命令名称,描述,帮助,别名,回调 core.registerCommand("test", "测试命令", "test", null, (sender, args) => { logger.info("发送者:" + sender) logger.info("参数:" + args) return true }); }; plugin.ev.onEnable = () => { logger.info("插件已启用。" + (plugin.enabled ? "是真的" : "是假的")); try { // Http 基于 OkHttp,可使用 OkHttp 的 API 自行构造 let result = http.get("https://github.com/mamoe/mirai"); if (result.isSuccessful()) { logger.info("Mirai GitHub主页长度:" + result.body().string().length()); } else { logger.error("无法访问Mirai GitHub主页"); } // 手动调用 OkHttp let client = http.newClient() .connectTimeout(5000, TimeUnit.MILLISECONDS) .readTimeout(5000, TimeUnit.MILLISECONDS) .build() let response = client.newCall( http.newRequest() .url("https://im.qq.com") .header("User-Agent", "NMSL Browser 1.0") .build() ).execute(); if (response.isSuccessful()) { logger.info("QQ主页长度:" + response.body().string().length()); } else { logger.error("无法访问QQ主页"); } } catch (e) { logger.error("无法获取网页", e) } regEv(); }; plugin.ev.onDisable = () => { logger.info("插件已禁用。"); }; plugin.ev.onUnload = () => { logger.info("插件已卸载。"); }; function regEv() { core.subscribeAlways(BotOnlineEvent, ev => { logger.info(ev); }); core.subscribeAlways(GroupMessageEvent, ev => { logger.info(ev); ev.group.sendMessage(new PlainText("MiraiJs 收到消息:").plus(ev.message)); }) } ``` 你可以在[这里](https://github.com/iTXTech/mirai-js/blob/master/examples/reply.js)找到它,更多内容请查看项目介绍 在脚本管理界面点击右上角`+`可直接添加脚本到MiraiAndroid 目前该功能仍在开发中 # 安装插件 你有两个办法安装插件 ## 使用app直接打开jar文件安装 这是最简单的方式。app切换到插件管理点击右上角选择即可,你也可以使用系统文件选择器直接打开jar文件 **如果你无法选择文件**,请使用第三方文件选择器选择(例如Mix) ## 使用pc转换后导入 请按照以下方法操作 * 找到`d8`编译器的运行脚本 d8工具已在新版`Android sdk`中自带,它就在`build-tools`中对应版本的文件夹下。在Windows平台他是一个bat文件 如果没有可到上面的交流群内下载 * 编译 打开终端,使用以下命令编译 ``` d8.bat --output 输出文件.jar 源文件 ``` 输出文件扩展名必须是jar或者是zip * 复制资源 使用压缩软件打开源jar文件,将里面的`plugin.yml`,`META-INF`和其他资源文件(除存放class文件夹的其他文件)复制到新的jar文件内 * 安装插件 将上一步的新的jar文件复制到手机的`/sdcard/Android/data/io.github.mzdluo123.mirai.android/files/plugins/` 重启即可使用插件,当然部分插件可能也会存在兼容性问题 # FAQ Q: 后台运行被系统杀死<br> A:请手动将应用添加到系统后台白名单 Q:应用崩溃或后台报错<br> A:如果是后台报错一般是插件或者是mirai-core的问题,是mirai-core的问题请在菜单内找到分享日志并到群内或开启issue反馈,插件的问题请联系对应开发者;如果是应用崩溃,请重启并按照上面的方法提交日志给我们 # 兼容的Console插件列表 以下插件由群友测试未发现问题,你可以到群内下载,或是到[插件中心](https://github.com/mamoe/mirai-plugins)手动下载jvm版并导入 * mirai-api-http * HsoSe * keywordReply * forward * CQHTTPMirai 对于其他插件请自行尝试;此外,如果你的插件使用了一些Android不支持的api(例如BufferedImage)那么使用了这个api的功能将绝对不能正常工作 # 关于支持的Android版本 我们尚不清楚MiraiAndroid究竟能在哪些Android版本上正常工作,需要大家进行测试 我们已经测试无问题的Android版本: * Android 10 * Android 8.1(无法在Android端编译插件) 其他版本还未进行测试,以下是测试要求: * 程序不闪退,不报错,不出现无响应,通知显示正常,能正常完成登录 * 能够在Android端编译jvm插件(可选) * 能够使用编译好的jvm插件发送消息,发送图片,处理事件和正确读写配置 * 能够正常运行两个脚本引擎的demo 从下一个release版本开始项目的minsdk版本将调整至21(Android 5.1),测试结果可以通过issue和交流群告诉我们,谢谢!(反馈时记得带上日志和Android版本,抓取日志可以在控制台右上角菜单内找到) # 消息推送(2.9新增) 必须使用自动登录并在设置中开启才能使用该功能 你可以发送广播来快速向指定群或联系人推送信息,这里是data的URI格式 ``` ma://sendGroupMsg?msg=消息&id=群号 ma://sendFriendMsg?msg=消息&id=账号 ma://sendFriendMsg?msg=消息&id=账号&at=要at的人 ``` ```kotlin sendBroadcast(Intent("io.github.mzdluo123.mirai.android.PushMsg").apply { data = Uri.parse("ma://sendGroupMsg?msg=HelloWorld&id=655057127") }) ``` 以下是auto.js的示例 ```js app.sendBroadcast({ action: "io.github.mzdluo123.mirai.android.PushMsg", data: "ma://sendGroupMsg?msg=来自autojs的消息&id=655057127" }) ``` 以下是tasker的示例 ```yaml ma (2) A1: 发送意图 [ 操作:io.github.mzdluo123.mirai.android.PushMsg 类别:None Mime类型: 数据:ma://sendGroupMsg?msg=来自tasker的消息&id=655057127 额外: 额外: 额外: 包: 类: 目标:Broadcast Receiver ] ``` ================================================ FILE: mirai-console/frontend/mirai-android/app/.gitignore ================================================ /build ================================================ FILE: mirai-console/frontend/mirai-android/app/build.gradle ================================================ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' apply plugin: 'org.jetbrains.kotlin.plugin.serialization' def CORE_VERSION = "1.2.1" def LUAMIRAI_VERSION = "0.5.0" android { compileSdkVersion 29 buildToolsVersion "29.0.2" defaultConfig { applicationId "io.github.mzdluo123.mirai.android" minSdkVersion 21 targetSdkVersion 29 versionCode 30 versionName "2.10.3" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" buildConfigField("String", "COREVERSION", "\"$CORE_VERSION\"") buildConfigField("String", "LUAMIRAI_VERSION", "\"$LUAMIRAI_VERSION\"") } buildFeatures { dataBinding = true } buildTypes { release { minifyEnabled false shrinkResources false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug{ minifyEnabled false shrinkResources false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } // To inline the bytecode built with JVM target 1.8 into // bytecode that is being built with JVM target 1.6. (e.g. navArgs) compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } kotlinOptions { jvmTarget = "1.8" } packagingOptions { exclude 'META-INF/DEPENDENCIES' exclude 'META-INF/LICENSE' exclude 'META-INF/LICENSE.txt' exclude 'META-INF/license.txt' exclude 'META-INF/NOTICE' exclude 'META-INF/NOTICE.txt' exclude 'META-INF/notice.txt' exclude 'META-INF/ASL2.0' exclude("META-INF/*.kotlin_module") } lintOptions { abortOnError false } testOptions { animationsDisabled = true } } dependencies { implementation (fileTree(dir: 'libs', include: ['*.jar'])) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation 'com.google.android.material:material:1.2.0' //androidx-core-ktx implementation 'androidx.core:core-ktx:1.3.1' //androidx-appcompat implementation 'androidx.appcompat:appcompat:1.1.0' //androidx-legacy implementation 'androidx.legacy:legacy-support-v4:1.0.0' //androidx-constraintlayout implementation 'androidx.constraintlayout:constraintlayout:1.1.3' //androidx-navigation implementation 'androidx.navigation:navigation-fragment:2.3.0' implementation 'androidx.navigation:navigation-ui:2.3.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.3.0' implementation 'androidx.navigation:navigation-ui-ktx:2.3.0' //androidx-lifecycle implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' //androidx-preference implementation 'androidx.preference:preference:1.1.1' //kotlinx-coroutines implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4' //zip implementation 'net.lingala.zip4j:zip4j:2.5.2' //BaseRecyclerViewAdapterHelper implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.2' //mirai-core implementation "net.mamoe:mirai-core-qqandroid:$CORE_VERSION" //mirai-lua implementation "com.ooooonly:luaMirai:$LUAMIRAI_VERSION" //noinspection DuplicatePlatformClasses //implementation 'org.json:json:20160212' //{ //exclude module: 'okio' //exclude module: 'okhttp3' //} //implementation 'com.ooooonly:giteeman:0.1.1' //splitties implementation("com.louiscad.splitties:splitties-fun-pack-android-base:3.0.0-alpha06") implementation("com.louiscad.splitties:splitties-fun-pack-android-appcompat:3.0.0-alpha06") //acra implementation "ch.acra:acra-core:5.1.3" implementation "ch.acra:acra-toast:5.1.3" //glide implementation 'com.github.bumptech.glide:glide:4.11.0' annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0' //yaml implementation group: 'org.yaml', name: 'snakeyaml', version: '1.26' implementation group: 'com.moandjiezana.toml', name: 'toml4j', version: '0.7.2' //okhttp3 implementation 'com.squareup.okhttp3:okhttp:4.7.2' //test implementation 'androidx.test.espresso:espresso-idling-resource:3.2.0' debugImplementation 'androidx.fragment:fragment-testing:1.2.4' testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.1' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' androidTestImplementation 'androidx.test.espresso:espresso-intents:3.2.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' // https://mvnrepository.com/artifact/net.mamoe/mirai-console // implementation group: 'net.mamoe', name: 'mirai-console', version: '0.5.1' // implementation "net.mamoe:mirai-core:0.39.1" // https://mvnrepository.com/artifact/org.yaml/snakeyaml // https://mvnrepository.com/artifact/com.moandjiezana.toml/toml4j } ================================================ FILE: mirai-console/frontend/mirai-android/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile # 配置目前还有点问题无法使用 -keepattributes *Annotation*,Signature -keepclasseswithmembers class * extends java.lang.Exception { *;} #kotlin -keep class kotlin.** { *; } -keep class kotlin.Metadata { *; } -dontwarn kotlin.** -keepclassmembers class **$WhenMappings { <fields>; } -keepclassmembers class kotlin.Metadata { public <methods>; } -assumenosideeffects class kotlin.jvm.internal.Intrinsics { static void checkParameterIsNotNull(java.lang.Object, java.lang.String); } -keepclasseswithmembernames class * { native <methods>; } -keepclassmembers enum * { *;} -keep class kotlinx.coroutines.** {*;} # jvm平台的一些不存在的类 -dontwarn java.awt.** -dontwarn javax.swing.** -dontwarn sun.misc.** -dontwarn org.jetbrains.kotlin.** # mirai 配置 -keep class net.mamoe.mirai.qqandroid.QQAndroid.$Companion { *; } -keepclasseswithmembers class * extends net.mamoe.mirai.BotFactory{ *;} -keep class net.mamoe.mirai.console.** { *; } -keep class net.mamoe.mirai.contact.** { *; } -keep class net.mamoe.mirai.event.** { *; } -keep class net.mamoe.mirai.message.** { *; } -keep class net.mamoe.mirai.network.** { *; } -keep class net.mamoe.mirai.utils.** { *; } -keep class net.mamoe.mirai.* { *; } # ktor -keep class io.ktor.client.** { *; } -keepclassmembers class io.ktor.** { volatile <fields>; } # json -keep class kotlinx.serialization.json.** {*;} -keep class kotlinx.serialization.* {*;} # yaml -keep class org.yaml.snakeyaml.* {*;} -keep class org.yaml.snakeyaml.util.* {*;} # okhttp -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase -dontwarn okhttp3.internal.platform.ConscryptPlatform ================================================ FILE: mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/TestWithIdleResources.kt ================================================ package io.github.mzdluo123.mirai.android import androidx.test.espresso.IdlingRegistry import org.junit.After import org.junit.Before open class TestWithIdleResources { /** * 所有测试的基类 * 请将所有测试继承这个类防止在未完成操作时进行下一步点击 * * 测试时请使用英文系统 * * */ @Before fun before() { IdlingRegistry.getInstance().register(IdleResources.loadingData) IdlingRegistry.getInstance().register(IdleResources.botServiceLoading) } @After fun after() { IdlingRegistry.getInstance().unregister(IdleResources.loadingData) IdlingRegistry.getInstance().register(IdleResources.botServiceLoading) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/activity/NavTest.kt ================================================ package io.github.mzdluo123.mirai.android.activity import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.filters.LargeTest import androidx.test.rule.ActivityTestRule import androidx.test.runner.AndroidJUnit4 import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.TestWithIdleResources import io.github.mzdluo123.mirai.android.childAtPosition import org.hamcrest.Matchers.`is` import org.hamcrest.Matchers.allOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @LargeTest @RunWith(AndroidJUnit4::class) class NavTest : TestWithIdleResources() { @Rule @JvmField var mActivityTestRule = ActivityTestRule(MainActivity::class.java) @Test fun navTest() { val appCompatImageButton = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton.perform(click()) val navigationMenuItemView = onView( allOf( childAtPosition( allOf( withId(R.id.design_navigation_view), childAtPosition( withId(R.id.nav_view), 0 ) ), 2 ), isDisplayed() ) ) navigationMenuItemView.perform(click()) val appCompatImageButton2 = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton2.perform(click()) val navigationMenuItemView2 = onView( allOf( childAtPosition( allOf( withId(R.id.design_navigation_view), childAtPosition( withId(R.id.nav_view), 0 ) ), 3 ), isDisplayed() ) ) navigationMenuItemView2.perform(click()) val actionMenuItemView = onView( allOf( withId(R.id.action_script_center), childAtPosition( childAtPosition( withId(R.id.toolbar), 2 ), 0 ), isDisplayed() ) ) actionMenuItemView.perform(click()) Thread.sleep(100) val appCompatImageButton3 = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton3.perform(click()) val appCompatImageButton4 = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton4.perform(click()) val navigationMenuItemView3 = onView( allOf( childAtPosition( allOf( withId(R.id.design_navigation_view), childAtPosition( withId(R.id.nav_view), 0 ) ), 4 ), isDisplayed() ) ) navigationMenuItemView3.perform(click()) val appCompatImageButton5 = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton5.perform(click()) val navigationMenuItemView4 = onView( allOf( childAtPosition( allOf( withId(R.id.design_navigation_view), childAtPosition( withId(R.id.nav_view), 0 ) ), 5 ), isDisplayed() ) ) navigationMenuItemView4.perform(click()) val appCompatImageButton6 = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton6.perform(click()) val navigationMenuItemView5 = onView( allOf( childAtPosition( allOf( withId(R.id.design_navigation_view), childAtPosition( withId(R.id.nav_view), 0 ) ), 1 ), isDisplayed() ) ) navigationMenuItemView5.perform(click()) val appCompatImageButton7 = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton7.perform(click()) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleIntentTest.kt ================================================ package io.github.mzdluo123.mirai.android.console import android.app.Instrumentation import android.content.Intent import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.intent.Intents.intending import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction import androidx.test.espresso.intent.rule.IntentsTestRule import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.TestWithIdleResources import io.github.mzdluo123.mirai.android.activity.MainActivity import io.github.mzdluo123.mirai.android.childAtPosition import org.hamcrest.CoreMatchers.allOf import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @LargeTest @RunWith(AndroidJUnit4::class) class ConsoleIntentTest : TestWithIdleResources() { @Rule @JvmField var mActivityTestRule = IntentsTestRule(MainActivity::class.java) private val device = UiDevice.getInstance(getInstrumentation()) @Test fun uploadLogTest() { val overflowMenuButton = onView( allOf( childAtPosition( childAtPosition( withId(R.id.toolbar), 2 ), 0 ), isDisplayed() ) ) overflowMenuButton.perform(click()) val appCompatTextView = onView( allOf( withId(R.id.title), withText("分享日志"), childAtPosition( childAtPosition( withId(R.id.content), 0 ), 0 ), isDisplayed() ) ) appCompatTextView.perform(click()) intending(hasAction(Intent.ACTION_CHOOSER)).respondWith( Instrumentation.ActivityResult( 0, null ) ) device.pressBack() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleTest.kt ================================================ package io.github.mzdluo123.mirai.android.console import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.* import androidx.test.espresso.assertion.ViewAssertions import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.LargeTest import androidx.test.rule.ActivityTestRule import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.TestWithIdleResources import io.github.mzdluo123.mirai.android.activity.MainActivity import io.github.mzdluo123.mirai.android.childAtPosition import org.hamcrest.CoreMatchers import org.hamcrest.Matchers import org.hamcrest.Matchers.allOf import org.hamcrest.core.IsInstanceOf import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @LargeTest @RunWith(AndroidJUnit4::class) @FixMethodOrder(MethodSorters.JVM) class ConsoleTest : TestWithIdleResources() { @Rule @JvmField var mActivityTestRule = ActivityTestRule(MainActivity::class.java) @Test fun fastLoginAndFastRestartTest() { val overflowMenuButton = onView( allOf( childAtPosition( childAtPosition( withId(R.id.toolbar), 2 ), 0 ), isDisplayed() ) ) overflowMenuButton.perform(click()) val appCompatTextView = onView( allOf( withId(R.id.title), withText("设置自动登录"), childAtPosition( childAtPosition( withId(R.id.content), 0 ), 0 ), isDisplayed() ) ) appCompatTextView.perform(click()) val appCompatEditText = onView( allOf( withId(R.id.qq_input), childAtPosition( childAtPosition( withId(android.R.id.custom), 0 ), 2 ), isDisplayed() ) ) appCompatEditText.perform(replaceText("1"), closeSoftKeyboard()) val appCompatEditText2 = onView( allOf( withId(R.id.password_input), childAtPosition( childAtPosition( withId(android.R.id.custom), 0 ), 3 ), isDisplayed() ) ) appCompatEditText2.perform(replaceText("2"), closeSoftKeyboard()) val appCompatButton = onView( allOf( withId(android.R.id.button1), withText("设置自动登录") ) ) appCompatButton.perform(scrollTo(), click()) val appCompatImageButton7 = onView( allOf( childAtPosition( allOf( withId(R.id.toolbar), childAtPosition( withClassName(Matchers.`is`("com.google.android.material.appbar.AppBarLayout")), 0 ) ), 1 ), isDisplayed() ) ) appCompatImageButton7.perform(click()) onView(withText("快速重启")).perform(click()) onView(withId(R.id.log_text)).check(ViewAssertions.matches(isDisplayed())) Thread.sleep(2000) //花两秒钟给控制台加载log onView(withId(R.id.log_text)).check( ViewAssertions.matches( withText( CoreMatchers.containsString( "自动登录" ) ) ) ) } @Test fun commandInputTest() { val appCompatEditText = onView( allOf( withId(R.id.command_input), isDisplayed() ) ) appCompatEditText.perform(replaceText("help"), closeSoftKeyboard()) val appCompatImageButton = onView(withId(R.id.commandSend_btn)) appCompatImageButton.perform(click()) val textView = onView( allOf( withId(R.id.log_text), childAtPosition( allOf( withId(R.id.main_scroll), childAtPosition( IsInstanceOf.instanceOf(android.view.ViewGroup::class.java), 0 ) ), 0 ), isDisplayed() ) ) textView.check( ViewAssertions.matches( withText( CoreMatchers.containsString( "android" ) ) ) ) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/script/ScriptManageTest.kt ================================================ package io.github.mzdluo123.mirai.android.script import androidx.navigation.findNavController import androidx.test.espresso.Espresso.onView import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.* import androidx.test.filters.LargeTest import androidx.test.rule.ActivityTestRule import androidx.test.runner.AndroidJUnit4 import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.TestWithIdleResources import io.github.mzdluo123.mirai.android.ToastMatcher import io.github.mzdluo123.mirai.android.activity.MainActivity import io.github.mzdluo123.mirai.android.childAtPosition import org.hamcrest.Matchers.allOf import org.junit.FixMethodOrder import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.MethodSorters @ExperimentalStdlibApi @LargeTest @FixMethodOrder(MethodSorters.JVM) @RunWith(AndroidJUnit4::class) class ScriptManageTest : TestWithIdleResources() { @Rule @JvmField var mActivityTestRule = ActivityTestRule(MainActivity::class.java) @Test fun scriptCenterInstall() { mActivityTestRule.runOnUiThread { mActivityTestRule.activity.findNavController(R.id.nav_host_fragment) .navigate(R.id.nav_scripts_center) } val cardView = onView( allOf( withId(R.id.cv_item), childAtPosition( childAtPosition( withId(R.id.rcl_scripts), 0 ), 0 ), isDisplayed() ) ) cardView.perform(click()) onView(withText("OK")).perform(click()) onView(withText("导入成功!")).inRoot(ToastMatcher()).check(matches(isDisplayed())) } @Test fun scriptDelete() { mActivityTestRule.runOnUiThread { mActivityTestRule.activity.findNavController(R.id.nav_host_fragment) .navigate(R.id.nav_scripts) } val cardView2 = onView( allOf( withId(R.id.cv_item), childAtPosition( childAtPosition( withId(R.id.script_recycler), 0 ), 0 ), isDisplayed() ) ) cardView2.perform(click()) val appCompatImageButton3 = onView( allOf( withId(R.id.btn_delete), withContentDescription("delete"), childAtPosition( childAtPosition( withId(R.id.custom), 0 ), 1 ), isDisplayed() ) ) appCompatImageButton3.perform(click()) onView(withText("OK")).perform(click()) onView(withText("当前无脚本")).check(matches(isDisplayed())) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/utils.kt ================================================ package io.github.mzdluo123.mirai.android; import android.os.IBinder import android.view.View import android.view.ViewGroup import android.view.WindowManager import androidx.test.espresso.Root import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.TypeSafeMatcher internal fun childAtPosition( parentMatcher: Matcher<View>, position: Int ): Matcher<View> { return object : TypeSafeMatcher<View>() { override fun describeTo(description: Description) { description.appendText("Child at position $position in parent ") parentMatcher.describeTo(description) } public override fun matchesSafely(view: View): Boolean { val parent = view.parent return parent is ViewGroup && parentMatcher.matches(parent) && view == parent.getChildAt(position) } } } class ToastMatcher : TypeSafeMatcher<Root>() { override fun describeTo(description: Description) { description.appendText("is toast") } public override fun matchesSafely(root: Root): Boolean { val type: Int = root.getWindowLayoutParams().get().type if (type == WindowManager.LayoutParams.TYPE_TOAST) { val windowToken: IBinder = root.getDecorView().getWindowToken() val appToken: IBinder = root.getDecorView().getApplicationWindowToken() if (windowToken === appToken) { //means this window isn't contained by any other windows. return true } } return false } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/AndroidManifest.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" package="io.github.mzdluo123.mirai.android"> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" /> <application android:name=".BotApplication" android:icon="@mipmap/ic_new_launcher" android:label="@string/app_name" android:largeHeap="true" android:roundIcon="@mipmap/ic_new_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true"> <!-- <receiver--> <!-- android:name=".receiver.PushMsgReceiver"--> <!-- android:enabled="true"--> <!-- android:exported="true">--> <!-- <intent-filter>--> <!-- <action android:name="io.github.mzdluo123.mirai.android.PushMsg"/>--> <!-- </intent-filter>--> <!-- </receiver>--> <activity android:name=".activity.UnsafeLoginActivity" android:excludeFromRecents="true" android:label="请完成登录验证" android:launchMode="singleTask" /> <activity android:name=".activity.PluginImportActivity"> <intent-filter tools:ignore="AppLinkUrlError"> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="application/java-archive" /> </intent-filter> </activity> <service android:name=".service.BotService" android:description="@string/service_description" android:enabled="true" android:exported="false" android:process=":BotProcess" /> <activity android:name=".activity.MainActivity" android:label="@string/app_name" android:theme="@style/StartTheme" android:windowSoftInputMode="stateVisible|adjustResize"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".activity.CaptchaActivity" android:excludeFromRecents="true" android:label="请输入验证码" android:launchMode="singleTask" android:taskAffinity="" /> <receiver android:name=".receiver.BootReceiver" android:enabled="true" android:exported="true"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED" /> <action android:name="android.intent.action.QUICKBOOT_POWERON" /> <category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.HOME" /> </intent-filter> </receiver> <provider android:name="androidx.core.content.FileProvider" android:authorities="io.github.mzdluo123.mirai.android.scriptprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/path_script" /> </provider> </application> </manifest> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/aidl/io/github/mzdluo123/mirai/android/IbotAidlInterface.aidl ================================================ // IbotAidlInterface.aidl package io.github.mzdluo123.mirai.android; // Declare any non-default types here with import statements interface IbotAidlInterface { //Console String[] getLog(); void clearLog(); void sendLog(String log); void runCmd(String cmd); byte[] getCaptcha(); String getUrl(); void submitVerificationResult(String result); long getLogonId(); //Script String[] getHostList(); boolean reloadScript(int index); void setScriptConfig(String config); void deleteScript(int index); int getScriptSize(); void openScript(int index); boolean createScript(String name,int type); void enableScript(int index); void disableScript(int index); String getBotInfo(); } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/AppSettings.kt ================================================ package io.github.mzdluo123.mirai.android import splitties.experimental.ExperimentalSplittiesApi import splitties.preferences.Preferences import kotlin.reflect.KProperty @ExperimentalSplittiesApi object AppSettings : Preferences("setting") { class IntPrefSaveAsStr(private val key: String, private val defaultValue: Int) { operator fun getValue(thisRef: Preferences, prop: KProperty<*>): Int { return prefs.getString(key, null)?.toInt() ?: defaultValue } operator fun setValue(thisRef: Preferences, prop: KProperty<*>, value: Int) { editor.putString(key, value.toString()).commit() } } var allowPushMsg by BoolPref("allow_push_msg_preference", false) var logBuffer by IntPrefSaveAsStr("log_buffer_preference", 300) var printToLogcat by BoolPref("print_to_logcat_preference", false) var refreshPerMinute by IntPrefSaveAsStr("status_refresh_count", 15) var startOnBoot by BoolPref("start_on_boot_preference", false) } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/BotApplication.kt ================================================ package io.github.mzdluo123.mirai.android import android.app.ActivityManager import android.app.Application import android.content.Context import android.content.Intent import android.os.Build import android.os.Process import io.github.mzdluo123.mirai.android.NotificationFactory.initNotification import io.github.mzdluo123.mirai.android.crash.MiraiAndroidReportSenderFactory import io.github.mzdluo123.mirai.android.service.BotService import io.ktor.client.HttpClient import kotlinx.serialization.json.Json import org.acra.ACRA import org.acra.config.CoreConfigurationBuilder import org.acra.config.ToastConfigurationBuilder import org.acra.data.StringFormat @ExperimentalUnsignedTypes class BotApplication : Application() { companion object { lateinit var context: BotApplication private set val httpClient = lazy { HttpClient() } val json = lazy { Json { } } } override fun onCreate() { super.onCreate() context = this val processName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) getProcessName() else myGetProcessName() // 防止服务进程多次初始化 if (processName?.isEmpty() == false && processName == packageName) { initNotification() } } //崩溃事件注册 override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) ACRA.init(this, CoreConfigurationBuilder(this).apply { setBuildConfigClass(BuildConfig::class.java) .setReportFormat(StringFormat.JSON) setReportSenderFactoryClasses(MiraiAndroidReportSenderFactory::class.java) getPluginConfigurationBuilder(ToastConfigurationBuilder::class.java) .setResText(R.string.acra_toast_text) .setEnabled(true) //不知道为什么开启的时候总是显示这个,先暂时禁用 }) } private fun myGetProcessName(): String? { val pid = Process.myPid() for (appProcess in (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses) { if (appProcess.pid == pid) { return appProcess.processName } } return null } internal fun startBotService() { val account = getSharedPreferences("account", Context.MODE_PRIVATE) this.startService(Intent(this, BotService::class.java).apply { putExtra("action", BotService.START_SERVICE) putExtra("qq", account.getLong("qq", 0)) putExtra("pwd", account.getString("pwd", null)) }) } internal fun stopBotService() { startService(Intent(this, BotService::class.java).apply { putExtra("action", BotService.STOP_SERVICE) }) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/IdleResources.kt ================================================ package io.github.mzdluo123.mirai.android import androidx.test.espresso.idling.CountingIdlingResource object IdleResources { // Android单元测试所需要的东西 val loadingData by lazy { CountingIdlingResource("logUploadDialogIdleResources") } val botServiceLoading by lazy { CountingIdlingResource("botServiceLoading") } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/NotificationFactory.kt ================================================ package io.github.mzdluo123.mirai.android import android.app.* import android.content.Intent import android.graphics.Bitmap import android.os.Build import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import io.github.mzdluo123.mirai.android.activity.MainActivity import io.github.mzdluo123.mirai.android.miraiconsole.AndroidLoginSolver import io.github.mzdluo123.mirai.android.service.BotService @ExperimentalUnsignedTypes object NotificationFactory { const val SERVICE_NOTIFICATION = "service" const val CAPTCHA_NOTIFICATION = "captcha" const val OFFLINE_NOTIFICATION = "offline" val context by lazy { BotApplication.context } private val notifyIntent = Intent(context, MainActivity::class.java).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } private val launchMainActivity = PendingIntent.getActivity( context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT ) internal fun initNotification() { val notificationManager = context.getSystemService(Application.NOTIFICATION_SERVICE) as NotificationManager if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // 只在8.0系统上注册通知通道,防止程序崩溃 val statusChannel = NotificationChannel( SERVICE_NOTIFICATION, "状态通知", NotificationManager.IMPORTANCE_MIN ) statusChannel.description = "Mirai正在运行的通知" val captchaChannel = NotificationChannel( CAPTCHA_NOTIFICATION, "验证码通知", NotificationManager.IMPORTANCE_HIGH ) captchaChannel.description = "登录需要输入验证码时的通知" val offlineChannel = NotificationChannel( OFFLINE_NOTIFICATION, "离线通知", NotificationManager.IMPORTANCE_HIGH ) offlineChannel.description = "Mirai因各种原因离线的通知" if (BuildConfig.DEBUG) { offlineChannel.importance = NotificationManager.IMPORTANCE_MIN captchaChannel.importance = NotificationManager.IMPORTANCE_MIN } notificationManager.createNotificationChannel(statusChannel) notificationManager.createNotificationChannel(captchaChannel) notificationManager.createNotificationChannel(offlineChannel) } } internal fun dismissAllNotification() { NotificationManagerCompat.from(context).apply { cancel(BotService.OFFLINE_NOTIFICATION_ID) cancel(AndroidLoginSolver.CAPTCHA_NOTIFICATION_ID) } } internal fun statusNotification( content: String = "请完成登录并将软件添加到系统后台运行白名单确保能及时处理消息", avatar: Bitmap? = null ): Notification { return NotificationCompat.Builder( context, SERVICE_NOTIFICATION ) .setSmallIcon(R.drawable.ic_extension_black_24dp)//设置状态栏的通知图标 .setAutoCancel(false) //禁止用户点击删除按钮删除 .setOngoing(true) //禁止滑动删除 .setShowWhen(true) //右上角的时间显示 .setOnlyAlertOnce(true) .setStyle(NotificationCompat.BigTextStyle()) .setContentIntent(launchMainActivity) .setContentTitle("MiraiAndroid") //创建通知 .setContentText(content) .setLargeIcon(avatar) .build() } internal fun offlineNotification(content: String, bigTheme: Boolean = false): Notification { val builder = NotificationCompat.Builder( context, OFFLINE_NOTIFICATION ) .setAutoCancel(false) .setOngoing(false) .setShowWhen(true) .setSmallIcon(R.drawable.ic_info_black_24dp) .setContentTitle("Mirai离线") .setContentText(content) if (bigTheme) { builder.setStyle(NotificationCompat.BigTextStyle()) } return builder.build() } internal fun captchaNotification(activity: Class<*>): Notification { val notifyIntent = Intent(context, activity).apply { flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK } val notifyPendingIntent = PendingIntent.getActivity( context, 0, notifyIntent, PendingIntent.FLAG_UPDATE_CURRENT ) return NotificationCompat.Builder( context, CAPTCHA_NOTIFICATION ) .setContentIntent(notifyPendingIntent) .setAutoCancel(false) //禁止滑动删除 .setOngoing(false) //右上角的时间显示 .setShowWhen(true) .setSmallIcon(R.drawable.ic_info_black_24dp) .setContentTitle("本次登录需要进行登录验证") .setContentText("点击这里开始验证") .build() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/CaptchaActivity.kt ================================================ package io.github.mzdluo123.mirai.android.activity import android.app.NotificationManager import android.content.Context import android.content.Intent import android.graphics.BitmapFactory import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.miraiconsole.AndroidLoginSolver import io.github.mzdluo123.mirai.android.service.BotService import io.github.mzdluo123.mirai.android.service.ServiceConnector import kotlinx.android.synthetic.main.activity_captcha.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @ExperimentalUnsignedTypes class CaptchaActivity : AppCompatActivity() { private lateinit var conn: ServiceConnector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_captcha) conn = ServiceConnector(this) lifecycle.addObserver(conn) conn.connectStatus.observe(this, Observer { if (it) { val data = conn.botService.captcha lifecycleScope.launch(Dispatchers.Main) { val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) captcha_view.setImageBitmap(bitmap) } } }) } override fun onStart() { super.onStart() val intent = Intent(baseContext, BotService::class.java) bindService(intent, conn, Context.BIND_AUTO_CREATE) captchaConfirm_btn.setOnClickListener { conn.botService.submitVerificationResult(captcha_input.text.toString()) // 删除通知 val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(AndroidLoginSolver.CAPTCHA_NOTIFICATION_ID) finish() } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/MainActivity.kt ================================================ package io.github.mzdluo123.mirai.android.activity import android.content.Intent import android.net.Uri import android.os.Bundle import android.util.Log import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.navigateUp import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import io.github.mzdluo123.mirai.android.BotApplication import io.github.mzdluo123.mirai.android.BuildConfig import io.github.mzdluo123.mirai.android.NotificationFactory import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.utils.SafeDns import io.github.mzdluo123.mirai.android.utils.shareText import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.app_bar_main.* import kotlinx.coroutines.* import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import okhttp3.OkHttpClient import okhttp3.Request import splitties.alertdialog.appcompat.alertDialog import splitties.alertdialog.appcompat.cancelButton import splitties.alertdialog.appcompat.message import splitties.alertdialog.appcompat.okButton import splitties.toast.toast import java.io.File import java.io.FileReader @ExperimentalUnsignedTypes class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { companion object { const val TAG = "MainActivity" } private val appBarConfiguration: AppBarConfiguration by lazy { AppBarConfiguration( setOf( R.id.nav_console, R.id.nav_plugins, R.id.nav_scripts, R.id.nav_setting, R.id.nav_about ), drawer_layout ) } private val navController:NavController by lazy{ findNavController(R.id.nav_host_fragment) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setTheme(R.style.AppTheme_NoActionBar) setContentView(R.layout.activity_main) setSupportActionBar(toolbar) setupActionBarWithNavController(navController, appBarConfiguration) nav_view.setupWithNavController(navController) BotApplication.context.startBotService() btn_exit.setOnClickListener { BotApplication.context.stopBotService() NotificationFactory.dismissAllNotification() finish() } btn_reboot.setOnClickListener { NotificationFactory.dismissAllNotification() launch { BotApplication.context.stopBotService() delay(200) BotApplication.context.startBotService() navController.popBackStack() navController.navigate(R.id.nav_console) // 重新启动console fragment,使其能够链接到服务 drawer_layout.closeDrawers() } } checkCrash() val exceptionHandler = CoroutineExceptionHandler { _, throwable -> toast("检查更新失败") throwable.printStackTrace() Log.e(TAG, throwable.message ?: return@CoroutineExceptionHandler) finish() BotApplication.context.stopBotService() } if (!BuildConfig.DEBUG) { toast("跳过更新检查") } else { lifecycleScope.launch(exceptionHandler) { checkUpdate() } } //throw Exception("测试异常") } override fun onSupportNavigateUp(): Boolean = navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() private suspend fun checkUpdate() { val rep = withContext(Dispatchers.IO) { val client = OkHttpClient.Builder().dns(SafeDns()).build() val res = client.newCall( Request.Builder() .url("https://api.github.com/repos/mzdluo123/MiraiAndroid/releases/latest") .build() ).execute().body?.string() client.dispatcher.executorService.shutdown(); client.connectionPool.evictAll(); client.cache?.close() return@withContext res } val json = BotApplication.json.value.parseToJsonElement(rep ?: throw IllegalStateException("返回为空")) if (json.jsonObject.containsKey("url")) { val body = json.jsonObject["body"]?.jsonPrimitive?.content ?: "暂无更新记录" val htmlUrl = json.jsonObject["html_url"]!!.jsonPrimitive.content val version = json.jsonObject["tag_name"]!!.jsonPrimitive.content if (version == BuildConfig.VERSION_NAME) { return } withContext(Dispatchers.Main) { alertDialog(title = "发现新版本 $version", message = body) { setPositiveButton("立即更新") { _, _ -> startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(htmlUrl))) } }.show() } } else { throw IllegalStateException("检查更新失败") } } private fun checkCrash() { val crashDataFile = File(getExternalFilesDir("crash"), "crashdata") if (!crashDataFile.exists()) return var crashData: String FileReader(crashDataFile).also { crashData = it.readText() }.close() alertDialog { message = "检测到你上一次异常退出,是否上传崩溃日志?" okButton { shareText(crashData, lifecycleScope) } cancelButton { } }.show() crashDataFile.renameTo( File( getExternalFilesDir("crash"), "crashdata${System.currentTimeMillis()}" ) ) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/PluginImportActivity.kt ================================================ package io.github.mzdluo123.mirai.android.activity import android.net.Uri import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.lifecycleScope import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.databinding.ActivityPluginImportBinding import io.github.mzdluo123.mirai.android.ui.plugin.PluginViewModel import io.github.mzdluo123.mirai.android.utils.askFileName import io.github.mzdluo123.mirai.android.utils.copyToFileDir import kotlinx.android.synthetic.main.activity_plugin_import.* import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import net.lingala.zip4j.ZipFile import net.mamoe.mirai.console.plugins.PluginDescription import org.yaml.snakeyaml.Yaml import java.io.File import java.io.FileReader @ExperimentalUnsignedTypes class PluginImportActivity : AppCompatActivity() { private lateinit var uri: Uri private lateinit var pluginViewModel: PluginViewModel private lateinit var dialog: AlertDialog private lateinit var activityPluginImportBinding: ActivityPluginImportBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_plugin_import) // uri = Uri.parse(intent.getStringExtra("uri")) val errorHandel = CoroutineExceptionHandler { _, _ -> Toast.makeText(this, "无法打开这个文件,请检查这是不是一个合法的插件jar文件", Toast.LENGTH_SHORT).show() finish() } uri = intent.data ?: return pluginViewModel = ViewModelProvider(this).get(PluginViewModel::class.java) activityPluginImportBinding = DataBindingUtil.setContentView(this, R.layout.activity_plugin_import) lifecycleScope.launch(errorHandel) { loadPluginData() } activityPluginImportBinding.importBtn.setOnClickListener { startImport() } } private fun createDialog() { dialog = AlertDialog.Builder(this) .setTitle("正在编译") .setMessage("这可能需要一些时间,请不要最小化") .setCancelable(false) .create() } private fun startImport() { createDialog() val exceptionHandler = CoroutineExceptionHandler { _, throwable -> lifecycleScope.launch(Dispatchers.Main) { dialog.dismiss() Toast.makeText( this@PluginImportActivity, "无法编译插件 \n${throwable}", Toast.LENGTH_LONG ).show() } } dialog.show() when (import_radioGroup.checkedRadioButtonId) { R.id.compile_radioButton -> { lifecycleScope.launch(exceptionHandler) { val name = withContext(Dispatchers.Main) { askFileName() } ?: return@launch withContext(Dispatchers.IO) { copyToFileDir( uri, name, this@PluginImportActivity.getExternalFilesDir(null)!!.absolutePath ) } pluginViewModel.compilePlugin( File(baseContext.getExternalFilesDir(null), name), desugaring_checkBox.isChecked ) withContext(Dispatchers.IO) { File(this@PluginImportActivity.getExternalFilesDir(null), name).delete() } dialog.dismiss() Toast.makeText(this@PluginImportActivity, "安装成功,重启后即可加载", Toast.LENGTH_SHORT) .show() finish() } } R.id.copy_radioButton -> { lifecycleScope.launch(exceptionHandler) { val name = withContext(Dispatchers.Main) { askFileName() } ?: return@launch withContext(Dispatchers.IO) { copyToFileDir( uri, name, this@PluginImportActivity.getExternalFilesDir("plugins")!!.absolutePath ) } dialog.dismiss() Toast.makeText(this@PluginImportActivity, "安装成功,重启后即可加载", Toast.LENGTH_SHORT) .show() finish() } } } } private suspend fun loadPluginData() = withContext(Dispatchers.IO) { val realFileName = "tmpfile.jar" baseContext.copyToFileDir(uri, realFileName, cacheDir.absolutePath) val cacheFile = File(cacheDir.absolutePath, realFileName) val zipFile = ZipFile(cacheFile) zipFile.extractFile("plugin.yml", cacheDir.absolutePath) val yml = Yaml() lateinit var plugInfo: PluginDescription FileReader(File(cacheDir.absolutePath, "plugin.yml")).use { with(yml.load<LinkedHashMap<String, Any>>(it)) { plugInfo = PluginDescription( file = File(cacheDir.absolutePath, "tmpfile.jar"), name = this.get("name") as String, author = kotlin.runCatching { this.get("author") as String }.getOrElse { "unknown" }, basePath = kotlin.runCatching { this.get("path") as String }.getOrElse { this.get("main") as String }, version = kotlin.runCatching { this.get("version") as String }.getOrElse { "unknown" }, info = kotlin.runCatching { this.get("info") as String }.getOrElse { "unknown" }, depends = kotlin.runCatching { this.get("depends") as List<String> }.getOrElse { listOf() } ) } withContext(Dispatchers.Main) { activityPluginImportBinding.pluginData = plugInfo } } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/UnsafeLoginActivity.kt ================================================ package io.github.mzdluo123.mirai.android.activity import android.annotation.SuppressLint import android.app.NotificationManager import android.content.Context import android.os.Bundle import android.view.KeyEvent import android.webkit.ConsoleMessage import android.webkit.WebChromeClient import android.webkit.WebViewClient import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.miraiconsole.AndroidLoginSolver import io.github.mzdluo123.mirai.android.service.ServiceConnector import kotlinx.android.synthetic.main.activity_unsafe_login.* import kotlinx.coroutines.delay import kotlinx.coroutines.launch @ExperimentalUnsignedTypes class UnsafeLoginActivity : AppCompatActivity() { private lateinit var conn: ServiceConnector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) conn = ServiceConnector(this) lifecycle.addObserver(conn) setContentView(R.layout.activity_unsafe_login) initWebView() refresh_unsafe_web.setOnRefreshListener { unsafe_login_web.reload() lifecycleScope.launch { delay(1000) refresh_unsafe_web.isRefreshing = false } } // Toast.makeText(this, "请在完成验证后点击右上角继续登录", Toast.LENGTH_LONG).show() } @SuppressLint("SetJavaScriptEnabled") private fun initWebView() { unsafe_login_web.webViewClient = object : WebViewClient() { // override fun shouldInterceptRequest( // view: WebView?, // request: WebResourceRequest? // ): WebResourceResponse? { // if (request != null) { // if ("https://report.qqweb.qq.com/report/compass/dc00898" in request.url.toString()) { // authFinish() // } // } // return super.shouldInterceptRequest(view, request) // } } unsafe_login_web.webChromeClient = object : WebChromeClient() { override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { // 按下回到qq按钮之后会打印这句话,于是就用这个解决了。。。。 if (consoleMessage?.message()?.startsWith("手Q扫码验证") == true) { authFinish() } return super.onConsoleMessage(consoleMessage) } } unsafe_login_web.settings.apply { javaScriptEnabled = true domStorageEnabled = true } conn.connectStatus.observe(this, Observer { if (it) { unsafe_login_web.loadUrl(conn.botService.url) } }) } private fun authFinish() { conn.botService.submitVerificationResult("") val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.cancel(AndroidLoginSolver.CAPTCHA_NOTIFICATION_ID) finish() } override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { if (keyCode == KeyEvent.KEYCODE_BACK) { if (unsafe_login_web.canGoBack()) { unsafe_login_web.goBack() return true } } return false } // override fun onCreateOptionsMenu(menu: Menu?): Boolean { // menuInflater.inflate(R.menu.unsafe_menu, menu) // return true // } // // override fun onOptionsItemSelected(item: MenuItem): Boolean { // authFinish() // return true // } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSender.kt ================================================ package io.github.mzdluo123.mirai.android.crash import android.content.Context import org.acra.data.CrashReportData import org.acra.sender.ReportSender import org.acra.sender.ReportSenderException import java.io.File import java.io.FileWriter //崩溃日志处理 class MiraiAndroidReportSender() : ReportSender { @Throws(ReportSenderException::class) override fun send( context: Context, report: CrashReportData ) { val outFile = File(context.getExternalFilesDir("crash"), "crashdata") FileWriter(outFile).also { it.write(report.toJSON()) }.close() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSenderFactory.kt ================================================ package io.github.mzdluo123.mirai.android.crash import android.content.Context import org.acra.config.CoreConfiguration import org.acra.sender.ReportSender import org.acra.sender.ReportSenderFactory class MiraiAndroidReportSenderFactory : ReportSenderFactory { override fun create( context: Context, config: CoreConfiguration ): ReportSender = MiraiAndroidReportSender() override fun enabled(config: CoreConfiguration): Boolean = true } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidLoginSolver.kt ================================================ package io.github.mzdluo123.mirai.android.miraiconsole import android.content.Context import androidx.core.app.NotificationManagerCompat import io.github.mzdluo123.mirai.android.NotificationFactory import io.github.mzdluo123.mirai.android.activity.CaptchaActivity import io.github.mzdluo123.mirai.android.activity.UnsafeLoginActivity import kotlinx.coroutines.CompletableDeferred import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.utils.LoginSolver @ExperimentalUnsignedTypes class AndroidLoginSolver(private val context: Context) : LoginSolver() { lateinit var verificationResult: CompletableDeferred<String> lateinit var captchaData: ByteArray lateinit var url: String companion object { const val CAPTCHA_NOTIFICATION_ID = 2 } override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? { MiraiConsole.frontEnd.pushLog(0L,"本次登录需要输入验证码,请在通知栏点击通知来输入") verificationResult = CompletableDeferred() captchaData = data NotificationManagerCompat.from(context).apply { notify( CAPTCHA_NOTIFICATION_ID, NotificationFactory.captchaNotification(CaptchaActivity::class.java) ) } return verificationResult.await() } override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { verificationResult = CompletableDeferred() this.url = url sendVerifyNotification() return verificationResult.await() } override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { verificationResult = CompletableDeferred() this.url = url sendVerifyNotification() return verificationResult.await() } private fun sendVerifyNotification() { MiraiConsole.frontEnd.pushLog(0L,"本次登录需要进行验证,请在通知栏点击通知进行验证") NotificationManagerCompat.from(context).apply { notify( CAPTCHA_NOTIFICATION_ID, NotificationFactory.captchaNotification(UnsafeLoginActivity::class.java) ) } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidMiraiConsole.kt ================================================ package io.github.mzdluo123.mirai.android.miraiconsole import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.Log import androidx.core.app.NotificationManagerCompat import io.github.mzdluo123.mirai.android.AppSettings import io.github.mzdluo123.mirai.android.BotApplication import io.github.mzdluo123.mirai.android.NotificationFactory import io.github.mzdluo123.mirai.android.script.ScriptManager import io.github.mzdluo123.mirai.android.service.BotService import io.github.mzdluo123.mirai.android.utils.LoopQueue import io.github.mzdluo123.mirai.android.utils.MiraiAndroidStatus import io.ktor.client.HttpClient import io.ktor.client.request.get import kotlinx.coroutines.* import net.mamoe.mirai.Bot import net.mamoe.mirai.console.utils.MiraiConsoleUI import net.mamoe.mirai.event.Listener import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotReloginEvent import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.SimpleLogger import splitties.experimental.ExperimentalSplittiesApi @ExperimentalSplittiesApi @ExperimentalUnsignedTypes class AndroidMiraiConsole(context: Context) : MiraiConsoleUI { private val logBuffer = AppSettings.logBuffer private val printToLogcat = AppSettings.printToLogcat val logStorage = LoopQueue<String>(logBuffer) val loginSolver = AndroidLoginSolver(context) // 使用一个[60s/refreshPerMinute]的数组存放每4秒消息条数 // 读取时增加最新一分钟,减去最老一分钟 private val refreshPerMinute = AppSettings.refreshPerMinute private val msgSpeeds = IntArray(refreshPerMinute) private var refreshCurrentPos = 0 private var sendOfflineMsgJob: Job? = null companion object { const val TAG = "MA" } override fun createLoginSolver(): LoginSolver = loginSolver override fun prePushBot(identity: Long) = Unit override fun pushBot(bot: Bot) { bot.pushToScriptManager(ScriptManager.instance) bot.subscribeBotLifeEvent() bot.startRefreshNotificationJob() } override fun pushBotAdminStatus(identity: Long, admins: List<Long>) = Unit override fun pushLog(identity: Long, message: String) { logStorage.add(message) if (printToLogcat) { Log.i(TAG, message) } } override fun pushLog( priority: SimpleLogger.LogPriority, identityStr: String, identity: Long, message: String ) { logStorage.add("[${priority.name}] $message") if (printToLogcat) { Log.i(TAG, "[${priority.name}] $message") } } override fun pushVersion(consoleVersion: String, consoleBuild: String, coreVersion: String) { logStorage.add(MiraiAndroidStatus.recentStatus().format()) } override suspend fun requestInput(hint: String): String = "" private fun Bot.startRefreshNotificationJob() { subscribeMessages { always { msgSpeeds[refreshCurrentPos] += 1 } } launch { val avatar = downloadAvatar() // 获取通知展示用的头像 var msgSpeed = 0 while (isActive) { /* * 总速度+=最新速度 [0] [1] ... [14] * 总速度-=最老速度 [1] [2] ... [0] */ msgSpeed += msgSpeeds[refreshCurrentPos] if (refreshCurrentPos != refreshPerMinute - 1) { refreshCurrentPos += 1 } else { refreshCurrentPos = 0 } msgSpeed -= msgSpeeds[refreshCurrentPos] msgSpeeds[refreshCurrentPos] = 0 NotificationManagerCompat.from(BotApplication.context).apply { notify( BotService.NOTIFICATION_ID, NotificationFactory.statusNotification("消息速度 ${msgSpeed}/min", avatar) ) } delay(60L / refreshPerMinute * 1000) } } } private suspend fun Bot.downloadAvatar(): Bitmap = try { pushLog(0L, "[INFO] 正在加载头像....") HttpClient().get<ByteArray>(selfQQ.avatarUrl).let { avatarData -> BitmapFactory.decodeByteArray(avatarData, 0, avatarData.size) } } catch (e: Exception) { delay(1000) downloadAvatar() } private fun Bot.subscribeBotLifeEvent() { subscribeAlways<BotOfflineEvent>(priority = Listener.EventPriority.HIGHEST) { if (this is BotOfflineEvent.Force) { NotificationManagerCompat.from(BotApplication.context).apply { notify( BotService.OFFLINE_NOTIFICATION_ID, NotificationFactory.offlineNotification(message, true) ) } return@subscribeAlways } if (this is BotOfflineEvent.Dropped) { sendOfflineMsgJob = GlobalScope.launch { delay(2000) if (!isActive) { return@launch } NotificationManagerCompat.from(BotApplication.context).apply { notify( BotService.OFFLINE_NOTIFICATION_ID, NotificationFactory.offlineNotification("请检查网络设置") ) } } } pushLog(0L, "[INFO] 发送离线通知....") } subscribeAlways<BotReloginEvent>(priority = Listener.EventPriority.HIGHEST) { pushLog(0L, "[INFO] 发送上线通知....") if (sendOfflineMsgJob != null && sendOfflineMsgJob!!.isActive) { sendOfflineMsgJob!!.cancel() } NotificationManagerCompat.from(BotApplication.context) .cancel(BotService.OFFLINE_NOTIFICATION_ID) } } private fun Bot.pushToScriptManager(manager: ScriptManager) { launch { manager.addBot(this@pushToScriptManager) } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/BootReceiver.kt ================================================ package io.github.mzdluo123.mirai.android.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.Build import io.github.mzdluo123.mirai.android.AppSettings import io.github.mzdluo123.mirai.android.service.BotService import splitties.experimental.ExperimentalSplittiesApi @ExperimentalSplittiesApi class BootReceiver : BroadcastReceiver() { // companion object{ // const val TAG = "BootReceiver" // } private val ACTION = "android.intent.action.BOOT_COMPLETED" override fun onReceive(context: Context, intent: Intent) { // Log.e(TAG,"收到广播") if (AppSettings.startOnBoot) { return } if (intent.action == ACTION) { val startIntent = Intent(context, BotService::class.java) startIntent.putExtra( "action", BotService.START_SERVICE ) val account = context.getSharedPreferences("account", Context.MODE_PRIVATE) val qq = account.getLong("qq", 0) val pwd = account.getString("pwd", null) startIntent.putExtra("qq", qq) startIntent.putExtra("pwd", pwd) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(startIntent) } else { context.startService(startIntent) } } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/PushMsgReceiver.kt ================================================ package io.github.mzdluo123.mirai.android.receiver import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.util.Log import io.github.mzdluo123.mirai.android.service.BotService class PushMsgReceiver(private val botService: BotService) : BroadcastReceiver() { companion object { val TAG = PushMsgReceiver::class.java.name } override fun onReceive(context: Context, intent: Intent) { try { val data = intent.data ?: return if (data.scheme != "ma") { return } val id = data.getQueryParameter("id")?.toLong() ?: return val msg = data.getQueryParameter("msg") ?: return when (data.host) { "sendGroupMsg" -> { val at = data.getQueryParameter("at")?.toLong() if (at != null) { botService.sendGroupMsgWithAT(id, msg, at) return } botService.sendGroupMsg(id, msg) } "sendFriendMsg" -> botService.sendFriendMsg(id, msg) } } catch (e: Exception) { e.printStackTrace() Log.e(TAG, e.toString()) } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/JavaScriptHost.kt ================================================ package io.github.mzdluo123.mirai.android.script import kotlinx.coroutines.runBlocking import net.mamoe.mirai.Bot import org.itxtech.miraijs.coreadapter.IPluginLogger import org.itxtech.miraijs.coreadapter.JsRuntime import java.io.File class JavaScriptHost(scriptFile: File, configFile: File) : ScriptHost(scriptFile, configFile) { var JsLogger = object : IPluginLogger { override fun debug(str: String) { logger(str) } override fun error(str: String, e: Any?) { logger(str) } override fun info(str: String) { logger(str) } override fun verbose(str: String) { logger(str) } override fun warning(str: String) { logger(str) } } var runtime = JsRuntime(scriptFile, JsLogger) override fun onFetchBot(bot: Bot) { if (!config.enable) return runtime.attachBot(bot) } override fun onCreate(): ScriptInfo { //只获取信息,enable时再创建运行时 runBlocking { runtime.load().join() } val runtimeInfo = runtime.pluginInfo return ScriptInfo( runtimeInfo.name, runtimeInfo.author, runtimeInfo.version, runtimeInfo.website, scriptFile.length() ) } override fun onDisable() { runtime.disable() } override fun onEnable() { runtime.enable() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/LuaScriptHost.kt ================================================ package io.github.mzdluo123.mirai.android.script import android.util.Log import com.ooooonly.luaMirai.lua.MiraiGlobals import kotlinx.coroutines.launch import net.mamoe.mirai.Bot import org.luaj.vm2.LuaTable import org.luaj.vm2.LuaValue import java.io.File class LuaScriptHost(scriptFile: File, configFile: File) : ScriptHost(scriptFile, configFile) { private lateinit var globals : MiraiGlobals override fun onCreate(): ScriptInfo { globals = MiraiGlobals(logger) globals.loadfile(scriptFile.absolutePath).call() var name = scriptFile.name.split(".").first() var author = "MiraiAndroid" var version = "0.1" var description = "MiraiAndroid Lua脚本" globals.get("Info").takeIf { it is LuaTable }?.let { var table = it as LuaTable name = table.get("name").takeUnless { it == LuaValue.NIL }?.toString()?:name author = table.get("author").takeUnless { it == LuaValue.NIL }?.toString() ?: author version = table.get("version").takeUnless { it == LuaValue.NIL }?.toString() ?: version description = table.get("description").takeUnless { it == LuaValue.NIL }?.toString() ?: description } return ScriptInfo(name, author, version, description, scriptFile.length()) } override fun onFetchBot(bot: Bot) { Log.i("fetchBot", bot.id.toString()) if (!config.enable) return bot.launch { globals.onLoad(bot) } } override fun onDisable() { globals.onFinish() globals.unSubsribeAll() Log.i("uninstall", info.name) } override fun onEnable() { } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHost.kt ================================================ package io.github.mzdluo123.mirai.android.script import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole import java.io.File import java.io.FileReader import java.io.FileWriter abstract class ScriptHost(val scriptFile: File, val configFile: File) { @Serializable data class ScriptConfig( var type: Int, var enable: Boolean = false, var data: String = "" ) @Serializable data class ScriptInfo( var name: String = "", var author: String = "", var version: String = "", var description: String = "", var fileLength: Long, var scriptType: Int = ScriptHostFactory.UNKNOWN, var enable: Boolean = true ) protected val logger: (String) -> Unit = { MiraiConsole.frontEnd.pushLog(0L, "[${ScriptHostFactory.NAMES[config.type]}] $it") } lateinit var config: ScriptConfig lateinit var info: ScriptInfo fun load() { info = onCreate() if (configFile.exists()) { val reader = FileReader(configFile) val text = reader.readText() reader.close() config = Json.decodeFromString(ScriptConfig.serializer(), text) } info.scriptType = config.type info.enable = config.enable saveConfig() } fun getInfoString(): String { return Json.encodeToString(ScriptInfo.serializer(), info) } fun disable() = onDisable() fun enable() = onEnable() fun enableIfPossible() { if (config.enable) enable() } fun installBot(bot: Bot) = onFetchBot(bot) protected abstract fun onFetchBot(bot: Bot)//传入bot事件 protected abstract fun onCreate(): ScriptInfo //载入事件,用于初始化环境,并读取脚本内信息到ScriptConfig protected abstract fun onDisable() //脚本被禁用事件 protected abstract fun onEnable() //脚本被启用事件 fun saveConfig() { val data = Json.encodeToString(ScriptConfig.serializer(), config) if (!configFile.exists()) configFile.createNewFile() val writer = FileWriter(configFile) writer.write(data) writer.flush() writer.close() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHostFactory.kt ================================================ package io.github.mzdluo123.mirai.android.script import kotlinx.serialization.json.Json import java.io.File import java.io.FileReader object ScriptHostFactory { const val UNKNOWN = 0 const val LUA = 1 const val JAVASCRIPT = 2 const val PYTHON = 3 const val KOTLINSCRIPT = 4 val NAMES = arrayOf("Unknown", "Lua", "JavaScript", "Python", "KotlinScript") fun getTypeFromSuffix(suffix: String) = when (suffix) { "lua" -> LUA "js" -> JAVASCRIPT "py" -> PYTHON "kts" -> KOTLINSCRIPT else -> UNKNOWN } fun getScriptHost(scriptFile: File, configFile: File, type: Int): ScriptHost? { var trueType: Int = type if (trueType == UNKNOWN) { if (configFile.exists()) { FileReader(configFile).apply { trueType = Json.decodeFromString(ScriptHost.ScriptConfig.serializer(), readText()).type }.close() if (trueType == UNKNOWN) trueType = getTypeFromSuffix(scriptFile.getSuffix()) } else { trueType = getTypeFromSuffix(scriptFile.getSuffix()) } } return when (trueType) { LUA -> LuaScriptHost(scriptFile, configFile).also { it.config = ScriptHost.ScriptConfig(trueType, false, "") } JAVASCRIPT -> JavaScriptHost(scriptFile, configFile).also { it.config = ScriptHost.ScriptConfig(trueType, false, "") } else -> null } } private fun File.getSuffix() = name.split(".").last() } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptManager.kt ================================================ package io.github.mzdluo123.mirai.android.script import android.content.Context import android.net.Uri import android.util.Log import io.github.mzdluo123.mirai.android.BotApplication import io.github.mzdluo123.mirai.android.utils.copyToFileDir import kotlinx.serialization.json.Json import net.mamoe.mirai.Bot import java.io.File class ScriptManager( private var context: Context, private var scriptDir: File, private var configDir: File ) { val hosts = mutableListOf<ScriptHost>() private val bots = mutableListOf<Bot>() val botsSize: Int get() = bots.size @ExperimentalUnsignedTypes companion object { val instance: ScriptManager by lazy { val context: Context = BotApplication.context val scriptDir = context.getExternalFilesDir("scripts") val configDir = context.getExternalFilesDir("data") ScriptManager(context, scriptDir!!, configDir!!) } fun unPackHostInfos(infoStrings: Array<String>): List<ScriptHost.ScriptInfo> = List(infoStrings.size) { Json.decodeFromString(ScriptHost.ScriptInfo.serializer(), infoStrings[it]) } fun copyFileToScriptDir(context: Context, uri: Uri, name: String): File = context.copyToFileDir( uri, name, context.getExternalFilesDir("scripts")!!.absolutePath ) } init { if (!scriptDir.exists()) scriptDir.mkdirs() if (!configDir.exists()) configDir.mkdirs() loadScripts() } fun addBot(bot: Bot) { bots.add(bot) hosts.forEach { it.installBot(bot) } } fun editConfig(index: Int, editor: ScriptHost.ScriptConfig.() -> Unit) { hosts[index].config.editor() } fun delete(index: Int) { hosts[index].disable() hosts[index].scriptFile.getConfigFile().delete() hosts[index].scriptFile.delete() hosts.removeAt(index) } private fun loadScripts() { scriptDir.listFiles()?.forEach { scriptFile -> //scriptFile.delete() //scriptFile.getConfigFile().delete() if (scriptFile.isFile) hosts.addHost(scriptFile, scriptFile.getConfigFile(), ScriptHostFactory.UNKNOWN) } } // fun createScriptFromUri(fromUri: Uri, type: Int): Boolean { // fromUri.getName(context).let { name -> // val scriptFile = context.copyToFileDir( // fromUri, // name!!, // scriptDir.absolutePath // ) // // hosts.addHost(scriptFile, scriptFile.getConfigFile(), type)?.let { host -> // bots.forEach { bot -> host.installBot(bot) } // return true // } ?: return false // } // } fun createScriptFromFile(scriptFile: File, type: Int): Boolean { hosts.addHost(scriptFile, scriptFile.getConfigFile(), type)?.let { host -> bots.forEach { bot -> host.installBot(bot) } return true } ?: return false } fun enable(index: Int) { Log.i("enable", index.toString()) if (hosts[index].config.enable) return hosts[index].enable() hosts[index].config.enable = true hosts[index].info.enable = true hosts[index].saveConfig() bots.forEach { hosts[index].installBot(it) } } fun enableAll() = hosts.forEach { host -> host.enable() } fun disable(index: Int) { Log.i("disable", index.toString()) if (!hosts[index].config.enable) return hosts[index].disable() hosts[index].config.enable = false hosts[index].info.enable = false hosts[index].saveConfig() } fun disableAll() = hosts.forEach { it.disable() } fun reload(index: Int) { hosts[index].disable() hosts[index].load() hosts[index].enableIfPossible() bots.forEach { hosts[index].installBot(it) } } fun reloadAll() = hosts.forEach { it.disable() it.load() it.enableIfPossible() bots.forEach { bot -> it.installBot(bot) } } fun getHostInfoStrings(): Array<String> = List(hosts.size) { hosts[it].getInfoString() }.toTypedArray() private fun MutableList<ScriptHost>.addHost( scriptFile: File, configFile: File, type: Int ): ScriptHost? { try { //Log.e("loading:", "${scriptFile.absolutePath}") val host = ScriptHostFactory.getScriptHost(scriptFile, scriptFile.getConfigFile(), type) host ?: throw Exception("未知的脚本类型!${scriptFile.absolutePath}") host.load() host.enableIfPossible() add(host) return host } catch (e: Exception) { Log.e("loadScriptError", e.message ?: return null) } return null } private fun File.getConfigFile() = File(configDir, name) // fun Uri.getName(context: Context) = // context.askFileName() } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/BotService.kt ================================================ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package io.github.mzdluo123.mirai.android.service import android.annotation.SuppressLint import android.app.Service import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri import android.os.Build import android.os.IBinder import android.os.PowerManager import android.provider.MediaStore import android.util.Log import androidx.core.content.FileProvider import io.github.mzdluo123.mirai.android.AppSettings import io.github.mzdluo123.mirai.android.BuildConfig import io.github.mzdluo123.mirai.android.IbotAidlInterface import io.github.mzdluo123.mirai.android.NotificationFactory import io.github.mzdluo123.mirai.android.miraiconsole.AndroidMiraiConsole import io.github.mzdluo123.mirai.android.receiver.PushMsgReceiver import io.github.mzdluo123.mirai.android.script.ScriptManager import io.github.mzdluo123.mirai.android.utils.MiraiAndroidStatus import io.github.mzdluo123.mirai.android.utils.register import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.ConsoleCommandSender import net.mamoe.mirai.console.command.ConsoleCommandSender.sendMessage import net.mamoe.mirai.console.command.ContactCommandSender import net.mamoe.mirai.console.utils.checkManager import net.mamoe.mirai.event.subscribeMessages import net.mamoe.mirai.message.data.At import net.mamoe.mirai.utils.SimpleLogger import java.io.File import kotlin.system.exitProcess @ExperimentalUnsignedTypes class BotService : Service(), CommandOwner { lateinit var consoleFrontEnd: AndroidMiraiConsole private set private val binder = BotBinder() private var isStart = false private lateinit var powerManager: PowerManager private lateinit var wakeLock: PowerManager.WakeLock private var bot: Bot? = null private val msgReceiver = PushMsgReceiver(this) private val allowPushMsg = AppSettings.allowPushMsg // 多进程调试辅助 // init { // Debug.waitForDebugger() // } companion object { const val START_SERVICE = 0 const val STOP_SERVICE = 1 const val NOTIFICATION_ID = 1 const val OFFLINE_NOTIFICATION_ID = 3 const val TAG = "BOT_SERVICE" } private fun createNotification() { NotificationFactory.statusNotification().let { startForeground(NOTIFICATION_ID, it) //设置为前台服务 } } override fun onBind(intent: Intent): IBinder = binder override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { try { intent?.getIntExtra( "action", START_SERVICE ).let { action -> when (action) { START_SERVICE -> startConsole(intent) STOP_SERVICE -> stopConsole() } } } catch (e: Exception) { Log.e("onStartCommand", e.message ?: "null") consoleFrontEnd.pushLog(0L, "onStartCommand:发生错误 $e") } return super.onStartCommand(intent, flags, startId) } @SuppressLint("InvalidWakeLockTag") override fun onCreate() { super.onCreate() consoleFrontEnd = AndroidMiraiConsole( baseContext ) powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "BotWakeLock") } private fun autoLogin(intent: Intent) { val qq = intent.getLongExtra("qq", 0) val pwd = intent.getStringExtra("pwd") if (qq == 0L) return //CommandManager.runCommand(ConsoleCommandSender, "login $qq $pwd") consoleFrontEnd.pushLog(0L, "[INFO] 自动登录....") val handler = CoroutineExceptionHandler { _, throwable -> consoleFrontEnd.pushLog(0L, "[ERROR] 自动登录失败 $throwable") } val bot = Bot(qq, pwd!!.chunkedHexToBytes()) { fileBasedDeviceInfo(getExternalFilesDir(null)!!.absolutePath + "/device.json") this.loginSolver = MiraiConsole.frontEnd.createLoginSolver() this.botLoggerSupplier = { SimpleLogger("[BOT $qq]") { _, message, e -> consoleFrontEnd.pushLog(0L, "[INFO] $message") e?.also { consoleFrontEnd.pushLog(0L, "[BOT ERROR $qq] $it") }?.printStackTrace() } } this.networkLoggerSupplier = { SimpleLogger("BOT $qq") { _, message, e -> consoleFrontEnd.pushLog(0L, "[NETWORK] $message") e?.also { consoleFrontEnd.pushLog(0L, "[NETWORK ERROR] $it") }?.printStackTrace() } } } this.bot = bot GlobalScope.launch(handler) { bot.login() } bot.subscribeMessages { startsWith("/") { message -> if (bot.checkManager(this.sender.id)) CommandManager.runCommand(ContactCommandSender(bot, this.subject), message) } } GlobalScope.launch(handler) { sendMessage("$qq login successes") } MiraiConsole.frontEnd.pushBot(bot) } private fun registerDefaultCommand() { register(_description = "显示MiraiAndroid运行状态", _name = "android") { sender, _ -> sender.sendMessage(MiraiAndroidStatus.recentStatus().format()) true } register(_description = "查看已加载的脚本", _name = "script", _usage = "script") { sender, _ -> sender.sendMessage(buildString { append("已加载${ScriptManager.instance.hosts.size}个脚本\n") ScriptManager.instance.hosts.joinTo( this, "\n" ) { "${it.info.name} ${it.info.version} by ${it.info.author}" } append("\n已加载Bot数量:${ScriptManager.instance.botsSize}") }) true } } @SuppressLint("WakelockTimeout") private fun startConsole(intent: Intent?) { if (isStart) return Log.e(TAG, "启动服务") try { wakeLock.acquire() } catch (e: Exception) { Log.e("wakeLockError", e.message ?: "null") } MiraiAndroidStatus.startTime = System.currentTimeMillis() MiraiConsole.start( consoleFrontEnd, consoleVersion = BuildConfig.COREVERSION, path = getExternalFilesDir(null).toString() ) registerReceiver() isStart = true createNotification() registerDefaultCommand() intent?.let { autoLogin(it) } } private fun stopConsole() { if (!isStart) return Log.e(TAG, "停止服务") if (allowPushMsg) { unregisterReceiver(msgReceiver) } ScriptManager.instance.disableAll() if (wakeLock.isHeld) { wakeLock.release() } MiraiConsole.stop() stopForeground(true) stopSelf() exitProcess(0) } private fun registerReceiver() { if (allowPushMsg) { MiraiConsole.frontEnd.pushLog(0L, "[MA] 正在启动消息推送广播监听器") val filter = IntentFilter().apply { addAction("io.github.mzdluo123.mirai.android.PushMsg") priority = 999 addDataScheme("ma") } registerReceiver(msgReceiver, filter) } } internal fun sendFriendMsg(id: Long, msg: String?) { bot?.launch { MiraiConsole.frontEnd.pushLog(0L, "[MA] 成功处理一个好友消息推送请求: $msg->$id") this@BotService.bot!!.getFriend(id).sendMessage(msg!!) } } internal fun sendGroupMsg(id: Long, msg: String?) { bot?.launch { MiraiConsole.frontEnd.pushLog(0L, "[MA] 成功处理一个群消息推送请求: $msg->$id") this@BotService.bot!!.getGroup(id).sendMessage(msg!!) } } internal fun sendGroupMsgWithAT(id: Long, msg: String?, user: Long) { bot?.launch { MiraiConsole.frontEnd.pushLog(0L, "[MA] 成功处理一个群消息推送请求: $msg->$id") val group = this@BotService.bot!!.getGroup(id) group.sendMessage(At(group[user]) + msg!!) } } @ExperimentalUnsignedTypes private fun String.chunkedHexToBytes(): ByteArray = this.asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() } .toList().toByteArray() inner class BotBinder : IbotAidlInterface.Stub() { override fun runCmd(cmd: String?) { cmd?.let { CommandManager.runCommand(ConsoleCommandSender, it) } } override fun getLog(): Array<String> { //防止 // ClassCastException: java.lang.Object[] cannot be cast to java.lang.String[] // 不知道有没有更好的写法 return consoleFrontEnd.logStorage.toArray( arrayOfNulls(consoleFrontEnd.logStorage.size) ) } override fun submitVerificationResult(result: String?) { result?.let { consoleFrontEnd.loginSolver.verificationResult.complete(it) } } override fun setScriptConfig(config: String?) { } override fun createScript(name: String, type: Int): Boolean { return ScriptManager.instance.createScriptFromFile(File(name), type) } override fun reloadScript(index: Int): Boolean { ScriptManager.instance.reload(index) return true } override fun clearLog() { consoleFrontEnd.logStorage.clear() } override fun enableScript(index: Int) { ScriptManager.instance.enable(index) } override fun disableScript(index: Int) { ScriptManager.instance.disable(index) } override fun getUrl(): String = consoleFrontEnd.loginSolver.url override fun getScriptSize(): Int = ScriptManager.instance.hosts.size override fun getCaptcha(): ByteArray = consoleFrontEnd.loginSolver.captchaData override fun getLogonId(): Long { return try { Bot.botInstances.first().id } catch (e: NoSuchElementException) { 0 } } override fun sendLog(log: String?) { consoleFrontEnd.logStorage.add(log) } override fun getBotInfo(): String = MiraiAndroidStatus.recentStatus().format() override fun openScript(index: Int) { val scriptFile = ScriptManager.instance.hosts[index].scriptFile val provideUri: Uri provideUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { FileProvider.getUriForFile( this@BotService, "io.github.mzdluo123.mirai.android.scriptprovider", scriptFile ) } else { Uri.fromFile(scriptFile) } startActivity( Intent("android.intent.action.VIEW").apply { addCategory("android.intent.category.DEFAULT") addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) putExtra(MediaStore.EXTRA_OUTPUT, provideUri) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) type = "text/plain" setDataAndType(provideUri, type) }) } override fun deleteScript(index: Int) { ScriptManager.instance.delete(index) } override fun getHostList(): Array<String> = ScriptManager.instance.getHostInfoStrings() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/ServiceConnector.kt ================================================ package io.github.mzdluo123.mirai.android.service import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.ServiceConnection import android.os.IBinder import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.MutableLiveData import androidx.lifecycle.OnLifecycleEvent import io.github.mzdluo123.mirai.android.IbotAidlInterface import io.github.mzdluo123.mirai.android.IdleResources class ServiceConnector(var context: Context) : ServiceConnection, LifecycleObserver { lateinit var botService: IbotAidlInterface private set var connectStatus = MutableLiveData(false) private set override fun onServiceDisconnected(name: ComponentName?) { connectStatus.value = false } override fun onServiceConnected(name: ComponentName?, service: IBinder?) { botService = IbotAidlInterface.Stub.asInterface(service) connectStatus.value = true if (!IdleResources.botServiceLoading.isIdleNow) { IdleResources.botServiceLoading.decrement() } } @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) fun connect() { if (!connectStatus.value!!) { context.bindService( Intent(context, BotService::class.java), this, Context.BIND_ABOVE_CLIENT ) if (IdleResources.botServiceLoading.isIdleNow) { IdleResources.botServiceLoading.increment() } } } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun disconnect() { if (connectStatus.value!!) { context.unbindService(this) connectStatus.value = false } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/about/AboutFragment.kt ================================================ package io.github.mzdluo123.mirai.android.ui.about import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import io.github.mzdluo123.mirai.android.BuildConfig import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.databinding.FragmentAboutBinding import kotlinx.android.synthetic.main.fragment_about.* import splitties.toast.toast class AboutFragment : Fragment() { private lateinit var aboutBinding: FragmentAboutBinding private var click = 0 override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Inflate the layout for this fragment val aboutBinding = DataBindingUtil.inflate<FragmentAboutBinding>( layoutInflater, R.layout.fragment_about, container, false ) aboutBinding.appVersion = requireContext().packageManager.getPackageInfo( requireContext().packageName, 0 ).versionName aboutBinding.coreVersion = BuildConfig.COREVERSION return aboutBinding.root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) github_btn.setOnClickListener { openUrl("https://github.com/mamoe/mirai") } github2_bth.setOnClickListener { openUrl("https://github.com/mzdluo123/MiraiAndroid") } imageView2.setOnClickListener { if (click < 4) { click++ return@setOnClickListener } imageView2.setImageResource(R.drawable.avatar) } btn_join_group.setOnClickListener { if (!joinQQGroup("df6wSbKtDBo3cMJ9ULtYAZeln5ZZuA9d")) { toast("拉起QQ失败,请确认你是否安装了QQ") } } } private fun openUrl(url: String) { val uri = Uri.parse(url) startActivity(Intent(Intent.ACTION_VIEW, uri)) } /**************** * * 发起添加群流程。群号:MiraiAndroid(1131127734) 的 key 为: df6wSbKtDBo3cMJ9ULtYAZeln5ZZuA9d * 调用 joinQQGroup(df6wSbKtDBo3cMJ9ULtYAZeln5ZZuA9d) 即可发起手Q客户端申请加群 MiraiAndroid(1131127734) * * @param key 由官网生成的key * @return 返回true表示呼起手Q成功,返回false表示呼起失败 */ private fun joinQQGroup(key: String): Boolean { val intent = Intent() intent.data = Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26jump_from%3Dwebapi%26k%3D$key") // 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) return try { startActivity(intent) true } catch (e: Exception) { // 未安装手Q或安装的版本不支持 false } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/console/ConsoleFragment.kt ================================================ package io.github.mzdluo123.mirai.android.ui.console import android.app.AlertDialog import android.content.Context import android.os.Bundle import android.os.DeadObjectException import android.util.Log import android.view.* import android.view.inputmethod.EditorInfo import android.widget.EditText import android.widget.ScrollView import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import com.bumptech.glide.Glide import com.bumptech.glide.load.resource.bitmap.RoundedCorners import com.bumptech.glide.request.RequestOptions import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.service.ServiceConnector import io.github.mzdluo123.mirai.android.utils.shareText import kotlinx.android.synthetic.main.fragment_home.* import kotlinx.coroutines.* import splitties.toast.toast import java.security.MessageDigest @ExperimentalUnsignedTypes class ConsoleFragment : Fragment() { companion object { const val TAG = "ConsoleFragment" } private var logRefreshJob: Job? = null private lateinit var conn: ServiceConnector private var autoScroll = true override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { conn = ServiceConnector(requireContext()) lifecycle.addObserver(conn) val root = inflater.inflate(R.layout.fragment_home, container, false) setHasOptionsMenu(true) return root } override fun onStart() { super.onStart() commandSend_btn.setOnClickListener { submitCmd() } shortcutBottom_btn.setOnClickListener { viewLifecycleOwner.lifecycleScope.launch { if (autoScroll) { autoScroll = false toast("自动滚动禁用") shortcutBottom_btn.setImageResource(R.drawable.ic_keyboard_arrow_down_black_24dp) } else { autoScroll = true toast("自动滚动启用") shortcutBottom_btn.setImageResource(R.drawable.ic_baseline_keyboard_arrow_up_24) } } } command_input.setOnEditorActionListener { _, actionId, _ -> if (actionId == EditorInfo.IME_ACTION_SEND) { submitCmd() } return@setOnEditorActionListener false } conn.connectStatus.observe(this, Observer { Log.d(TAG, "service status $it") if (logRefreshJob != null && logRefreshJob!!.isActive) { return@Observer } if (it) { startRefreshLoop() } }) } override fun onResume() { super.onResume() startLoadAvatar() if (logRefreshJob != null && logRefreshJob!!.isActive) { logRefreshJob!!.cancel() } startRefreshLoop() } override fun onPause() { super.onPause() if (logRefreshJob != null && logRefreshJob!!.isActive) { logRefreshJob!!.cancel() } } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_console, menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { super.onOptionsItemSelected(item) when (item.itemId) { R.id.action_setAutoLogin -> { setAutoLogin() } R.id.action_report -> context?.shareText( buildString { append(conn.botService.botInfo) append("\n") append("========以下是控制台log=======\n") append(conn.botService.log.joinToString(separator = "\n")) }, lifecycleScope ) /* R.id.action_battery -> { ignoreBatteryOptimization(requireActivity()) } R.id.action_fast_restart -> { NotificationFactory.dismissAllNotification() restart() }*/ } return false } // @SuppressLint("BatteryLife") // private fun ignoreBatteryOptimization(activity: Activity) { // val powerManager = // getSystemService(requireContext(), PowerManager::class.java) // // 判断当前APP是否有加入电池优化的白名单,如果没有,弹出加入电池优化的白名单的设置对话框。 // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // val hasIgnored = powerManager!!.isIgnoringBatteryOptimizations(activity.packageName) // if (!hasIgnored) { // val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) // intent.data = Uri.parse("package:" + activity.packageName) // startActivity(intent) // } else { // Toast.makeText(context, "您已授权忽略电池优化", Toast.LENGTH_SHORT).show() // } // } else { // Toast.makeText(requireContext(), "只有新版Android才需要这个操作哦", Toast.LENGTH_SHORT).show() // // } // } private fun submitCmd() { var command = command_input.text.toString() lifecycleScope.launch(Dispatchers.Default) { if (command.startsWith("/")) { command = command.substring(1) } conn.botService.runCmd(command) } command_input.text.clear() } private fun setAutoLogin() { val alertView = View.inflate(activity, R.layout.dialog_autologin, null) val pwdInput = alertView.findViewById<EditText>(R.id.password_input) val qqInput = alertView.findViewById<EditText>(R.id.qq_input) val accountStore = requireActivity().getSharedPreferences("account", Context.MODE_PRIVATE) val dialog = AlertDialog.Builder(activity) .setView(alertView) .setCancelable(true) .setTitle("设置自动登录") .setPositiveButton("设置自动登录") { _, _ -> accountStore.edit().putLong("qq", qqInput.text.toString().toLong()) .putString("pwd", md5(pwdInput.text.toString())).apply() Toast.makeText(activity, "设置成功,重启后生效", Toast.LENGTH_SHORT).show() } .setNegativeButton("取消自动登录") { _, _ -> accountStore.edit().putLong("qq", 0L).putString("pwd", "").apply() Toast.makeText(activity, "设置成功,重启后生效", Toast.LENGTH_SHORT).show() } .setNeutralButton("取消") { dialog, _ -> dialog.dismiss() } dialog.show() } private fun startRefreshLoop() { if (!conn.connectStatus.value!!) { return } logRefreshJob = viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Default) { log_text?.clearComposingText() try { withContext(Dispatchers.Main) { Log.d(TAG, "start loop") } while (isActive) { val text = conn.botService.log.joinToString(separator = "\n") withContext(Dispatchers.Main) { log_text?.text = text if (autoScroll) { main_scroll.fullScroll(ScrollView.FOCUS_DOWN) } } delay(200) } } catch (e: DeadObjectException) { // ignore } withContext(Dispatchers.Main) { log_text?.append("\n无法连接到服务,可能是正在重启") } } } private fun startLoadAvatar() { viewLifecycleOwner.lifecycleScope.launch { while (true) { try { val id = conn.botService.logonId if (id != 0L) { Glide.with(requireActivity()) .load("http://q1.qlogo.cn/g?b=qq&nk=$id&s=640") .apply( RequestOptions().error(R.mipmap.ic_new_launcher_round) .transform(RoundedCorners(40)) ) .into(requireActivity().findViewById(R.id.head_imageVIew)) return@launch } } catch (e: UninitializedPropertyAccessException) { // pass } catch (e: DeadObjectException) { //pass } delay(1000) } } } private fun md5(str: String): String { val digest = MessageDigest.getInstance("MD5") val result = digest.digest(str.toByteArray()) //没转16进制之前是16位 println("result${result.size}") //转成16进制后是32字节 return toHex(result) } private fun toHex(byteArray: ByteArray): String { //转成16进制后是32字节 return with(StringBuilder()) { byteArray.forEach { val hex = it.toInt() and (0xFF) val hexStr = Integer.toHexString(hex) if (hexStr.length == 1) { append("0").append(hexStr) } else { append(hexStr) } } toString() } } // private fun restart() = viewLifecycleOwner.lifecycleScope.launch { // conn.disconnect() // BotApplication.context.stopBotService() // delay(200) // BotApplication.context.startBotService() // conn.connect() // } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginFragment.kt ================================================ package io.github.mzdluo123.mirai.android.ui.plugin import android.app.Activity import android.content.Intent import android.os.Build import android.os.Bundle import android.view.* import android.widget.Toast import androidx.appcompat.widget.PopupMenu import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.viewholder.BaseViewHolder import io.github.mzdluo123.mirai.android.R import kotlinx.android.synthetic.main.fragment_plugin.* import java.io.File class PluginFragment : Fragment() { private lateinit var pluginViewModel: PluginViewModel private lateinit var adapter: PluginsAdapter companion object { const val SELECT_RESULT_CODE = 1 } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { pluginViewModel = ViewModelProvider(this).get(PluginViewModel::class.java) val root = inflater.inflate(R.layout.fragment_plugin, container, false) setHasOptionsMenu(true) adapter = PluginsAdapter() adapter.setOnItemClickListener { _, view, position -> val menu = PopupMenu(requireContext(), view) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { menu.gravity = Gravity.END } menu.menuInflater.inflate(R.menu.plugin_manage, menu.menu) menu.setOnMenuItemClickListener { item -> when (item.itemId) { R.id.action_delete -> { pluginViewModel.deletePlugin(position) Toast.makeText(activity, "删除成功,重启后生效", Toast.LENGTH_SHORT).show() return@setOnMenuItemClickListener true } else -> return@setOnMenuItemClickListener true } } menu.show() } pluginViewModel.pluginList.observe(viewLifecycleOwner, Observer { adapter.data = it.toMutableList() adapter.notifyDataSetChanged() }) return root } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) plugin_recycler.adapter = adapter plugin_recycler.layoutManager = LinearLayoutManager(activity) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.plugin_add, menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == android.R.id.home) { return false } val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { // Filter to only show results that can be "opened", such as a // file (as opposed to a list of contacts or timezones) addCategory(Intent.CATEGORY_OPENABLE) // Filter to show only images, using the image MIME data type. // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". // To search for all documents available via installed storage providers, // it would be "*/*". type = "application/java-archive" } startActivityForResult(intent, SELECT_RESULT_CODE) return true } override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { // The ACTION_OPEN_DOCUMENT intent was sent with the request code // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the // response to some other intent, and the code below shouldn't run at all. if (requestCode == SELECT_RESULT_CODE && resultCode == Activity.RESULT_OK) { // The document selected by the user won't be returned in the intent. // Instead, a URI to that document will be contained in the return intent // provided to this method as a parameter. // Pull that URI using resultData.getData(). resultData?.data?.also { uri -> startActivity( // Intent(activity, PluginImportActivity::class.java).putExtra( // "uri", // uri.toString() // ) Intent(Intent.ACTION_VIEW, uri) ) } } } override fun onResume() { super.onResume() pluginViewModel.refreshPluginList() } } class PluginsAdapter() : BaseQuickAdapter<File, BaseViewHolder>(R.layout.item_plugin) { override fun convert(holder: BaseViewHolder, item: File) { holder.setText(R.id.pluginName_text, item.name) holder.setText(R.id.pluginSize_text, "${item.length() / 1024}kb") } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginViewModel.kt ================================================ package io.github.mzdluo123.mirai.android.ui.plugin import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.github.mzdluo123.mirai.android.BotApplication import io.github.mzdluo123.mirai.android.utils.DexCompiler import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.File @ExperimentalUnsignedTypes class PluginViewModel : ViewModel() { val pluginList = MutableLiveData<List<File>>() init { refreshPluginList() } private fun loadPluginList(): List<File> { val fileList = mutableListOf<File>() BotApplication.context.getExternalFilesDir("plugins")?.listFiles()?.forEach { if (it.isFile) { fileList.add(it) } } return fileList } fun deletePlugin(pos: Int) { val file = pluginList.value?.get(pos) ?: return file.delete() refreshPluginList() } fun refreshPluginList() { viewModelScope.launch { pluginList.postValue(loadPluginList()) } } suspend fun compilePlugin(file: File, desugaring: Boolean) { val workDir = BotApplication.context.getExternalFilesDir(null) ?: return val tempDir = BotApplication.context.cacheDir val compiler = DexCompiler(workDir, tempDir) withContext(Dispatchers.IO) { if (tempDir.exists()) { deleteDir(tempDir) } tempDir.mkdir() } withContext(Dispatchers.Default) { val out = compiler.compile(file,desugaring) compiler.copyResourcesAndMove(file, out) } } private fun deleteDir(path: File) { path.listFiles()?.forEach { if (it.isFile) { it.delete() } else { if (it.isDirectory) { deleteDir(it) } } } path.delete() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterFragment.kt ================================================ package io.github.mzdluo123.mirai.android.ui.script import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.* import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager import com.ooooonly.giteeman.GiteeFile import io.github.mzdluo123.mirai.android.IdleResources import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.script.ScriptHostFactory import io.github.mzdluo123.mirai.android.service.ServiceConnector import kotlinx.android.synthetic.main.fragment_script_center.* import kotlinx.coroutines.* import splitties.alertdialog.appcompat.* import splitties.toast.toast @ExperimentalStdlibApi class ScriptCenterFragment : Fragment(), CoroutineScope by MainScope() { private lateinit var scriptViewModel: ScriptCenterViewModel private lateinit var adapter: ScriptCenterListAdapter private lateinit var botServiceConnection: ServiceConnector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) botServiceConnection = ServiceConnector(requireContext()) lifecycle.addObserver(botServiceConnection) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.fragment_script_center, container, false).also { setHasOptionsMenu(true) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_script_center, menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> false R.id.action_upload_script -> { context?.alertDialog { message = """ 你需要 1.注册gitee账号 2.向脚本仓库地址“https://gitee.com/ooooonly/lua-mirai-project/tree/master/ScriptCenter”提交你的“Pull Request” 3.等待审核即可上架 """.trimIndent() title = "如何上传脚本" setPositiveButton("前往仓库地址") { _, _ -> context.startActivity( Intent( Intent.ACTION_VIEW, Uri.parse("https://gitee.com/ooooonly/lua-mirai-project/tree/master/ScriptCenter") ) ) } setNegativeButton("取消") { _, _ -> } }?.show() true } else -> true } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) adapter = ScriptCenterListAdapter { selectedFile -> if (selectedFile.isFile) { var alert: androidx.appcompat.app.AlertDialog? = null alert = context?.alertDialog { message = "是否导入${selectedFile.fileName}?" okButton { val progressDialog = context.alertDialog { message = "正在导入" IdleResources.loadingData.increment() } progressDialog.show() val exceptionHandler = CoroutineExceptionHandler { _, throwable -> progressDialog.dismiss() IdleResources.loadingData.decrement() context.toast("导入失败!\n$throwable") } launch(exceptionHandler) { withContext(Dispatchers.IO) { val filePath = requireContext().getExternalFilesDir("scripts")!!.absolutePath + "/" + selectedFile.fileName selectedFile.saveToFile(filePath) val scriptType = ScriptHostFactory.getTypeFromSuffix(filePath.split(".").last()) val result = botServiceConnection.botService.createScript( filePath, scriptType ) if (!result) throw Exception() } progressDialog?.dismiss() context.toast("导入成功!") IdleResources.loadingData.decrement() } } cancelButton {} } alert?.show() } else { scriptViewModel.showFiles(selectedFile) } } adapter.setEmptyView(layoutInflater.inflate(R.layout.fragment_script_center_empty, null)) rcl_scripts.adapter = adapter rcl_scripts.layoutManager = LinearLayoutManager(activity) /* rcl_scripts.addItemDecoration( DividerItemDecoration( context, DividerItemDecoration.HORIZONTAL ) )*/ } override fun onResume() { super.onResume() botServiceConnection.connectStatus.observe(this, Observer { if (it) { scriptViewModel = ScriptCenterViewModel() scriptViewModel.fileList.observe(viewLifecycleOwner, Observer { adapter.data = it.toMutableList() adapter.notifyDataSetChanged() }) IdleResources.loadingData.increment() scriptViewModel.showFiles( GiteeFile( "ooooonly", "lua-mirai-project", "ScriptCenter", rootLevel = 2, showParent = true ) ) } }) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterListAdapter.kt ================================================ package io.github.mzdluo123.mirai.android.ui.script import androidx.cardview.widget.CardView import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.viewholder.BaseViewHolder import com.ooooonly.giteeman.GiteeFile import io.github.mzdluo123.mirai.android.R @ExperimentalStdlibApi class ScriptCenterListAdapter(var listener: (GiteeFile) -> Unit) : BaseQuickAdapter<GiteeFile, BaseViewHolder>(R.layout.item_script_center_list) { override fun convert(holder: BaseViewHolder, item: GiteeFile) { with(holder) { setVisible(R.id.iv_file, item.isFile) setVisible(R.id.iv_folder, item.isDictionary) setText(R.id.tv_name, item.fileName) holder.getView<CardView>(R.id.cv_item).setOnClickListener { listener(item) } } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterViewModel.kt ================================================ package io.github.mzdluo123.mirai.android.ui.script import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.ooooonly.giteeman.GiteeFile import io.github.mzdluo123.mirai.android.IdleResources import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @ExperimentalStdlibApi class ScriptCenterViewModel() : ViewModel() { val fileList = MutableLiveData<List<GiteeFile>>() fun showFiles(parent: GiteeFile) { viewModelScope.launch { withContext( Dispatchers.IO ) { fileList.postValue(parent.listFiles()) IdleResources.loadingData.decrement() } } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptFragment.kt ================================================ package io.github.mzdluo123.mirai.android.ui.script import android.app.Activity import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.* import android.widget.Button import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.script.ScriptHostFactory import io.github.mzdluo123.mirai.android.script.ScriptManager import io.github.mzdluo123.mirai.android.service.ServiceConnector import io.github.mzdluo123.mirai.android.utils.askFileName import kotlinx.android.synthetic.main.fragment_script.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import splitties.alertdialog.appcompat.* import splitties.toast.toast class ScriptFragment : Fragment(), ScriptInfoDialogFragment.ScriptInfoDialogFragmentListener { companion object { const val IMPORT_SCRIPT = 2 } private lateinit var scriptViewModel: ScriptViewModel private val adapter: ScriptListAdapter by lazy { ScriptListAdapter(this) } private lateinit var botServiceConnection: ServiceConnector override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) botServiceConnection = ServiceConnector(requireContext()) lifecycle.addObserver(botServiceConnection) } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? = inflater.inflate(R.layout.fragment_script, container, false).also { setHasOptionsMenu(true) adapter.setEmptyView(inflater.inflate(R.layout.fragment_script_empty, null).apply { findViewById<Button>(R.id.btn_script_center).setOnClickListener { findNavController().navigate(R.id.action_nav_scripts_to_nav_scripts_center) } }) } override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) script_recycler.adapter = adapter script_recycler.addItemDecoration( DividerItemDecoration( context, DividerItemDecoration.HORIZONTAL ) ) script_recycler.layoutManager = LinearLayoutManager(activity) } override fun onResume() { super.onResume() botServiceConnection.connectStatus.observe(this, Observer { if (it) { scriptViewModel = ScriptViewModel(botServiceConnection.botService) scriptViewModel.observe(viewLifecycleOwner, Observer { adapter.data = it.toMutableList() adapter.notifyDataSetChanged() }) scriptViewModel.refreshScriptList() } }) } override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.menu_script, menu) } override fun onOptionsItemSelected(item: MenuItem): Boolean = when (item.itemId) { android.R.id.home -> false R.id.action_add_script -> { startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { addCategory(Intent.CATEGORY_OPENABLE) type = "*/*" }, IMPORT_SCRIPT) true } R.id.action_script_center -> { findNavController().navigate(R.id.action_nav_scripts_to_nav_scripts_center) true } else -> true } override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { super.onActivityResult(requestCode, resultCode, intent) if (requestCode == IMPORT_SCRIPT && resultCode == Activity.RESULT_OK) { intent?.data?.also { importScript(it) } } } private fun importScript(uri: Uri) { uri.path ?: return val scriptType = ScriptHostFactory.getTypeFromSuffix(uri.path!!.split(".").last()) if (scriptType != ScriptHostFactory.UNKNOWN) { importScript(uri, scriptType) return } context?.alertDialog { title = "未知脚本的后缀名,请手动选择脚本类型" setItems(arrayOf("Lua", "JavaScript", "Python", "KotlinScript")) { _, type -> importScript(uri, type + 1) } } } private fun importScript(uri: Uri, type: Int) { viewLifecycleOwner.lifecycleScope.launch { val name = withContext(Dispatchers.Main) { requireActivity().askFileName() } ?: return@launch val scriptFile = ScriptManager.copyFileToScriptDir(requireContext(), uri, name) val result = scriptViewModel.createScriptFromFile(scriptFile, type) if (result) { context?.toast("导入成功,当前脚本数量:${scriptViewModel.hostSize}") } else { context?.toast("导入失败,请检查脚本是否有误!") } } } override fun onDeleteScript(index: Int) { context?.alertDialog { message = "删除脚本后无法恢复,是否确定?" okButton { scriptViewModel.deleteScript(index) } cancelButton { } }?.show() } override fun onSaveScript(index: Int) { } override fun onReloadScript(index: Int) { context?.alertDialog { message = "重新加载该脚本?" okButton { scriptViewModel.reloadScript(index) requireContext().toast("重载完毕") } cancelButton { } }?.show() } override fun onOpenScript(index: Int) { scriptViewModel.openScript(index) } override fun onEnableScript(index: Int) { scriptViewModel.enableScript(index) requireContext().toast("已启用") } override fun onDisableScript(index: Int) { scriptViewModel.disableScript(index) requireContext().toast("已禁用") } fun showScriptInfo(index: Int) { ScriptInfoDialogFragment(index, scriptViewModel, this).show(parentFragmentManager, "script") } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptInfoDialogFragment.kt ================================================ package io.github.mzdluo123.mirai.android.ui.script import android.app.Dialog import android.os.Bundle import android.widget.ImageButton import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.script.ScriptHostFactory import io.github.mzdluo123.mirai.android.utils.formatFileLength class ScriptInfoDialogFragment( var scriptIndex: Int, var scriptViewModel: ScriptViewModel, var listener: ScriptInfoDialogFragmentListener ) : DialogFragment() { interface ScriptInfoDialogFragmentListener { fun onDeleteScript(index: Int) fun onSaveScript(index: Int) fun onReloadScript(index: Int) fun onOpenScript(index: Int) fun onEnableScript(index: Int) fun onDisableScript(index: Int) } override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { return activity?.let { scriptViewModel.hosts.value?.get(scriptIndex)?.let { info -> val builder = AlertDialog.Builder(it) val root = requireActivity().layoutInflater.inflate(R.layout.dialog_script_info, null) builder.setTitle(info.name).setView(root) root.findViewById<TextView>(R.id.tv_script_info).text = buildString { append("脚本类型:") append(ScriptHostFactory.NAMES[info.scriptType]) append("\n大小:") append(formatFileLength(info.fileLength)) append("\n作者:") append(info.author) append("\n版本:") append(info.version) append("\n说明:") append(info.description) } val deleteBotton = root.findViewById<ImageButton>(R.id.btn_delete) val openBotton = root.findViewById<ImageButton>(R.id.btn_edit) val reloadBotton = root.findViewById<ImageButton>(R.id.btn_reload) val saveBotton = root.findViewById<ImageButton>(R.id.btn_save) val dialog = builder.create() deleteBotton.setOnClickListener { listener.onDeleteScript(scriptIndex) dialog.dismiss() } openBotton.setOnClickListener { listener.onOpenScript(scriptIndex) dialog.dismiss() } reloadBotton.setOnClickListener { listener.onReloadScript(scriptIndex) dialog.dismiss() } saveBotton.setOnClickListener { listener.onSaveScript(scriptIndex) dialog.dismiss() } dialog } } ?: throw IllegalStateException("Activity cannot be null") } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptListAdapter.kt ================================================ package io.github.mzdluo123.mirai.android.ui.script import android.util.Log import android.widget.Switch import androidx.cardview.widget.CardView import com.chad.library.adapter.base.BaseQuickAdapter import com.chad.library.adapter.base.viewholder.BaseViewHolder import io.github.mzdluo123.mirai.android.R import io.github.mzdluo123.mirai.android.script.ScriptHost class ScriptListAdapter(var fragment: ScriptFragment) : BaseQuickAdapter<ScriptHost.ScriptInfo, BaseViewHolder>(R.layout.item_script) { override fun convert(holder: BaseViewHolder, item: ScriptHost.ScriptInfo) { with(holder) { setText(R.id.tv_script_alias, item.name) setText(R.id.tv_script_author, item.author) setText(R.id.tv_script_version, item.version) holder.getView<CardView>(R.id.cv_item).setOnClickListener { fragment.showScriptInfo(holder.layoutPosition) } holder.getView<Switch>(R.id.swt_enable).apply { Log.i("item.enable", item.enable.toString()) setOnCheckedChangeListener(null) setChecked(item.enable) setOnCheckedChangeListener { _, check -> Log.i("checkChange", check.toString()) if (check) fragment.onEnableScript(holder.layoutPosition) else fragment.onDisableScript(holder.layoutPosition) } } } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptViewModel.kt ================================================ package io.github.mzdluo123.mirai.android.ui.script import androidx.lifecycle.* import io.github.mzdluo123.mirai.android.IbotAidlInterface import io.github.mzdluo123.mirai.android.IdleResources import io.github.mzdluo123.mirai.android.script.ScriptHost import io.github.mzdluo123.mirai.android.script.ScriptManager import kotlinx.coroutines.launch import java.io.File class ScriptViewModel(private val serviceHelper: IbotAidlInterface) : ViewModel() { val hosts = MutableLiveData<List<ScriptHost.ScriptInfo>>() val hostSize: Int get() = serviceHelper.scriptSize fun observe(owner: LifecycleOwner, observer: Observer<in List<ScriptHost.ScriptInfo>>) = hosts.observe(owner, observer) fun createScriptFromFile(scriptFile: File, type: Int): Boolean { val result = serviceHelper.createScript(scriptFile.absolutePath, type) if (!result) return false refreshScriptList() return true } fun refreshScriptList() = viewModelScope.launch { hosts.postValue(ScriptManager.unPackHostInfos(serviceHelper.getHostList())) } fun withLoadingIdleResources(content: () -> Unit) { IdleResources.loadingData.increment() content() IdleResources.loadingData.decrement() } fun reloadScript(index: Int) = serviceHelper.reloadScript(index).also { refreshScriptList() } fun deleteScript(index: Int) = withLoadingIdleResources { serviceHelper.deleteScript(index).also { refreshScriptList() } } fun openScript(index: Int) = withLoadingIdleResources { serviceHelper.openScript(index) } fun enableScript(index: Int) = withLoadingIdleResources { serviceHelper.enableScript(index).also { refreshScriptList() } } fun disableScript(index: Int) = withLoadingIdleResources { serviceHelper.disableScript(index).also { refreshScriptList() } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/setting/SettingFragment.kt ================================================ package io.github.mzdluo123.mirai.android.ui.setting import android.content.Intent import android.net.Uri import android.os.Build import android.os.Bundle import android.os.PowerManager import android.provider.Settings import android.text.InputType import androidx.core.content.ContextCompat import androidx.preference.EditTextPreference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import androidx.preference.SwitchPreference import io.github.mzdluo123.mirai.android.R class SettingFragment : PreferenceFragmentCompat() { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceManager.sharedPreferencesName = "setting" setPreferencesFromResource(R.xml.setting_screen, rootKey) findPreference<EditTextPreference>("log_buffer_preference")?.apply { summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance() setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER } } findPreference<EditTextPreference>("status_refresh_count")?.apply { summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance() setOnBindEditTextListener { it.inputType = InputType.TYPE_CLASS_NUMBER } } findPreference<SwitchPreference>("ignore_battery_optimization")?.apply { /* setOnPreferenceClickListener { preference -> true } */ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { data = Uri.parse("package:" + requireActivity().packageName) } } } } override fun onResume() { super.onResume() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val hasIgnored = ContextCompat.getSystemService( requireContext(), PowerManager::class.java )!!.isIgnoringBatteryOptimizations(requireContext().packageName) PreferenceManager.getDefaultSharedPreferences(requireContext()).apply { edit().putBoolean("ignore_battery_optimization", hasIgnored).apply() } } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/CommandFastRegister.kt ================================================ package io.github.mzdluo123.mirai.android.utils import net.mamoe.mirai.console.command.Command import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.command.CommandOwner import net.mamoe.mirai.console.command.CommandSender fun CommandOwner.register( _alias: List<String> = listOf(), _description: String, _name: String, _usage: String = "/$_name", _block :suspend (CommandSender, List<String>) -> Boolean ){ CommandManager.register(this, object : Command { override val alias: List<String> get() = _alias override val description: String get() = _description override val name: String get() = _name override val usage: String get() = _usage override suspend fun onCommand(sender: CommandSender, args: List<String>): Boolean { return _block(sender,args) } }) } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/DeviceStatus.java ================================================ package io.github.mzdluo123.mirai.android.utils; import android.app.ActivityManager; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.telephony.TelephonyManager; import android.text.format.Formatter; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; public class DeviceStatus { public static String getSystemAvaialbeMemorySize(Context context) { ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); // 获得MemoryInfo对象 ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); // 获得系统可用内存,保存在MemoryInfo对象上 mActivityManager.getMemoryInfo(memoryInfo); long memSize = memoryInfo.availMem; // 字符类型转换 String availMemStr = Formatter.formatFileSize(context, memSize);// 调用系统函数,字符串转换 long -String KB/MB return availMemStr; } public static String getTotalMemory(Context context) { String str1 = "/proc/meminfo";// 系统内存信息文件 String str2; String[] arrayOfString; String initial_memory = ""; try { FileReader localFileReader = new FileReader(str1); BufferedReader localBufferedReader = new BufferedReader(localFileReader, 8192); str2 = localBufferedReader.readLine();// 读取meminfo第一行,系统总内存大小 arrayOfString = str2.split("//s+"); String mom = arrayOfString[0].split(":")[1].split("kB")[0]; initial_memory = Formatter.formatFileSize(context, Integer.valueOf(mom.trim()).intValue() * 1024); // 获得系统总内存,单位是MB,转换为GB localBufferedReader.close(); } catch (IOException e) { } return initial_memory;// Byte转换为KB或者MB,内存大小规格化 } enum NetState { WIFI("wifi", 1), CDMA("2G", 2), UMTS("3G", 3), LTE("4G", 4), UNKOWN("unkonw", 5); private int state; private String type; NetState(String type, int state) { this.state = state; this.type = type; } } public static String getCurrentNetType(Context context) { // String type = "unknown"; int state = NetState.UNKOWN.state; String type = NetState.UNKOWN.type; ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = cm.getActiveNetworkInfo(); if (info == null) { // type = "unknown"; state = NetState.UNKOWN.state; ; } else if (info.getType() == ConnectivityManager.TYPE_WIFI) { // type = "wifi"; state = NetState.WIFI.state; } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) { int subType = info.getSubtype(); if (subType == TelephonyManager.NETWORK_TYPE_CDMA || subType == TelephonyManager.NETWORK_TYPE_GPRS || subType == TelephonyManager.NETWORK_TYPE_EDGE) { // type = "2g"; state = NetState.CDMA.state; } else if (subType == TelephonyManager.NETWORK_TYPE_UMTS || subType == TelephonyManager.NETWORK_TYPE_HSDPA || subType == TelephonyManager.NETWORK_TYPE_EVDO_A || subType == TelephonyManager.NETWORK_TYPE_EVDO_0 || subType == TelephonyManager.NETWORK_TYPE_EVDO_B) { // type = "3g"; state = NetState.UMTS.state; } else {// LTE是3g到4g的过渡,是3.9G的全球标准 if (subType == // TelephonyManager.NETWORK_TYPE_LTE) // type = "4g"; state = NetState.LTE.state; } } switch (state) { case 1: type = NetState.WIFI.type; break; case 2: type = NetState.CDMA.type; break; case 3: type = NetState.UMTS.type; break; case 4: type = NetState.LTE.type; break; case 5: default: type = NetState.UNKOWN.type; break; } return type; } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/DexCompiler.java ================================================ package io.github.mzdluo123.mirai.android.utils; import android.util.Log; import com.android.tools.r8.CompilationFailedException; import com.android.tools.r8.D8; import com.android.tools.r8.D8Command; import com.android.tools.r8.OutputMode; import net.lingala.zip4j.ZipFile; import net.lingala.zip4j.exception.ZipException; import java.io.File; import java.io.IOException; import java.util.ArrayList; public class DexCompiler { private File tempDir, pluginDir; public DexCompiler(File fileDir, File cache) { tempDir = cache; if (!tempDir.exists()){ tempDir.mkdir(); } pluginDir = new File(fileDir.getAbsolutePath(), "plugins"); } public File compile(File jarFile, Boolean desugaring) throws CompilationFailedException, IOException { String outName = jarFile.getName().substring(0, jarFile.getName().length() - 4) + "-android.jar"; File outFile = new File(tempDir, outName).getAbsoluteFile(); // if (!outFile.exists()) { // outFile.createNewFile(); // } Log.e("输出路径", outFile.getAbsolutePath()); D8Command.Builder command = D8Command.builder() .addProgramFiles(jarFile.getAbsoluteFile().toPath()) .setOutput(outFile.toPath(), OutputMode.DexIndexed) .setMinApiLevel(26); if (!desugaring) { command.setDisableDesugaring(true); } // String[] cmd = new String[3]; // cmd[0] = "--output"; // cmd[1] = outFile.getAbsolutePath(); // cmd[2] = jarFile.getAbsolutePath(); // D8Command command = D8Command.parse(cmd, Origin.root()).build(); D8.run(command.build()); return outFile; } public void copyResourcesAndMove(File origin, File newFile) throws IOException { ZipFile originZip = new ZipFile(origin); ZipFile newZip = new ZipFile(newFile); ArrayList<File> resources = new ArrayList<>(); originZip.getFileHeaders().forEach(i -> { try { originZip.extractFile(i, tempDir.getAbsolutePath()); } catch (ZipException e) { e.printStackTrace(); } }); for (File file : tempDir.listFiles()) { if (file.isFile() && !file.getName().equals(newFile.getName())) { resources.add(file); } } newZip.addFiles(resources); newZip.addFolder(new File(tempDir, "META-INF")); newFile.renameTo(new File(pluginDir, newFile.getName())); } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/LoopQueue.java ================================================ //-------------------------------------------------- // Class LimitedLoopQueue //-------------------------------------------------- // Written by Kenvix <i@kenvix.com> //-------------------------------------------------- package io.github.mzdluo123.mirai.android.utils; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Collection; import java.util.Iterator; import java.util.NoSuchElementException; import java.util.Queue; @SuppressWarnings("unchecked") public class LoopQueue<E> implements Queue<E> { @Nullable private Object[] container; private int insertPos = 0; private int headElementPos = 0; public LoopQueue(int size) { this.container = new Object[size]; } @Override public int size() { if (insertPos >= headElementPos) return insertPos - headElementPos; else return container.length - (headElementPos - insertPos); } @Override public boolean isEmpty() { return insertPos == headElementPos; } @Override public boolean contains(@Nullable Object o) { if (o == null) return false; for (int i = 0; i < size(); i++) { int pos = elementIndexOf(i); if (o.equals(container[pos])) return true; } return false; } @NotNull @Override public Iterator<E> iterator() { return new Iterator<E>() { private int iterHeadElementPos = headElementPos; @Override public boolean hasNext() { return insertPos != iterHeadElementPos; } @Override public E next() { E head = (E) container[iterHeadElementPos]; iterHeadElementPos = (iterHeadElementPos + 1) % container.length; return head; } }; } @NotNull @Override public E[] toArray() { Object[] result = new Object[size()]; for (int i = 0; i < size(); i++) { int pos = elementIndexOf(i); result[i] = container[pos]; } return (E[]) result; } @NotNull @Override public <T> T[] toArray(@NotNull T[] a) { Object[] result; int size = size(); if (a.length >= size) result = a; else result = new Object[size()]; for (int i = 0; i < size(); i++) { int pos = elementIndexOf(i); result[i] = container[pos]; } return (T[]) result; } private void nextElementPos() { container[headElementPos] = null; headElementPos = (headElementPos + 1) % container.length; } private void nextInsertPos() { insertPos = (insertPos + 1) % container.length; } @Contract(value = "null -> fail; !null -> param1", pure = true) private <T> T assertElementNotNull(T obj) { if (obj == null) throw new NoSuchElementException(); else return obj; } private int elementIndexOf(int offset) { return (headElementPos + offset) % container.length; } @Override public boolean add(E e) { container[insertPos] = e; if (((insertPos + 1) % container.length) == headElementPos) { nextElementPos(); } nextInsertPos(); return true; } @Override public boolean remove(Object o) { return false; } @Override public boolean containsAll(@NotNull Collection<?> c) { return false; //TODO } @Override public boolean addAll(@NotNull Collection<? extends E> c) { return false; //TODO } @Override public boolean removeAll(@NotNull Collection<?> c) { return false; //TODO } @Override public boolean retainAll(@NotNull Collection<?> c) { return false; //TODO } @Override public void clear() { for (int i = 0; i < size(); i++) { container[elementIndexOf(i)] = null; } } @Override public boolean offer(E e) { return add(e); } @Override public E remove() { return assertElementNotNull(poll()); } @Override public E poll() { E head = peek(); nextElementPos(); return head; } @Override public E element() { return assertElementNotNull(peek()); } @Override public E peek() { return (E) container[headElementPos]; } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/MiraiAndroidStatus.kt ================================================ package io.github.mzdluo123.mirai.android.utils import android.content.Context import android.os.Build import io.github.mzdluo123.mirai.android.AppSettings import io.github.mzdluo123.mirai.android.BotApplication import io.github.mzdluo123.mirai.android.BuildConfig import splitties.experimental.ExperimentalSplittiesApi import java.text.SimpleDateFormat class MiraiAndroidStatus ( var miraiAndroidVersion:String, var coreVersion:String, var luaMiraiVersion:String, var releaseVersion:String, var sdkVersion:Int, var memorySize:String, var netType:String, var startTime:String, var logBuffer:Int ) { @ExperimentalSplittiesApi @ExperimentalUnsignedTypes companion object { var startTime: Long = 0 fun recentStatus(context: Context = BotApplication.context): MiraiAndroidStatus = MiraiAndroidStatus( context.packageManager.getPackageInfo(context.packageName, 0).versionName, BuildConfig.COREVERSION, BuildConfig.LUAMIRAI_VERSION, Build.VERSION.RELEASE, Build.VERSION.SDK_INT, DeviceStatus.getSystemAvaialbeMemorySize(context.applicationContext), DeviceStatus.getCurrentNetType(context.applicationContext), SimpleDateFormat.getDateTimeInstance().format(startTime), AppSettings.logBuffer ) } fun format():String = buildString{ append("MiraiAndroid v") append(miraiAndroidVersion) append("\n") append("MiraiCore v") append(coreVersion) append("\n") append("LuaMirai v") append(luaMiraiVersion) append("\n") append("系统版本 ") append(releaseVersion) append(" SDK ") append(sdkVersion) append("\n") append("内存可用 ") append(memorySize) append("\n") append("网络 ") append(netType) append("\n") append("启动时间 ") append(startTime) append("\n") append("日志缓存行数 ") append(logBuffer) } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/TextSharer.kt ================================================ package io.github.mzdluo123.mirai.android.utils import android.content.Context import android.content.Intent import androidx.lifecycle.LifecycleCoroutineScope import io.github.mzdluo123.mirai.android.IdleResources import kotlinx.coroutines.* import splitties.alertdialog.appcompat.alertDialog import splitties.alertdialog.appcompat.message import splitties.alertdialog.appcompat.title import splitties.toast.toast fun Context.shareText(text: String, scope: LifecycleCoroutineScope) { scope.launch { val waitingDialog = alertDialog { message = "正在上传" title = "请稍后" //setCancelable(false) } waitingDialog.show() IdleResources.loadingData.increment() val errorHandle = CoroutineExceptionHandler { _, _ -> waitingDialog.dismiss() toast("上传失败!") } val url = async(errorHandle) { paste(text) } withContext(Dispatchers.Main) { val urlResult = url.await() waitingDialog.dismiss() IdleResources.loadingData.decrement() startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply { type = "text/plain" putExtra(Intent.EXTRA_SUBJECT, "MiraiAndroid日志分享") putExtra(Intent.EXTRA_TEXT, urlResult) flags = Intent.FLAG_ACTIVITY_NEW_TASK }, "分享到")) } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/dnsQuery.kt ================================================ package io.github.mzdluo123.mirai.android.utils import io.github.mzdluo123.mirai.android.BotApplication import io.ktor.client.request.get import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import okhttp3.Dns import java.net.InetAddress @ExperimentalUnsignedTypes class SafeDns : Dns { override fun lookup(hostname: String): List<InetAddress> { return runBlocking { val res = BotApplication.httpClient.value.get<String>("https://cloudflare-dns.com/dns-query?name=$hostname&type=A") { headers.append("accept", "application/dns-json") } val json = BotApplication.json.value.parseToJsonElement(res) return@runBlocking listOf( InetAddress.getByName( json.jsonObject["Answer"]?.jsonArray?.get( 0 )?.jsonObject?.get("data")?.jsonPrimitive?.content ) ) } } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/fileUtils.kt ================================================ package io.github.mzdluo123.mirai.android.utils import android.content.Context import android.content.DialogInterface import android.net.Uri import android.view.View import android.widget.EditText import androidx.appcompat.app.AlertDialog import io.github.mzdluo123.mirai.android.R import kotlinx.coroutines.CompletableDeferred import java.io.File import java.io.IOException @Throws(IOException::class) fun Context.copyToFileDir(uri: Uri, name: String, path: String): File { val plugin = File(path, name) plugin.createNewFile() val output = plugin.outputStream() this.contentResolver?.openInputStream(uri)?.use { val buf = ByteArray(1024) var bytesRead: Int while (it.read(buf).also { bytesRead = it } > 0) { output.write(buf, 0, bytesRead) } } output.close() return plugin } fun formatFileLength(length: Long): String? { var length = length val suffixs = arrayOf("B", "K", "M", "G", "T", "P") var suffixIndex = 0 var remain = 0f while (length >= 1024) { remain = (remain + length % 1024) / 1024 length /= 1024 suffixIndex++ } return String.format("%.2f%s", remain + length, suffixs[suffixIndex]) } suspend fun Context.askFileName(): String? { val name = CompletableDeferred<String?>() val view = View.inflate(this@askFileName, R.layout.dialog_ask_filename,null) val editText = view.findViewById<EditText>(R.id.filename_input) val dialog = AlertDialog.Builder(this@askFileName) .setView(view) .setPositiveButton("确定", DialogInterface.OnClickListener { _, _ -> name.complete(editText.text.toString()) }) .setNegativeButton( "取消", DialogInterface.OnClickListener { _, _ -> name.complete(null) }) .setCancelable(false) .setTitle("请输入文件名称") .create() dialog.show() return name.await()?.trim() } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/utils/pasteBin.kt ================================================ package io.github.mzdluo123.mirai.android.utils import io.github.mzdluo123.mirai.android.BotApplication import io.ktor.client.request.forms.MultiPartFormDataContent import io.ktor.client.request.forms.formData import io.ktor.client.request.post import io.ktor.client.statement.HttpResponse import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @OptIn(ExperimentalUnsignedTypes::class) suspend fun paste(text: String): String { return withContext(Dispatchers.IO) { val res = BotApplication.httpClient.value.post<HttpResponse>("https://paste.ubuntu.com/") { body = MultiPartFormDataContent(formData { append("poster", "MiraiAndroid") append("syntax", "text") append("expiration", "") append("content", text) }) } return@withContext "https://paste.ubuntu.com" + res.headers["Location"].toString() } } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/java/java/awt/image/BufferedImage.java ================================================ package java.awt.image; //防止崩溃 public class BufferedImage { } ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/color/color_drawer_item.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> <item android:color="@color/colorMenuSelected" android:state_checked="true" /> <item android:color="@color/colorNavText" /> </selector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_add_white_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FFFFFF" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_android_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:pathData="M17.6,11.48 L19.44,8.3a0.63,0.63 0,0 0,-1.09 -0.63l-1.88,3.24a11.43,11.43 0,0 0,-8.94 0L5.65,7.67a0.63,0.63 0,0 0,-1.09 0.63L6.4,11.48A10.81,10.81 0,0 0,1 20L23,20A10.81,10.81 0,0 0,17.6 11.48ZM7,17.25A1.25,1.25 0,1 1,8.25 16,1.25 1.25,0 0,1 7,17.25ZM17,17.25A1.25,1.25 0,1 1,18.25 16,1.25 1.25,0 0,1 17,17.25Z" android:fillColor="@android:color/white" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_baseline_folder_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_baseline_insert_drive_file_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_baseline_publish_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M5,4v2h14L19,4L5,4zM5,14h4v6h6v-6h4l-7,-7 -7,7z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_battery_alert_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M15.67,4L14,4L14,2h-4v2L8.33,4C7.6,4 7,4.6 7,5.33v15.33C7,21.4 7.6,22 8.33,22h7.33c0.74,0 1.34,-0.6 1.34,-1.33L17,5.33C17,4.6 16.4,4 15.67,4zM13,18h-2v-2h2v2zM13,14h-2L11,9h2v5z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_chat_bubble_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M20,2H4c-1.1,0 -2,0.9 -2,2v18l4,-4h14c1.1,0 2,-0.9 2,-2V4c0,-1.1 -0.9,-2 -2,-2z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_check_white_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FFFFFF" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_delete_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_desktop_windows_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_edit_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_exit_to_app_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:tint="?attr/colorControlNormal" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M10.09,15.59L11.5,17l5,-5 -5,-5 -1.41,1.41L12.67,11H3v2h9.67l-2.58,2.59zM19,3H5c-1.11,0 -2,0.9 -2,2v4h2V5h14v14H5v-4H3v4c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2V5c0,-1.1 -0.9,-2 -2,-2z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_extension_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M20.5,11H19V7c0,-1.1 -0.9,-2 -2,-2h-4V3.5C13,2.12 11.88,1 10.5,1S8,2.12 8,3.5V5H4c-1.1,0 -1.99,0.9 -1.99,2v3.8H3.5c1.49,0 2.7,1.21 2.7,2.7s-1.21,2.7 -2.7,2.7H2V20c0,1.1 0.9,2 2,2h3.8v-1.5c0,-1.49 1.21,-2.7 2.7,-2.7 1.49,0 2.7,1.21 2.7,2.7V22H17c1.1,0 2,-0.9 2,-2v-4h1.5c1.38,0 2.5,-1.12 2.5,-2.5S21.88,11 20.5,11z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_info_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM13,17h-2v-6h2v6zM13,9h-2L11,7h2v2z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_insert_drive_file_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M6,2c-1.1,0 -1.99,0.9 -1.99,2L4,20c0,1.1 0.89,2 1.99,2L18,22c1.1,0 2,-0.9 2,-2L20,8l-6,-6L6,2zM13,9L13,3.5L18.5,9L13,9z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_keyboard_return_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M19,7v4H5.83l3.58,-3.59L8,6l-6,6 6,6 1.41,-1.41L5.83,13H21V7z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_launcher_background.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108"> <path android:fillColor="#3DDC84" android:pathData="M0,0h108v108h-108z" /> <path android:fillColor="#00000000" android:pathData="M9,0L9,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,0L19,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M29,0L29,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M39,0L39,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M49,0L49,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,0L59,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,0L69,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M79,0L79,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M89,0L89,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M99,0L99,108" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,9L108,9" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,19L108,19" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,29L108,29" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,39L108,39" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,49L108,49" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,59L108,59" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,69L108,69" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,79L108,79" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,89L108,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M0,99L108,99" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,29L89,29" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,39L89,39" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,49L89,49" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,59L89,59" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,69L89,69" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M19,79L89,79" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M29,19L29,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M39,19L39,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M49,19L49,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M59,19L59,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M69,19L69,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> <path android:fillColor="#00000000" android:pathData="M79,19L79,89" android:strokeWidth="0.8" android:strokeColor="#33FFFFFF" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_local_printshop_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M19,8L5,8c-1.66,0 -3,1.34 -3,3v6h4v4h12v-4h4v-6c0,-1.66 -1.34,-3 -3,-3zM16,19L8,19v-5h8v5zM19,12c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1 1,0.45 1,1 -0.45,1 -1,1zM18,3L6,3v4h12L18,3z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_new_launcher_foreground.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108"> <group android:scaleX="0.16740224" android:scaleY="0.16740224" android:translateX="35.817326" android:translateY="35.817326"> <path android:pathData="M5.67,5.85l68.73,0l0,113.65l-68.73,92.54l0,-206.19z" android:fillColor="#0c3756" /> <path android:pathData="M74.4,5.85l137.46,0l-137.46,68.73l0,-68.73z" android:fillColor="#418fc3" /> <path android:pathData="M74.4,119.5l34.48,-44.92l102.98,137.46l0,-206.19l-137.46,68.73l0,44.92z" android:fillColor="#40a5dd" /> </group> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_refresh_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_restore_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_save_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24.0" android:viewportHeight="24.0"> <path android:fillColor="#FF000000" android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_settings_black_24dp.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" android:pathData="M19.1,12.9a2.8,2.8 0,0 0,0.1 -0.9,2.8 2.8,0 0,0 -0.1,-0.9l2.1,-1.6a0.7,0.7 0,0 0,0.1 -0.6L19.4,5.5a0.7,0.7 0,0 0,-0.6 -0.2l-2.4,1a6.5,6.5 0,0 0,-1.6 -0.9l-0.4,-2.6a0.5,0.5 0,0 0,-0.5 -0.4H10.1a0.5,0.5 0,0 0,-0.5 0.4L9.3,5.4a5.6,5.6 0,0 0,-1.7 0.9l-2.4,-1a0.4,0.4 0,0 0,-0.5 0.2l-2,3.4c-0.1,0.2 0,0.4 0.2,0.6l2,1.6a2.8,2.8 0,0 0,-0.1 0.9,2.8 2.8,0 0,0 0.1,0.9L2.8,14.5a0.7,0.7 0,0 0,-0.1 0.6l1.9,3.4a0.7,0.7 0,0 0,0.6 0.2l2.4,-1a6.5,6.5 0,0 0,1.6 0.9l0.4,2.6a0.5,0.5 0,0 0,0.5 0.4h3.8a0.5,0.5 0,0 0,0.5 -0.4l0.3,-2.6a5.6,5.6 0,0 0,1.7 -0.9l2.4,1a0.4,0.4 0,0 0,0.5 -0.2l2,-3.4c0.1,-0.2 0,-0.4 -0.2,-0.6ZM12,15.6A3.6,3.6 0,1 1,15.6 12,3.6 3.6,0 0,1 12,15.6Z"/> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_share_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="@android:color/white" android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/ic_store_white_24.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24" android:tint="?attr/colorControlNormal"> <path android:fillColor="#FFFFFF" android:pathData="M20,4L4,4v2h16L20,4zM21,14v-2l-1,-5L4,7l-1,5v2h1v6h10v-6h4v6h2v-6h1zM12,18L6,18v-4h6v4z" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/icon.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="223.44dp" android:height="223.44dp" android:viewportWidth="223.44" android:viewportHeight="223.44"> <path android:pathData="M5.67,5.85l68.73,0l0,113.65l-68.73,92.54l0,-206.19z" android:fillColor="#0c3756" /> <path android:pathData="M74.4,5.85l137.46,0l-137.46,68.73l0,-68.73z" android:fillColor="#418fc3" /> <path android:pathData="M74.4,119.5l34.48,-44.92l102.98,137.46l0,-206.19l-137.46,68.73l0,44.92z" android:fillColor="#40a5dd" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/loading_background.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:drawable="@color/colorStartBackground" /> <item> <bitmap android:gravity="clip_horizontal|center" android:src="@drawable/mirai_b" /> </item> </layer-list> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable/side_nav_bar.xml ================================================ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:angle="135" android:endColor="#0C3756" android:startColor="#40A5DD" android:type="linear" /> </shape> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ <vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt" android:width="108dp" android:height="108dp" android:viewportWidth="108" android:viewportHeight="108"> <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z"> <aapt:attr name="android:fillColor"> <gradient android:endX="85.84757" android:endY="92.4963" android:startX="42.9492" android:startY="49.59793" android:type="linear"> <item android:color="#44000000" android:offset="0.0" /> <item android:color="#00000000" android:offset="1.0" /> </gradient> </aapt:attr> </path> <path android:fillColor="#FFFFFF" android:fillType="nonZero" android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" android:strokeWidth="1" android:strokeColor="#00000000" /> </vector> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/activity_captcha.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:id="@+id/captcha_view" android:layout_width="match_parent" android:layout_height="100dp" android:layout_weight="1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.13" /> <EditText android:id="@+id/captcha_input" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginEnd="24dp" android:layout_weight="1" android:ems="10" android:hint="验证码" android:inputType="textPersonName" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/captcha_view" /> <Button android:id="@+id/captchaConfirm_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="提交" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/captcha_input" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/activity_main.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" tools:openDrawer="start"> <include layout="@layout/app_bar_main" android:layout_width="match_parent" android:layout_height="match_parent" /> <com.google.android.material.navigation.NavigationView android:id="@+id/nav_view" android:layout_width="wrap_content" android:layout_height="match_parent" android:layout_gravity="start" android:fitsSystemWindows="true" app:headerLayout="@layout/nav_header_main" app:menu="@menu/menu_activity_main_drawer" app:itemTextColor="@color/color_drawer_item" app:itemIconTint="@color/color_drawer_item"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_gravity="bottom" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:layout_marginBottom="8dp"> <Button android:id="@+id/btn_exit" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_weight="1" android:drawableLeft="@drawable/ic_exit_to_app_24dp" android:text="退出应用" android:textColor="@color/color_drawer_item" android:textSize="14sp" /> <Button android:id="@+id/btn_reboot" style="@style/Widget.MaterialComponents.Button.TextButton" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_gravity="bottom" android:layout_weight="1" android:drawableLeft="@drawable/ic_restore_24" android:text="快速重启" android:textColor="@color/color_drawer_item" android:textSize="14sp" /> </LinearLayout> </com.google.android.material.navigation.NavigationView> </androidx.drawerlayout.widget.DrawerLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/activity_plugin_import.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="pluginData" type="net.mamoe.mirai.console.plugins.PluginDescription" /> </data> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".activity.PluginImportActivity"> <androidx.cardview.widget.CardView android:id="@+id/cardView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="16dp" android:layout_marginEnd="8dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/pluginName_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="16dp" android:text="@{pluginData.name}" android:textSize="24sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:text="插件名称" /> <TextView android:id="@+id/pluginVersion_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:text="@{pluginData.version}" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="@+id/pluginName_text" app:layout_constraintStart_toEndOf="@+id/pluginName_text" tools:text="v1.0" /> <TextView android:id="@+id/pluginMain_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@{pluginData.basePath}" android:textSize="14sp" app:layout_constraintStart_toStartOf="@+id/pluginName_text" app:layout_constraintTop_toBottomOf="@+id/pluginVersion_text" tools:text="com.example.xxxx" /> <TextView android:id="@+id/pluginAuthor_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@{`BY: `+pluginData.author}" android:textSize="14sp" app:layout_constraintStart_toStartOf="@+id/pluginMain_text" app:layout_constraintTop_toBottomOf="@+id/pluginMain_text" tools:text="BY: HelloWorld" /> <TextView android:id="@+id/pluginInfo_text" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp" android:maxLines="5" android:singleLine="false" android:text="@{pluginData.info}" android:textSize="14sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="@+id/pluginName_text" app:layout_constraintTop_toBottomOf="@+id/pluginAuthor_text" tools:text="这里是插件info信息dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd " /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView> <TextView android:id="@+id/textView5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="你要导入这个插件吗?" android:textSize="18sp" app:layout_constraintStart_toStartOf="@+id/cardView" app:layout_constraintTop_toBottomOf="@+id/cardView" /> <TextView android:id="@+id/textView4" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="请注意,导入未知来源的插件可能会存在未知的风险,如果你信任本插件请点击下面的按钮来完成导入" app:layout_constraintEnd_toEndOf="@+id/cardView" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="@+id/textView5" app:layout_constraintTop_toBottomOf="@+id/textView5" /> <TextView android:id="@+id/textView6" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="该功能为实验性功能,如在导入过程中出现错误请换一台设备重试,或是到群内或是GitHub寻找手动导入方法" app:layout_constraintEnd_toEndOf="@+id/cardView" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="@+id/cardView" app:layout_constraintTop_toBottomOf="@+id/textView4" /> <Button android:id="@+id/import_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="导入" app:layout_constraintEnd_toEndOf="@+id/textView4" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="@+id/textView4" app:layout_constraintTop_toBottomOf="@+id/desugaring_checkBox" /> <RadioGroup android:id="@+id/import_radioGroup" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="8dp" android:layout_marginEnd="24dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView6"> <RadioButton android:id="@+id/compile_radioButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:checked="true" android:text="编译并安装" /> <RadioButton android:id="@+id/copy_radioButton" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="仅安装" /> </RadioGroup> <CheckBox android:id="@+id/desugaring_checkBox" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:checked="true" android:text="脱糖(desugaring)" app:layout_constraintEnd_toEndOf="@+id/import_radioGroup" app:layout_constraintStart_toStartOf="@+id/import_radioGroup" app:layout_constraintTop_toBottomOf="@+id/import_radioGroup" /> </androidx.constraintlayout.widget.ConstraintLayout> </layout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/activity_unsafe_login.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".activity.UnsafeLoginActivity"> <androidx.swiperefreshlayout.widget.SwipeRefreshLayout android:id="@+id/refresh_unsafe_web" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <WebView android:id="@+id/unsafe_login_web" android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/app_bar_main.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".activity.MainActivity"> <com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="@style/AppTheme.AppBarOverlay"> <androidx.appcompat.widget.Toolbar android:id="@+id/toolbar" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/AppTheme.PopupOverlay" /> </com.google.android.material.appbar.AppBarLayout> <include layout="@layout/content_main" /> </androidx.coordinatorlayout.widget.CoordinatorLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/content_main.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:showIn="@layout/app_bar_main"> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:navGraph="@navigation/mobile_navigation" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/dialog_ask_filename.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView11" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="16dp" android:layout_marginEnd="24dp" android:text="请在此处设置导入的文件名称,文件名称将会作为导入后的插件名称并显示在这里" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/textView12" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="需包括扩展名,例如test.jar test.lua test.js等" app:layout_constraintEnd_toEndOf="@+id/textView11" app:layout_constraintStart_toStartOf="@+id/textView11" app:layout_constraintTop_toBottomOf="@+id/textView11" /> <EditText android:id="@+id/filename_input" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="24dp" android:layout_marginTop="16dp" android:layout_marginEnd="24dp" android:ems="10" android:hint="请输入" android:inputType="textPersonName" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView12" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/dialog_autologin.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="10dp" android:layout_margin="10dp"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="在下面输入你的账号和密码,软件将会保存你的账号和密码并在应用启动时完成登录,你可以随时取消." /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="我们存储密码的md5值,请注意设备安全" /> <EditText android:id="@+id/qq_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="QQ号" android:inputType="number" /> <EditText android:id="@+id/password_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:hint="密码" android:inputType="textPassword" /> </LinearLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/dialog_script_info.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:app="http://schemas.android.com/apk/res-auto"> <TextView android:id="@+id/tv_script_info" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:paddingLeft="25dp" android:paddingRight="25dp" android:textAppearance="@style/TextAppearance.AppCompat.Medium" android:textSize="16sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageButton android:id="@+id/btn_delete" android:layout_width="0dp" android:layout_height="50dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:background="#00FFFFFF" android:contentDescription="delete" android:foreground="?android:attr/selectableItemBackgroundBorderless" android:tint="@color/colorBtnImg" android:src="@drawable/ic_delete_black_24dp" app:layout_constraintDimensionRatio="w,1:1" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toLeftOf="@id/btn_reload" app:layout_constraintTop_toBottomOf="@id/tv_script_info" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/btn_reload" android:layout_width="0dp" android:layout_height="50dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:background="#00FFFFFF" android:contentDescription="reload" android:foreground="?android:attr/selectableItemBackgroundBorderless" android:tint="@color/colorBtnImg" android:src="@drawable/ic_refresh_black_24dp" app:layout_constraintDimensionRatio="w,1:1" app:layout_constraintLeft_toRightOf="@id/btn_delete" app:layout_constraintRight_toLeftOf="@id/btn_edit" app:layout_constraintTop_toBottomOf="@id/tv_script_info" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/btn_edit" android:layout_width="0dp" android:layout_height="50dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:background="#00FFFFFF" android:contentDescription="edit" android:foreground="?android:attr/selectableItemBackgroundBorderless" android:tint="@color/colorBtnImg" android:src="@drawable/ic_edit_black_24dp" app:layout_constraintDimensionRatio="w,1:1" app:layout_constraintRight_toLeftOf="@id/btn_save" app:layout_constraintTop_toBottomOf="@id/tv_script_info" app:layout_constraintLeft_toRightOf="@id/btn_reload" app:layout_constraintBottom_toBottomOf="parent" /> <ImageButton android:id="@+id/btn_save" android:layout_width="0dp" android:layout_height="50dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:background="#00FFFFFF" android:contentDescription="save" android:foreground="?android:attr/selectableItemBackgroundBorderless" android:tint="@color/colorBtnImg" android:src="@drawable/ic_save_black_24dp" app:layout_constraintDimensionRatio="w,1:1" app:layout_constraintTop_toBottomOf="@id/tv_script_info" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toRightOf="@id/btn_edit" app:layout_constraintBottom_toBottomOf="parent" android:visibility="gone" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/fragment_about.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> <data> <variable name="appVersion" type="String" /> <variable name="coreVersion" type="String" /> </data> <ScrollView android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.about.AboutFragment"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/imageView2" android:layout_width="wrap_content" android:layout_height="150dp" android:src="@drawable/mirai_a" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <ImageView android:id="@+id/imageView3" android:layout_width="wrap_content" android:layout_height="50dp" android:src="@drawable/mirai_b" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView2" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="关于Mirai" android:textSize="24sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/btn_join_group" /> <TextView android:id="@+id/textView3" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:text="Mirai 是一个在全平台下运行,提供 QQ Android 和 TIM PC 协议支持的高效率机器人库" android:textAlignment="center" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView2" /> <TextView android:id="@+id/textView7" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="一切开发旨在学习,请勿用于非法用途" android:textAlignment="center" android:textSize="20sp" android:textStyle="bold" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/github2_bth" /> <Button android:id="@+id/github_btn" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="16dp" android:layout_marginEnd="8dp" android:text="Mirai的Github" android:textAllCaps="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView3" /> <TextView android:id="@+id/textView8" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="24dp" android:text="关于MiraiAndroid" android:textAlignment="center" android:textSize="24sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/github_btn" /> <TextView android:id="@+id/textView9" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="(实验性)在Android上运行Mirai-console" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView8" /> <TextView android:id="@+id/textView10" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:layout_marginEnd="16dp" android:text="相比使用Termux或者是Linux Deploy等应用运行mirai的方案,该项目提供的方案具有更好的性能以及更少的资源占用,但可能存在兼容性问题" android:textAlignment="center" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView9" /> <Button android:id="@+id/github2_bth" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginTop="16dp" android:layout_marginEnd="8dp" android:text="MiraiAndroid的Github" android:textAllCaps="false" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView10" /> <TextView android:id="@+id/version_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="@{`MiraiAndroid v`+appVersion+` MiraiCore v`+coreVersion}" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/imageView3" tools:text="版本号" /> <Button android:id="@+id/btn_join_group" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:text="加入交流群" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/version_text" /> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> </layout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/fragment_home.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.console.ConsoleFragment"> <ScrollView android:id="@+id/main_scroll" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toTopOf="@+id/command_input" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> <TextView android:id="@+id/log_text" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:scrollbars="vertical" android:text="正在加载..." android:textIsSelectable="false" /> </ScrollView> <EditText android:id="@+id/command_input" android:layout_width="0dp" android:layout_height="wrap_content" android:hint="请输入命令" android:imeOptions="actionSend" android:singleLine="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/commandSend_btn" app:layout_constraintStart_toEndOf="@+id/shortcutBottom_btn"> </EditText> <ImageButton android:id="@+id/commandSend_btn" android:layout_width="50dp" android:layout_height="wrap_content" android:scaleType="fitCenter" android:src="@drawable/ic_keyboard_return_black_24dp" android:tint="@color/colorBtnImg" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="@+id/command_input" /> <ImageButton android:id="@+id/shortcutBottom_btn" android:layout_width="50dp" android:layout_height="wrap_content" android:src="@drawable/ic_baseline_keyboard_arrow_up_24" android:tint="@color/colorBtnImg" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/command_input"> </ImageButton> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/fragment_plugin.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.plugin.PluginFragment"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/plugin_recycler" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/fragment_script.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".ui.script.ScriptFragment"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/script_recycler" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/fragment_script_center.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rcl_scripts" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/fragment_script_center_empty.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/fragment_script_empty.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView14" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="当前无脚本" android:textSize="18sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/btn_script_center" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="前往脚本中心" android:layout_marginTop="20dp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/textView14" /> </androidx.constraintlayout.widget.ConstraintLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/item_plugin.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="8dp" android:layout_marginEnd="8dp" android:layout_marginTop="4dp" android:layout_marginBottom="4dp"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/pluginName_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="未知脚本" android:textSize="18sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/pluginSize_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:text="大小" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@+id/pluginName_text" app:layout_constraintTop_toBottomOf="@+id/pluginName_text" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView> </FrameLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/item_script.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.cardview.widget.CardView android:id="@+id/cv_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true" android:focusable="true" android:foreground="?android:attr/selectableItemBackgroundBorderless" app:cardElevation="0dp"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tv_script_alias" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="未知脚本" android:textSize="18sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/tv_script_author" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:text="作者" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="@id/tv_script_alias" app:layout_constraintTop_toBottomOf="@id/tv_script_alias" /> <TextView android:id="@+id/tv_script_version" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:text="版本" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@id/tv_script_author" app:layout_constraintTop_toBottomOf="@id/tv_script_alias" /> <Switch android:id="@+id/swt_enable" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="5dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView> </FrameLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/item_script_center_list.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" xmlns:app="http://schemas.android.com/apk/res-auto"> <androidx.cardview.widget.CardView android:id="@+id/cv_item" android:layout_width="match_parent" android:layout_height="wrap_content" android:clickable="true" android:focusable="true" android:foreground="?android:attr/selectableItemBackgroundBorderless" app:cardElevation="0dp"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content"> <ImageView android:id="@+id/iv_file" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginStart="10dp" android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_baseline_insert_drive_file_24" /> <ImageView android:id="@+id/iv_folder" android:layout_width="30dp" android:layout_height="30dp" android:layout_marginStart="10dp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_baseline_folder_24" /> <TextView android:id="@+id/tv_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="10dp" android:text="TextView" android:textSize="18sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toEndOf="@+id/iv_file" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </androidx.cardview.widget.CardView> </FrameLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/layout/nav_header_main.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="@dimen/nav_header_height" android:background="@drawable/side_nav_bar" android:gravity="bottom" android:orientation="vertical" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingBottom="@dimen/activity_vertical_margin" android:theme="@style/ThemeOverlay.AppCompat.Dark"> <ImageView android:id="@+id/head_imageVIew" android:layout_width="wrap_content" android:layout_height="wrap_content" android:contentDescription="@string/nav_header_desc" android:paddingTop="@dimen/nav_header_vertical_spacing" app:srcCompat="@mipmap/ic_new_launcher_round" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="@dimen/nav_header_vertical_spacing" android:text="MiraiAndroid" android:textAppearance="@style/TextAppearance.AppCompat.Body1" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Mirai-console的Android应用,目前处于实验性阶段" /> </LinearLayout> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/menu/menu_activity_main_drawer.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" tools:showIn="navigation_view"> <group android:checkableBehavior="single"> <item android:id="@+id/nav_console" android:icon="@drawable/ic_desktop_windows_black_24dp" android:title="控制台" /> <item android:id="@+id/nav_plugins" android:icon="@drawable/ic_extension_black_24dp" android:title="插件管理" /> <item android:id="@+id/nav_scripts" android:icon="@drawable/ic_insert_drive_file_black_24dp" android:title="脚本管理" /> <item android:id="@+id/nav_setting" android:icon="@drawable/ic_settings_black_24dp" android:title="设置" /> <item android:id="@+id/nav_about" android:icon="@drawable/ic_info_black_24dp" android:title="关于" /> </group> </menu> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/menu/menu_console.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_setAutoLogin" android:title="设置自动登录" /> <item android:id="@+id/action_report" android:icon="@drawable/ic_share_24" android:title="分享日志" app:showAsAction="always" /> <!--item android:id="@+id/action_battery" android:title="忽略电池优化" /--> <!--item android:id="@+id/action_fast_restart" android:title="快速重启" /--> </menu> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/menu/menu_script.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_script_center" android:icon="@drawable/ic_store_white_24" android:iconTint="#FFFFFF" android:title="脚本中心" app:showAsAction="always" /> <item android:id="@+id/action_add_script" android:icon="@drawable/ic_add_white_24dp" android:iconTint="#FFFFFF" android:title="添加脚本" app:showAsAction="always" /> </menu> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/menu/menu_script_center.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_upload_script" android:icon="@drawable/ic_store_white_24" android:iconTint="#FFFFFF" android:title="如何上传脚本" /> </menu> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/menu/plugin_add.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_add_plugin" android:icon="@drawable/ic_add_white_24dp" android:iconTint="#FFFFFF" android:title="添加插件" app:showAsAction="always" /> </menu> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/menu/plugin_manage.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_delete" android:icon="@drawable/ic_delete_black_24dp" android:title="删除" /> </menu> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/menu/unsafe_menu.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <menu xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/action_finish" android:icon="@drawable/ic_check_white_24dp" android:title="完成验证" android:visible="true" app:showAsAction="always" /> </menu> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background" /> <foreground android:drawable="@drawable/ic_launcher_foreground" /> </adaptive-icon> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@drawable/ic_launcher_background" /> <foreground android:drawable="@drawable/ic_launcher_foreground" /> </adaptive-icon> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/mipmap-anydpi-v26/ic_new_launcher.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@color/ic_new_launcher_background" /> <foreground android:drawable="@drawable/ic_new_launcher_foreground" /> </adaptive-icon> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/mipmap-anydpi-v26/ic_new_launcher_round.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <background android:drawable="@color/ic_new_launcher_background" /> <foreground android:drawable="@drawable/ic_new_launcher_foreground" /> </adaptive-icon> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/navigation/mobile_navigation.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/mobile_navigation" app:startDestination="@+id/nav_console"> <fragment android:id="@+id/nav_console" android:name="io.github.mzdluo123.mirai.android.ui.console.ConsoleFragment" android:label="控制台" tools:layout="@layout/fragment_home"/> <!--fragment android:id="@+id/nav_home_second" android:name="io.github.mzdluo123.mirai.android.ui.console.HomeSecondFragment" android:label="@string/home_second" tools:layout="@layout/fragment_home_second"> <argument android:name="myArg" app:argType="string" /> </fragment--> <fragment android:id="@+id/nav_plugins" android:name="io.github.mzdluo123.mirai.android.ui.plugin.PluginFragment" android:label="插件管理" tools:layout="@layout/fragment_plugin" /> <fragment android:id="@+id/nav_scripts" android:name="io.github.mzdluo123.mirai.android.ui.script.ScriptFragment" android:label="脚本管理" tools:layout="@layout/fragment_script"> <action android:id="@+id/action_nav_scripts_to_nav_scripts_center" app:destination="@id/nav_scripts_center" /> </fragment> <fragment android:id="@+id/nav_scripts_center" android:name="io.github.mzdluo123.mirai.android.ui.script.ScriptCenterFragment" android:label="脚本中心" tools:layout="@layout/fragment_script_center" /> <fragment android:id="@+id/nav_about" android:name="io.github.mzdluo123.mirai.android.ui.about.AboutFragment" android:label="关于" /> <fragment android:id="@+id/nav_setting" android:name="io.github.mzdluo123.mirai.android.ui.setting.SettingFragment" android:label="设置" /> </navigation> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values/colors.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#418FC3</color> <color name="colorPrimaryDark">#12517E</color> <color name="colorAccent">#56BBFF</color> <color name="colorBtnImg">#000000</color> <color name="colorMenuSelected">#1879BA</color> <color name="colorNavText">#000000</color> <color name="colorStartBackground">#FFFFFF</color> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values/dimens.xml ================================================ <resources> <!-- Default screen margins, per the Android Design guidelines. --> <dimen name="activity_horizontal_margin">16dp</dimen> <dimen name="activity_vertical_margin">16dp</dimen> <dimen name="nav_header_vertical_spacing">8dp</dimen> <dimen name="nav_header_height">200dp</dimen> <dimen name="fab_margin">16dp</dimen> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values/ic_new_launcher_background.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <resources> <color name="ic_new_launcher_background">#FFFFFF</color> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values/strings.xml ================================================ <resources> <string name="app_name">MiraiAndroid</string> <string name="navigation_drawer_open">Open navigation drawer</string> <string name="navigation_drawer_close">Close navigation drawer</string> <string name="nav_header_title">Android Studio</string> <string name="nav_header_subtitle">android.studio@android.com</string> <string name="nav_header_desc">Navigation header</string> <string name="action_settings">Settings</string> <string name="menu_home">Home</string> <string name="menu_gallery">Gallery</string> <string name="menu_slideshow">Slideshow</string> <string name="home_second">Home Second</string> <string name="service_description">MiraiAndroid用于运行Bot的服务</string> <string name="acra_toast_text">MiraiAndroid运行时发生异常!</string> <string name="hello_blank_fragment">Hello blank fragment</string> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values/styles.xml ================================================ <resources> <!-- Base application theme. --> <style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> </style> <style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> </style> <style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.MaterialComponents.Dark.ActionBar" /> <style name="AppTheme.PopupOverlay" parent="ThemeOverlay.MaterialComponents.Light" /> <style name="StartTheme" parent="AppTheme"> <item name="android:windowBackground">@drawable/loading_background</item> <item name="android:windowFullscreen">true</item> </style> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values-night/colors-night.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorAccent">#FFFFFF</color> <color name="colorBtnImg">#FFFFFF</color> <color name="colorNavText">#FFFFFF</color> <color name="colorStartBackground">#000000</color> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values-v21/styles.xml ================================================ <resources> <style name="AppTheme.NoActionBar"> <item name="windowActionBar">false</item> <item name="windowNoTitle">true</item> <item name="android:statusBarColor">@android:color/transparent</item> </style> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/values-v29/styles.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="Theme.AppCompat.DayNight.DarkActionBar"> <!-- Customize your theme here. --> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorAccent">@color/colorAccent</item> <item name="android:forceDarkAllowed">true</item> </style> <style name="StartTheme" parent="AppTheme"> <item name="android:windowBackground">@drawable/loading_background</item> <item name="android:windowFullscreen">true</item> </style> </resources> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/xml/path_script.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <paths> <external-files-path name="script" path="scripts" /> </paths> ================================================ FILE: mirai-console/frontend/mirai-android/app/src/main/res/xml/setting_screen.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <PreferenceCategory android:title="日志"> <EditTextPreference android:defaultValue="300" android:icon="@drawable/ic_local_printshop_24" android:key="log_buffer_preference" android:selectAllOnFocus="true" android:singleLine="true" android:title="日志缓存行数" app:useSimpleSummaryProvider="true" /> <SwitchPreference android:defaultValue="false" android:icon="@drawable/ic_local_printshop_24" android:key="print_to_logcat_preference" android:summary="你可以使用 TAG: MA 级别:INFO 来看到log" android:title="打印日志到logcat" /> </PreferenceCategory> <PreferenceCategory android:title="启动"> <SwitchPreference android:defaultValue="false" android:icon="@drawable/ic_android_24" android:key="start_on_boot_preference" android:summary="请加入开机自启白名单以确保能够正常开机启动" android:title="开机启动" /> <SwitchPreference android:defaultValue="false" android:icon="@drawable/ic_battery_alert_24" android:key="ignore_battery_optimization" android:title="忽略电池优化" /> </PreferenceCategory> <PreferenceCategory android:title="其他"> <EditTextPreference android:defaultValue="15" android:icon="@drawable/ic_restore_24" android:key="status_refresh_count" android:selectAllOnFocus="true" android:singleLine="true" android:title="状态栏每分钟刷新次数[1-60]" app:useSimpleSummaryProvider="true" /> <SwitchPreference android:defaultValue="false" android:icon="@drawable/ic_baseline_publish_24" android:key="allow_push_msg_preference" android:summary="使用方法请看项目GitHub" android:title="允许使用广播推送消息" /> </PreferenceCategory> <EditTextPreference android:selectAllOnFocus="true" android:singleLine="true" android:enabled="false" android:title="部分设置重启后才会生效!" /> </PreferenceScreen> ================================================ FILE: mirai-console/frontend/mirai-android/build.gradle ================================================ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { ext.kotlin_version = '1.4.0' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:4.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version" // https://mvnrepository.com/artifact/com.jfrog.bintray/com.jfrog.bintray.gradle.plugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { google() jcenter() maven { url 'https://jitpack.io' } } } task clean(type: Delete) { delete rootProject.buildDir } ================================================ FILE: mirai-console/frontend/mirai-android/gradle/wrapper/gradle-wrapper.properties ================================================ #Sun Apr 26 21:31:38 CST 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip ================================================ FILE: mirai-console/frontend/mirai-android/gradle.properties ================================================ # Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx1536m # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official ================================================ FILE: mirai-console/frontend/mirai-android/gradlew ================================================ #!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" ================================================ FILE: mirai-console/frontend/mirai-android/gradlew.bat ================================================ @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: mirai-console/frontend/mirai-android/settings.gradle ================================================ rootProject.name='MiraiAndroid' include ':app' ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import BinaryCompatibilityConfigurator.configureBinaryValidator plugins { kotlin("jvm") kotlin("plugin.serialization") id("java") `maven-publish` } kotlin { explicitApiWarning() } dependencies { compileAndTestRuntime(project(":mirai-core-utils")) compileAndTestRuntime(project(":mirai-console")) compileAndTestRuntime(project(":mirai-core-api")) compileAndTestRuntime(project(":mirai-core-utils")) compileAndTestRuntime(`kotlin-jvm-blocking-bridge`) compileAndTestRuntime(`kotlin-stdlib-jdk8`) } version = Versions.consoleTerminal description = "Console frontend abstract" configurePublishing("mirai-console-frontend-base") configureBinaryValidator(null) ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/compatibility-validation/jvm/api/jvm.api ================================================ public abstract class net/mamoe/mirai/console/frontendbase/AbstractMiraiConsoleFrontendImplementation : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/console/MiraiConsoleImplementation { public fun <init> (Ljava/lang/String;)V public fun createLoggerFactory (Lnet/mamoe/mirai/console/MiraiConsoleImplementation$FrontendLoggingInitContext;)Lnet/mamoe/mirai/utils/MiraiLogger$Factory; public fun getBuiltInPluginLoaders ()Ljava/util/List; public fun getCommandManager ()Lnet/mamoe/mirai/console/command/CommandManager; public fun getConfigStorageForBuiltIns ()Lnet/mamoe/mirai/console/data/PluginDataStorage; public fun getConfigStorageForJvmPluginLoader ()Lnet/mamoe/mirai/console/data/PluginDataStorage; public fun getConsoleDataScope ()Lnet/mamoe/mirai/console/MiraiConsoleImplementation$ConsoleDataScope; public fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public fun getDataStorageForBuiltIns ()Lnet/mamoe/mirai/console/data/PluginDataStorage; public fun getDataStorageForJvmPluginLoader ()Lnet/mamoe/mirai/console/data/PluginDataStorage; protected abstract fun getFrontendBase ()Lnet/mamoe/mirai/console/frontendbase/FrontendBase; public fun getJvmPluginLoader ()Lnet/mamoe/mirai/console/plugin/jvm/JvmPluginLoader; } public abstract class net/mamoe/mirai/console/frontendbase/FrontendBase { public fun <init> ()V public fun getDaemonThreadGroup ()Ljava/lang/ThreadGroup; public fun getLogDropAnsi ()Z public fun getLoggingDirectory ()Ljava/nio/file/Path; public fun getLoggingRecorder ()Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder; public abstract fun getScope ()Lkotlinx/coroutines/CoroutineScope; public fun getThreadGroup ()Ljava/lang/ThreadGroup; public abstract fun getWorkingDirectory ()Ljava/nio/file/Path; protected fun initLogRecorder ()Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder; protected fun initScreen_forwardStdToMiraiLogger ()V protected fun initScreen_forwardStdToScreen ()V public fun newDaemon (Ljava/lang/String;Ljava/lang/Runnable;)Ljava/lang/Thread; public fun newThread (Ljava/lang/String;Ljava/lang/Runnable;)Ljava/lang/Thread; public fun newThreadFactory (Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)Ljava/util/concurrent/ThreadFactory; public static synthetic fun newThreadFactory$default (Lnet/mamoe/mirai/console/frontendbase/FrontendBase;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/util/concurrent/ThreadFactory; public abstract fun printToScreenDirectly (Ljava/lang/String;)V public fun recordToLogging (Ljava/lang/String;)V } public final class net/mamoe/mirai/console/frontendbase/logging/AllDroppedLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder { public static final field INSTANCE Lnet/mamoe/mirai/console/frontendbase/logging/AllDroppedLogRecorder; public fun record (Ljava/lang/String;)V } public abstract class net/mamoe/mirai/console/frontendbase/logging/AsyncLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder { public fun <init> (Lnet/mamoe/mirai/console/frontendbase/FrontendBase;I)V public synthetic fun <init> (Lnet/mamoe/mirai/console/frontendbase/FrontendBase;IILkotlin/jvm/internal/DefaultConstructorMarker;)V protected abstract fun asyncRecord (Ljava/lang/String;)V protected final fun getChannel ()Lkotlinx/coroutines/channels/Channel; protected final fun getDispatcher ()Lkotlinx/coroutines/CoroutineDispatcher; protected final fun getSubscope ()Lkotlinx/coroutines/CoroutineScope; protected final fun getThreadPool ()Ljava/util/concurrent/ScheduledExecutorService; public fun record (Ljava/lang/String;)V } public class net/mamoe/mirai/console/frontendbase/logging/AsyncLogRecorderForwarded : net/mamoe/mirai/console/frontendbase/logging/AsyncLogRecorder { public fun <init> (Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;I)V public synthetic fun <init> (Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;IILkotlin/jvm/internal/DefaultConstructorMarker;)V protected fun asyncRecord (Ljava/lang/String;)V protected final fun getDelegate ()Lnet/mamoe/mirai/console/frontendbase/logging/LogRecorder; } public class net/mamoe/mirai/console/frontendbase/logging/DailySplitLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder { protected field lastDate I protected field writer Ljava/io/Writer; public fun <init> (Ljava/nio/file/Path;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;Ljava/time/format/DateTimeFormatter;)V public synthetic fun <init> (Ljava/nio/file/Path;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;Ljava/time/format/DateTimeFormatter;ILkotlin/jvm/internal/DefaultConstructorMarker;)V protected final fun acquireFileWriter ()V protected final fun getBase ()Lnet/mamoe/mirai/console/frontendbase/FrontendBase; protected final fun getDateFormatter ()Ljava/time/format/DateTimeFormatter; protected final fun getDirectory ()Ljava/nio/file/Path; public fun record (Ljava/lang/String;)V } public abstract class net/mamoe/mirai/console/frontendbase/logging/LogRecorder { public fun <init> ()V public abstract fun record (Ljava/lang/String;)V } public class net/mamoe/mirai/console/frontendbase/logging/WriterLogRecorder : net/mamoe/mirai/console/frontendbase/logging/LogRecorder { public fun <init> (Ljava/io/Writer;Lnet/mamoe/mirai/console/frontendbase/FrontendBase;)V protected final fun getBase ()Lnet/mamoe/mirai/console/frontendbase/FrontendBase; protected final fun getWriter ()Ljava/io/Writer; public fun record (Ljava/lang/String;)V } ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/resources/META-INF/services/org.slf4j.spi.SLF4JServiceProvider ================================================ net.mamoe.mirai.console.internal.logging.externalbind.slf4j.MiraiConsoleSLF4JService ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/src/AbstractMiraiConsoleFrontendImplementation.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.frontendbase import kotlinx.coroutines.* import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.command.CommandManager import net.mamoe.mirai.console.data.MultiFilePluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.PlatformLogger import kotlin.coroutines.CoroutineContext /** * [MiraiConsoleImplementation] 的基本抽象实现 * * @param frontendCoroutineName 该前端的名字, 如 `"MiraiConsoleImplementationTerminal"` * @see FrontendBase */ @OptIn(ConsoleFrontEndImplementation::class) public abstract class AbstractMiraiConsoleFrontendImplementation( frontendCoroutineName: String, ) : MiraiConsoleImplementation, CoroutineScope { // region 此 region 的 字段 / 方法 为 console 默认/内部 实现, 如无必要不建议修改 @OptIn(ConsoleInternalApi::class) private val delegateCoroutineScope by lazy { CoroutineScope( SupervisorJob() + CoroutineName(frontendCoroutineName) + CoroutineExceptionHandler { coroutineContext, throwable -> if (throwable is CancellationException) { return@CoroutineExceptionHandler } val coroutineName = coroutineContext[CoroutineName]?.name ?: "<unnamed>" MiraiConsole.mainLogger.error("Exception in coroutine $coroutineName", throwable) } ) } override val coroutineContext: CoroutineContext get() = delegateCoroutineScope.coroutineContext override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }) override val jvmPluginLoader: JvmPluginLoader by lazy { backendAccess.createDefaultJvmPluginLoader(coroutineContext) } override val commandManager: CommandManager by lazy { backendAccess.createDefaultCommandManager(coroutineContext) } override val consoleDataScope: MiraiConsoleImplementation.ConsoleDataScope by lazy { @OptIn(ConsoleExperimentalApi::class) MiraiConsoleImplementation.ConsoleDataScope.createDefault( coroutineContext, dataStorageForBuiltIns, configStorageForBuiltIns ) } @ConsoleExperimentalApi override val dataStorageForJvmPluginLoader: PluginDataStorage by lazy { MultiFilePluginDataStorage(rootPath.resolve("data")) } @ConsoleExperimentalApi override val dataStorageForBuiltIns: PluginDataStorage by lazy { MultiFilePluginDataStorage(rootPath.resolve("data")) } @ConsoleExperimentalApi override val configStorageForJvmPluginLoader: PluginDataStorage by lazy { MultiFilePluginDataStorage(rootPath.resolve("config")) } @ConsoleExperimentalApi override val configStorageForBuiltIns: PluginDataStorage by lazy { MultiFilePluginDataStorage(rootPath.resolve("config")) } // endregion // region protected abstract val frontendBase: FrontendBase // endregion // region Logging @OptIn(MiraiInternalApi::class) override fun createLoggerFactory(context: MiraiConsoleImplementation.FrontendLoggingInitContext): MiraiLogger.Factory { @Suppress("INVISIBLE_MEMBER") frontendBase.initScreen_forwardStdToScreen() // region Default Fallback Implementation @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge.defaultLoggerFactory = { class DefaultMiraiConsoleFactory : MiraiLogger.Factory { // Don't directly use ::println // ::println will query System.out every time. private val stdout: ((String) -> Unit) = System.out::println override fun create(requester: Class<*>, identity: String?): MiraiLogger { return PlatformLogger(identity ?: requester.kotlin.simpleName ?: requester.simpleName, stdout) } } DefaultMiraiConsoleFactory() } // endregion val factoryImpl = context.acquirePlatformImplementation() context.invokeAfterInitialization { @Suppress("INVISIBLE_MEMBER") frontendBase.initScreen_forwardStdToMiraiLogger() } return factoryImpl } // endregion } ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/src/FrontendBase.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.frontendbase import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.frontendbase.logging.AsyncLogRecorderForwarded import net.mamoe.mirai.console.frontendbase.logging.DailySplitLogRecorder import net.mamoe.mirai.console.frontendbase.logging.LogRecorder import net.mamoe.mirai.utils.MiraiLogger import java.io.PrintStream import java.nio.file.Path import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicInteger /** * 前端的基本实现 * * @see AbstractMiraiConsoleFrontendImplementation */ public abstract class FrontendBase { /** * 所属前端的 [CoroutineScope] * * Implementation note: 直接返回前端实例 */ public abstract val scope: CoroutineScope public open val threadGroup: ThreadGroup by lazy { ThreadGroup("Mirai Console FrontEnd Threads") } public open val daemonThreadGroup: ThreadGroup by lazy { ThreadGroup(threadGroup, "Mirai Console FrontEnd Daemon Threads") } public open val loggingRecorder: LogRecorder by lazy { initLogRecorder() } /** * Console 的运行目录 * * Implementation note: 返回 [MiraiConsoleImplementation.rootPath] */ public abstract val workingDirectory: Path /** * 日志存放目录 */ public open val loggingDirectory: Path by lazy { workingDirectory.resolve("logs") } /** * 存储的日志是否需要去除 ansi 标志 */ public open val logDropAnsi: Boolean get() = true /** * 创建一个新的非守护线程, 此线程不会预先启动 */ public open fun newThread(name: String, task: Runnable): Thread { return Thread(threadGroup, task, name) } /** * 创建一个新的守护线程, 此线程不会预先启动 */ public open fun newDaemon(name: String, task: Runnable): Thread { return Thread(daemonThreadGroup, task, name).apply { isDaemon = true } } /** * 创建一个新的 [ThreadFactory], 创建的线程的名字为 `$name#{counter}` */ public open fun newThreadFactory(name: String, isDemon: Boolean, postSetup: (Thread) -> Unit = {}): ThreadFactory { return object : ThreadFactory { private val group = ThreadGroup(if (isDemon) daemonThreadGroup else threadGroup, name) private val counter = AtomicInteger() override fun newThread(r: Runnable): Thread { return Thread(group, r, "$name#${counter.getAndIncrement()}").also { it.isDaemon = isDemon }.also(postSetup) } } } /** * 将一条信息直接打印到前端的屏幕上 * * Implementation note: 打印时不需要添加任何修饰, [msg] 已经是格式化好的信息 */ public abstract fun printToScreenDirectly(msg: String) @Suppress("FunctionName") protected open fun initScreen_forwardStdToScreen() { val forwarder = RepipedMessageForward { msg -> printToScreenDirectly(msg) recordToLogging(msg) } val printer = PrintStream(forwarder.pipedOutputStream, true, "UTF-8") System.setOut(printer) // stderr is reserved for printing fatal errors when something crashed in logger factory initialization } @Suppress("FunctionName") protected open fun initScreen_forwardStdToMiraiLogger() { val logStdout = MiraiLogger.Factory.create(javaClass, "stdout") val logStderr = MiraiLogger.Factory.create(javaClass, "stderr") val forwarderStdout = RepipedMessageForward(logStdout::info) val forwarderStderr = RepipedMessageForward(logStderr::warning) System.setOut(PrintStream(forwarderStdout.pipedOutputStream, true, "UTF-8")) System.setErr(PrintStream(forwarderStderr.pipedOutputStream, true, "UTF-8")) } /** * 将一条消息记录至日志 (不会显示至屏幕) */ public open fun recordToLogging(msg: String) { loggingRecorder.record(msg) } protected open fun initLogRecorder(): LogRecorder { return AsyncLogRecorderForwarded( DailySplitLogRecorder( loggingDirectory, this ), this ) } } ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/src/RepipedMessageForward.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.frontendbase import java.io.ByteArrayOutputStream import java.io.OutputStream internal class RepipedMessageForward( private val output: (String) -> Unit, ) : ByteArrayOutputStream(1024 * 1024) { internal val pipedOutputStream: OutputStream get() = this private var lastCheckIndex = 0 @Synchronized override fun write(b: ByteArray?, off: Int, len: Int) { super.write(b, off, len) flush() } @Synchronized override fun write(b: Int) { super.write(b) flush() } @Synchronized override fun write(b: ByteArray?) { super.write(b) flush() } @Synchronized override fun flush() { topLoop@ while (true) { var index = lastCheckIndex val end = this.count var lastIsLr = false // last char is '\r' while (index < end) { val c = buf[index].toInt() and 0xFF when (c shr 4) { in 0..7 -> { /* 0xxxxxxx*/ if (c == '\r'.code) { lastIsLr = true } else if (c == 10) { // NEW LINE: \n val strend = if (lastIsLr) { index - 1 } else { index } val strx = String(buf, 0, strend, Charsets.UTF_8) index++ System.arraycopy( buf, index, buf, 0, end - index ) // A \n // index = 1 // string with ln = 2 count -= index lastCheckIndex = 0 output(strx) continue@topLoop // same as return flush() } else { lastIsLr = false } index++ } 12, 13 -> { /* 110x xxxx 10xx xxxx*/ index += 2 lastIsLr = false } 14 -> { /* 1110 xxxx 10xx xxxx 10xx xxxx */ index += 3 lastIsLr = false } else -> { /* 10xx xxxx, 1111 xxxx */ index++// Ignored lastIsLr = false } } } lastCheckIndex = index break } } } ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/src/logging/LogRecorder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.frontendbase.logging import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.onFailure import net.mamoe.mirai.console.frontendbase.FrontendBase import net.mamoe.mirai.console.util.AnsiMessageBuilder.Companion.dropAnsi import net.mamoe.mirai.utils.childScope import java.io.Writer import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption import java.time.Instant import java.time.ZoneId import java.time.format.DateTimeFormatter import java.util.concurrent.Executors import java.util.concurrent.ScheduledExecutorService public abstract class LogRecorder { public abstract fun record(msg: String) } public object AllDroppedLogRecorder : LogRecorder() { override fun record(msg: String) { } } public abstract class AsyncLogRecorder( private val base: FrontendBase, pipelineSize: Int = 2048, ) : LogRecorder() { protected val channel: Channel<String> = Channel(pipelineSize) protected val threadPool: ScheduledExecutorService = Executors.newScheduledThreadPool( 3, base.newThreadFactory("Mirai Console Logging", true) ) protected val dispatcher: CoroutineDispatcher = threadPool.asCoroutineDispatcher() protected val subscope: CoroutineScope = base.scope.childScope(name = "Mirai Console Async Logging", dispatcher) init { base.scope.coroutineContext.job.invokeOnCompletion { channel.close() threadPool.shutdown() } subscope.launch { while (isActive) { val nextLine = channel.receive() asyncRecord(nextLine) } } } override fun record(msg: String) { if (!subscope.isActive) return // Died channel.trySend(msg).onFailure { base.scope.launch(start = CoroutineStart.UNDISPATCHED) { channel.send(msg) } } } protected abstract fun asyncRecord(msg: String) } public open class AsyncLogRecorderForwarded( protected val delegate: LogRecorder, base: FrontendBase, pipelineSize: Int = 2048, ) : AsyncLogRecorder(base, pipelineSize) { override fun asyncRecord(msg: String) { delegate.record(msg) } } public open class WriterLogRecorder( protected val writer: Writer, protected val base: FrontendBase, ) : LogRecorder() { override fun record(msg: String) { try { writer.append( if (base.logDropAnsi) { msg.dropAnsi() } else msg ).append('\n').flush() } catch (e: Throwable) { base.printToScreenDirectly(e.stackTraceToString()) } } } public open class DailySplitLogRecorder( protected val directory: Path, protected val base: FrontendBase, protected val dateFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern( "yyyy-MM-dd'.log'" ), ) : LogRecorder() { @JvmField protected var writer: Writer? = null @JvmField protected var lastDate: Int = -1 protected fun acquireFileWriter() { val instantNow = Instant.now() .atZone(ZoneId.systemDefault()) val dayNow = instantNow.dayOfYear if (dayNow != lastDate) { lastDate = dayNow writer?.close() val logPath = directory.resolve(dateFormatter.format(instantNow)) logPath.parent?.let { pt -> if (!Files.isDirectory(pt)) { Files.createDirectories(pt) } } writer = Files.newBufferedWriter( logPath, Charsets.UTF_8, StandardOpenOption.CREATE, StandardOpenOption.APPEND ) } } override fun record(msg: String) { try { acquireFileWriter() (writer ?: error("Writer not setup")).append( if (base.logDropAnsi) { msg.dropAnsi() } else msg ).append('\n').flush() } catch (e: Throwable) { base.printToScreenDirectly(e.stackTraceToString()) } } } ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/src/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.frontendbase ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/test/RepipedMessageForwardTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.frontendbase import java.io.OutputStreamWriter import java.io.PrintStream import kotlin.test.Test import kotlin.test.assertEquals internal class RepipedMessageForwardTest { private val pendingMsg = mutableListOf<String>() private val ouptut = RepipedMessageForward(pendingMsg::add).pipedOutputStream @Test fun testPrintStream() { val ps = PrintStream(ouptut) ps.println("ABC") ps.append("D").append("E").append("F").println("G") ps.println("LLOO") assertEquals(3, pendingMsg.size) assertEquals("ABC", pendingMsg.removeAt(0)) assertEquals("DEFG", pendingMsg.removeAt(0)) assertEquals("LLOO", pendingMsg.removeAt(0)) } @Test fun testCRLF() { OutputStreamWriter(ouptut).use { writer -> writer.append("LINE").append("AAA").append("OOOO").append("\r\n") writer.append("Line1125744\r\n") writer.append("AFFXZ\r\n") } assertEquals(3, pendingMsg.size) assertEquals("LINEAAAOOOO", pendingMsg.removeAt(0)) assertEquals("Line1125744", pendingMsg.removeAt(0)) assertEquals("AFFXZ", pendingMsg.removeAt(0)) } @Test fun testLF() { OutputStreamWriter(ouptut).use { writer -> writer.append("LINE").append("\n") writer.append("Line5\n") writer.append("AFFXZ\n") writer.append("NO\rCR REMOVED\n") } assertEquals(4, pendingMsg.size) assertEquals("LINE", pendingMsg.removeAt(0)) assertEquals("Line5", pendingMsg.removeAt(0)) assertEquals("AFFXZ", pendingMsg.removeAt(0)) assertEquals("NO\rCR REMOVED", pendingMsg.removeAt(0)) } @Test fun testCRLFMixing() { OutputStreamWriter(ouptut).use { writer -> writer.append("LF\n") writer.append("CRLF\r\n") writer.append("LFLF\n\n") } assertEquals(4, pendingMsg.size) assertEquals("LF", pendingMsg.removeAt(0)) assertEquals("CRLF", pendingMsg.removeAt(0)) assertEquals("LFLF", pendingMsg.removeAt(0)) assertEquals("", pendingMsg.removeAt(0)) } @Test fun testEmptyLines_LF() { OutputStreamWriter(ouptut).use { writer -> repeat(7) { writer.append("\n") } } assertEquals(7, pendingMsg.size) repeat(7) { assertEquals("", pendingMsg.removeAt(0)) } } @Test fun testEmptyLines_CRLF() { OutputStreamWriter(ouptut).use { writer -> repeat(7) { writer.append("\r\n") } } assertEquals(7, pendingMsg.size) repeat(7) { assertEquals("", pendingMsg.removeAt(0)) } } } ================================================ FILE: mirai-console/frontend/mirai-console-frontend-base/test/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.frontendbase ================================================ FILE: mirai-console/frontend/mirai-console-terminal/.gitignore ================================================ /run ================================================ FILE: mirai-console/frontend/mirai-console-terminal/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import shadow.registerRegularShadowTaskForJvmProject import shadow.shadowImplementation plugins { kotlin("jvm") kotlin("plugin.serialization") id("java") `maven-publish` } val shadow: Configuration = project.configurations.create("shadow") kotlin { optInForTestSourceSets("net.mamoe.mirai.console.util.ConsoleExperimentalApi") optInForTestSourceSets("net.mamoe.mirai.console.ConsoleFrontEndImplementation") } dependencies { api(project(":mirai-core-api")) api(project(":mirai-console")) implementation(project(":mirai-core-utils")) shadowImplementation(jline) shadowImplementation(jansi) shadowImplementation(project(":mirai-console-frontend-base")) implementation(`kotlin-jvm-blocking-bridge`) testImplementation(project(":mirai-core")) } version = Versions.consoleTerminal description = "Console Terminal CLI frontend for mirai" configurePublishing("mirai-console-terminal", addShadowJar = false) val shadowJar = registerRegularShadowTaskForJvmProject(listOf(shadow)) publishing { publications.getByName("mavenJava", MavenPublication::class) { artifacts.artifact(shadowJar) } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/BufferedOutputStream.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.terminal import java.io.ByteArrayOutputStream import java.io.OutputStream private const val LN = 10.toByte() internal class BufferedOutputStream @JvmOverloads constructor( private val size: Int = 1024 * 1024, private val logger: (String?) -> Unit ) : ByteArrayOutputStream(size + 1) { override fun write(b: Int) { if (this.count >= size) { flush() } if (b == 10) { flush() } else { super.write(b) } } override fun write(b: ByteArray) { write(b, 0, b.size) } private fun ByteArray.findSplitter(off: Int, end: Int): Int { var o = off while (o < end) { if (get(o) == LN) { return o } o++ } return -1 } override fun write(b: ByteArray, off: Int, len: Int) { val ed = off + len if (ed > b.size || ed < 0) { throw ArrayIndexOutOfBoundsException() } write0(b, off, ed) } private fun write0(b: ByteArray, off: Int, end: Int) { val size = end - off if (size < 1) return val spl = b.findSplitter(off, end) if (spl == -1) { val over = this.size - (size + count) if (over < 0) { // cutting write0(b, off, end + over) flush() write0(b, off - over, end) } else { super.write(b, off, size) } } else { write0(b, off, spl) flush() write0(b, spl + 1, end) } } override fun writeTo(out: OutputStream?) { throw UnsupportedOperationException() } override fun flush() { logger(String(buf, 0, count, Charsets.UTF_8)) count = 0 } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/ConsoleInputImpl.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.terminal import net.mamoe.mirai.console.util.ConsoleInput internal object ConsoleInputImpl : ConsoleInput { override suspend fun requestInput(hint: String): String { return JLineInputDaemon.nextInput(hint) } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/ConsoleTerminalSettings.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ /* * @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp> */ package net.mamoe.mirai.console.terminal import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target( AnnotationTarget.CLASS, AnnotationTarget.TYPEALIAS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY, AnnotationTarget.FIELD, AnnotationTarget.CONSTRUCTOR ) @MustBeDocumented annotation class ConsoleTerminalExperimentalApi @ConsoleTerminalExperimentalApi object ConsoleTerminalSettings { @ConsoleFrontEndImplementation @JvmField var launchOptions: MiraiConsoleImplementation.ConsoleLaunchOptions = MiraiConsoleImplementation.ConsoleLaunchOptions() @JvmField var setupAnsi: Boolean = System.getProperty("os.name") .lowercase() .contains("windows") // Just for Windows @JvmField var noConsole: Boolean = false @JvmField var noAnsi: Boolean = false @JvmField var noConsoleSafeReading: Boolean = false @JvmField var noConsoleReadingReplacement: String = "" @JvmField var noLogging = false } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/ConsoleThread.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ExperimentalCommandDescriptors::class, ConsoleExperimentalApi::class) package net.mamoe.mirai.console.terminal import kotlinx.coroutines.* import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.command.* import net.mamoe.mirai.console.command.CommandExecuteResult.* import net.mamoe.mirai.console.command.descriptor.AbstractCommandValueParameter.StringConstant import net.mamoe.mirai.console.command.descriptor.CommandReceiverParameter import net.mamoe.mirai.console.command.descriptor.CommandValueParameter import net.mamoe.mirai.console.command.descriptor.ExperimentalCommandDescriptors import net.mamoe.mirai.console.command.parse.CommandCall import net.mamoe.mirai.console.command.parse.CommandValueArgument import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.cast import net.mamoe.mirai.console.util.safeCast import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.warning import org.jline.reader.EndOfFileException import org.jline.reader.UserInterruptException import kotlin.reflect.KClass import kotlin.reflect.full.isSubclassOf val consoleLogger by lazy { MiraiLogger.Factory.create(MiraiConsole::class, "console") } @OptIn(ExperimentalCommandDescriptors::class) internal fun startupConsoleThread() { if (terminal is NoConsole) return MiraiConsole.launch(CoroutineName("Input Cancelling Daemon")) { while (isActive) { delay(2000) } }.invokeOnCompletion { runCatching { // 应该仅关闭用户输入 terminal.reader().shutdown() }.exceptionOrNull()?.printStackTrace() } MiraiConsole.launch(CoroutineName("Console Command")) { while (true) { val next = try { JLineInputDaemon.nextCmd().let { when { it.isBlank() -> it it.startsWith(CommandManager.commandPrefix) -> it it == "?" -> CommandManager.commandPrefix + BuiltInCommands.HelpCommand.primaryName else -> CommandManager.commandPrefix + it } } } catch (e: InterruptedException) { return@launch } catch (e: CancellationException) { return@launch } catch (e: UserInterruptException) { signalHandler("INT") return@launch } catch (eof: EndOfFileException) { consoleLogger.warning("Closing input service...") return@launch } catch (e: Throwable) { consoleLogger.error("Error in reading next command", e) consoleLogger.warning("Closing input service...") return@launch } if (next.isBlank()) { continue } try { // consoleLogger.debug("INPUT> $next") when (val result = ConsoleCommandSender.executeCommand(next)) { is Success -> { } is IllegalArgument -> { // user wouldn't want stacktrace for a parser error unless it is in debugging mode (to do). val message = result.exception.message if (message != null) { consoleLogger.warning(message) } else consoleLogger.warning(result.exception) } is ExecutionFailed -> { consoleLogger.error(result.exception) } is UnresolvedCommand -> { consoleLogger.warning { "未知指令: ${next}, 输入 ? 获取帮助" } } is PermissionDenied -> { consoleLogger.warning { "权限不足." } } is UnmatchedSignature -> { consoleLogger.warning { "参数不匹配, 你是否想执行: \n" + result.failureReasons.render( result.command, result.call ) } } is Failure -> { consoleLogger.warning { result.toString() } } } } catch (e: InterruptedException) { return@launch } catch (e: CancellationException) { return@launch } catch (e: Throwable) { consoleLogger.error("Unhandled exception", e) } } } } @OptIn(ExperimentalCommandDescriptors::class) private fun List<UnmatchedCommandSignature>.render(command: Command, call: CommandCall): String { val list = this.filter lambda@{ signature -> if (signature.failureReason.safeCast<FailureReason.InapplicableValueArgument>()?.parameter is StringConstant) return@lambda false if (signature.signature.valueParameters.anyStringConstantUnmatched(call.valueArguments)) return@lambda false true } if (list.isEmpty()) { return command.usage } return list.joinToString("\n") { it.render(command) } } private fun List<CommandValueParameter<*>>.anyStringConstantUnmatched(arguments: List<CommandValueArgument>): Boolean { return this.zip(arguments).any { (parameter, argument) -> parameter is StringConstant && !parameter.accepts(argument, null) } } @OptIn(ExperimentalCommandDescriptors::class) internal fun UnmatchedCommandSignature.render(command: Command): String { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") val usage = net.mamoe.mirai.console.internal.command.CommandReflector.generateUsage(command, null, listOf(this.signature)) return usage.trim() + " (${failureReason.render()})" } @OptIn(ExperimentalCommandDescriptors::class) internal fun FailureReason.render(): String { return when (this) { is FailureReason.InapplicableReceiverArgument -> "需要由 ${this.parameter.renderAsName()} 执行" is FailureReason.InapplicableArgument -> "参数类型错误" is FailureReason.TooManyArguments -> "参数过多" is FailureReason.NotEnoughArguments -> "参数不足" is FailureReason.ResolutionAmbiguity -> "调用歧义" is FailureReason.ArgumentLengthMismatch -> { // should not happen, render it anyway. "参数长度不匹配" } } } @OptIn(ExperimentalCommandDescriptors::class) internal fun CommandReceiverParameter<*>.renderAsName(): String { val classifier = this.type.classifier.cast<KClass<out CommandSender>>() return when { classifier.isSubclassOf(ConsoleCommandSender::class) -> "控制台" // 只有 classifier 明确为 SystemCommandSender 才有系统的意思 // classifier 为子类时不满足 "系统" 的定义 classifier == SystemCommandSender::class -> "系统" classifier.isSubclassOf(FriendCommandSenderOnMessage::class) -> "好友私聊" classifier.isSubclassOf(FriendCommandSender::class) -> "好友" classifier.isSubclassOf(MemberCommandSenderOnMessage::class) -> "群内发言" classifier.isSubclassOf(MemberCommandSender::class) -> "群成员" classifier.isSubclassOf(GroupTempCommandSenderOnMessage::class) -> "群临时会话" classifier.isSubclassOf(GroupTempCommandSender::class) -> "群临时好友" classifier.isSubclassOf(UserCommandSender::class) -> "用户" else -> classifier.simpleName ?: classifier.toString() } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/JLineInputDaemon.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ConsoleExperimentalApi::class) package net.mamoe.mirai.console.terminal import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.Runnable import kotlinx.coroutines.isActive import kotlinx.coroutines.suspendCancellableCoroutine import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.utils.ConcurrentLinkedDeque import net.mamoe.mirai.utils.cast import org.jline.reader.MaskingCallback import org.jline.reader.impl.LineReaderImpl import org.jline.utils.AttributedStringBuilder import org.jline.utils.AttributedStyle import java.util.* import kotlin.concurrent.withLock internal object JLineInputDaemon : Runnable { lateinit var terminal0: MiraiConsoleImplementationTerminal private val readerImpl: LineReaderImpl get() = lineReader.cast() private var pausedByDaemon: Boolean = false private var canResumeByNewRequest: Boolean = false class Request( val masked: Boolean = false, val delayable: Boolean = false, val coroutine: CancellableContinuation<String>, val prompt: String? = null, ) private val pwdMasker = object : MaskingCallback { override fun display(line: String): String { return buildString(line.length) { repeat(line.length) { append('*') } } } override fun history(line: String?): String? { return null } } val queue = ConcurrentLinkedDeque<Request>() val queueDelayable = ConcurrentLinkedDeque<Request>() val queueStateChangeNoticer = Object() var processing: Request? = null override fun run() { while (terminal0.isActive) { val nextTask = queue.poll() ?: queueDelayable.poll() if (nextTask == null) { synchronized(queueStateChangeNoticer) { if (queue.isEmpty() && queueDelayable.isEmpty()) { queueStateChangeNoticer.wait() } } continue } if (nextTask.coroutine.isCompleted) continue synchronized(queueStateChangeNoticer) { processing = nextTask updateFlags(nextTask) } val rsp = kotlin.runCatching { lineReader.readLine( nextTask.prompt ?: "> ", null, if (nextTask.masked) pwdMasker else null, null ) } val crtProcessing: Request synchronized(queueStateChangeNoticer) { crtProcessing = processing ?: error("!processing lost") processing = null } crtProcessing.coroutine.resumeWith(rsp) } } internal fun sendRequest(req: Request) { if (terminal is NoConsole) { req.coroutine.resumeWith(kotlin.runCatching { lineReader.readLine() }) return } req.coroutine.invokeOnCancellation { if (req.delayable) { queueDelayable } else { queue }.remove(req) synchronized(queueStateChangeNoticer) { if (processing !== req) return@invokeOnCancellation val nnextTask: Request while (true) { val nnextTask2 = queue.poll() ?: queueDelayable.poll() if (nnextTask2 == null) { suspendReader(true) return@invokeOnCancellation } if (nnextTask2.coroutine.isCompleted) continue nnextTask = nnextTask2 break } processing = nnextTask updateFlags(nnextTask) if (lineReader.isReading) { readerImpl.redisplay() } } } synchronized(queueStateChangeNoticer) { val crtProcessing = processing if (crtProcessing != null) { if (crtProcessing.delayable) { processing = req queueDelayable.addLast(crtProcessing) updateFlags(req) if (lineReader.isReading) { readerImpl.redisplay() } return@synchronized } } if (req.delayable) { queueDelayable } else { queue }.addLast(req) queueStateChangeNoticer.notify() if (crtProcessing != null && crtProcessing.coroutine.isCompleted) { val nnextTask: Request while (true) { val nnextTask2 = queue.poll() ?: queueDelayable.poll() if (nnextTask2 == null) { nnextTask = req break } if (nnextTask2.coroutine.isCompleted) continue nnextTask = nnextTask2 break } processing = nnextTask updateFlags(nnextTask) if (lineReader.isReading) { readerImpl.redisplay() } } } tryResumeReader(true) } private fun updateFlags(req: Request) { if (req.masked) { lineReaderMaskingCallback[lineReader] = pwdMasker } else { lineReaderMaskingCallback[lineReader] = null } readerImpl.setPrompt(req.prompt ?: "> ") } internal fun suspendReader(canResumeByNewRequest: Boolean): Unit = terminalExecuteLock.withLock { if (!lineReader.isReading) return terminal.pause() pausedByDaemon = true this.canResumeByNewRequest = canResumeByNewRequest lineReaderReadingField.setBoolean(lineReader, false) terminalDisplay.update(Collections.emptyList(), 0) } internal fun tryResumeReader(byNewReq: Boolean): Unit = terminalExecuteLock.withLock { if (!pausedByDaemon) return if (byNewReq && !canResumeByNewRequest) return pausedByDaemon = false terminal.resume() lineReaderReadingField.setBoolean(lineReader, true) readerImpl.redisplay() } suspend fun nextInput(hint: String): String = suspendCancellableCoroutine { cort -> sendRequest( Request( masked = false, delayable = false, coroutine = cort, prompt = "$hint> " ) ) } suspend fun nextCmd(): String = suspendCancellableCoroutine { cort -> sendRequest(Request(masked = false, delayable = true, coroutine = cort)) } suspend fun nextPwd(): String = suspendCancellableCoroutine { cort -> sendRequest( Request( masked = true, delayable = false, coroutine = cort, prompt = AttributedStringBuilder() .style(AttributedStyle.DEFAULT.background(AttributedStyle.CYAN).foreground(AttributedStyle.WHITE)) .append("PASSWORD") .style(AttributedStyle.DEFAULT) .append("> ") .toAnsi() ) ) } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/LoggingService.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.terminal import net.mamoe.mirai.utils.TestOnly // Used by https://github.com/iTXTech/soyuz, change with care. internal sealed class LoggingService { @TestOnly internal lateinit var switchLogFileNow: () -> Unit internal abstract fun pushLine(line: String) } internal class LoggingServiceNoop : LoggingService() { override fun pushLine(line: String) { } init { @OptIn(TestOnly::class) switchLogFileNow = {} } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/MiraiConsoleImplementationTerminal.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress( "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER", "INVISIBLE_SETTER", "INVISIBLE_GETTER", "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER", "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER_WARNING", "EXPOSED_SUPER_CLASS" ) @file:OptIn(ConsoleInternalApi::class, ConsoleFrontEndImplementation::class, ConsoleTerminalExperimentalApi::class) package net.mamoe.mirai.console.terminal import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsoleFrontEndDescription import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.data.MultiFilePluginDataStorage import net.mamoe.mirai.console.data.PluginDataStorage import net.mamoe.mirai.console.fontend.ProcessProgress import net.mamoe.mirai.console.frontendbase.AbstractMiraiConsoleFrontendImplementation import net.mamoe.mirai.console.frontendbase.FrontendBase import net.mamoe.mirai.console.frontendbase.logging.AllDroppedLogRecorder import net.mamoe.mirai.console.frontendbase.logging.LogRecorder import net.mamoe.mirai.console.plugin.jvm.JvmPluginLoader import net.mamoe.mirai.console.plugin.loader.PluginLoader import net.mamoe.mirai.console.terminal.ConsoleInputImpl.requestInput import net.mamoe.mirai.console.terminal.noconsole.AllEmptyLineReader import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.console.terminal.noconsole.SystemOutputPrintStream import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInput import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.console.util.SemVersion import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.StandardCharImageLoginSolver import org.fusesource.jansi.Ansi import org.jline.reader.LineReader import org.jline.reader.LineReaderBuilder import org.jline.reader.impl.completer.NullCompleter import org.jline.terminal.Terminal import org.jline.terminal.TerminalBuilder import org.jline.terminal.impl.AbstractWindowsTerminal import java.nio.file.Path import java.nio.file.Paths /** * mirai-console-terminal 后端实现 * * @see MiraiConsoleTerminalLoader CLI 入口点 */ @ConsoleExperimentalApi open class MiraiConsoleImplementationTerminal @JvmOverloads constructor( final override val rootPath: Path = Paths.get(System.getProperty("user.dir", ".")).toAbsolutePath(), override val builtInPluginLoaders: List<Lazy<PluginLoader<*, *>>> = listOf(lazy { JvmPluginLoader }), override val frontEndDescription: MiraiConsoleFrontEndDescription = ConsoleFrontEndDescImpl, override val consoleCommandSender: MiraiConsoleImplementation.ConsoleCommandSenderImpl = ConsoleCommandSenderImplTerminal, override val dataStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val dataStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("data")), override val configStorageForJvmPluginLoader: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), override val configStorageForBuiltIns: PluginDataStorage = MultiFilePluginDataStorage(rootPath.resolve("config")), ) : AbstractMiraiConsoleFrontendImplementation("MiraiConsoleImplementationTerminal") { @Suppress("MemberVisibilityCanBePrivate") override val frontendBase = object : FrontendBase() { override val scope: CoroutineScope get() = this@MiraiConsoleImplementationTerminal override val workingDirectory: Path get() = this@MiraiConsoleImplementationTerminal.rootPath override fun initLogRecorder(): LogRecorder { if (ConsoleTerminalSettings.noLogging) { return AllDroppedLogRecorder } return super.initLogRecorder() } override fun initScreen_forwardStdToScreen() { lineReader super.initScreen_forwardStdToScreen() } override fun printToScreenDirectly(msg: String) { printToScreen(msg) @Suppress("DEPRECATION") logService.pushLine(msg) } } override val consoleInput: ConsoleInput get() = ConsoleInputImpl override val isAnsiSupported: Boolean get() = true @Deprecated("Used by iTXTech; for binary compatibility") internal val logService: LoggingService override fun createLoginSolver(requesterBot: Long, configuration: BotConfiguration): LoginSolver { LoginSolver.Default?.takeIf { it !is StandardCharImageLoginSolver }?.let { return it } return StandardCharImageLoginSolver(input = { requestInput("LOGIN> ") }) } init { with(rootPath.toFile()) { mkdir() require(isDirectory) { "rootDir $absolutePath is not a directory" } @Suppress("DEPRECATION") logService = LoggingServiceNoop() } } override val consoleLaunchOptions: MiraiConsoleImplementation.ConsoleLaunchOptions get() = ConsoleTerminalSettings.launchOptions override fun preStart() { registerSignalHandler() JLineInputDaemon.terminal0 = this if (terminal !is NoConsole) { frontendBase.newDaemon("JLine Input Daemon", JLineInputDaemon).start() } launch(CoroutineName("Mirai Console Terminal Downloading Progress Bar Updater")) { while (isActive) { downloadingProgressDaemonStub() } } } override fun createNewProcessProgress(): ProcessProgress { if (terminal is NoConsole) return super.createNewProcessProgress() containDownloadingProgress = true kotlin.runCatching { downloadingProgressCoroutine?.resumeWith(Result.success(Unit)) } return TerminalProcessProgress(lineReader).also { terminalDownloadingProgresses.add(it) terminal.writer().print("\u001B[?25l") // hide cursor } } } val lineReader: LineReader by lazy { val terminal = terminal if (terminal is NoConsole) return@lazy AllEmptyLineReader LineReaderBuilder.builder() .terminal(terminal) .completer(NullCompleter()) .build() } val terminal: Terminal = run { if (ConsoleTerminalSettings.noConsole) { SystemOutputPrintStream // init value return@run NoConsole } TerminalBuilder.builder() .name("Mirai Console") .system(true) .jansi(true) .dumb(true) .paused(true) .signalHandler { signalHandler(it.name) } .build() .let { terminal -> if (terminal is AbstractWindowsTerminal) { val pumpField = runCatching { AbstractWindowsTerminal::class.java.getDeclaredField("pump").also { it.isAccessible = true } }.onFailure { err -> err.printStackTrace() return@let terminal.also { it.resume() } }.getOrThrow() var response = terminal terminal.setOnClose { response = NoConsole } terminal.resume() val pumpThread = pumpField[terminal] as? Thread ?: return@let NoConsole @Suppress("ControlFlowWithEmptyBody") while (pumpThread.state == Thread.State.NEW); Thread.sleep(1000) terminal.setOnClose(null) return@let response } terminal.resume() terminal } } private object ConsoleFrontEndDescImpl : MiraiConsoleFrontEndDescription { override val name: String get() = "Terminal" override val vendor: String get() = "Mamoe Technologies" // net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version // is console's version not frontend's version override val version: SemVersion = SemVersion(net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst) } internal val ANSI_RESET = Ansi().reset().toString() ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/MiraiConsoleTerminalLoader.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress( "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER", "INVISIBLE_SETTER", "INVISIBLE_GETTER", "INVISIBLE_ABSTRACT_MEMBER_FROM_SUPER", ) @file:OptIn(ConsoleInternalApi::class, ConsoleTerminalExperimentalApi::class, ConsoleFrontEndImplementation::class) package net.mamoe.mirai.console.terminal import kotlinx.coroutines.* import net.mamoe.mirai.console.ConsoleFrontEndImplementation import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.MiraiConsoleImplementation import net.mamoe.mirai.console.MiraiConsoleImplementation.Companion.start import net.mamoe.mirai.console.data.AutoSavePluginDataHolder import net.mamoe.mirai.console.terminal.noconsole.SystemOutputPrintStream import net.mamoe.mirai.console.util.ConsoleExperimentalApi import net.mamoe.mirai.console.util.ConsoleInternalApi import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.childScope import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.verbose import org.jline.utils.Signals import java.io.FileDescriptor import java.io.FileOutputStream import java.io.PrintStream import java.lang.Runnable import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong import kotlin.system.exitProcess /** * mirai-console-terminal CLI 入口点 */ object MiraiConsoleTerminalLoader { // Note: Do not run this in IDEA, as you will get invalid classpath and `java.lang.NoClassDefFoundError`. // Run `RunTerminal.kt` under `test` source set instead. @OptIn(ConsoleExperimentalApi::class) @JvmStatic fun main(args: Array<String>) { parse(args, exitProcess = true) startAsDaemon() try { runBlocking { MiraiConsole.job.invokeOnCompletion { err -> if (err != null) { Thread.sleep(1000) // 保证错误信息打印完全 } } MiraiConsole.job.join() } } catch (e: CancellationException) { // ignored } // Avoid plugin started some non-daemon threads exitProcessAndForceHalt(0) } @ConsoleTerminalExperimentalApi fun printHelpMessage() { val help = listOf( "" to "Mirai-Console[Terminal FrontEnd] v" + net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.versionConst, "" to " [ BackEnd] v" + kotlin.runCatching { net.mamoe.mirai.console.internal.MiraiConsoleBuildConstants.version }.getOrElse { "<unknown>" }, "" to "", "--help" to "显示此帮助", "" to "", "--no-console" to "使用无终端操作环境", "--no-logging" to "禁用 console 日志文件", "--dont-setup-terminal-ansi" to "[NoConsole] [Windows Only] 不进行ansi console初始化工作", "--no-ansi" to "[NoConsole] 禁用 ansi", "--safe-reading" to "[NoConsole] 如果启动此选项, console在获取用户输入的时候会获得一个安全的替换符\n" + " 如果不启动, 将会直接 error", "--reading-replacement <string>" to "[NoConsole] Console尝试读取命令的替换符, 默认是空字符串\n" + " 使用此选项会自动开启 --safe-reading", ) val prefixPlaceholder = String(CharArray( help.maxOfOrNull { it.first.length }!! + 3 ) { ' ' }) fun printOption(optionName: String, value: String) { if (optionName == "") { println(value) return } print(optionName) print(prefixPlaceholder.substring(optionName.length)) val lines = value.split('\n').iterator() if (lines.hasNext()) println(lines.next()) lines.forEach { line -> print(prefixPlaceholder) println(line) } } help.forEach { (optionName, value) -> printOption(optionName, value) } } @ConsoleTerminalExperimentalApi fun parse(args: Array<String>, exitProcess: Boolean = false) { val iterator = args.iterator() while (iterator.hasNext()) { when (val option = iterator.next()) { "--help" -> { printHelpMessage() if (exitProcess) exitProcess(0) return } "--no-console" -> { ConsoleTerminalSettings.noConsole = true } "--dont-setup-terminal-ansi" -> { ConsoleTerminalSettings.setupAnsi = false } "--no-logging" -> { ConsoleTerminalSettings.noLogging = true } "--no-ansi" -> { ConsoleTerminalSettings.noAnsi = true ConsoleTerminalSettings.setupAnsi = false } "--reading-replacement" -> { ConsoleTerminalSettings.noConsoleSafeReading = true if (iterator.hasNext()) { ConsoleTerminalSettings.noConsoleReadingReplacement = iterator.next() } else { println("Bad option `--reading-replacement`") println("Usage: --reading-replacement <string>") if (exitProcess) exitProcess(1) return } } "--safe-reading" -> { ConsoleTerminalSettings.noConsoleSafeReading = true } else -> { println("Unknown option `$option`") printHelpMessage() if (exitProcess) exitProcess(1) return } } } if (ConsoleTerminalSettings.noConsole) SystemOutputPrintStream // Setup Output Channel } @Suppress("MemberVisibilityCanBePrivate") @ConsoleExperimentalApi fun startAsDaemon(instance: MiraiConsoleImplementationTerminal = MiraiConsoleImplementationTerminal()) { instance.start() startupConsoleThread() } } @OptIn(ConsoleExperimentalApi::class) internal object ConsoleDataHolder : AutoSavePluginDataHolder, CoroutineScope by MiraiConsole.childScope("ConsoleDataHolder") { @ConsoleExperimentalApi override val autoSaveIntervalMillis: LongRange = 60_000L..10.times(60_000) @ConsoleExperimentalApi override val dataHolderName: String get() = "Terminal" } private val shutdownSignals = arrayOf( "INT", "TERM", "QUIT" ) internal val signalHandler: (String) -> Unit = initSignalHandler() @OptIn(ConsoleExperimentalApi::class) private fun initSignalHandler(): (String) -> Unit { val shutdownMonitorLock = AtomicBoolean(false) val lastSignalTimestamp = AtomicLong(0) return handler@{ signalName -> if (signalName == "WINCH") { // Windows CMD.exe resized return@handler } runCatching { MiraiConsole.mainLogger }.onFailure { // mirai-console not yet initialized System.err.println("[TERMINAL] [WARNING] Received signal $signalName") System.err.println("[TERMINAL] [WARNING] This signal will be processed later because mirai-console not yet initialized.") // Try later if (signalName in shutdownSignals) { @OptIn(DelicateCoroutinesApi::class) GlobalScope.launch { delay(500L) signalHandler(signalName) } } return@handler } // JLine may process other signals MiraiConsole.mainLogger.verbose { "Received signal $signalName" } if (signalName !in shutdownSignals) return@handler MiraiConsole.mainLogger.debug { "Handled signal $signalName" } run multiSignalHandler@{ val crtTime = System.currentTimeMillis() val last = lastSignalTimestamp.getAndSet(crtTime) if (crtTime - last < 1000L) { MiraiConsole.mainLogger.debug { "Multi signal $signalName" } MiraiConsole.mainLogger.info { "Process will be killed after 0.5s" } @OptIn(DelicateCoroutinesApi::class) GlobalScope.launch { delay(500L) exitProcessAndForceHalt(-5) } } } MiraiConsole.shutdown() // Shutdown by signal requires process be killed if (shutdownMonitorLock.compareAndSet(false, true)) { val pool = Executors.newFixedThreadPool(4, object : ThreadFactory { private val counter = AtomicInteger() override fun newThread(r: Runnable): Thread { return Thread(r, "Mirai Console Signal-Shutdown Daemon #" + counter.getAndIncrement()).also { it.isDaemon = true } } }) @OptIn(DelicateCoroutinesApi::class) GlobalScope.launch(pool.asCoroutineDispatcher()) { MiraiConsole.job.join() delay(15000) // Force kill process if plugins started non-daemon threads exitProcessAndForceHalt(-5) } @OptIn(DelicateCoroutinesApi::class) GlobalScope.launch(pool.asCoroutineDispatcher()) { delay(1000L * 60) // timed out // Force kill process if plugins started non-daemon threads exitProcessAndForceHalt(-5) } } } } internal fun registerSignalHandler() { fun reg(name: String) { Signals.register(name) { signalHandler(name) } } shutdownSignals.forEach { reg(it) } } internal fun exitProcessAndForceHalt(code: Int): Nothing { MiraiConsole.mainLogger.debug { "[exitProcessAndForceHalt] called with code $code" } val exitFuncName = arrayOf("exit", "halt") val shutdownClasses = arrayOf("java.lang.System", "java.lang.Runtime", "java.lang.Shutdown") val isShutdowning = Thread.getAllStackTraces().asSequence().flatMap { it.value.asSequence() }.any { stackTrace -> stackTrace.className in shutdownClasses && stackTrace.methodName in exitFuncName } MiraiConsole.mainLogger.debug { "[exitProcessAndForceHalt] isShutdowning = $isShutdowning" } val task = Runnable { Thread.sleep(15000L) runCatching { net.mamoe.mirai.console.internal.shutdown.ShutdownDaemon.dumpCrashReport(true) } val fc = when (code) { 0 -> 5784171 else -> code } MiraiConsole.mainLogger.debug { "[exitProcessAndForceHalt] timed out, force halt with code $fc" } Runtime.getRuntime().halt(fc) } if (isShutdowning) { task.run() error("Runtime.halt returned normally, while it was supposed to halt JVM.") } else { Thread(task, "Mirai Console Force Halt Daemon").start() exitProcess(code) } } internal object ConsoleCommandSenderImplTerminal : MiraiConsoleImplementation.ConsoleCommandSenderImpl { override suspend fun sendMessage(message: String) { kotlin.runCatching { printToScreen(message + ANSI_RESET) }.onFailure { exception -> // If failed. It means JLine Terminal not working... PrintStream(FileOutputStream(FileDescriptor.err)).use { it.println("Exception while ConsoleCommandSenderImplTerminal.sendMessage") exception.printStackTrace(it) } } } override suspend fun sendMessage(message: Message) { return sendMessage(message.toString()) } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/TerminalProcessProgress.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.terminal import kotlinx.coroutines.delay import kotlinx.coroutines.suspendCancellableCoroutine import net.mamoe.mirai.console.fontend.ProcessProgress import net.mamoe.mirai.console.terminal.noconsole.NoConsole import net.mamoe.mirai.utils.ConcurrentLinkedQueue import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.currentTimeMillis import org.jline.reader.MaskingCallback import org.jline.reader.impl.LineReaderImpl import org.jline.utils.AttributedString import org.jline.utils.AttributedStringBuilder import org.jline.utils.AttributedStyle import org.jline.utils.Display import java.lang.reflect.Field import kotlin.concurrent.withLock import kotlin.coroutines.Continuation import kotlin.reflect.KProperty internal class TerminalProcessProgress( private val reader: org.jline.reader.LineReader, ) : ProcessProgress { private var totalSize: Long = 1 private var processed: Long = 0 private val txt: StringBuilder = StringBuilder() private val renderedTxt: StringBuilder = StringBuilder() private var failed: Boolean = false private var disposed: Boolean = false @JvmField var pendingErase: Boolean = false @JvmField var eraseTimestamp: Long = 0 @JvmField var ansiMsg: AttributedString = AttributedString.EMPTY private var lastTerminalWidth = 0 private var needRerender: Boolean = true private var needUpdateTxt: Boolean = true override fun updateText(txt: CharSequence) { this.txt.setLength(0) this.txt.append(txt) needUpdateTxt = true needRerender = true } override fun updateText(txt: String) { updateText(txt as CharSequence) } override fun setTotalSize(totalSize: Long) { this.totalSize = totalSize needRerender = true } override fun update(processed: Long) { this.processed = processed needRerender = true } override fun update(processed: Long, totalSize: Long) { this.processed = processed this.totalSize = totalSize needRerender = true } override fun markFailed() { failed = true needRerender = true } internal fun updateTxt(terminalWidth: Int) { // region check need to update if (needUpdateTxt || lastTerminalWidth != terminalWidth) { // <text changed / screen width changed> lastTerminalWidth = terminalWidth synchronized(renderedTxt) { renderedTxt.setLength(0) renderedTxt.append(txt) // paddings if (renderedTxt.length < terminalWidth) { repeat(terminalWidth - renderedTxt.length) { renderedTxt.append(' ') } } } } else if (!needRerender) { // nothing changed return } /* else { <api require rerender> } */ lastTerminalWidth = terminalWidth // endregion synchronized(renderedTxt) { val renderedTextWidth = when (terminalWidth) { 0 -> renderedTxt.length else -> terminalWidth } val finalAnsiLineBuilder = AttributedStringBuilder() if (failed) { finalAnsiLineBuilder.style( AttributedStyle.DEFAULT .background(AttributedStyle.RED) .foreground(AttributedStyle.BLACK) ) finalAnsiLineBuilder.append(renderedTxt, 0, renderedTextWidth) } else { val downpcent = (renderedTextWidth * processed / totalSize).toInt() if (downpcent > 0) { finalAnsiLineBuilder.style( AttributedStyle.DEFAULT .background(AttributedStyle.GREEN) .foreground(AttributedStyle.BLACK) ) finalAnsiLineBuilder.append(renderedTxt, 0, downpcent) } if (downpcent < renderedTextWidth) { finalAnsiLineBuilder.style( AttributedStyle.DEFAULT .background(AttributedStyle.WHITE) .foreground(AttributedStyle.BLACK) ) finalAnsiLineBuilder.append(renderedTxt, downpcent, renderedTextWidth) } } ansiMsg = finalAnsiLineBuilder.toAttributedString() needUpdateTxt = false needRerender = false } } override fun rerender() { updateTerminalDownloadingProgresses() } override fun close() { if (disposed) return disposed = true totalSize = 1 processed = 1 needUpdateTxt = true updateTxt(reader.terminal.width) if (failed) { terminalDownloadingProgresses.remove(this) printToScreen(ansiMsg) ansiMsg = AttributedString.EMPTY return } // terminalDownloadingProgresses.remove(this) pendingErase = true eraseTimestamp = System.currentTimeMillis() + 1500L updateTerminalDownloadingProgresses() // prePrintNewLog() // reader.printAbove(ansiMsg) // ansiMsg = AttributedString.EMPTY } } internal val terminalDisplay: Display by object : kotlin.properties.ReadOnlyProperty<Any?, Display> { val delegate: () -> Display by lazy { val terminal = terminal if (terminal is NoConsole) { val display = Display(terminal, false) return@lazy { display } } val lr = lineReader val field = LineReaderImpl::class.java.declaredFields.first { it.type == Display::class.java } field.isAccessible = true return@lazy { field[lr] as Display } } override fun getValue(thisRef: Any?, property: KProperty<*>): Display { return delegate() } } internal val lineReaderMaskingCallback: Field by lazy { val field = LineReaderImpl::class.java.declaredFields.first { MaskingCallback::class.java.isAssignableFrom(it.type) } field.isAccessible = true field } internal val lineReaderReadingField: Field by lazy { val field = LineReaderImpl::class.java.getDeclaredField("reading") field.isAccessible = true field } internal val terminalExecuteLock: java.util.concurrent.locks.Lock by lazy { val terminal = terminal if (terminal is NoConsole) return@lazy java.util.concurrent.locks.ReentrantLock() val lr = lineReader val field = LineReaderImpl::class.java.declaredFields.first { java.util.concurrent.locks.Lock::class.java.isAssignableFrom(it.type) } field.isAccessible = true field[lr].cast() } private val terminalDownloadingProgressesNoticer = Object() internal var containDownloadingProgress: Boolean = false get() = field || terminalDownloadingProgresses.isNotEmpty() internal val terminalDownloadingProgresses = ConcurrentLinkedQueue<TerminalProcessProgress>() internal var downloadingProgressCoroutine: Continuation<Unit>? = null internal suspend fun downloadingProgressDaemonStub() { delay(500L) if (containDownloadingProgress) { updateTerminalDownloadingProgresses() } else { suspendCancellableCoroutine<Unit> { cp -> downloadingProgressCoroutine = cp } downloadingProgressCoroutine = null } } internal fun updateTerminalDownloadingProgresses() { if (!containDownloadingProgress) return runCatching { downloadingProgressCoroutine?.resumeWith(Result.success(Unit)) } terminalExecuteLock.withLock { JLineInputDaemon.suspendReader(false) if (terminalDownloadingProgresses.isNotEmpty()) { val wid = terminal.width if (wid == 0) { // Run in idea if (terminalDownloadingProgresses.removeIf { it.pendingErase }) { updateTerminalDownloadingProgresses() return } terminalDisplay.update(listOf(AttributedString.EMPTY), 0, false) // Error in idea when more than one bar displaying terminalDisplay.update(listOf(terminalDownloadingProgresses.peek()?.let { it.updateTxt(0); it.ansiMsg } ?: AttributedString.EMPTY), 0) } else { if (terminalDownloadingProgresses.size > 4) { // to mush. delete some completed status var allowToDelete = terminalDownloadingProgresses.size - 4 terminalDownloadingProgresses.removeIf { pg -> if (allowToDelete == 0) { return@removeIf false } if (pg.pendingErase) { allowToDelete-- return@removeIf true } return@removeIf false } } terminalDisplay.update(terminalDownloadingProgresses.map { it.updateTxt(wid); it.ansiMsg }, 0) cleanupErase() } } else { terminalDisplay.update(emptyList(), 0) (lineReader as LineReaderImpl).let { lr -> if (lr.isReading) { lr.redisplay() } } noticeDownloadingProgressEmpty() terminal.writer().print("\u001B[?25h") // show cursor } } } internal fun printToScreen(msg: String) { if (!containDownloadingProgress) { if (msg.endsWith(ANSI_RESET)) { lineReader.printAbove(msg) } else { lineReader.printAbove(msg + ANSI_RESET) } return } terminalExecuteLock.withLock { terminalDisplay.update(emptyList(), 0) if (msg.endsWith(ANSI_RESET)) { lineReader.printAbove(msg) } else { lineReader.printAbove(msg + ANSI_RESET) } updateTerminalDownloadingProgresses() cleanupErase() } } internal fun printToScreen(msg: AttributedString) { if (!containDownloadingProgress) { return lineReader.printAbove(msg) } terminalExecuteLock.withLock { terminalDisplay.update(emptyList(), 0) lineReader.printAbove(msg) updateTerminalDownloadingProgresses() cleanupErase() } } internal fun cleanupErase() { val now = currentTimeMillis() terminalDownloadingProgresses.removeIf { pg -> if (!pg.pendingErase) return@removeIf false if (now > pg.eraseTimestamp) { pg.ansiMsg = AttributedString.EMPTY return@removeIf true } return@removeIf false } } private fun noticeDownloadingProgressEmpty() { synchronized(terminalDownloadingProgressesNoticer) { containDownloadingProgress = false if (terminalDownloadingProgresses.isEmpty()) { terminalDownloadingProgressesNoticer.notifyAll() } JLineInputDaemon.tryResumeReader(false) } } internal fun waitDownloadingProgressEmpty() { synchronized(terminalDownloadingProgressesNoticer) { if (containDownloadingProgress) { terminalDownloadingProgressesNoticer.wait() } } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/net/mamoe/mirai/console/pure/MiraiConsolePureLoader.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.terminal.net.mamoe.mirai.console.pure import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader import net.mamoe.mirai.utils.DeprecatedSinceMirai @Deprecated( message = "Please use MiraiConsoleTerminalLoader", level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith( "MiraiConsoleTerminalLoader", "net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader" ) ) @DeprecatedSinceMirai(errorSince = "2.0", hiddenSince = "2.10") object MiraiConsolePureLoader { @Deprecated( message = "for binary compatibility", level = DeprecationLevel.HIDDEN ) @JvmStatic @DeprecatedSinceMirai(errorSince = "2.0", hiddenSince = "2.10") fun main(args: Array<String>) { System.err.println("WARNING: Mirai Console Pure已经更名为 Mirai Console Terminal") System.err.println("请使用新的入口点 net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader") MiraiConsoleTerminalLoader.main(args) } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/src/noconsole/NoConsole.kt ================================================ /* * 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 */ /* * @author Karlatemp <karlatemp@vip.qq.com> <https://github.com/Karlatemp> */ @file:OptIn(ConsoleTerminalExperimentalApi::class) package net.mamoe.mirai.console.terminal.noconsole import net.mamoe.mirai.console.terminal.ConsoleTerminalExperimentalApi import net.mamoe.mirai.console.terminal.ConsoleTerminalSettings import org.jline.keymap.KeyMap import org.jline.reader.* import org.jline.terminal.Attributes import org.jline.terminal.MouseEvent import org.jline.terminal.Size import org.jline.terminal.Terminal import org.jline.terminal.impl.AbstractTerminal import org.jline.utils.AttributedString import org.jline.utils.NonBlockingReader import java.io.File import java.io.InputStream import java.io.OutputStream import java.io.PrintWriter private const val LN_INT = '\n'.code private const val LN_BYTE = '\n'.code.toByte() internal object NoConsoleNonBlockingReader : NonBlockingReader() { override fun read(timeout: Long, isPeek: Boolean): Int { return LN_INT } override fun close() {} override fun readBuffered(b: CharArray?): Int { return 0 } } internal object AllNextLineInputStream : InputStream() { override fun read(): Int = LN_INT override fun available(): Int = 1 override fun read(b: ByteArray, off: Int, len: Int): Int { for (i in off until (off + len)) { b[i] = LN_BYTE } return len } override fun close() {} } internal object AllIgnoredOutputStream : OutputStream() { override fun close() {} override fun write(b: ByteArray, off: Int, len: Int) {} override fun write(b: ByteArray) {} override fun write(b: Int) {} override fun flush() {} } internal val SystemOutputPrintStream by lazy { @OptIn(ConsoleTerminalExperimentalApi::class) if (ConsoleTerminalSettings.setupAnsi) { org.fusesource.jansi.AnsiConsole.systemInstall() } System.out } private val ANSI_REGEX = """\u001b\[[0-9a-zA-Z;]*?m""".toRegex() internal object AllEmptyLineReader : LineReader { override fun printAbove(str: String?) { if (str == null) return @OptIn(ConsoleTerminalExperimentalApi::class) if (ConsoleTerminalSettings.noAnsi) { SystemOutputPrintStream.println(ANSI_REGEX.replace(str, "")) } else SystemOutputPrintStream.println(str) } @OptIn(ConsoleTerminalExperimentalApi::class) override fun readLine(): String = if (ConsoleTerminalSettings.noConsoleSafeReading) ConsoleTerminalSettings.noConsoleReadingReplacement else throw EndOfFileException("Unsupported Reading line when console front-end closed.") // region private fun <T> ignored(): T = error("Ignored") override fun readLine(mask: Char?): String = readLine() override fun readLine(prompt: String?): String = readLine() override fun readLine(prompt: String?, mask: Char?): String = readLine() override fun readLine(prompt: String?, mask: Char?, buffer: String?): String = readLine() override fun readLine(prompt: String?, rightPrompt: String?, mask: Char?, buffer: String?): String = readLine() override fun readLine( prompt: String?, rightPrompt: String?, maskingCallback: MaskingCallback?, buffer: String? ): String = readLine() override fun printAbove(str: AttributedString?) { str?.let { printAbove(it.toAnsi()) } } override fun defaultKeyMaps(): MutableMap<String, KeyMap<Binding>> = ignored() override fun isReading(): Boolean = false override fun variable(name: String?, value: Any?) = this override fun option(option: LineReader.Option?, value: Boolean) = this override fun callWidget(name: String?) {} override fun getVariables(): MutableMap<String, Any> = ignored() override fun getVariable(name: String?): Any = ignored() override fun setVariable(name: String?, value: Any?) {} override fun isSet(option: LineReader.Option?): Boolean = ignored() override fun setOpt(option: LineReader.Option?) {} override fun unsetOpt(option: LineReader.Option?) {} override fun getTerminal(): Terminal = NoConsole override fun getWidgets(): MutableMap<String, Widget> = ignored() override fun getBuiltinWidgets(): MutableMap<String, Widget> = ignored() override fun getBuffer(): Buffer = ignored() override fun getAppName(): String = "Mirai Console" override fun runMacro(macro: String?) {} override fun readMouseEvent(): MouseEvent = ignored() override fun getHistory(): History = ignored() override fun getParser(): Parser = ignored() override fun getHighlighter(): Highlighter = ignored() override fun getExpander(): Expander = ignored() override fun getKeyMaps(): MutableMap<String, KeyMap<Binding>> = ignored() override fun getKeyMap(): String = ignored() override fun setKeyMap(name: String?): Boolean = ignored() override fun getKeys(): KeyMap<Binding> = ignored() override fun getParsedLine(): ParsedLine = ignored() override fun getSearchTerm(): String = ignored() override fun getRegionActive(): LineReader.RegionType = ignored() override fun getRegionMark(): Int = ignored() override fun addCommandsInBuffer(commands: MutableCollection<String>?) {} override fun editAndAddInBuffer(file: File?) {} override fun getLastBinding(): String = ignored() override fun getTailTip(): String = ignored() override fun setTailTip(tailTip: String?) {} override fun setAutosuggestion(type: LineReader.SuggestionType?) {} override fun getAutosuggestion(): LineReader.SuggestionType = ignored() // endregion } internal object NoConsole : AbstractTerminal( "No Console", "No Console" ) { override fun reader(): NonBlockingReader = NoConsoleNonBlockingReader private val AllIgnoredPrintWriter = object : PrintWriter(AllIgnoredOutputStream) { override fun close() {} override fun flush() {} } // We don't need it. Mirai-Console using LineReader to print messages. override fun writer(): PrintWriter = AllIgnoredPrintWriter override fun input(): InputStream = AllNextLineInputStream override fun output(): OutputStream = AllIgnoredOutputStream private val attributes0 = Attributes() override fun getAttributes(): Attributes { return Attributes(attributes0) } override fun setAttributes(attr: Attributes?) { attr?.let { attributes0.copy(it) } } private val size0 = Size(189, 53) override fun getSize(): Size { return Size().also { it.copy(size0) } } override fun setSize(size: Size?) { size?.let { size0.copy(it) } } } ================================================ FILE: mirai-console/frontend/mirai-console-terminal/test/RunTerminal.kt ================================================ /* * 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.console.terminal import kotlinx.coroutines.runBlocking import net.mamoe.mirai.Bot import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.extensions.BotConfigurationAlterer import net.mamoe.mirai.console.logging.LoggerController import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.SimpleLogger import java.io.File fun main() { configureUserDir() val terminal = object : MiraiConsoleImplementationTerminal() { override val loggerController: LoggerController = object : LoggerController { override fun shouldLog(identity: String?, priority: SimpleLogger.LogPriority): Boolean = true } } val mockPlugin = object : KotlinPlugin(JvmPluginDescription("org.test.test", "1.0.0")) {} MiraiConsoleTerminalLoader.startAsDaemon(terminal) terminal.backendAccess.globalComponentStorage.contribute( BotConfigurationAlterer, mockPlugin, BotConfigurationAlterer { _, configuration -> configuration.networkLoggerSupplier = { MiraiLogger.Factory.create(Bot::class, "Net.${it.id}") } // deploy configuration } ) runCatching { runBlocking { MiraiConsole.job.join() } } } internal fun configureUserDir() { val projectDir = runCatching { File(".").resolve("frontend").resolve("mirai-console-terminal").takeIf { it.isDirectory } ?: File(".").resolve("mirai-console/frontend").resolve("mirai-console-terminal") }.getOrElse { return } if (projectDir.isDirectory) { val run = projectDir.resolve("run") run.mkdir() System.setProperty("user.dir", run.absolutePath) println("[Mirai Console] Set user.dir = ${run.absolutePath}") } } ================================================ FILE: mirai-console/tools/compiler-annotations/README.md ================================================ # mirai-console-compiler-common Mirai Console 编译器注解模块。 提供 `ResolveContext` 和 `RestrictedScope`,帮助 [IntelliJ 插件](../intellij-plugin) 进行语境推断。 ================================================ FILE: mirai-console/tools/compiler-annotations/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("multiplatform") `maven-publish` } version = Versions.console description = "Mirai Console compiler annotations" kotlin { explicitApi() apply(plugin = "explicit-api") configureJvmTargetsHierarchical("net.mamoe.mirai.compiler.annotations") } configureMppPublishing() ================================================ FILE: mirai-console/tools/compiler-annotations/src/androidMain/AndroidManifest.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright 2019-2023 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/dev/LICENSE --> <manifest package="net.mamoe.mirai.console.compiler.common"> </manifest> ================================================ FILE: mirai-console/tools/compiler-annotations/src/commonMain/kotlin/CheckerConstants.kt ================================================ /* * 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.console.compiler.common import kotlin.jvm.JvmField /** * @suppress 这是内部 API. 可能在任意时刻变动 */ public object CheckerConstants { // @Language("RegExp") public const val PLUGIN_ID_PATTERN: String = """([a-zA-Z]\w*(?:\.[a-zA-Z]\w*)*)\.([a-zA-Z]\w*(?:-\w+)*)""" @JvmField public val PLUGIN_ID_REGEX: Regex = Regex(PLUGIN_ID_PATTERN) @JvmField public val PLUGIN_FORBIDDEN_NAMES: Array<String> = arrayOf("main", "console", "plugin", "config", "data") } ================================================ FILE: mirai-console/tools/compiler-annotations/src/commonMain/kotlin/ResolveContext.kt ================================================ /* * 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("unused") package net.mamoe.mirai.console.compiler.common import kotlin.annotation.AnnotationTarget.* /** * 标记一个参数的语境类型, 用于帮助编译器和 IntelliJ 插件进行语境推断. */ @Target(VALUE_PARAMETER, PROPERTY, FIELD, FUNCTION, TYPE, TYPE_PARAMETER) @Retention(AnnotationRetention.BINARY) public annotation class ResolveContext( vararg val kinds: Kind, ) { /** * 元素数量可能在任意时间被改动 */ public enum class Kind { /////////////////////////////////////////////////////////////////////////// // ConstantKind /////////////////////////////////////////////////////////////////////////// /* * WARNING: IF YOU CHANGE NAMES HERE, * YOU SHOULD ALSO CHANGE THEIR COUNTERPARTS AT net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind */ /** * PluginDescription.id */ PLUGIN_ID, // ILLEGAL_PLUGIN_DESCRIPTION /** * PluginDescription.name */ PLUGIN_NAME, // ILLEGAL_PLUGIN_DESCRIPTION /** * PluginDescription.version * SemVersion.Companion.invoke */ SEMANTIC_VERSION, // ILLEGAL_PLUGIN_DESCRIPTION /** * SemVersion.Companion.parseRangeRequirement */ VERSION_REQUIREMENT, // ILLEGAL_VERSION_REQUIREMENT /** * Command.allNames */ COMMAND_NAME, // ILLEGAL_COMMAND_NAME /** * PermissionId.name */ PERMISSION_NAMESPACE, // ILLEGAL_PERMISSION_NAMESPACE /** * PermissionId.name */ PERMISSION_NAME, // ILLEGAL_PERMISSION_NAME /** * PermissionId.parseFromString */ PERMISSION_ID, // ILLEGAL_PERMISSION_ID /** * 标注一个泛型, 要求这个泛型必须拥有一个公开无参 (或所有参数都可选) 构造器. * * PluginData.value */ RESTRICTED_NO_ARG_CONSTRUCTOR, // NOT_CONSTRUCTABLE_TYPE RESTRICTED_CONSOLE_COMMAND_OWNER, RESTRICTED_ABSTRACT_MESSAGE_KEYS; public companion object } public companion object } ================================================ FILE: mirai-console/tools/compiler-annotations/src/commonMain/kotlin/RestrictedScope.kt ================================================ /* * 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.console.compiler.common import kotlin.annotation.AnnotationTarget.FUNCTION /** * 标记一个函数, 在其函数体内限制特定一些函数的使用. * * @suppress 这是实验性 API, 可能会在未来有不兼容变更 */ @Target(FUNCTION) @Retention(AnnotationRetention.BINARY) public annotation class RestrictedScope( vararg val kinds: Kind, ) { public enum class Kind { PERMISSION_REGISTER, // ILLEGAL_PERMISSION_REGISTER_USE COMMAND_REGISTER, // ILLEGAL_COMMAND_REGISTER_USE } } ================================================ FILE: mirai-console/tools/compiler-common/README.md ================================================ # mirai-console-compiler-common Mirai Console 编译器后端通用模块. ## ================================================ FILE: mirai-console/tools/compiler-common/build.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java") `maven-publish` } repositories { maven("https://maven.aliyun.com/repository/central") } version = Versions.console description = "Mirai Console compiler resolve" dependencies { api(`jetbrains-annotations`) // api(`kotlinx-coroutines-jdk8`) api(project(":mirai-console-compiler-annotations")) compileOnly(`kotlin-compiler_forIdea`) testRuntimeOnly(`kotlin-compiler_forIdea`) } configurePublishing("mirai-console-compiler-common") ================================================ FILE: mirai-console/tools/compiler-common/src/diagnostics/MiraiConsoleErrors.kt ================================================ /* * 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.console.compiler.common.diagnostics import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.diagnostics.DiagnosticFactory0.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory1.create import org.jetbrains.kotlin.diagnostics.DiagnosticFactory2.create import org.jetbrains.kotlin.diagnostics.Errors import org.jetbrains.kotlin.diagnostics.Severity.ERROR import org.jetbrains.kotlin.diagnostics.Severity.WARNING import org.jetbrains.kotlin.psi.* /** * 如何增加一个错误: * 1. 在 [MiraiConsoleErrors] 添加 * 2. 在 [MiraiConsoleErrorsRendering] 添加对应的 render */ object MiraiConsoleErrors { // plugin desc @JvmField val ILLEGAL_PLUGIN_DESCRIPTION = create<PsiElement, String>(ERROR) @JvmField val ILLEGAL_VERSION_REQUIREMENT = create<PsiElement, String, String>(ERROR) // plugin data @JvmField val NOT_CONSTRUCTABLE_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR) @JvmField val USING_DERIVED_MUTABLE_MAP_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR) @JvmField val USING_DERIVED_MAP_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR) @JvmField val USING_DERIVED_LIST_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR) @JvmField val USING_DERIVED_MUTABLE_LIST_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR) @JvmField val USING_DERIVED_CONCURRENT_MAP_TYPE = create<KtTypeProjection, KtCallExpression, String>(ERROR) @JvmField val UNSERIALIZABLE_TYPE = create<PsiElement, ClassDescriptor>(ERROR) @JvmField val READ_ONLY_VALUE_CANNOT_BE_VAR = create<PsiElement>(ERROR) // command @JvmField val ILLEGAL_COMMAND_NAME = create<PsiElement, String, String>(ERROR) @JvmField val ILLEGAL_COMMAND_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR) @JvmField val RESTRICTED_CONSOLE_COMMAND_OWNER = create<PsiElement>(WARNING) @JvmField val PROHIBITED_ABSTRACT_MESSAGE_KEYS = create<PsiElement>(WARNING) @JvmField val ILLEGAL_COMMAND_DECLARATION_RECEIVER = create<KtTypeReference>(ERROR) // permission @JvmField val ILLEGAL_PERMISSION_NAME = create<PsiElement, String, String>(ERROR) @JvmField val ILLEGAL_PERMISSION_ID = create<PsiElement, String, String>(ERROR) @JvmField val ILLEGAL_PERMISSION_NAMESPACE = create<PsiElement, String, String>(ERROR) @JvmField val ILLEGAL_PERMISSION_REGISTER_USE = create<PsiElement, KtNamedDeclaration, String>(ERROR) // @JvmField // val INAPPLICABLE_COMMAND_ANNOTATION = create<PsiElement, String>(ERROR) @Suppress("ObjectPropertyName", "unused") @JvmField @Deprecated("", level = DeprecationLevel.ERROR) val _init: Any = run { Errors.Initializer.initializeFactoryNamesAndDefaultErrorMessages( MiraiConsoleErrors::class.java, MiraiConsoleErrorsRendering ) } } ================================================ FILE: mirai-console/tools/compiler-common/src/diagnostics/MiraiConsoleErrorsRendering.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.compiler.common.diagnostics import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_REGISTER_USE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_REGISTER_USE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.PROHIBITED_ABSTRACT_MESSAGE_KEYS import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.UNSERIALIZABLE_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_LIST_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_MAP_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_MUTABLE_LIST_TYPE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.USING_DERIVED_MUTABLE_MAP_TYPE import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticFactoryToRendererMap import org.jetbrains.kotlin.diagnostics.rendering.Renderers /** * @see MiraiConsoleErrors */ object MiraiConsoleErrorsRendering : DefaultErrorMessages.Extension { private val MAP = DiagnosticFactoryToRendererMap("MiraiConsole").apply { put( ILLEGAL_PLUGIN_DESCRIPTION, "{0}", Renderers.TO_STRING, ) put( NOT_CONSTRUCTABLE_TYPE, "类型 ''{1}'' 无法通过反射直接构造, 需要提供默认值.", Renderers.TO_STRING, Renderers.TO_STRING, ) put( UNSERIALIZABLE_TYPE, "类型 ''{0}'' 无法被自动序列化, 需要添加序列化器", Renderers.FQ_NAMES_IN_TYPES, ) put( ILLEGAL_COMMAND_NAME, "指令名 ''{0}'' 无效: {1}", Renderers.TO_STRING, Renderers.TO_STRING, ) put( ILLEGAL_PERMISSION_NAME, "权限名 ''{0}'' 无效: {1}", Renderers.TO_STRING, Renderers.TO_STRING, ) put( ILLEGAL_PERMISSION_ID, "权限 Id ''{0}'' 无效: {1}", Renderers.TO_STRING, Renderers.TO_STRING, ) put( ILLEGAL_PERMISSION_NAMESPACE, "权限命名空间 ''{0}'' 无效: {1}", Renderers.TO_STRING, Renderers.TO_STRING, ) put( ILLEGAL_COMMAND_REGISTER_USE, "''{0}'' 无法使用在 ''{1}'' 环境下.", Renderers.DECLARATION_NAME, Renderers.TO_STRING ) put( ILLEGAL_PERMISSION_REGISTER_USE, "''{0}'' 无法使用在 ''{1}'' 环境下.", Renderers.DECLARATION_NAME, Renderers.TO_STRING ) put( ILLEGAL_VERSION_REQUIREMENT, "{1}", Renderers.TO_STRING, Renderers.TO_STRING ) put( ILLEGAL_COMMAND_DECLARATION_RECEIVER, "指令函数的接收者参数必须为 CommandSender 及其子类或无接收者.", ) put( RESTRICTED_CONSOLE_COMMAND_OWNER, "插件不允许使用 ConsoleCommandOwner 构造指令, 请使用插件主类作为 CommandOwner", ) put( PROHIBITED_ABSTRACT_MESSAGE_KEYS, "使用 MessageContent.Key 等抽象消息类型的 Key 没有意义", ) put( READ_ONLY_VALUE_CANNOT_BE_VAR, "在 ReadOnlyPluginData 中不可定义 'var' by value", ) put( USING_DERIVED_MAP_TYPE, "使用 'Map' 的派生类型 {1}.", Renderers.TO_STRING, Renderers.TO_STRING, ) put( USING_DERIVED_MUTABLE_MAP_TYPE, "使用 'MutableMap' 的派生类型 {1}.", Renderers.TO_STRING, Renderers.TO_STRING, ) put( USING_DERIVED_LIST_TYPE, "使用 'List' 的派生类型 {1}.", Renderers.TO_STRING, Renderers.TO_STRING, ) put( USING_DERIVED_MUTABLE_LIST_TYPE, "使用 'MutableList' 的派生类型 {1}.", Renderers.TO_STRING, Renderers.TO_STRING, ) put( USING_DERIVED_CONCURRENT_MAP_TYPE, "使用 'ConcurrentMap' 的派生类型 {1}.", Renderers.TO_STRING, Renderers.TO_STRING, ) // put( // INAPPLICABLE_COMMAND_ANNOTATION, // "''{0}'' 无法在顶层函数使用.", // Renderers.TO_STRING, // ) } override fun getMap() = MAP } ================================================ FILE: mirai-console/tools/compiler-common/src/resolve/resolveCommon.kt ================================================ /* * 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.console.compiler.common.resolve import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.calls.components.hasDefaultValue fun Annotated.hasAnnotation(fqName: FqName) = this.annotations.hasAnnotation(fqName) fun Annotated.findAnnotation(fqName: FqName) = this.annotations.findAnnotation(fqName) val PsiElement.allChildrenWithSelfSequence: Sequence<PsiElement> get() = sequence { yield(this@allChildrenWithSelfSequence) for (child in children) { yieldAll(child.allChildrenWithSelfSequence) } } val PsiElement.childrenWithSelf: List<PsiElement> get() = listOf(this, *children) inline fun <reified E> PsiElement.findParent(): E? = this.parents.filterIsInstance<E>().firstOrNull() val PsiElement.parents: Sequence<PsiElement> get() { val seed = if (this is PsiFile) null else parent return generateSequence(seed) { if (it is PsiFile) null else it.parent } } fun ClassDescriptor.findNoArgConstructor(): ClassConstructorDescriptor? { return constructors.find { desc -> desc.valueParameters.all { it.hasDefaultValue() } } } fun ClassDescriptor.hasNoArgConstructor(): Boolean = this.findNoArgConstructor() != null ================================================ FILE: mirai-console/tools/compiler-common/src/resolve/resolveTypes.kt ================================================ /* * 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("unused") package net.mamoe.mirai.console.compiler.common.resolve import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.firstValue import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.EnumValue /////////////////////////////////////////////////////////////////////////// // OTHERS /////////////////////////////////////////////////////////////////////////// val SERIALIZABLE_FQ_NAME = FqName("kotlinx.serialization.Serializable") val AUTO_SERVICE = FqName("com.google.auto.service.AutoService") /////////////////////////////////////////////////////////////////////////// // Command /////////////////////////////////////////////////////////////////////////// val COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.CompositeCommand.SubCommand") val SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME = FqName("net.mamoe.mirai.console.command.SimpleCommand.Handler") val COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.CommandSender") val CONSOLE_COMMAND_SENDER_FQ_NAME = FqName("net.mamoe.mirai.console.command.ConsoleCommandSender") val CONSOLE_COMMAND_OWNER_FQ_NAME = FqName("net.mamoe.mirai.console.command.ConsoleCommandOwner") /////////////////////////////////////////////////////////////////////////// // Plugin /////////////////////////////////////////////////////////////////////////// val PLUGIN_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.Plugin") val JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") val SIMPLE_JVM_PLUGIN_DESCRIPTION_FQ_NAME = FqName("net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription") /////////////////////////////////////////////////////////////////////////// // PluginData /////////////////////////////////////////////////////////////////////////// val PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME = FqName("net.mamoe.mirai.console.data.value") val READ_ONLY_PLUGIN_DATA_FQ_NAME = FqName("net.mamoe.mirai.console.data.ReadOnlyPluginData") /////////////////////////////////////////////////////////////////////////// // Resolve /////////////////////////////////////////////////////////////////////////// val RESOLVE_CONTEXT_FQ_NAME = FqName("net.mamoe.mirai.console.compiler.common.ResolveContext") val PROHIBITED_MESSAGE_KEYS = arrayOf( FqName("net.mamoe.mirai.message.data.MessageContent.Key") ) /** * net.mamoe.mirai.console.compiler.common.ResolveContext.Kind */ typealias ResolveContextKind = ResolveContext.Kind fun ResolveContext.Kind.Companion.valueOfOrNull(string: String) = ResolveContext.Kind.values().find { it.name == string } val Annotated.resolveContextKinds: List<ResolveContextKind>? get() { val ann = this.findAnnotation(RESOLVE_CONTEXT_FQ_NAME) ?: return null // https://github.com/mamoe/mirai-console/issues/363 if (ann.allValueArguments.isEmpty()) return null return ann.allValueArguments .firstValue() .castOrNull<ArrayValue>()?.value ?.mapNotNull { value -> val (_, enumEntryName) = value.castOrNull<EnumValue>()?.value ?: return@mapNotNull null ResolveContextKind.valueOfOrNull(enumEntryName.asString()) } ?: return null // undetermined kind } ================================================ FILE: mirai-console/tools/compiler-common/src/utilCommon.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.console.compiler.common import org.jetbrains.kotlin.name.FqName import kotlin.contracts.contract val SERIALIZABLE_FQ_NAME = FqName("kotlinx.serialization.Serializable") fun <K, V> Map<K, V>.firstValue(): V = this.entries.first().value fun <K, V> Map<K, V>.firstKey(): K = this.entries.first().key inline fun <reified T : Any> Any?.castOrNull(): T? { contract { returnsNotNull() implies (this@castOrNull is T) } return this as? T } inline fun <reified T : Any> Any?.cast(): T { contract { returns() implies (this@cast is T) } return this as T } ================================================ FILE: mirai-console/tools/gradle-plugin/.gitignore ================================================ src/main/kotlin/VersionConstants.kt ================================================ FILE: mirai-console/tools/gradle-plugin/README.md ================================================ # Mirai Console Gradle Plugin Mirai Console Gradle 插件。 ## 在构建中使用插件 参考 [ConfiguringProjects](../../docs/ConfiguringProjects.md#b使用-gradle-插件配置项目) 。 ## 功能 - 为 `main` 源集配置 `mirai-core-api`,`mirai-console` 依赖 - 为 `test` 源集配置 `mirai-core`, `mirai-console-terminal` 的依赖 (用于启动测试) - 配置 Kotlin 编译目标为 1.8 - 配置 Kotlin 编译器 `jvm-default` 设置为 `all`, 即为所有接口中的默认实现生成 Java 1.8 起支持的 `default` 方法 - 配置 Java 编译目标为 1.8 - 配置 Java 编译编码为 UTF-8 - 配置插件 JAR 打包构建任务 `buildPlugin`(带依赖, 成品 JAR 可以被 Mirai Console 加载) 支持 Kotlin 多平台项目(Multiplatform Projects)。对于 MPP,每个 JVM 或 Android 目标平台都会被如上配置,对应打包任务带有编译目标的名称,如 `buildPluginJvm`。 ### 任务 `buildPlugin` 用于打包插件和依赖为可以放入 Mirai Console `plugins` 目录加载的插件 JAR。 ### 任务 `buildPluginLegacy` 用于打包插件和依赖为可以放入 Mirai Console `plugins` 目录加载的插件 JAR。 #### 执行 `buildPlugin` ```shell script ./gradlew buildPlugin ``` 打包结果存放在 `build/mirai/` 目录下。 ## 配置 若要修改 Mirai Console Gradle 插件的默认配置,在 `build.gradle.kts` 或 `build.gradle` 内,使用 `mirai`: ```kotlin mirai { // this: MiraiConsoleExtension // 修改配置,如: jvmTarget = JavaVersion.VERSION_1_8 } ``` 有关所有可修改的配置,参见 [MiraiConsoleExtension](src/main/kotlin/MiraiConsoleExtension.kt) 。 ### 修改 Java 编译目标 Mirai Console Gradle 会覆盖 Java 编译目标为 `1.8`. 若要修改该值, 请通过: ```kotlin mirai { // this: MiraiConsoleExtension jvmTarget = JavaVersion.VERSION_16 } ``` ### 打包依赖 使用任务 `buildPlugin` 即可打包插件 JAR。打包结果输出在 `build/mirai/`。 自 2.11,Mirai Console Gradle 在打包 JAR(`buildPlugin`) 时默认不会携带外部依赖, 而是会保存一份依赖列表,在加载插件时下载。如果您使用了不可在默认仓库搜索到的依赖, 请以如下配置将依赖打包进入 JAR。 ```kotlin dependencies { implementation("org.example:test:1.0.0") // 正常地添加一个普通依赖,用于编译 "shadowLink"("org.example:test") // 告知 mirai-console 在打包插件时包含此依赖;无需包含版本号 } ``` 特别的, 如果使用了子项目,Mirai Console Gradle 默认也会打包进 JAR,通常这也是期望的行为。 如果您希望 Mirai Console Gradle 像处理一般依赖一样处理 Gradle 子项目(不打包),请使用以下配置: ```kotlin dependencies { implementation(project(":nested")) // 正常地添加一个子项目依赖,用于编译 "asNormalDep"(project(":nested")) // 告知 mirai-console 在打包插件时将此子项目依赖作为普通依赖处理,即不打包 } ``` > 要获取有关插件依赖的更多信息,可参考[插件文档](../../docs/plugin/JVMPlugin.md)。 #### 如何确定是否需要打包依赖 如果插件依赖的都是可在 Maven Central 找到的构建,则无需打包。 [//]: # (### `publishPlugin`) [//]: # () [//]: # (配置好 Bintray 参数,使用 `./gradlew publishPlugin` 可自动发布并上传插件到 Bintray。) [//]: # () [//]: # (如果仓库是公开的,上传的插件在未来可以被 [mirai-console-loader]&#40;https://github.com/iTXTech/mirai-console-loader&#41;) [//]: # (自动识别并展示在社区插件列表中。) [//]: # () [//]: # (```kotlin) [//]: # (mirai {) [//]: # ( publishing {) [//]: # ( repo = "mirai") [//]: # ( packageName = "chat-command") [//]: # ( }) [//]: # (}) [//]: # (```) [//]: # () [//]: # (*2021/3/21 更新:* 由于 Bintray JCenter 即将关闭,随着论坛的发展,mirai) [//]: # (正在策划插件中心服务。待插件中心完成后将会提供更好的插件分发平台。) #### 排除依赖 (过时) 在 2.11 以前,如果要在打包 JAR(`buildPluginLegacy`)时排除一些依赖,请使用如下配置: ```kotlin mirai { excludeDependency("com.google.code.gson", "gson") } ``` **插件一般不需要手动排除依赖**。Mirai Console 已经包含的依赖都会自动在打包过程中被排除。 ================================================ FILE: mirai-console/tools/gradle-plugin/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java-gradle-plugin") id("com.gradle.plugin-publish") groovy id("java") //signing `maven-publish` } val integTest = sourceSets.create("integTest") /** * Because we use [compileOnly] for `kotlin-gradle-plugin`, it would be missing * in `plugin-under-test-metadata.properties`. Here we inject the jar into TestKit plugin * classpath via [PluginUnderTestMetadata] to avoid [NoClassDefFoundError]. */ val kotlinVersionForIntegrationTest: Configuration by configurations.creating dependencies { compileOnly(gradleApi()) compileOnly(gradleKotlinDsl()) compileOnly(kotlin("gradle-plugin-api")) compileOnly(kotlin("gradle-plugin")) compileOnly(kotlin("stdlib")) implementation("com.google.code.gson:gson:2.8.6") api("com.github.jengelman.gradle.plugins:shadow:6.0.0") api(`jetbrains-annotations`) // override vulnerable Log4J version // https://blog.gradle.org/log4j-vulnerability implementation(`log4j-api`) implementation(`log4j-core`) testApi(kotlin("test-junit5")) testApi(`junit-jupiter-api`) testApi(`junit-jupiter-params`) "integTestApi"(kotlin("test-junit5")) "integTestApi"(`junit-jupiter-api`) "integTestApi"(`junit-jupiter-params`) "integTestImplementation"(`junit-jupiter-engine`) "integTestImplementation"(gradleTestKit()) kotlinVersionForIntegrationTest(kotlin("gradle-plugin", "1.5.21")) } tasks.named<PluginUnderTestMetadata>("pluginUnderTestMetadata") { pluginClasspath.from(kotlinVersionForIntegrationTest) } version = Versions.console description = "Gradle plugin for Mirai Console" kotlin { explicitApi() } @Suppress("UnstableApiUsage") gradlePlugin { testSourceSets(integTest) website.set("https://github.com/mamoe/mirai") vcsUrl.set("https://github.com/mamoe/mirai") plugins { create("miraiConsole") { id = "net.mamoe.mirai-console" displayName = "Mirai Console" description = project.description implementationClass = "net.mamoe.mirai.console.gradle.MiraiConsoleGradlePlugin" tags.set(listOf("framework", "kotlin", "mirai")) } } } val integrationTestTask = tasks.register<Test>("integTest") { description = "Runs the integration tests." group = "verification" testClassesDirs = integTest.output.classesDirs classpath = integTest.runtimeClasspath mustRunAfter(tasks.test) } tasks.check { dependsOn(integrationTestTask) } tasks { val generateBuildConstants by registering { group = "mirai" doLast { projectDir.resolve("src/main/kotlin/VersionConstants.kt").apply { createNewFile() } .writeText( projectDir.resolve("src/main/kotlin/VersionConstants.kt.template").readText() .replace("$\$CONSOLE_VERSION$$", Versions.console) .replace("$\$CORE_VERSION$$", Versions.core) ) } } afterEvaluate { getByName("compileKotlin").dependsOn(generateBuildConstants) } } if (System.getenv("MIRAI_IS_SNAPSHOTS_PUBLISHING")?.toBoolean() == true) { configurePublishing("mirai-console-gradle", skipPublicationSetup = true) } ================================================ FILE: mirai-console/tools/gradle-plugin/gradle.properties ================================================ # # 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 # flatten.sourceset=false ================================================ FILE: mirai-console/tools/gradle-plugin/src/integTest/kotlin/AbstractTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.gradle import org.gradle.testkit.runner.GradleRunner import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.api.io.TempDir import java.io.File abstract class AbstractTest { @JvmField @TempDir var tempDirField: File? = null val tempDir: File get() = tempDirField!! lateinit var buildFile: File lateinit var settingsFile: File lateinit var propertiesFile: File @OptIn(ExperimentalStdlibApi::class) fun runGradle(vararg arguments: String) { System.gc() GradleRunner.create() .withProjectDir(tempDir) .withPluginClasspath() .withGradleVersion("7.2") .forwardOutput() .withEnvironment(System.getenv()) .withArguments(buildList { addAll(arguments) add("-P") add("kotlin.compiler.execution.strategy=in-process") add("-D") add("org.gradle.jvmargs=-Xmx512m") add("-D") add("file.encoding=UTF-8") }) .build() } @BeforeEach fun setup() { println("Temp path is " + tempDir.absolutePath) settingsFile = File(tempDir, "settings.gradle") settingsFile.delete() settingsFile.writeText( """ pluginManagement { repositories { gradlePluginPortal() mavenCentral() } } """ ) File(tempDir, "gradle.properties").apply { delete() writeText( """ org.gradle.daemon=false org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 """.trimIndent() ) } buildFile = File(tempDir, "build.gradle") buildFile.delete() val ktVersion = "1.6.0" val replacedMiraiVersion = "2.11.0-RC2" buildFile.writeText( """ plugins { id("org.jetbrains.kotlin.jvm") version "$ktVersion" id("net.mamoe.mirai-console") } repositories { mavenCentral() } // Mirai dev versions not available in gradle test. // So using a released version to run tests ({ def modules = [ 'mirai-core-api', 'mirai-core-api-jvm', 'mirai-core', 'mirai-core-jvm', 'mirai-core-utils', 'mirai-core-utils-jvm', 'mirai-console', 'mirai-console-terminal', 'mirai-console-compiler-annotations', 'mirai-console-compiler-common', ]; allprojects { configurations.all { resolutionStrategy.eachDependency { DependencyResolveDetails details -> if (details.requested.group == 'net.mamoe') { if (modules.contains(details.requested.name)) { details.useVersion '$replacedMiraiVersion' } } if (details.requested.group == 'org.jetbrains.kotlin') { details.useVersion '$ktVersion' } } } } })(); """ ) // buildFile = new File(tempDir, "build.gradle.kts") // buildFile.delete() // buildFile << """ // plugins { // kotlin("jvm") version "1.4.30" // id("net.mamoe.mirai-console") // } // repositories { // mavenCentral() // } // """ } @JvmField @RegisterExtension internal val after: AfterEachCallback = AfterEachCallback { context -> if (context.executionException.isPresent) { val inst = context.requiredTestInstance as AbstractTest println("====================== build.gradle ===========================") println(inst.tempDir.resolve("build.gradle").readText()) println("==================== settings.gradle ==========================") println(inst.tempDir.resolve("settings.gradle").readText()) } } } ================================================ FILE: mirai-console/tools/gradle-plugin/src/integTest/kotlin/KotlinTransitiveDependenciesIntegrationTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.gradle import org.gradle.testkit.runner.GradleRunner import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.io.TempDir import java.io.ByteArrayOutputStream import java.io.File import java.io.PrintWriter import kotlin.test.Test class KotlinTransitiveDependenciesIntegrationTest { @Test fun `user can override Kotlin plugin version`(@TempDir dir: File) { // We're packaging the plugin with kotlin transitive dependencies > 1.4.30 // This is testing that users are free to use different versions val userSpecifiedKotlinPluginVersion = "1.5.21" dir.resolve("settings.gradle").writeText("") dir.resolve("build.gradle").writeText( """ plugins { id 'net.mamoe.mirai-console' id 'org.jetbrains.kotlin.jvm' version '$userSpecifiedKotlinPluginVersion' } """.trimIndent() ) val stdout = ByteArrayOutputStream() val stderr = ByteArrayOutputStream() GradleRunner.create() .withProjectDir(dir) .withGradleVersion("7.2") .withPluginClasspath() .forwardStdOutput(PrintWriter(stdout)) .forwardStdError(PrintWriter(stderr)) .withArguments(listOf("dependencies")) .build() System.out.println(stdout) System.err.println(stderr) Assertions.assertTrue( stdout.toString() .contains("org.jetbrains.kotlin:kotlin-compiler-embeddable:${userSpecifiedKotlinPluginVersion}") ) } } ================================================ FILE: mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestBuildPlugin.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("DuplicatedCode", "FunctionName") package net.mamoe.mirai.console.gradle import org.junit.jupiter.api.DisplayName import java.io.File import java.util.zip.ZipFile import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertNotNull import kotlin.test.assertNull import kotlin.test.assertTrue class TestBuildPlugin : AbstractTest() { private fun File.wt(text: String) { parentFile?.mkdirs() writeText(text) } @Test @DisplayName("project as normal dependency") fun buildWithMultiProjectsAsNormalDependency() { settingsFile.appendText( """ include("nested1") include("nested0") """.trimIndent() ) tempDir.resolve("nested1/build.gradle").wt( """ plugins { id("org.jetbrains.kotlin.jvm") } repositories { mavenCentral() } """.trimIndent() ) tempDir.resolve("nested0/build.gradle").wt( """ plugins { id("org.jetbrains.kotlin.jvm") id("net.mamoe.mirai-console") } dependencies { api project(":nested1") api "com.zaxxer:SparseBitSet:1.2" } repositories { mavenCentral() } """.trimIndent() ) tempDir.resolve("build.gradle").appendText( """ dependencies { implementation project(":nested0") asNormalDep project(":nested0") } """.trimIndent() ) tempDir.resolve("nested0/src/main/kotlin/test.kt").wt( """ package nested public class TestClass """.trimIndent() ) tempDir.resolve("nested1/src/main/kotlin/test.kt").wt( """ package nested1 public class TestClass """.trimIndent() ) tempDir.resolve("src/main/kotlin/test.kt").wt( """ package thetop public class TestClass """.trimIndent() ) runGradle(":buildPlugin", "--stacktrace", "--info") ZipFile(findJar()).use { zipFile -> val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } val dpShared = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-shared.txt") ).use { it.readBytes().decodeToString() } assertFalse { dpShared.contains("com.zaxxer:SparseBitSet:1.2") } assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") } assertTrue { dpPrivate.contains(":nested0") } assertTrue { dpPrivate.contains(":nested1") } assertNotNull(zipFile.getEntry("thetop/TestClass.class")) assertNull(zipFile.getEntry("nested/TestClass.class")) assertNull(zipFile.getEntry("nested1/TestClass.class")) } } @Test @DisplayName("project as normal dependency 2") fun buildWithMultiProjectsAsNormalDependency2() { settingsFile.appendText( """ include("nested1") include("nested0") """.trimIndent() ) tempDir.resolve("nested1/build.gradle").wt( """ plugins { id("org.jetbrains.kotlin.jvm") } repositories { mavenCentral() } """.trimIndent() ) tempDir.resolve("nested0/build.gradle").wt( """ plugins { id("org.jetbrains.kotlin.jvm") id("net.mamoe.mirai-console") } dependencies { api project(":nested1") api "com.zaxxer:SparseBitSet:1.2" } repositories { mavenCentral() } """.trimIndent() ) tempDir.resolve("build.gradle").appendText( """ dependencies { implementation project(":nested0") asNormalDep project(":nested1") } """.trimIndent() ) tempDir.resolve("nested0/src/main/kotlin/test.kt").wt( """ package nested public class TestClass """.trimIndent() ) tempDir.resolve("nested1/src/main/kotlin/test.kt").wt( """ package nested1 public class TestClass """.trimIndent() ) tempDir.resolve("src/main/kotlin/test.kt").wt( """ package thetop public class TestClass """.trimIndent() ) runGradle(":buildPlugin", "--stacktrace", "--info") ZipFile(findJar()).use { zipFile -> val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } val dpShared = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-shared.txt") ).use { it.readBytes().decodeToString() } assertFalse { dpShared.contains("com.zaxxer:SparseBitSet:1.2") } assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") } assertFalse { dpPrivate.contains(":nested0") } assertTrue { dpPrivate.contains(":nested1") } assertNotNull(zipFile.getEntry("thetop/TestClass.class")) assertNotNull(zipFile.getEntry("nested/TestClass.class")) assertNull(zipFile.getEntry("nested1/TestClass.class")) } } @Test @DisplayName("no api extends if using implementation") fun buildWithMultiProjectsWithoutApi() { settingsFile.appendText( """ include("nested") """.trimIndent() ) tempDir.resolve("nested/build.gradle").wt( """ plugins { id("org.jetbrains.kotlin.jvm") id("net.mamoe.mirai-console") } dependencies { api "com.zaxxer:SparseBitSet:1.2" } repositories { mavenCentral() } """.trimIndent() ) tempDir.resolve("build.gradle").appendText( """ dependencies { implementation project(":nested") } """.trimIndent() ) tempDir.resolve("nested/src/main/kotlin/test.kt").wt( """ package nested public class TestClass """.trimIndent() ) tempDir.resolve("src/main/kotlin/test.kt").wt( """ package thetop public class TestClass """.trimIndent() ) runGradle(":buildPlugin", "--stacktrace", "--info") ZipFile(findJar()).use { zipFile -> val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } val dpShared = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-shared.txt") ).use { it.readBytes().decodeToString() } assertFalse { dpShared.contains("com.zaxxer:SparseBitSet:1.2") } assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") } assertNotNull(zipFile.getEntry("thetop/TestClass.class")) assertNotNull(zipFile.getEntry("nested/TestClass.class")) } } @Test @DisplayName("build with multi projects") fun buildWithMultiProjects() { settingsFile.appendText( """ include("nested") """.trimIndent() ) tempDir.resolve("nested/build.gradle").wt( """ plugins { id("org.jetbrains.kotlin.jvm") id("net.mamoe.mirai-console") } dependencies { api "com.zaxxer:SparseBitSet:1.2" implementation "com.google.code.gson:gson:2.8.9" api "org.slf4j:slf4j-simple:1.7.32" } repositories { mavenCentral() } """.trimIndent() ) tempDir.resolve("build.gradle").appendText( """ dependencies { api project(":nested") shadowLink "org.slf4j:slf4j-simple" } """.trimIndent() ) tempDir.resolve("nested/src/main/kotlin/test.kt").wt( """ package nested public class TestClass """.trimIndent() ) tempDir.resolve("src/main/kotlin/test.kt").wt( """ package thetop public class TestClass """.trimIndent() ) runGradle(":buildPlugin", "dependencies", "--stacktrace", "--info") checkOutput() ZipFile(findJar()).use { zipFile -> assertNotNull(zipFile.getEntry("thetop/TestClass.class")) assertNotNull(zipFile.getEntry("nested/TestClass.class")) } } @Test fun `can build plugin`() { tempDir.resolve("build.gradle").appendText( """ dependencies { api "com.zaxxer:SparseBitSet:1.2" implementation "com.google.code.gson:gson:2.8.9" api "org.slf4j:slf4j-simple:1.7.32" shadowLink "org.slf4j:slf4j-simple" } """.trimIndent() ) runGradle("buildPlugin", "dependencies", "--stacktrace", "--info") checkOutput() } @Test fun `can build with bom dependencies`() { tempDir.resolve("build.gradle").appendText( """ dependencies { implementation platform("com.fasterxml.jackson:jackson-bom:2.12.4") } """.trimIndent() ) runGradle("buildPlugin", "dependencies", "--stacktrace", "--info") ZipFile(findJar()).use { zipFile -> val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } assertFalse { dpPrivate.contains("com.fasterxml.jackson:jackson-bom") } } } @Test fun `can build with bom dependencies 2`() { tempDir.resolve("build.gradle").appendText( """ dependencies { implementation "com.fasterxml.jackson.core:jackson-annotations:2.12.4" } """.trimIndent() ) runGradle("buildPlugin", "dependencies", "--stacktrace", "--info") ZipFile(findJar()).use { zipFile -> val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } assertFalse { dpPrivate.contains("com.fasterxml.jackson:jackson-bom") } assertTrue { dpPrivate.contains("com.fasterxml.jackson.core:jackson-annotations") } } } @Test @DisplayName("Ktor 2.x available") fun `ktor 2_x`() { tempDir.resolve("build.gradle").appendText( """ dependencies { implementation "io.ktor:ktor-client-core:2.0.0" } """.trimIndent() ) runGradle("buildPlugin", "dependencies", "--stacktrace", "--info") ZipFile(findJar()).use { zipFile -> val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } assertTrue { dpPrivate.contains("io.ktor:ktor-client-core:2.0.0") } assertTrue { dpPrivate.contains("io.ktor:ktor-client-core-jvm:2.0.0") } } } @Test @DisplayName("can shadow special libraries that another used") fun issue2070() { tempDir.resolve("build.gradle").appendText( """ dependencies { implementation("cn.hutool:hutool-extra:5.8.2") shadowLink("cn.hutool:hutool-core") } """.trimIndent() ) runGradle("buildPlugin", "dependencies", "--stacktrace", "--info") ZipFile(findJar()).use { zipFile -> assertNotNull(zipFile.getEntry("cn/hutool/core/annotation/Alias.class")) val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } assertFalse { dpPrivate.contains("hutool-core") } } } private fun findJar(): File = tempDir.resolve("build/mirai").listFiles()!!.first { it.name.endsWith(".mirai2.jar") } private fun checkOutput() { val jar = findJar() ZipFile(jar).use { zipFile -> assertNotNull(zipFile.getEntry("org/slf4j/impl/SimpleLogger.class")) val dpPrivate = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-private.txt") ).use { it.readBytes().decodeToString() } val dpShared = zipFile.getInputStream( zipFile.getEntry("META-INF/mirai-console-plugin/dependencies-shared.txt") ).use { it.readBytes().decodeToString() } assertTrue { dpShared.contains("com.zaxxer:SparseBitSet:1.2") } assertFalse { dpShared.contains("com.google.code.gson:gson") } assertFalse { dpShared.contains("org.slf4j:slf4j-simple") } assertTrue { dpPrivate.contains("com.zaxxer:SparseBitSet:1.2") } assertTrue { dpPrivate.contains("com.google.code.gson:gson:2.8.9") } assertFalse { dpPrivate.contains("org.slf4j:slf4j-simple") } assertFalse { dpPrivate.contains("io.ktor") } } } } ================================================ FILE: mirai-console/tools/gradle-plugin/src/integTest/kotlin/TestPluginApply.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.gradle import kotlin.test.Test class TestPluginApply : AbstractTest() { @Test fun `can apply plugin`() { runGradle("clean", "--stacktrace") } } ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/BuildMiraiPluginTask.kt ================================================ /* * 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.console.gradle import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Internal import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import java.io.File import javax.inject.Inject @CacheableTask public open class BuildMiraiPluginTask @Inject constructor( @JvmField internal val target: KotlinTarget ) : ShadowJar() { /** * ShadowJar 打包结果 */ @get:Internal public val output: File get() = outputs.files.singleFile public companion object { /** * Kotlin 单平台或 Java 时的默认 task 名. */ public const val DEFAULT_NAME: String = "buildPlugin" } } ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/BuildMiraiPluginV2.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.gradle import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.artifacts.* import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.attributes.AttributeContainer import org.gradle.api.capabilities.Capability import org.gradle.api.file.DuplicatesStrategy import org.gradle.api.internal.artifacts.ivyservice.DefaultLenientConfiguration import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ArtifactVisitor import org.gradle.api.internal.artifacts.ivyservice.resolveengine.artifact.ResolvableArtifact import org.gradle.api.internal.file.FileCollectionInternal import org.gradle.api.internal.file.FileCollectionStructureVisitor import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskContainer import org.gradle.internal.DisplayName import org.gradle.internal.component.external.model.ModuleComponentArtifactIdentifier import org.gradle.jvm.tasks.Jar import org.gradle.kotlin.dsl.create import org.gradle.kotlin.dsl.get import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import java.io.File import javax.inject.Inject @Suppress("RedundantLambdaArrow", "RemoveExplicitTypeArguments") public open class BuildMiraiPluginV2 : Jar() { public companion object { public const val FILE_SUFFIX: String = "mirai2.jar" } // @get:Internal private lateinit var metadataTask: GenMetadataTask internal open class GenMetadataTask @Inject internal constructor( @JvmField internal val orgTask: BuildMiraiPluginV2, ) : DefaultTask() { companion object { val miraiDependencies = mutableSetOf( "net.mamoe:mirai-core-api", "net.mamoe:mirai-core-api-jvm", "net.mamoe:mirai-core-api-android", "net.mamoe:mirai-core", "net.mamoe:mirai-core-jvm", "net.mamoe:mirai-core-android", "net.mamoe:mirai-core-utils", "net.mamoe:mirai-core-utils-jvm", "net.mamoe:mirai-core-utils-android", "net.mamoe:mirai-console", "net.mamoe:mirai-console-terminal", ) } @Suppress("LocalVariableName") @TaskAction internal fun run() { val runtime = mutableSetOf<String>() val api = mutableSetOf<String>() val linkedDependencies = mutableSetOf<String>() val linkToApi = mutableSetOf<String>() val shadowedFiles = mutableSetOf<File>() val shadowedDependencies = mutableSetOf<String>() val subprojects = mutableSetOf<String>() val subprojects_fullpath = mutableSetOf<String>() // val subprojects_unlinked_fullpath = mutableSetOf<String>() val subprojects_linked_fullpath = mutableSetOf<String>() project.configurations.findByName(MiraiConsoleGradlePlugin.MIRAI_SHADOW_CONF_NAME)?.allDependencies?.forEach { dep -> if (dep is ExternalModuleDependency) { val artId = "${dep.group}:${dep.name}" shadowedDependencies.add(artId) } } project.configurations.findByName(MiraiConsoleGradlePlugin.MIRAI_AS_NORMAL_DEP_CONF_NAME)?.allDependencies?.forEach { dep1 -> fun resolve0(dep: Dependency) { if (dep is ProjectDependency) { linkedDependencies.add("${dep.group}:${dep.name}") subprojects_linked_fullpath.add(dep.dependencyProject.path) dep.dependencyProject.configurations.findByName("apiElements")?.allDependencies?.forEach { resolve0(it) } dep.dependencyProject.configurations.findByName("implementation")?.allDependencies?.forEach { resolve0(it) } } } resolve0(dep1) } fun deepForeachDependencies(conf: Configuration?, action: (Dependency) -> Unit) { (conf ?: return).allDependencies.forEach { dep -> action(dep) if (dep is ProjectDependency) { subprojects.add("${dep.group}:${dep.name}") deepForeachDependencies(dep.dependencyProject.configurations.findByName(conf.name), action) } } } fun resolveProject(project: Project, doResolveApi: Boolean) { deepForeachDependencies(project.configurations.findByName("apiElements")) { dep -> if (dep is ExternalModuleDependency) { val artId = "${dep.group}:${dep.name}" linkedDependencies.add(artId) if (doResolveApi) { linkToApi.add(artId) } } if (dep is ProjectDependency) { subprojects_fullpath.add(dep.dependencyProject.path) subprojects.add("${dep.group}:${dep.name}") resolveProject(dep.dependencyProject, doResolveApi) } } project.configurations.findByName("implementation")?.allDependencies?.forEach { dep -> if (dep is ExternalModuleDependency) { linkedDependencies.add("${dep.group}:${dep.name}") } if (dep is ProjectDependency) { subprojects_fullpath.add(dep.dependencyProject.path) subprojects.add("${dep.group}:${dep.name}") resolveProject(dep.dependencyProject, false) } } } resolveProject(project, true) linkedDependencies.removeAll(shadowedDependencies) linkToApi.removeAll(shadowedDependencies) linkedDependencies.addAll(miraiDependencies) fun ResolvedDependency.depId(): String = "$moduleGroup:$moduleName" val runtimeClasspath = project.configurations["runtimeClasspath"].resolvedConfiguration fun markAsResolved(resolvedDependency: ResolvedDependency) { val depId = resolvedDependency.depId() if (depId !in shadowedDependencies) { linkedDependencies.add(depId) } resolvedDependency.children.forEach { markAsResolved(it) } } fun linkDependencyTo(resolvedDependency: ResolvedDependency, dependencies: MutableCollection<String>) { // bom files if (resolvedDependency.allModuleArtifacts.any { it.extension == "jar" }) kotlin.run link@{ if (resolvedDependency.depId() in shadowedDependencies) return@link dependencies.add(resolvedDependency.module.toString()) } resolvedDependency.children.forEach { linkDependencyTo(it, dependencies) } } fun resolveDependency(resolvedDependency: ResolvedDependency) { val depId = resolvedDependency.depId() logger.info { "resolving : $depId" } if (depId in linkedDependencies) { markAsResolved(resolvedDependency) linkDependencyTo(resolvedDependency, runtime) if (depId in linkToApi) { linkDependencyTo(resolvedDependency, api) } return } if (depId in subprojects) { resolvedDependency.children.forEach { resolveDependency(it) } return } } runtimeClasspath.firstLevelModuleDependencies.forEach { resolveDependency(it) } /*subprojects_fullpath.forEach { usedProject -> val subProj = project.project(usedProject) if ("${subProj.group}:${subProj.name}" !in linkedDependencies) { subprojects_unlinked_fullpath.add(usedProject) } }*/ logger.info { "linkedDependencies: $linkedDependencies" } logger.info { "linkToAPi : $linkToApi" } logger.info { "api : $api" } logger.info { "runtime : $runtime" } logger.info { "subprojects : $subprojects" } logger.info { "subprojects_linked: $subprojects_linked_fullpath" } // logger.info { "subprojects_unlink: $subprojects_unlinked_fullpath" } val lenientConfiguration = runtimeClasspath.lenientConfiguration if (lenientConfiguration is DefaultLenientConfiguration) { val resolvedArtifacts = mutableSetOf<ResolvedArtifact>() lenientConfiguration.select().visitArtifacts(object : ArtifactVisitor { override fun prepareForVisit(source: FileCollectionInternal.Source): FileCollectionStructureVisitor.VisitType { return FileCollectionStructureVisitor.VisitType.Visit } override fun visitArtifact( variantName: DisplayName, variantAttributes: AttributeContainer, capabilities: MutableList<out Capability>, artifact: ResolvableArtifact ) { resolvedArtifacts.add(artifact.toPublicView()) } override fun requireArtifactFiles(): Boolean = false override fun visitFailure(failure: Throwable) {} }, false) resolvedArtifacts } else { runtimeClasspath.resolvedArtifacts }.forEach { artifact -> val artId = artifact.id if (artId is ModuleComponentArtifactIdentifier) { val cid = artId.componentIdentifier if ("${cid.group}:${cid.module}" in linkedDependencies) { return@forEach } } val cid = artId.componentIdentifier if (cid is ProjectComponentIdentifier) { if (cid.projectPath in subprojects_linked_fullpath) { return@forEach } } logger.info { " `- $artId - ${artId.javaClass}" } shadowedFiles.add(artifact.file) } shadowedFiles.forEach { file -> if (file.isDirectory) { orgTask.from(file) } else if (file.extension == "jar") { orgTask.from(project.zipTree(file)) } else { orgTask.from(file) } } temporaryDir.also { it.mkdirs() }.let { tmpDir -> tmpDir.resolve("api.txt").writeText(api.sorted().joinToString("\n")) tmpDir.resolve("runtime.txt").writeText(runtime.sorted().joinToString("\n")) orgTask.from(tmpDir.resolve("api.txt")) { copy -> copy.into("META-INF/mirai-console-plugin") copy.rename { "dependencies-shared.txt" } } orgTask.from(tmpDir.resolve("runtime.txt")) { copy -> copy.into("META-INF/mirai-console-plugin") copy.rename { "dependencies-private.txt" } } } } } internal fun registerMetadataTask(tasks: TaskContainer, metadataTaskName: String) { metadataTask = tasks.create<GenMetadataTask>(metadataTaskName, this) } internal fun init(target: KotlinTarget) { dependsOn(metadataTask) archiveExtension.set(FILE_SUFFIX) duplicatesStrategy = DuplicatesStrategy.WARN val compilations = target.compilations.filter { it.name == KotlinCompilation.MAIN_COMPILATION_NAME } @Suppress("DEPRECATION") // New API requires Kotlin 1.8.0, but we must support lower versions compilations.forEach { dependsOn(it.compileKotlinTask) from(it.output.allOutputs) metadataTask.dependsOn(it.compileKotlinTask) } exclude { elm -> elm.path.startsWith("META-INF/") && elm.name.endsWith(".sf", ignoreCase = true) } } } ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/IGNORED_DEPENDENCIES_IN_SHADOW.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiConsoleGradlePluginKt") package net.mamoe.mirai.console.gradle internal val IGNORED_DEPENDENCIES_IN_SHADOW = arrayOf( "org.jetbrains.kotlin:kotlin-stdlib", "org.jetbrains.kotlin:kotlin-stdlib-common", "org.jetbrains.kotlin:kotlin-stdlib-metadata", "org.jetbrains.kotlin:kotlin-stdlib-jvm", "org.jetbrains.kotlin:kotlin-stdlib-jdk7", "org.jetbrains.kotlin:kotlin-stdlib-jdk8", "org.jetbrains.kotlin:kotlin-reflect", "org.jetbrains.kotlin:kotlin-reflect-common", "org.jetbrains.kotlin:kotlin-reflect-metadata", "org.jetbrains.kotlin:kotlin-reflect-jvm", "org.jetbrains.kotlinx:kotlinx-serialization-core", "org.jetbrains.kotlinx:kotlinx-serialization-core-common", "org.jetbrains.kotlinx:kotlinx-serialization-core-metadata", "org.jetbrains.kotlinx:kotlinx-serialization-core-jvm", "org.jetbrains.kotlinx:kotlinx-serialization-runtime", "org.jetbrains.kotlinx:kotlinx-serialization-runtime-common", "org.jetbrains.kotlinx:kotlinx-serialization-runtime-metadata", "org.jetbrains.kotlinx:kotlinx-serialization-runtime-jvm", "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-common", "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-metadata", "org.jetbrains.kotlinx:kotlinx-serialization-protobuf-jvm", "org.jetbrains.kotlinx:kotlinx-coroutines-core", "org.jetbrains.kotlinx:kotlinx-coroutines-core-common", "org.jetbrains.kotlinx:kotlinx-coroutines-core-metadata", "org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm", "org.jetbrains.kotlinx:kotlinx-io", "org.jetbrains.kotlinx:kotlinx-io-common", "org.jetbrains.kotlinx:kotlinx-io-metadata", "org.jetbrains.kotlinx:kotlinx-io-jvm", "org.jetbrains.kotlinx:kotlinx-coroutines-io", "org.jetbrains.kotlinx:kotlinx-coroutines-io-common", "org.jetbrains.kotlinx:kotlinx-coroutines-io-metadata", "org.jetbrains.kotlinx:kotlinx-coroutines-io-jvm", "org.jetbrains.kotlinx:kotlinx-coroutines-core-jdk7", "org.jetbrains.kotlinx:kotlinx-coroutines-core-jdk8", "org.jetbrains.kotlinx:kotlinx-coroutines-jdk7", "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8", "org.jetbrains.kotlinx:atomicFu", "org.jetbrains.kotlinx:atomicFu-common", "org.jetbrains.kotlinx:atomicFu-metadata", "org.jetbrains.kotlinx:atomicFu-jvm", "org.jetbrains:annotations:19.0.0", "io.ktor:ktor-client", "io.ktor:ktor-client-common", "io.ktor:ktor-client-metadata", "io.ktor:ktor-client-jvm", "io.ktor:ktor-client-cio", "io.ktor:ktor-client-cio-common", "io.ktor:ktor-client-cio-metadata", "io.ktor:ktor-client-cio-jvm", "io.ktor:ktor-client-core", "io.ktor:ktor-client-core-common", "io.ktor:ktor-client-core-metadata", "io.ktor:ktor-client-core-jvm", "io.ktor:ktor-client-network", "io.ktor:ktor-client-network-common", "io.ktor:ktor-client-network-metadata", "io.ktor:ktor-client-network-jvm", "io.ktor:ktor-client-util", "io.ktor:ktor-client-util-common", "io.ktor:ktor-client-util-metadata", "io.ktor:ktor-client-util-jvm", "io.ktor:ktor-client-http", "io.ktor:ktor-client-http-common", "io.ktor:ktor-client-http-metadata", "io.ktor:ktor-client-http-jvm", "org.bouncyCastle:bcProv-jdk15on", "net.mamoe:mirai-core", "net.mamoe:mirai-core-metadata", "net.mamoe:mirai-core-common", "net.mamoe:mirai-core-jvm", "net.mamoe:mirai-core-android", "net.mamoe:mirai-core-jvmCommon", "net.mamoe:mirai-core-commonJvm", "net.mamoe:mirai-core-utils", "net.mamoe:mirai-core-utils-metadata", "net.mamoe:mirai-core-utils-common", "net.mamoe:mirai-core-utils-jvm", "net.mamoe:mirai-core-utils-android", "net.mamoe:mirai-core-utils-jvmCommon", "net.mamoe:mirai-core-utils-commonJvm", "net.mamoe:mirai-core-api", "net.mamoe:mirai-core-api-metadata", "net.mamoe:mirai-core-api-common", "net.mamoe:mirai-core-api-jvm", "net.mamoe:mirai-core-api-android", "net.mamoe:mirai-core-api-jvmCommon", "net.mamoe:mirai-core-api-commonJvm", "net.mamoe:mirai-core-qqAndroid", "net.mamoe:mirai-core-qqAndroid-metadata", "net.mamoe:mirai-core-qqAndroid-common", "net.mamoe:mirai-core-qqAndroid-jvm", "net.mamoe:mirai-console", "net.mamoe:mirai-console-api", // for future "net.mamoe:mirai-console-terminal", "net.mamoe:mirai-console-graphical", "net.mamoe.yamlKt:yamlKt", "net.mamoe.yamlKt:yamlKt-common", "net.mamoe.yamlKt:yamlKt-metadata", "net.mamoe.yamlKt:yamlKt-jvm", "net.mamoe:kotlin-jvm-blocking-bridge", "net.mamoe:kotlin-jvm-blocking-bridge-common", "net.mamoe:kotlin-jvm-blocking-bridge-metadata", "net.mamoe:kotlin-jvm-blocking-bridge-jvm" ).map { it.lowercase() } .map { MiraiConsoleExtension.ExcludedDependency(it.substringBefore(':'), it.substringAfterLast(':')) } .toTypedArray() ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/MiraiConsoleExtension.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.gradle import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar import org.gradle.api.JavaVersion import org.gradle.api.XmlProvider import org.gradle.api.plugins.PluginContainer import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.JavaExec /** * ``` * mirai { * // 配置 * } * ``` */ // must be open public open class MiraiConsoleExtension { /** * 为 `true` 时不自动添加 mirai-core-api 的依赖 * * 默认: `false` */ public var noCoreApi: Boolean = false /** * 为 `true` 时不自动为 test 模块添加 mirai-core 的依赖. * * 默认: `false` */ public var noTestCore: Boolean = false /** * 为 `true` 时不自动添加 mirai-console 的依赖. * * 默认: `false` */ public var noConsole: Boolean = false /** * 自动添加的 mirai-core 和 mirai-core-api 的版本. * * 默认: 与本 Gradle 插件编译时的 mirai-core 版本相同. [VersionConstants.CORE_VERSION] */ public var coreVersion: String = VersionConstants.CORE_VERSION /** * 自动添加的 mirai-console 后端和前端的版本. * * 默认: 与本 Gradle 插件版本相同. [VersionConstants.CONSOLE_VERSION] */ public var consoleVersion: String = VersionConstants.CONSOLE_VERSION /** * 自动为 test 模块添加的前端依赖名称 * * 为 `null` 时不自动为 test 模块添加前端依赖. * * 默认: [MiraiConsoleFrontEndKind.TERMINAL] */ public var useTestConsoleFrontEnd: MiraiConsoleFrontEndKind? = MiraiConsoleFrontEndKind.TERMINAL /** * Java 和 Kotlin 编译目标. 至少为 [JavaVersion.VERSION_1_8]. * * 一般人不需要修改此项. * * 默认: [JavaVersion.VERSION_1_8] */ public var jvmTarget: JavaVersion = JavaVersion.VERSION_1_8 /** * 默认会配置 Kotlin 编译器参数 "-Xjvm-default=all". 将此项设置为 `false` 可避免配置. * * 一般人不需要修改此项. * * 默认: `false` */ public var dontConfigureKotlinJvmDefault: Boolean = false /** * 配置 gradle task runConsole. 将此项设置为 `false` 时不会配置测试环境 */ public var consoleTestRuntime: Boolean = true internal val shadowConfigurations: MutableList<ShadowJar.() -> Unit> = mutableListOf() internal val excludedDependencies: MutableSet<ExcludedDependency> = mutableSetOf() internal val consoleTestRuntimeConf: MutableList<JavaExec.() -> Unit> = mutableListOf() public fun setupConsoleTestRuntime(configure: JavaExec.() -> Unit) { consoleTestRuntimeConf.add(configure) } internal data class ExcludedDependency( val group: String, val name: String ) /** * 配置 [ShadowJar] (即打包插件) */ public fun configureShadow(configure: ShadowJar.() -> Unit) { shadowConfigurations.add(configure) } /** * 在插件打包时忽略一个依赖 * * @param notation 格式为 "groupId:name". 如 "org.jetbrains.kotlin:kotlin-stdlib" */ public fun excludeDependency(notation: String) { requireNotNull(notation.count { it == ':' } == 1) { "Invalid dependency notation $notation." } excludedDependencies.add(ExcludedDependency(notation.substringBefore(':'), notation.substringAfter(':'))) } /** * 在插件打包时忽略一个依赖 * * @param group 如 "org.jetbrains.kotlin" * @param name 如 "kotlin-stdlib" */ public fun excludeDependency(group: String, name: String) { excludedDependencies.add(ExcludedDependency(group, name)) } /** * Bintray 插件成品 JAR 发布 配置. * * @see PluginPublishing * @since 1.1 */ public val publishing: PluginPublishing = PluginPublishing() /** * 控制自动配置 Bintray 发布. 默认为 `false`, 表示不自动配置发布. * * 开启后将会: * - 创建名为 "mavenJava" 的 [MavenPublication] * - [应用][PluginContainer.apply] [BintrayPlugin], 配置 Bintray 相关参数 * - 创建 task "publishPlugin" * * @since 1.1 */ public var publishingEnabled: Boolean = false /** * 开启自动配置 Bintray 插件成品 JAR 发布, 并以 [configure] 配置 [PluginPublishing]. * * @see [PluginPublishing] * @see publishingEnabled * @since 1.1 */ public inline fun publishing(crossinline configure: PluginPublishing.() -> Unit) { publishingEnabled = true publishing.run(configure) } /** * 开启自动配置 Bintray 插件成品 JAR 发布. * * @see [PluginPublishing] * @see publishingEnabled * @since 1.1 */ public fun publishing() { publishingEnabled = true } /** * Bintray 插件成品 JAR 发布 配置. * * 对于一个属性 PROP, 会按以下顺序依次尝试读取: * 1. Gradle 参数 * - "gradle.properties" * - ext * - Gradle -P 启动参数 * 2. [System.getProperty] "PROP" * 3. 当前和所有父 project 根目录下 "PROP" 文件的内容 * 4. [System.getenv] "PROP" * * @see publishing * @see publishingEnabled * @since 1.1 */ public class PluginPublishing internal constructor() { /////////////////////////////////////////////////////////////////////////// // Required arguments /////////////////////////////////////////////////////////////////////////// /** * Bintray 账户名. 必须. * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.user" * * @see [PluginPublishing] */ public var user: String? = null /** * Bintray 账户 key. 必须. * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.key" */ public var key: String? = null /** * 目标仓库名称. 必须. * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.repo" */ public var repo: String? = null /** * 目标仓库名称. 必须. * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.package" */ public var packageName: String? = null /////////////////////////////////////////////////////////////////////////// // Optional arguments /////////////////////////////////////////////////////////////////////////// // Artifact /** * 发布的 artifact id. 默认为 `project.name`. * * artifact id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "mirai-console" */ public var artifactId: String? = null /** * 发布的 group id. 默认为 `project.group`. * * group id 是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "net.mamoe" */ public var groupId: String? = null /** * 发布的版本号, 默认为 `project.version` * * 版本号是类似于 "net.mamoe:mirai-console:1.1.0" 中的 "1.1.0" */ public var version: String? = null /** * 发布的描述, 默认为 `project.description` */ public var description: String? = null // Bintray /** * Bintray organization 名. 可选. * 若为 `null`, 将会以 [PluginPublishing] 中描述的步骤获取 "bintray.org". * 仍然无法获取时发布到 [user] 账号下的仓库 [repo], 否则发布到指定 [org] 下的仓库 [repo]. */ public var org: String? = null /** * 上传后自动发布. 默认 `true`. */ public var publish: Boolean = true /** * 当文件冲突时覆盖. 默认 `false`. */ public var override: Boolean = false // Custom configurations internal val mavenPomConfigs = mutableListOf<XmlProvider.() -> Unit>() internal val mavenPublicationConfigs = mutableListOf<MavenPublication.() -> Unit>() /** * 自定义配置 maven pom.xml [XmlProvider] */ public fun mavenPom(configure: XmlProvider.() -> Unit) { mavenPomConfigs.add(configure) } /** * 自定义配置 [MavenPublication] */ public fun mavenPublication(configure: MavenPublication.() -> Unit) { mavenPublicationConfigs.add(configure) } } } /** * @see MiraiConsoleExtension.useTestConsoleFrontEnd */ public enum class MiraiConsoleFrontEndKind { TERMINAL, } ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/MiraiConsoleGradlePlugin.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiConsoleGradlePluginKt") package net.mamoe.mirai.console.gradle import com.github.jengelman.gradle.plugins.shadow.ShadowPlugin import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.attributes.Attribute import org.gradle.api.attributes.Usage import org.gradle.api.plugins.JavaPlugin import org.gradle.api.plugins.JavaPluginExtension import org.gradle.api.tasks.JavaExec import org.gradle.api.tasks.compile.JavaCompile import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.dsl.KotlinProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinSingleTargetExtension import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation.Companion.MAIN_COMPILATION_NAME import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet import org.jetbrains.kotlin.gradle.plugin.KotlinTarget public class MiraiConsoleGradlePlugin : Plugin<Project> { public companion object { internal const val MIRAI_SHADOW_CONF_NAME: String = "shadowLink" internal const val MIRAI_AS_NORMAL_DEP_CONF_NAME: String = "asNormalDep" internal const val MIRAI_DIRECT_RUN_CONSOLE_CONF_NAME: String = "testConsoleRuntime" public const val FILE_SUFFIX: String = "mirai.jar" } private fun KotlinSourceSet.configureSourceSet(project: Project, target: KotlinTarget) { try { languageSettings.optIn("kotlin.RequiresOptIn") } catch (e: NoSuchMethodError) { // User is using < 1.6 target.compilations.forEach { compilation -> compilation.kotlinOptions.freeCompilerArgs += "-Xopt-in=kotlin.RequiresOptIn" } } dependencies { configureDependencies(project, this@configureSourceSet, target) } } private fun Project.configureTarget(target: KotlinTarget) { val miraiExtension = project.miraiExtension for (compilation in target.compilations) with(compilation) { kotlinOptions { if (this !is KotlinJvmOptions) return@kotlinOptions jvmTarget = miraiExtension.jvmTarget.toString() if (!miraiExtension.dontConfigureKotlinJvmDefault) freeCompilerArgs = freeCompilerArgs + "-Xjvm-default=all" } } when (target.platformType) { KotlinPlatformType.jvm, KotlinPlatformType.androidJvm, KotlinPlatformType.common -> { target.compilations.flatMap { it.allKotlinSourceSets }.forEach { sourceSet -> sourceSet.configureSourceSet(project, target) } } else -> { } } } @Suppress("SpellCheckingInspection") private fun KotlinDependencyHandler.configureDependencies( project: Project, sourceSet: KotlinSourceSet, target: KotlinTarget ) { val miraiExtension = project.miraiExtension val isJvm = target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm if (!miraiExtension.noCoreApi) compileOnly("net.mamoe:mirai-core-api:${miraiExtension.coreVersion}") if (!miraiExtension.noConsole && isJvm) compileOnly("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") if (sourceSet.name.endsWith("test", ignoreCase = true)) { if (!miraiExtension.noCoreApi) api("net.mamoe:mirai-core-api:${miraiExtension.coreVersion}") if (!miraiExtension.noConsole && isJvm) api("net.mamoe:mirai-console:${miraiExtension.consoleVersion}") if (!miraiExtension.noTestCore) api("net.mamoe:mirai-core:${miraiExtension.coreVersion}") if (isJvm) { when (miraiExtension.useTestConsoleFrontEnd) { MiraiConsoleFrontEndKind.TERMINAL -> api("net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}") null -> { } } } } } private fun Project.configureCompileTarget() { extensions.findByType(JavaPluginExtension::class.java)?.apply { val miraiExtension = miraiExtension sourceCompatibility = miraiExtension.jvmTarget targetCompatibility = miraiExtension.jvmTarget } tasks.withType(JavaCompile::class.java) { it.options.encoding = "UTF8" } } private fun Project.registerGradleTasks() { val miraiExtension = this.miraiExtension // Array<String>: [jar name, jar extension] val buildPluginTasks = mutableListOf<Pair<Task, Array<String>>>() tasks.findByName("shadowJar")?.enabled = false fun registerBuildPluginTask(target: KotlinTarget, isSingleTarget: Boolean) { tasks.create( "buildPlugin".wrapNameWithPlatform(target, isSingleTarget), BuildMiraiPluginV2::class.java ).also { buildPluginV2 -> buildPluginV2.group = "mirai" buildPluginV2.registerMetadataTask( tasks, "miraiPrepareMetadata".wrapNameWithPlatform(target, isSingleTarget) ) buildPluginV2.init(target) buildPluginV2.destinationDirectory.value( project.layout.projectDirectory.dir(project.buildDir.name).dir("mirai") ) buildPluginTasks.add(buildPluginV2 to arrayOf(project.name + "-dev", BuildMiraiPluginV2.FILE_SUFFIX)) } tasks.create( "buildPluginLegacy".wrapNameWithPlatform(target, isSingleTarget), BuildMiraiPluginTask::class.java, target ).apply shadow@{ group = "mirai" archiveExtension.set(FILE_SUFFIX) val compilations = target.compilations.filter { it.name == MAIN_COMPILATION_NAME } compilations.forEach { @Suppress("DEPRECATION") // We need to support older Kotlin versions dependsOn(it.compileKotlinTask) from(it.output.allOutputs) } from(project.configurations.getByName("runtimeClasspath").copyRecursive { dependency -> for (excludedDependency in IGNORED_DEPENDENCIES_IN_SHADOW + miraiExtension.excludedDependencies) { if (excludedDependency.group.equals(dependency.group, ignoreCase = true) && excludedDependency.name.equals(dependency.name, ignoreCase = true) ) return@copyRecursive false } true }) exclude { file -> file.name.endsWith(".sf", ignoreCase = true) } destinationDirectory.value(project.layout.projectDirectory.dir(project.buildDir.name).dir("mirai")) miraiExtension.shadowConfigurations.forEach { it.invoke(this@shadow) } } } val targets = kotlinTargets val isSingleTarget = targets.size == 1 targets.forEach { target -> registerBuildPluginTask(target, isSingleTarget) } if (miraiExtension.consoleTestRuntime) { dependencies.add( MIRAI_DIRECT_RUN_CONSOLE_CONF_NAME, "net.mamoe:mirai-core-api:${miraiExtension.coreVersion}" ) dependencies.add( MIRAI_DIRECT_RUN_CONSOLE_CONF_NAME, "net.mamoe:mirai-core:${miraiExtension.coreVersion}" ) dependencies.add( MIRAI_DIRECT_RUN_CONSOLE_CONF_NAME, "net.mamoe:mirai-console:${miraiExtension.consoleVersion}" ) val frontendDep = when (miraiExtension.useTestConsoleFrontEnd) { MiraiConsoleFrontEndKind.TERMINAL -> "net.mamoe:mirai-console-terminal:${miraiExtension.consoleVersion}" null -> null } if (frontendDep != null) { dependencies.add(MIRAI_DIRECT_RUN_CONSOLE_CONF_NAME, frontendDep) } tasks.register("runConsole", JavaExec::class.java) { runConsole -> runConsole.group = "mirai" runConsole.classpath += configurations.getByName(MIRAI_DIRECT_RUN_CONSOLE_CONF_NAME) runConsole.mainClass.set( when (miraiExtension.useTestConsoleFrontEnd) { MiraiConsoleFrontEndKind.TERMINAL -> "net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader" null -> "ERROR_mirai_console_frontend_not_found" } ) // default runs on $projectDir/debug-sandbox runConsole.workingDir = project.projectDir.resolve("debug-sandbox") runConsole.doFirst { // working dir can be changed by user val plugins = runConsole.workingDir.resolve("plugins") plugins.mkdirs() buildPluginTasks.forEach { (buildPluginTask, jarNameMetadata) -> val (jarName, jarSuffix) = jarNameMetadata buildPluginTask.outputs.files.files.forEachIndexed { index, outFile -> val name = jarName + if (index == 0) { "" } else { "-$index" } + '.' + jarSuffix outFile.copyTo(plugins.resolve(name), overwrite = true) } } } runConsole.standardInput = System.`in` runConsole.jvmArgs("-Dmirai.console.skip-end-user-readme") buildPluginTasks.forEach { runConsole.dependsOn(it.first) } miraiExtension.consoleTestRuntimeConf.forEach { it.invoke(runConsole) } } } } private fun Project.setupConfigurations() { configurations.create(MIRAI_SHADOW_CONF_NAME).isCanBeResolved = false configurations.create(MIRAI_AS_NORMAL_DEP_CONF_NAME).isCanBeResolved = false configurations.create(MIRAI_DIRECT_RUN_CONSOLE_CONF_NAME).let { runConsoleConf -> runConsoleConf.attributes { ac -> fun attribute(key: String, value: String) { ac.attribute(Attribute.of(key, String::class.java), value) } attribute("org.jetbrains.kotlin.platform.type", "jvm") ac.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage::class.java, Usage.JAVA_RUNTIME)) } } } override fun apply(target: Project): Unit = with(target) { extensions.create("mirai", MiraiConsoleExtension::class.java) plugins.apply(JavaPlugin::class.java) plugins.apply("org.gradle.maven-publish") // plugins.apply("org.gradle.maven") plugins.apply(ShadowPlugin::class.java) project.setupConfigurations() afterEvaluate { configureCompileTarget() kotlinTargets.forEach { configureTarget(it) } registerGradleTasks() configurePublishing() } } } internal val Project.miraiExtension: MiraiConsoleExtension get() = extensions.findByType(MiraiConsoleExtension::class.java) ?: error("Cannot find MiraiConsoleExtension in project ${this.name}") internal val Project.kotlinTargets: Collection<KotlinTarget> get() { val kotlinExtension = extensions.findByType(KotlinProjectExtension::class.java) ?: error("Kotlin plugin not applied. Please read https://www.kotlincn.net/docs/reference/using-gradle.html") return when (kotlinExtension) { is KotlinMultiplatformExtension -> kotlinExtension.targets is KotlinSingleTargetExtension<*> -> listOf(kotlinExtension.target) else -> error("[MiraiConsole] Internal error: kotlinExtension is neither KotlinMultiplatformExtension nor KotlinSingleTargetExtension") } } internal val Project.kotlinJvmOrAndroidTargets: Collection<KotlinTarget> get() = kotlinTargets.filter { it.platformType == KotlinPlatformType.jvm || it.platformType == KotlinPlatformType.androidJvm } ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/VersionConstants.kt.template ================================================ /* * 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.console.gradle internal object VersionConstants { const val CONSOLE_VERSION = "$$CONSOLE_VERSION$$" // value is written here automatically during build const val CORE_VERSION = "$$CORE_VERSION$$" // value is written here automatically during build } ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/dsl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.gradle import org.gradle.api.logging.Logger internal inline fun Logger.info(msg: () -> String) { if (isInfoEnabled) info(msg()) } ================================================ FILE: mirai-console/tools/gradle-plugin/src/main/kotlin/publishing.kt ================================================ /* * 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.console.gradle import com.google.gson.Gson import org.gradle.api.Project import org.gradle.api.artifacts.Configuration import org.gradle.api.publish.maven.MavenPublication import org.gradle.api.tasks.bundling.Jar import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.getValue import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.registering import org.jetbrains.kotlin.gradle.plugin.KotlinTarget import java.util.* //private val Project.selfAndParentProjects: Sequence<Project> // get() = generateSequence(this) { it.parent } // //private fun Project.findPropertySmart(propName: String): String? { // return findProperty(propName)?.toString() // ?: System.getProperty(propName) // ?: selfAndParentProjects.map { it.projectDir.resolve(propName) }.find { it.exists() }?.readText() // ?: System.getenv(propName) //} // //private class PropertyNotFoundException(message: String) : RuntimeException(message) // //private fun Project.findPropertySmartOrFail(propName: String): String { // return findPropertySmart(propName) // ?: throw PropertyNotFoundException("[Mirai Console] Cannot find property for publication: '$propName'. Please check your 'mirai' configuration.") //} internal fun Project.configurePublishing() { if (!miraiExtension.publishingEnabled) return val isSingleTarget = kotlinJvmOrAndroidTargets.size == 1 kotlinJvmOrAndroidTargets.forEach { registerPublishPluginTasks(it, isSingleTarget) registerMavenPublications(it, isSingleTarget) } } // effectively public internal data class PluginMetadata( val metadataVersion: Int, val groupId: String, val artifactId: String, val version: String, val description: String?, val dependencies: List<String> ) internal fun String.wrapNameWithPlatform(target: KotlinTarget, isSingleTarget: Boolean): String { return if (isSingleTarget) this else "$this${ target.name.replaceFirstChar { if (it.isLowerCase()) it.titlecase( Locale.getDefault() ) else it.toString() } }" } private fun Project.registerPublishPluginTasks(target: KotlinTarget, isSingleTarget: Boolean) { tasks.register("generatePluginMetadata".wrapNameWithPlatform(target, isSingleTarget)).get().apply { group = "mirai" val metadataFile = project.buildDir.resolve("mirai") .resolve(if (isSingleTarget) "mirai-plugin.metadata" else "mirai-plugin-${target.name}.metadata") outputs.file(metadataFile) doLast { val mirai = miraiExtension val output = outputs.files.singleFile output.parentFile.mkdir() fun getConfigurationsToInclude(): List<Configuration> { val compilation = target.compilations["main"] return compilation.relatedConfigurationNames.map { configurations[it] } } val dependencies = getConfigurationsToInclude().flatMap { it.allDependencies }.map { "${it.group}:${it.name}:${it.version}" }.distinct() val json = Gson().toJson( PluginMetadata( metadataVersion = 1, groupId = mirai.publishing.groupId ?: project.group.toString(), artifactId = mirai.publishing.artifactId ?: project.name, version = mirai.publishing.version ?: project.version.toString(), description = mirai.publishing.description ?: project.description, dependencies = dependencies ) ) logger.info("Generated mirai plugin metadata json: $json") output.writeText(json) } Unit } // val bintrayUpload = tasks.getByName(BintrayUploadTask.getTASK_NAME()).dependsOn( // "buildPlugin".wrapNameWithPlatform(target, isSingleTarget), // generateMetadataTask, // // "shadowJar", // tasks.filterIsInstance<BuildMiraiPluginTask>().single { it.target == target } // ) // tasks.register("publishPlugin".wrapNameWithPlatform(target, isSingleTarget)).get().apply { // group = "mirai" // dependsOn(bintrayUpload) // } } private fun Project.registerMavenPublications(target: KotlinTarget, isSingleTarget: Boolean) { val mirai = miraiExtension @Suppress("DEPRECATION") val sourcesJar by tasks.registering(Jar::class) { archiveClassifier.set("sources") from(sourceSets["main"].allSource) } publishing { /* repositories { maven { // change to point to your repo, e.g. http://my.org/repo url = uri("$buildDir/repo") } }*/ publications.register( "mavenJava".wrapNameWithPlatform(target, isSingleTarget), MavenPublication::class.java ) { publication -> with(publication) { from(components["java"]) this.groupId = mirai.publishing.groupId ?: project.group.toString() this.artifactId = mirai.publishing.artifactId ?: project.name this.version = mirai.publishing.version ?: project.version.toString() pom.withXml { xml -> val root = xml.asNode() root.appendNode("description", project.description) root.appendNode("name", project.name) // root.appendNode("url", vcs) root.children().last() mirai.publishing.mavenPomConfigs.forEach { it.invoke(xml) } } artifact(sourcesJar.get()) artifact(tasks.filterIsInstance<BuildMiraiPluginTask>().single { it.target == target }) artifact( mapOf( "source" to tasks.getByName( "generatePluginMetadata".wrapNameWithPlatform( target, isSingleTarget ) ).outputs.files.singleFile, "extension" to "mirai.metadata" ) ) mirai.publishing.mavenPublicationConfigs.forEach { it.invoke(this) } } } } } @PublishedApi internal val Project.sourceSets: org.gradle.api.tasks.SourceSetContainer get() = (this as org.gradle.api.plugins.ExtensionAware).extensions.getByName("sourceSets") as org.gradle.api.tasks.SourceSetContainer /** * Configures the [publishing][org.gradle.api.publish.PublishingExtension] extension. */ @PublishedApi internal fun Project.publishing(configure: org.gradle.api.publish.PublishingExtension.() -> Unit): Unit = (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("publishing", configure) ================================================ FILE: mirai-console/tools/gradle-plugin/test/net/mamoe/mirai/console/gradle/AbstractTest.groovy ================================================ /* * 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.console.gradle import kotlin.Pair import org.gradle.testkit.runner.GradleRunner import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.io.TempDir abstract class AbstractTest { @TempDir public File tempDir File buildFile File settingsFile File propertiesFile private static Pair<String, Integer> getProxy() { if (System.getenv("user.name") == "Him188") new Pair<String, Integer>("127.0.0.1", 7890) else null } def gradleRunner() { GradleRunner.create() .withProjectDir(tempDir) .withPluginClasspath() .forwardOutput() .withEnvironment(System.getenv()) } @BeforeEach void setup() { println('Temp path is ' + tempDir.absolutePath) settingsFile = new File(tempDir, "settings.gradle") settingsFile.delete() settingsFile << """ pluginManagement { repositories { gradlePluginPortal() mavenCentral() } } """ propertiesFile = new File(tempDir, "gradle.properties") propertiesFile.delete() def proxy = getProxy() if (proxy != null) propertiesFile << """ |systemProp.http.proxyHost=${proxy.first} |systemProp.http.proxyPort=${proxy.second} |systemProp.https.proxyHost=${proxy.first} |systemProp.https.proxyPort=${proxy.second} """.stripMargin() // buildFile = new File(tempDir, "build.gradle") // buildFile.delete() // buildFile << """ // plugins { // id 'org.jetbrains.kotlin.jvm' version '1.4.32' // id 'net.mamoe.mirai-console' // } // repositories { // mavenCentral() // } // """ buildFile = new File(tempDir, "build.gradle.kts") buildFile.delete() buildFile << """ plugins { kotlin("jvm") version "1.4.30" id("net.mamoe.mirai-console") } repositories { mavenCentral() } """ } @AfterEach void cleanup() { tempDir.deleteDir() } } ================================================ FILE: mirai-console/tools/gradle-plugin/test/net/mamoe/mirai/console/gradle/TestPluginApply.groovy ================================================ /* * 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.console.gradle import org.junit.jupiter.api.Test import static org.gradle.testkit.runner.TaskOutcome.SUCCESS class TestPluginApply extends AbstractTest { @Test void "can apply plugin"() { def result = gradleRunner() .withArguments("clean", "--stacktrace") .build() assert result.task('clean').outcome == SUCCESS } } ================================================ FILE: mirai-console/tools/intellij-plugin/.gitignore ================================================ run/idea-sandbox !src/creator/build ================================================ FILE: mirai-console/tools/intellij-plugin/README.md ================================================ # mirai-console-intellij [IntelliJ](https://www.jetbrains.com/idea/) 平台的 Mirai 和 Mirai Console 开发辅助插件,支持 IntelliJ IDEA Community/Ultimate,Android Studio。 主要提供一些编辑中的错误诊断。 ## mirai-core 诊断 ### `Message` #### [UsingStringPlusMessageInspection](src/diagnostics/UsingStringPlusMessageInspection.kt#L33) - 检查并报错 `String + Message` 的使用 > `String + Message` 实际上是 `String + Message.toString()`,`Message` 会被转为 `String` 再与 `String` 相加。 ```kotlin val str: String = "" val plain: PlainText = PlainText("") str + plain // ^^^ // 使用 String + Message 会导致 Message 被转换为 String 再相加 ``` 提供修复 [ConvertToPlainTextFix](src/diagnostics/fix/ConvertToPlainTextFix.kt#L26) ```kotlin // before str + plain // after PlainText(str) + plain ``` ## mirai-console 诊断 ### `Plugin` #### [PluginMainServiceNotConfiguredInspection](src/diagnostics/PluginMainServiceNotConfiguredInspection.kt#L38)`PluginMainServiceNotConfiguredInspe 检查插件主类服务(即 `META-INF/services`)配置。在未正确配置时报错并提供自动修复 [ConfigurePluginMainServiceFix](src/diagnostics/fix/ConfigurePluginMainServiceFix.kt#L26): ```kotlin object MyPluginMain : KotlinPlugin() // ^^^^^^^^^^^^ // 插件主类服务未配置 ``` 自动修复会在 `META-INF/services` 创建一个 `net.mamoe.mirai.console.plugin.jvm.JvmPlugin` 文件。 ### `PluginDescription` - ILLEGAL_PLUGIN_DESCRIPTION: 检查插件 ID, 名称等 - ILLEGAL_VERSION_REQUIREMENT: 检查插件依赖版本号 ### `PluginData` 检查 `PluginData.value` 的泛型, - NOT_CONSTRUCTABLE_TYPE: 在该类型无法被反射构造时报错 - UNSERIALIZABLE_TYPE: 在该类型无法被序列化时报错 - READ_ONLY_VALUE_CANNOT_BE_VAR: 检查 `ReadOnlyPluginData` 中的 `var` 并提供修复 > 通常能被反射构造的类型需要有一个公开的所有参数都可选的构造器。在 Java 则需一个公开无参构造器。 ### `Command` - ILLEGAL_COMMAND_NAME: 检查指令名称 - ILLEGAL_COMMAND_REGISTER_USE: 检查一些错误的 `CommandManager.registerCommand` 使用 - RESTRICTED_CONSOLE_COMMAND_OWNER: 检查错误的 `ConsoleCommandOwner` 使用 - ILLEGAL_COMMAND_DECLARATION_RECEIVER: 检查指令定义的接收者参数 (只允许 `CommandSender` 类型) ### `Permission` - ILLEGAL_PERMISSION_NAME: 检查权限名称 - ILLEGAL_PERMISSION_NAMESPACE: 检查权限命名空间 - ILLEGAL_PERMISSION_REGISTER_USE: 检查一些错误的 `CommandManager.registerCommand` 使用 ================================================ FILE: mirai-console/tools/intellij-plugin/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java") `maven-publish` id("org.jetbrains.intellij") version Versions.intellijGradlePlugin } repositories { maven("https://maven.aliyun.com/repository/central") // IntelliJ dependencies are very large (>500MB) mavenCentral() } version = Versions.consoleIntellij description = "IntelliJ plugin for Mirai Console" // See https://github.com/JetBrains/gradle-intellij-plugin/ intellij { version.set(Versions.intellij) downloadSources.set(IDEA_ACTIVE) updateSinceUntilBuild.set(false) sandboxDir.set(projectDir.resolve("run/idea-sandbox").absolutePath) plugins.set( listOf( // "org.jetbrains.kotlin:${Versions.kotlinIntellijPlugin}", // @eap "java", "gradle", "org.jetbrains.kotlin" ) ) } java { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } tasks.getByName("publishPlugin", org.jetbrains.intellij.tasks.PublishPluginTask::class) { val pluginKey = project.findProperty("jetbrains.hub.key")?.toString() if (pluginKey != null) { logger.info("Found jetbrains.hub.key") token.set(pluginKey) } else { logger.info("jetbrains.hub.key not found") } } fun File.resolveMkdir(relative: String): File { return this.resolve(relative).apply { mkdirs() } } kotlin.target.compilations.all { kotlinOptions { jvmTarget = "17" apiVersion = "1.9" // bundled Kotlin is 1.7.20 languageVersion = "1.9" // idea requires 1.9 } } // https://plugins.jetbrains.com/docs/intellij/kotlin.html#kotlin-standard-library tasks.withType<org.jetbrains.intellij.tasks.PatchPluginXmlTask> { sinceBuild.set("223") untilBuild.set("232.*") pluginDescription.set( """ Plugin development support for <a href='https://github.com/mamoe/mirai'>Mirai Console</a> <h3>Features</h3> <ul> <li>Inspections for plugin properties.</li> <li>Inspections for illegal calls.</li> <li>Intentions for resolving serialization problems.</li> </ul> """.trimIndent() ) changeNotes.set( """ See <a href="https://github.com/mamoe/mirai/releases">https://github.com/mamoe/mirai/releases</a> """.trimIndent() ) } dependencies { implementation(project(":mirai-console-compiler-common")) { exclude("org.jetbrains.kotlin", "kotlin-stdlib") exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7") exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8") } // implementation(project(":mirai-console-compiler-common")) { // isTransitive = false // } } ================================================ FILE: mirai-console/tools/intellij-plugin/gradle.properties ================================================ # # Copyright 2019-2022 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/dev/LICENSE # kotlin.stdlib.default.dependency=false ================================================ FILE: mirai-console/tools/intellij-plugin/resources/META-INF/plugin.xml ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <idea-plugin> <id>net.mamoe.mirai-console</id> <name>Mirai Console</name> <vendor email="support@mamoe.net" url="https://github.com/mamoe/"> Mamoe Technologies </vendor> <depends>com.intellij.modules.java</depends> <depends>com.intellij.modules.platform</depends> <depends>org.jetbrains.kotlin</depends> <depends>com.intellij.gradle</depends> <extensions defaultExtensionNs="com.intellij"> <moduleType id="MIRAI_CONSOLE_PLUGIN_MODULE" implementationClass="net.mamoe.mirai.console.intellij.wizard.MiraiModuleType"/> <moduleBuilder id="MIRAI_MODULE" builderClass="net.mamoe.mirai.console.intellij.wizard.MiraiModuleBuilder"/> <fileTemplateGroup implementation="net.mamoe.mirai.console.intellij.assets.FileTemplateRegistrar"/> <codeInsight.lineMarkerProvider language="JAVA" implementationClass="net.mamoe.mirai.console.intellij.line.marker.PluginMainLineMarkerProvider"/> <codeInsight.lineMarkerProvider language="kotlin" implementationClass="net.mamoe.mirai.console.intellij.line.marker.PluginMainLineMarkerProvider"/> <codeInsight.lineMarkerProvider language="JAVA" implementationClass="net.mamoe.mirai.console.intellij.line.marker.CommandDeclarationLineMarkerProvider"/> <codeInsight.lineMarkerProvider language="kotlin" implementationClass="net.mamoe.mirai.console.intellij.line.marker.CommandDeclarationLineMarkerProvider"/> <localInspection groupPath="Mirai console" language="kotlin" shortName="PluginMainServiceNotConfigured" bundle="messages.InspectionGadgetsBundle" key="plugin.service.not.configured.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.plugin.service.issues" enabledByDefault="true" level="WARNING" implementationClass="net.mamoe.mirai.console.intellij.diagnostics.PluginMainServiceNotConfiguredInspection"/> <localInspection groupPath="Mirai console" language="JAVA" shortName="PluginMainServiceNotConfiguredJava" bundle="messages.InspectionGadgetsBundle" key="plugin.service.not.configured.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.plugin.service.issues" enabledByDefault="true" level="WARNING" implementationClass="net.mamoe.mirai.console.intellij.diagnostics.PluginMainServiceNotConfiguredInspection"/> <localInspection groupPath="Mirai console" language="kotlin" shortName="ResourceNotClosed" bundle="messages.InspectionGadgetsBundle" key="resource.not.closed.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.mirai.core.issues" enabledByDefault="true" level="WARNING" implementationClass="net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspection"/> <localInspection groupPath="Mirai console" language="JAVA" shortName="ResourceNotClosedJava" bundle="messages.InspectionGadgetsBundle" key="resource.not.closed.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.mirai.core.issues" enabledByDefault="true" level="WARNING" implementationClass="net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspection"/> <localInspection groupPath="Mirai console" language="kotlin" shortName="UsingStringPlusMessage" bundle="messages.InspectionGadgetsBundle" key="using.string.plus.message.display.name" groupBundle="messages.InspectionsBundle" groupKey="group.names.message.issues" enabledByDefault="true" level="WARNING" implementationClass="net.mamoe.mirai.console.intellij.diagnostics.UsingStringPlusMessageInspection"/> <intentionAction> <className>net.mamoe.mirai.console.intellij.diagnostics.fix.WrapWithResourceUseCallIntention</className> <category>Mirai console</category> </intentionAction> <intentionAction> <className>net.mamoe.mirai.console.intellij.diagnostics.fix.WrapWithResourceUseCallJavaIntention</className> <category>Mirai console</category> </intentionAction> </extensions> <extensions defaultExtensionNs="org.jetbrains.kotlin"> <storageComponentContainerContributor implementation="net.mamoe.mirai.console.intellij.IDEContainerContributor"/> <quickFixContributor implementation="net.mamoe.mirai.console.intellij.QuickFixRegistrar"/> </extensions> </idea-plugin> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/.gitignore.ft ================================================ # User-specific stuff .idea/ *.iml *.ipr *.iws # IntelliJ out/ # mpeltonen/sbt-idea plugin .idea_modules/ # JIRA plugin atlassian-ide-plugin.xml # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Package Files # *.jar *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* ### Linux ### *~ # temporary files which can be created if a process still has a handle open of a deleted file .fuse_hidden* # KDE directory preferences .directory # Linux trash folder which might appear on any partition or disk .Trash-* # .nfs files are created when an open file is removed but is still being accessed .nfs* ### macOS ### # General .DS_Store .AppleDouble .LSOverride # Icon must end with two \r Icon # Thumbnails ._* # Files that might appear in the root of a volume .DocumentRevisions-V100 .fseventsd .Spotlight-V100 .TemporaryItems .Trashes .VolumeIcon.icns .com.apple.timemachine.donotpresent # Directories potentially created on remote AFP share .AppleDB .AppleDesktop Network Trash Folder Temporary Items .apdisk ### Windows ### # Windows thumbnail cache files Thumbs.db Thumbs.db:encryptable ehthumbs.db ehthumbs_vista.db # Dump file *.stackdump # Folder config file [Dd]esktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ # Windows Installer files *.cab *.msi *.msix *.msm *.msp # Windows shortcuts *.lnk ### Gradle ### .gradle build/ # Ignore Gradle GUI config gradle-app.setting # Cache of project .gradletasknamecache ### Gradle Patch ### **/build/ # Common working directory run/ # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) !gradle-wrapper.jar ### Mirai Console Files ### # Local Test Launch point src/test/kotlin/RunTerminal.kt # Mirai console files with direct bootstrap /config /data /plugins /bots # Local Test Launch Point working directory /debug-sandbox ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/.gitignore.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new .gitignore for Gradle projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Gradle gradle-wrapper.properties.ft ================================================ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.1-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Gradle gradle-wrapper.properties.html ================================================ <!-- ~ Copyright 2019-2023 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new gradle-wrapper.properties for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Gradle gradle.properties.ft ================================================ kotlin.code.style=official ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Gradle gradle.properties.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new gradle.properties for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin build.gradle.ft ================================================ plugins { id 'org.jetbrains.kotlin.jvm' version '$KOTLIN_VERSION' id 'org.jetbrains.kotlin.plugin.serialization' version '$KOTLIN_VERSION' id 'net.mamoe.mirai-console' version '$MIRAI_VERSION' } group = '$GROUP_ID' version = '$VERSION' repositories { #if($USE_PROXY_REPO) maven { url 'https://maven.aliyun.com/repository/public' } #end mavenCentral() } mirai { jvmTarget JavaVersion.VERSION_1_8 } ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin build.gradle.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new build.gradle for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin build.gradle.kts.ft ================================================ plugins { val kotlinVersion = "$KOTLIN_VERSION" kotlin("jvm") version kotlinVersion kotlin("plugin.serialization") version kotlinVersion id("net.mamoe.mirai-console") version "$MIRAI_VERSION" } group = "$GROUP_ID" version = "$VERSION" repositories { #if($USE_PROXY_REPO) maven("https://maven.aliyun.com/repository/public") #end mavenCentral() } mirai { jvmTarget = JavaVersion.VERSION_1_8 } ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin build.gradle.kts.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new build.gradle.kts for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin main class Java.java.ft ================================================ package $PACKAGE_NAME; import net.mamoe.mirai.console.plugin.jvm.JavaPlugin; import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescriptionBuilder; public final class ${CLASS_NAME} extends JavaPlugin{ public static final ${CLASS_NAME} INSTANCE=new ${CLASS_NAME}(); #set($HAS_DETAILS = ${PLUGIN_AUTHOR} != "" || ${PLUGIN_DEPENDS_ON} != "" || ${PLUGIN_INFO} != "" || ${PLUGIN_NAME} != "") private ${CLASS_NAME}(){ #if($HAS_DETAILS == false) super(new JvmPluginDescriptionBuilder("$PLUGIN_ID","$PLUGIN_VERSION").build());#end #if($HAS_DETAILS) super(new JvmPluginDescriptionBuilder("$PLUGIN_ID","$PLUGIN_VERSION") #if($PLUGIN_NAME != "").name("$PLUGIN_NAME") #end#if($PLUGIN_INFO != "").info("$PLUGIN_INFO") #end#if($PLUGIN_AUTHOR != "").author("$PLUGIN_AUTHOR") #end#if($PLUGIN_DEPENDS_ON != "").dependsOn("$PLUGIN_DEPENDS_ON") #end .build());#end } @Override public void onEnable(){ getLogger().info("Plugin loaded!"); } } ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin main class Java.java.ft.back ================================================ package $PACKAGE_NAME; import net.mamoe.mirai.console.plugin.jvm.JavaPlugin; import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription; public final class ${CLASS_NAME} extends JavaPlugin { public static final ${CLASS_NAME} INSTANCE = new ${CLASS_NAME}(); private ${CLASS_NAME}() { super(JvmPluginDescription.loadFromResource("plugin.yml", ${CLASS_NAME}.class.getClassLoader())); } @Override public void onEnable() { getLogger().info("Plugin loaded!"); } } ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin main class Java.java.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new plugin main class for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin main class Kotlin.kt.ft ================================================ package $PACKAGE_NAME import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.utils.info #set($HAS_DETAILS = ${PLUGIN_AUTHOR} != "" || ${PLUGIN_DEPENDS_ON} != "" || ${PLUGIN_INFO} != "") object $CLASS_NAME : KotlinPlugin( JvmPluginDescription( id = "${PLUGIN_ID}", #if(${PLUGIN_NAME} != "")name = "${PLUGIN_NAME}", #end version = "${PLUGIN_VERSION}", ) #if($HAS_DETAILS){ #end #if(${PLUGIN_AUTHOR} != "")author("${PLUGIN_AUTHOR}") #end #if(${PLUGIN_DEPENDS_ON} != "")dependsOn("${PLUGIN_DEPENDS_ON}") #end #if(${PLUGIN_INFO} != "")info("""${PLUGIN_INFO}""") #end #if($HAS_DETAILS) } #end ) { override fun onEnable() { logger.info { "Plugin loaded" } } } ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin main class Kotlin.kt.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new plugin main class for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin main service.txt.ft ================================================ ${PACKAGE_NAME}.${CLASS_NAME} ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin main service.txt.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new plugin main class for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin settings.gradle.ft ================================================ pluginManagement { repositories { #if($USE_PROXY_REPO) maven { url "https://maven.aliyun.com/repository/gradle-plugin" } #end gradlePluginPortal() } } rootProject.name = "$ARTIFACT_ID" ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin settings.gradle.html ================================================ <!-- ~ Copyright 2019-2023 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new settings.gradle for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin settings.gradle.kts.ft ================================================ pluginManagement { repositories { #if($USE_PROXY_REPO) maven("https://maven.aliyun.com/repository/gradle-plugin") #end gradlePluginPortal() } } rootProject.name = "$ARTIFACT_ID" ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/Plugin settings.gradle.kts.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new settings.gradle.kts for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/RunTerminal.run.xml.ft ================================================ <component name="ProjectRunConfigurationManager"> <configuration default="false" name="Run Mirai Console" type="GradleRunConfiguration" factoryName="Gradle" folderName="Mirai"> <ExternalSystemSettings> <option name="executionName" /> <option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalSystemIdString" value="GRADLE" /> <option name="scriptParameters" value="" /> <option name="taskDescriptions"> <list /> </option> <option name="taskNames"> <list> <option value=":runConsole" /> </list> </option> <option name="vmOptions" /> </ExternalSystemSettings> <ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess> <ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess> <DebugAllEnabled>false</DebugAllEnabled> <method v="2" /> </configuration> </component> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/RunTerminal.run.xml.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new settings.gradle.kts for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/account.properties.ft ================================================ id=123456 password=pwd ================================================ FILE: mirai-console/tools/intellij-plugin/resources/fileTemplates/code/account.properties.html ================================================ <!-- ~ Copyright 2019-2022 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/dev/LICENSE --> <html> <body> <p>This is a built-in file template used to create a new gradle.properties for Mirai Console Plugin projects.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/inspectionDescriptions/PluginMainServiceNotConfigured.html ================================================ <!-- ~ 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/dev/LICENSE --> <html> <body> <p>检查插件主类服务配置情况。 </p> <!-- tooltip end --> <!--<p>Text after this comment will only be shown in the settings of the inspection.</p>--> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/inspectionDescriptions/ResourceNotClosed.html ================================================ <!-- ~ 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/dev/LICENSE --> <html> <body> <p>检查 ExternalResource 没有 close 的情况。 </p> <!-- tooltip end --> <!--<p>Text after this comment will only be shown in the settings of the inspection.</p>--> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/inspectionDescriptions/UsingStringPlusMessage.html ================================================ <!-- ~ 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/dev/LICENSE --> <html> <body> <p>Detects `String + Message` calls.</p> <!-- tooltip end --> <p>Detects `String + Message` calls. `String + Message` is essentially `String + Message.toString()` and is different from `Message + Message`. Such conversions are implicit and may lead to confusing behavior.</p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/intentionDescriptions/WrapWithResourceUseCallIntention/after.receiver.template ================================================ resource.use { it.uploadAsImage(contact) } ================================================ FILE: mirai-console/tools/intellij-plugin/resources/intentionDescriptions/WrapWithResourceUseCallIntention/before.receiver.template ================================================ resource.uploadAsImage(contact) ================================================ FILE: mirai-console/tools/intellij-plugin/resources/intentionDescriptions/WrapWithResourceUseCallIntention/description.html ================================================ <!-- ~ 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/dev/LICENSE --> <html> <body> <p>将资源直接使用转换为 `.use { ... }` </p> <!-- tooltip end --> <p>将 `resource.sendAsImageTo(contact)` 转换为 `resource.use { it.sendAsImageTo(contact) }` </p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/intentionDescriptions/WrapWithResourceUseCallJavaIntention/after.action.template ================================================ ExternalResource resource; Contact contact; resource.use { it.uploadAsImage(contact) } ================================================ FILE: mirai-console/tools/intellij-plugin/resources/intentionDescriptions/WrapWithResourceUseCallJavaIntention/before.action.template ================================================ ExternalResource resource; Contact contact; resource.uploadAsImage(contact) ================================================ FILE: mirai-console/tools/intellij-plugin/resources/intentionDescriptions/WrapWithResourceUseCallJavaIntention/description.html ================================================ <!-- ~ 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/dev/LICENSE --> <html> <body> <p>将资源直接使用转换为 `.use { ... }` </p> <!-- tooltip end --> <p>将 `resource.sendAsImageTo(contact)` 转换为 `resource.use { it.sendAsImageTo(contact) }` </p> </body> </html> ================================================ FILE: mirai-console/tools/intellij-plugin/resources/messages/InspectionGadgetsBundle.properties ================================================ # # 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/dev/LICENSE # plugin.service.not.configured.display.name=Plugin main not configured using.string.plus.message.display.name=Using string plus message resource.not.closed.display.name=Resource not closed ================================================ FILE: mirai-console/tools/intellij-plugin/resources/messages/InspectionsBundle.properties ================================================ # # 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/dev/LICENSE # group.names.plugin.service.issues=Plugin service issues group.names.message.issues=Message issues group.names.mirai.core.issues=Mirai core issues ================================================ FILE: mirai-console/tools/intellij-plugin/resources/messages/MiraiProjectWizardBundle.properties ================================================ # # Copyright 2019-2023 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/dev/LICENSE # module.presentation.name=Mirai Console Plugin module.description="Template for building plugins for <b>Mirai Console</b>" title.plugin.description=Plugin Description label.plugin.version=Plugin Version: label.version.stable=Stable label.version.prerelease=Prerelease label.version.nightly=Nightly label.plugin.id=Plugin ID: label.plugin.name=Plugin Name: label.mirai.version.loading=Loading... label.mirai.version=Mirai Version: error.failed.to.download.mirai.version=Failed to download version list, please select a version manually error.please.wait.for.mirai.version=Please wait for downloading version list label.plugin.author=Plugin Author: comment.plugin.id=Should consist of English characters and/or numbers, '.', ':', '-'. Example: "net.mamoe:chat-command". comment.plugin.name=Example: "Chat Command". comment.plugin.version=Should comply <a href="https://semver.org/">Semantic Versioning</a>, not including 'v'. Example: "0.1.0". comment.mirai.version=Minimum version of Mirai Console this plugin requires on. label.plugin.dependencies=Plugin Dependencies: label.plugin.info=Plugin Info: comment.plugin.dependencies=Each dependency is in format "id:VersionRequirement" (not including quotation marks). To declare optional dependency, add "?" at the end. \ Example: "net.mamoe.chat-command:[1.0.0, 2.0.0)?" declares and optional dependency on "net.mamoe.chat-command", requiring its version to be greater or equal to 1.0.0 and smaller than 2.0.0. \ Separate multiple dependencies into liens. \ Syntax for "VersionRequirement" can be found at <a href="https://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html">Apache Ivy version-matchers</a>. comment.plugin.info=Introductory information about this plugin. text.hint.plugin.info=Optional text.hint.plugin.dependencies=Optional validation.plugin.name.forbidden.character="{0}" is forbidden in plugin name validation.illegal.plugin.id=Invalid plugin id "{0}" validation.illegal.version=Invalid version.\n{0} no.error.message=No error message text.use.proxy.repo=Use Aliyun Maven repository ================================================ FILE: mirai-console/tools/intellij-plugin/resources/messages/MiraiProjectWizardBundle_zh.properties ================================================ # # Copyright 2019-2023 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/dev/LICENSE # module.presentation.name=Mirai Console 插件 module.description="构建 <b>Mirai Console</b> 插件的模板" title.plugin.description=插件信息 label.plugin.version=插件版本号: label.version.stable=稳定版 label.version.prerelease=预览版 label.version.nightly=开发版 label.plugin.id=插件 ID: label.plugin.name=插件名称: label.mirai.version.loading=加载中... label.mirai.version=Mirai 版本号: error.failed.to.download.mirai.version=下载版本列表失败, 请手动选择版本号 error.please.wait.for.mirai.version=请等待下载版本列表 label.plugin.author=插件作者: comment.plugin.id=只能包含以下内容: 英文字母, 数字, '.', ':', '-'. 示例: "net.mamoe.chat-command". comment.plugin.name=示例: "Chat Command". comment.plugin.version=需遵循 <a href="https://semver.org/">语义化版本</a>, 不包含 'v'. 示例: "0.1.0". comment.mirai.version=插件依赖的最低 Mirai Console 版本号 label.plugin.dependencies=插件依赖: label.plugin.info=插件描述: comment.plugin.dependencies=每个依赖格式为 "id:版本要求" (不包括引号). 若要定义可选依赖, 请在末尾添加 "?". \ 示例: "net.mamoe.chat-command:[1.0.0, 2.0.0)?" 定义对 "net.mamoe.chat-command" 的可选依赖, 要求其版本大于或等于 1.0.0 且小于 2.0.0. \ 多个依赖以换行符分割. \ "版本要求" 语法可在 <a href="https://ant.apache.org/ivy/history/latest-milestone/settings/version-matchers.html">Apache Ivy version-matchers</a> 查看. comment.plugin.info=对该插件的介绍信息. text.hint.plugin.info=可留空 text.hint.plugin.dependencies=可留空 validation.plugin.name.forbidden.character=插件名称中不允许存在 "{0}" validation.illegal.plugin.id=插件 ID 无效: "{0}" validation.illegal.version=插件版本无效\n{0} no.error.message=无错误信息 text.use.proxy.repo=使用阿里云 Maven 镜像 ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/.gitignore ================================================ local.properties build/ build .gradle .idea ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ plugins { kotlin("jvm") version "1.8.20" kotlin("plugin.serialization") version "1.8.20" id("net.mamoe.mirai-console") version "2.99.0-local" java } group = "org.example" version = "1.0-SNAPSHOT" dependencies { } repositories { mavenCentral() mavenLocal() } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/gradle/wrapper/gradle-wrapper.properties ================================================ # # Copyright 2019-2023 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/dev/LICENSE # distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/gradle.properties ================================================ kotlin.code.style=official ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/gradlew ================================================ #!/usr/bin/env sh # # Copyright 2015 the original author or authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin or MSYS, switch paths to Windows format before running java if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=`expr $i + 1` done case $i in 0) set -- ;; 1) set -- "$args0" ;; 2) set -- "$args0" "$args1" ;; 3) set -- "$args0" "$args1" "$args2" ;; 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" exec "$JAVACMD" "$@" ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/gradlew.bat ================================================ @rem @rem Copyright 2015 the original author or authors. @rem @rem Licensed under the Apache License, Version 2.0 (the "License"); @rem you may not use this file except in compliance with the License. @rem You may obtain a copy of the License at @rem @rem https://www.apache.org/licenses/LICENSE-2.0 @rem @rem Unless required by applicable law or agreed to in writing, software @rem distributed under the License is distributed on an "AS IS" BASIS, @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Resolve any "." and ".." in APP_HOME to make it shorter. for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/settings.gradle.kts ================================================ rootProject.name = "test-project" pluginManagement { repositories { mavenLocal() gradlePluginPortal() jcenter() } } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/java/test/ResourceNotClosedInspectionTestJava.java ================================================ package test; import net.mamoe.mirai.contact.Contact; import net.mamoe.mirai.message.data.Image; import net.mamoe.mirai.utils.ExternalResource; import org.example.myplugin.ResourceNotClosedInspectionTestKt; import java.io.File; import java.io.IOException; import static org.example.myplugin.ResourceNotClosedInspectionTestKt.magic; public class ResourceNotClosedInspectionTestJava { public static Object funA() { return new Object(); } public static void funB(Object obj) { System.out.println(obj); } public static void main(String[] args) { // https://github.com/mamoe/mirai-console/issues/294 funB(funA()); File file = magic(); Contact contact = magic(); // useImage(contact.uploadImage(ExternalResource.create(file))); useImage(Contact.uploadImage(contact, ExternalResource.create(file))); useImage(Contact.uploadImage(contact, file)); try (final ExternalResource resource = ExternalResource.create(file)) { useImage(contact.uploadImage(resource)); } } static void useImage(Image image) { } } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/java/test/TestJavaPlugin.java ================================================ package test; import net.mamoe.mirai.console.command.Command; import net.mamoe.mirai.console.command.CommandOwner; import net.mamoe.mirai.console.command.descriptor.CommandSignatureFromKFunction; import net.mamoe.mirai.console.command.java.JCompositeCommand; import net.mamoe.mirai.console.command.java.JSimpleCommand; import net.mamoe.mirai.console.permission.Permission; import net.mamoe.mirai.console.plugin.jvm.JavaPlugin; import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription; import org.jetbrains.annotations.NotNull; import java.util.List; public class TestJavaPlugin extends JavaPlugin { public TestJavaPlugin(@NotNull JvmPluginDescription description) { super(description); } } class TestCommand extends JSimpleCommand { public TestCommand(@NotNull CommandOwner owner, @NotNull String primaryName, @NotNull String[] secondaryNames, @NotNull Permission basePermission) { super(owner, primaryName, secondaryNames, basePermission); } @Handler public void test(String s) { } } class TestCommand2 extends JCompositeCommand { public TestCommand2(@NotNull CommandOwner owner, @NotNull String primaryName, @NotNull String[] secondaryNames, @NotNull Permission parentPermission) { super(owner, primaryName, secondaryNames, parentPermission); } @SubCommand("test") public void test() {} @SubCommand({}) public void subCmd() { } } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/AbstractMessageKeysUsages.kt ================================================ /* * 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/dev/LICENSE */ package org.example.myplugin import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.messageChainOf fun main() { val chain = messageChainOf() chain[MessageContent] } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/ConsoleCommandOwnerCheck.kt ================================================ /* * 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 org.example.myplugin import net.mamoe.mirai.console.command.ConsoleCommandOwner import net.mamoe.mirai.console.command.SimpleCommand object MySimpleCommand0002 : SimpleCommand( ConsoleCommandOwner, "foo", description = "示例指令" ) {} ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MyPluginMain.kt ================================================ package org.example.myplugin import kotlinx.serialization.Serializable import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.AutoSavePluginData import net.mamoe.mirai.console.data.value import net.mamoe.mirai.console.permission.PermissionId import net.mamoe.mirai.console.permission.PermissionService import net.mamoe.mirai.console.plugin.jvm.JvmPluginDescription import net.mamoe.mirai.console.plugin.jvm.KotlinPlugin import net.mamoe.mirai.console.util.SemVersion const val T = "org.example" // 编译期常量 object MyPluginMain : KotlinPlugin( JvmPluginDescription( T, "1.0-M4", ) { name(".") } ) { override fun onEnable() { super.onEnable() PermissionService.INSTANCE.register(permissionId("dvs"), "ok") PermissionService.INSTANCE.register(permissionId("perm with space"), "error") PermissionId("Namespace with space", "Name with space") SemVersion.parseRangeRequirement("1.0") SemVersion.parseRangeRequirement("<br/>") SemVersion.parseRangeRequirement("SB YELLOW") SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 || ") SemVersion.parseRangeRequirement("1.0.0 || 2.0.0") SemVersion.parseRangeRequirement("1.0.0 || 2.0.0 && 3.0.0") SemVersion.parseRangeRequirement("{}") SemVersion.parseRangeRequirement("||") SemVersion.parseRangeRequirement(">= 114.514 || = 1919.810 || (1.1, 1.2)") SemVersion.parseRangeRequirement("0.0.0 || {90.48}") SemVersion.parseRangeRequirement("{114514.1919810}") SemVersion.parseRangeRequirement("}") } fun test() { } } val x = "弱智黄色" object MyData : AutoSavePluginData("") { val value by value("") val value2 by value<Map<String, String>>() } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/MySimpleCommand.kt ================================================ /* * 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 org.example.myplugin import kotlinx.serialization.Serializable import net.mamoe.mirai.console.command.CommandSender import net.mamoe.mirai.console.command.ConsoleCommandOwner import net.mamoe.mirai.console.command.SimpleCommand import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.value object MySimpleCommand0001 : SimpleCommand( ConsoleCommandOwner, "foo", description = "示例指令" ) {} object MySimpleCommand000 : SimpleCommand( MyPluginMain, "foo", description = "示例指令" ) { @Handler suspend fun CommandSender.handle(int: Int, str: String) { } @Handler suspend fun String.bad(int: Int, str: String) { } } object DataTest : AutoSavePluginConfig("data") { val pp by value(NoDefaultValue(1)) } object DataTest1 : ReadOnlyPluginConfig("data") { var pp by value<String>() // var should be reported } @Serializable data class HasDefaultValue( val x: Int = 0, ) @Serializable data class NoDefaultValue( val y: Int, ) ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/ReadOnlyPluginDataVar.kt ================================================ /* * 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 org.example.myplugin import kotlinx.serialization.Serializable import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.value import org.example.myplugin.DataTest1.provideDelegate object DataTest2 : ReadOnlyPluginConfig("data") { var pp by value<String>() val x by value<V>(V("")) // var should be reported class V constructor(val s: String) } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/ResourceNotClosedInspectionTest.kt ================================================ package org.example.myplugin import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact.Companion.sendImage import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image.Key.queryUrl import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage import java.io.File class ResourceNotClosedInspectionTest { suspend fun useResource() { val file = magic<File>() val contact = magic<Contact>() contact.uploadImage(file.toExternalResource()) // should report warning contact.sendImage(file) // should report warning //file.toExternalResource().uploadAsImage(contact) file.toExternalResource().uploadAsImage(contact) file.toExternalResource().sendAsImageTo(contact) // contact.uploadImage(file) // should ok // replace to net.mamoe.mirai.contact.Contact.Companion.uploadImage } } fun <T> magic(): T = null!! ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/StringPlusMessageInspectionTest.kt ================================================ /* * 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 org.example.myplugin import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.toPlainText fun main() { val x: String = "" val plain: PlainText = PlainText("") x + plain // x + plain run { x } + plain run { x.apply { x.let { also { x } } } } + plain run { x.apply { x.let { also { x } } } }.plus(plain) x + plain 1 + plain x.plus(plain) 1.plus(plain) } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/UsingDerivedMap.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package org.example.myplugin import net.mamoe.mirai.console.data.AutoSavePluginConfig import net.mamoe.mirai.console.data.ReadOnlyPluginConfig import net.mamoe.mirai.console.data.value import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.messageChainOf import org.example.myplugin.DataTest1.provideDelegate import java.util.* import java.util.concurrent.ConcurrentHashMap object UsingDerivedMap : AutoSavePluginConfig("data") { var p1 by value<MutableMap<String, String>>() var p2 by value<Map<String, String>>() var p4 by value<HashMap<String, String>>() var p3 by value<ConcurrentHashMap<String, String>>() var p8 by value<List<String>>() var p5 by value<ArrayList<String>>() var p7 by value<AbstractList<String>>() var p6 by value<MessageChain>(messageChainOf()) // no error } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/main/kotlin/org/example/myplugin/WrapWithResourceUseCallIntentionTest.kt ================================================ package org.example.myplugin import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo import java.io.File class WrapWithResourceUseCallIntentionTest { suspend fun test() { val file = magic<File>() val contact = magic<Contact>() val resource = magic<ExternalResource>() resource.sendAsImageTo(contact) resource.run { sendAsImageTo(contact) } } } ================================================ FILE: mirai-console/tools/intellij-plugin/run/projects/test-project/src/test/kotlin/RunConsole.kt ================================================ import net.mamoe.mirai.alsoLogin import net.mamoe.mirai.console.MiraiConsole import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.enable import net.mamoe.mirai.console.plugin.PluginManager.INSTANCE.load import net.mamoe.mirai.console.terminal.MiraiConsoleTerminalLoader import org.example.myplugin.MyPluginMain suspend fun main() { MiraiConsoleTerminalLoader.startAsDaemon() MyPluginMain.load() // 主动加载插件, Console 会调用 MyPluginMain.onLoad MyPluginMain.enable() // 主动启用插件, Console 会调用 MyPluginMain.onEnable val bot = MiraiConsole.addBot(123456, "").alsoLogin() // 登录一个测试环境的 Bot MiraiConsole.job.join() } ================================================ FILE: mirai-console/tools/intellij-plugin/src/IDEContainerContributor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij import com.intellij.psi.PsiElement import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.intellij.diagnostics.CommandDeclarationChecker import net.mamoe.mirai.console.intellij.diagnostics.ContextualParametersChecker import net.mamoe.mirai.console.intellij.diagnostics.PluginDataValuesChecker import net.mamoe.mirai.console.intellij.util.DEBUG_ENABLED import net.mamoe.mirai.console.intellij.util.runIgnoringErrors import org.jetbrains.kotlin.analyzer.ModuleInfo import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useInstance import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.extensions.StorageComponentContainerContributor import org.jetbrains.kotlin.idea.base.projectStructure.unwrapModuleSourceInfo import org.jetbrains.kotlin.idea.facet.KotlinFacet import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import java.io.File class IDEContainerContributor : StorageComponentContainerContributor { override fun registerModuleComponents( container: StorageComponentContainer, platform: org.jetbrains.kotlin.platform.TargetPlatform, moduleDescriptor: ModuleDescriptor, ) { if (moduleDescriptor.hasMiraiConsoleDependency()) { container.useInstance(ContextualParametersChecker().wrapIgnoringExceptionIfNotDebug()) container.useInstance((PluginDataValuesChecker() as CallChecker).wrapIgnoringExceptionIfNotDebug()) container.useInstance((PluginDataValuesChecker() as DeclarationChecker).wrapIgnoringExceptionIfNotDebug()) container.useInstance(CommandDeclarationChecker().wrapIgnoringExceptionIfNotDebug()) } } private fun DeclarationChecker.wrapIgnoringExceptionIfNotDebug(): DeclarationChecker { if (DEBUG_ENABLED) { return this } return DeclarationCheckerIgnoringExceptions(this) } private fun CallChecker.wrapIgnoringExceptionIfNotDebug(): CallChecker { if (DEBUG_ENABLED) { return this } return CallCheckerIgnoringExceptions(this) } class CallCheckerIgnoringExceptions( private val delegate: CallChecker ) : CallChecker { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { runIgnoringErrors { delegate.check(resolvedCall, reportOn, context) } } } class DeclarationCheckerIgnoringExceptions( private val delegate: DeclarationChecker ) : DeclarationChecker { override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext ) { runIgnoringErrors { delegate.check(declaration, descriptor, context) } } } } fun ModuleDescriptor.hasMiraiConsoleDependency(): Boolean { // /.m2/repository/net/mamoe/kotlin-jvm-blocking-bridge-compiler-embeddable/1.4.0/kotlin-jvm-blocking-bridge-compiler-embeddable-1.4.0.jar val pluginJpsJarName = "mirai-console" val module = getCapability(ModuleInfo.Capability)?.unwrapModuleSourceInfo()?.module ?: return false val facet = KotlinFacet.get(module) ?: return false val pluginClasspath = facet.configuration.settings.compilerArguments?.castOrNull<K2JVMCompilerArguments>()?.classpathAsList0 ?: return false if (pluginClasspath.none { path -> path.name.contains(pluginJpsJarName) }) return false return true } private var K2JVMCompilerArguments.classpathAsList0: List<File> get() = classpath.orEmpty().split(File.pathSeparator).map(::File) set(value) { classpath = value.joinToString(separator = File.pathSeparator, transform = { it.path }) } ================================================ FILE: mirai-console/tools/intellij-plugin/src/QuickFixRegistrar.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij import com.intellij.codeInsight.intention.IntentionAction import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors import net.mamoe.mirai.console.intellij.diagnostics.fix.* import org.jetbrains.kotlin.diagnostics.DiagnosticFactory import org.jetbrains.kotlin.idea.quickfix.KotlinIntentionActionsFactory import org.jetbrains.kotlin.idea.quickfix.QuickFixContributor import org.jetbrains.kotlin.idea.quickfix.QuickFixes class QuickFixRegistrar : QuickFixContributor { override fun registerQuickFixes(quickFixes: QuickFixes) { fun DiagnosticFactory<*>.registerFactory(vararg factory: KotlinIntentionActionsFactory) { quickFixes.register(this, *factory) } @Suppress("unused") fun DiagnosticFactory<*>.registerActions(vararg action: IntentionAction) { quickFixes.register(this, *action) } MiraiConsoleErrors.UNSERIALIZABLE_TYPE.registerFactory(AddSerializerFix) MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.registerFactory(ProvideDefaultValueFix) MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR.registerFactory(ConvertToValFix) MiraiConsoleErrors.USING_DERIVED_MAP_TYPE.registerFactory(ConvertToMapFix) MiraiConsoleErrors.USING_DERIVED_MUTABLE_MAP_TYPE.registerFactory(ConvertToMutableMapFix) MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE.registerFactory(ConvertToConcurrentMapFix) MiraiConsoleErrors.USING_DERIVED_LIST_TYPE.registerFactory(ConvertToListFix) MiraiConsoleErrors.USING_DERIVED_MUTABLE_LIST_TYPE.registerFactory(ConvertToMutableListFix) } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/assets/Assets.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.assets import com.intellij.openapi.util.IconLoader import javax.swing.Icon object Icons { val CommandDeclaration: Icon = IconLoader.getIcon("/icons/commandDeclaration.svg", Icons::class.java) val PluginMainDeclaration: Icon = IconLoader.getIcon("/icons/pluginMainDeclaration.png", Icons::class.java) val MainIcon: Icon = PluginMainDeclaration } object FT { // file template const val BuildGradleKts = "Plugin build.gradle.kts" const val BuildGradle = "Plugin build.gradle" const val SettingsGradleKts = "Plugin settings.gradle.kts" const val SettingsGradle = "Plugin settings.gradle" const val GradleWrapperProperties = "Gradle gradle-wrapper.properties" const val GradleProperties = "Gradle gradle.properties" const val PluginMainKt = "Plugin main class Kotlin.kt" const val PluginMainJava = "Plugin main class Java.java" const val PluginMainService = "Plugin main service.txt" const val Gitignore = ".gitignore" const val RunTerminalRun = "RunTerminal.run.xml" const val AccountProperties = "account.properties" } ================================================ FILE: mirai-console/tools/intellij-plugin/src/assets/FileTemplateRegistrar.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.assets import com.intellij.ide.fileTemplates.FileTemplateDescriptor import com.intellij.ide.fileTemplates.FileTemplateGroupDescriptor class FileTemplateRegistrar : com.intellij.ide.fileTemplates.FileTemplateGroupDescriptorFactory { override fun getFileTemplatesDescriptor(): FileTemplateGroupDescriptor { return FileTemplateGroupDescriptor("Mirai", Icons.PluginMainDeclaration).apply { addTemplate(FileTemplateDescriptor(FT.BuildGradleKts)) addTemplate(FileTemplateDescriptor(FT.BuildGradle)) addTemplate(FileTemplateDescriptor(FT.PluginMainKt)) addTemplate(FileTemplateDescriptor(FT.PluginMainJava)) addTemplate(FileTemplateDescriptor(FT.PluginMainService)) addTemplate(FileTemplateDescriptor(FT.GradleWrapperProperties)) addTemplate(FileTemplateDescriptor(FT.GradleProperties)) addTemplate(FileTemplateDescriptor(FT.SettingsGradleKts)) addTemplate(FileTemplateDescriptor(FT.SettingsGradle)) addTemplate(FileTemplateDescriptor(FT.Gitignore)) addTemplate(FileTemplateDescriptor(FT.RunTerminalRun)) } } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/CommandDeclarationChecker.kt ================================================ /* * 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.console.intellij.diagnostics import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_DECLARATION_RECEIVER import net.mamoe.mirai.console.compiler.common.resolve.COMMAND_SENDER_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.hasAnnotation import net.mamoe.mirai.console.intellij.resolve.hasSuperType import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtNamedFunction import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext class CommandDeclarationChecker : DeclarationChecker { override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext ) { if (declaration !is KtNamedFunction) return // exclusive checks or return when { descriptor.hasAnnotation(SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME) -> { } descriptor.hasAnnotation(COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME) -> { } else -> return } // common checks checkCommandReceiverParameter(declaration)?.let { context.report(it) } } companion object { fun checkCommandReceiverParameter(declaration: KtNamedFunction): Diagnostic? { val receiverTypeRef = declaration.receiverTypeReference ?: return null // no receiver, accept. val receiver = receiverTypeRef.resolveReferencedType() ?: return null // unresolved type if (!receiver.hasSuperType(COMMAND_SENDER_FQ_NAME)) { return ILLEGAL_COMMAND_DECLARATION_RECEIVER.on(receiverTypeRef) } return null } } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/ContextualParametersChecker.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.psi.PsiElement import net.mamoe.mirai.console.compiler.common.CheckerConstants import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_COMMAND_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_ID import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAME import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PERMISSION_NAMESPACE import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_PLUGIN_DESCRIPTION import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.ILLEGAL_VERSION_REQUIREMENT import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.PROHIBITED_ABSTRACT_MESSAGE_KEYS import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors.RESTRICTED_CONSOLE_COMMAND_OWNER import net.mamoe.mirai.console.compiler.common.resolve.CONSOLE_COMMAND_OWNER_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.PROHIBITED_MESSAGE_KEYS import net.mamoe.mirai.console.compiler.common.resolve.ResolveContextKind import net.mamoe.mirai.console.compiler.common.resolve.resolveContextKinds import net.mamoe.mirai.console.intellij.resolve.getResolvedCall import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import net.mamoe.mirai.console.intellij.util.RequirementHelper import net.mamoe.mirai.console.intellij.util.RequirementParser import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.inspections.collections.isCalling import org.jetbrains.kotlin.psi.KtReferenceExpression import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import java.util.* import kotlin.reflect.KFunction2 val CallCheckerContext.bindingContext get() = trace.bindingContext /** * Checks parameters with [ResolveContextKind] */ class ContextualParametersChecker : CallChecker { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { for ((parameter, resolvedArgument) in resolvedCall.valueArguments) { for (valueArgument in resolvedArgument.arguments) { checkArgument(parameter, valueArgument, context, valueArgument.asElement()) } } } // // override fun check( // declaration: KtDeclaration, // descriptor: DeclarationDescriptor, // context: DeclarationCheckerContext, // ) { // val calls = declaration.bodyCalls(context.bindingContext) ?: return // // for ((call, _) in calls) { // for ((parameter, resolvedArgument) in call.valueArguments) { // for (valueArgument in resolvedArgument.arguments) { // checkArgument(parameter, valueArgument, context) // } // } // } // } private fun checkArgument( parameter: ValueParameterDescriptor, argument: ValueArgument, context: CallCheckerContext, inspectionTarget: PsiElement, ) { val elementCheckers = parameter.resolveContextKinds?.mapNotNull(checkersMap::get) ?: return if (elementCheckers.isEmpty()) return val resolvedConstants = argument.resolveStringConstantValues(context.bindingContext)?.toList() ?: return for (elementChecker in elementCheckers) { if (resolvedConstants.isEmpty()) { elementChecker(context, inspectionTarget, argument, null)?.let { context.trace.report(it) } } else { for (resolvedConstant in resolvedConstants) { elementChecker( context, inspectionTarget, argument, resolvedConstant )?.let { context.trace.report(it) } } } } } companion object { private val ID_REGEX: Regex = CheckerConstants.PLUGIN_ID_REGEX private val FORBIDDEN_ID_NAMES: Array<String> = CheckerConstants.PLUGIN_FORBIDDEN_NAMES private const val syntax = """类似于 "net.mamoe.mirai.example-plugin", 其中 "net.mamoe.mirai" 为 groupId, "example-plugin" 为插件名""" const val SEMANTIC_VERSIONING_PATTERN = """^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?${'$'}""" /** * https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string */ private val SEMANTIC_VERSIONING_REGEX = Regex(SEMANTIC_VERSIONING_PATTERN) fun checkPluginId(inspectionTarget: PsiElement, value: String): Diagnostic? { if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件 Id 不能为空. \n插件 Id$syntax") if (value.none { it == '.' }) return ILLEGAL_PLUGIN_DESCRIPTION.on( inspectionTarget, "插件 Id '$value' 无效. 插件 Id 必须同时包含 groupId 和插件名称. $syntax" ) val lowercaseId = value.lowercase() if (ID_REGEX.matchEntire(value) == null) { return ILLEGAL_PLUGIN_DESCRIPTION.on( inspectionTarget, "插件 Id 无效. 正确的插件 Id 应该满足正则表达式 '${ID_REGEX.pattern}', \n$syntax" ) } FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseId }?.let { illegal -> return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件 Id. 确保插件 Id 不完全是这个名称") } return null } fun checkPluginName(inspectionTarget: PsiElement, value: String): Diagnostic? { if (value.isBlank()) return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "插件名不能为空") val lowercaseName = value.lowercase() FORBIDDEN_ID_NAMES.firstOrNull { it == lowercaseName }?.let { illegal -> return ILLEGAL_PLUGIN_DESCRIPTION.on(inspectionTarget, "'$illegal' 不允许作为插件名. 确保插件名不完全是这个名称") } return null } fun checkPluginVersion(inspectionTarget: PsiElement, value: String): Diagnostic? { if (!SEMANTIC_VERSIONING_REGEX.matches(value)) { return ILLEGAL_PLUGIN_DESCRIPTION.on( inspectionTarget, "版本号无效: '$value'. \nhttps://semver.org/lang/zh-CN/" ) } return null } fun checkCommandName(inspectionTarget: PsiElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不能为空") value.any { it.isWhitespace() } -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "暂时不允许指令名中存在空格") value.contains(':') -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不允许包含 ':'") value.contains('.') -> ILLEGAL_COMMAND_NAME.on(inspectionTarget, value, "指令名不允许包含 '.'") else -> null } } fun checkPermissionNamespace(inspectionTarget: PsiElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不能为空") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAMESPACE.on( inspectionTarget, value, "不允许权限命名空间中存在空格" ) value.contains(':') -> ILLEGAL_PERMISSION_NAMESPACE.on(inspectionTarget, value, "权限命名空间不允许包含 ':'") else -> null } } fun checkPermissionName(inspectionTarget: PsiElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不能为空") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "不允许权限名称中存在空格") value.contains(':') -> ILLEGAL_PERMISSION_NAME.on(inspectionTarget, value, "权限名称不允许包含 ':'") else -> null } } fun checkPermissionId(inspectionTarget: PsiElement, value: String): Diagnostic? { return when { value.isBlank() -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "权限 Id 不能为空") value.any { it.isWhitespace() } -> ILLEGAL_PERMISSION_ID.on(inspectionTarget, value, "暂时不允许权限 Id 中存在空格") value.count { it == ':' } != 1 -> ILLEGAL_PERMISSION_ID.on( inspectionTarget, value, "权限 Id 必须为 \"命名空间:名称\". 且命名空间和名称均不能包含 ':'" ) else -> null } } @Suppress("UNUSED_PARAMETER") fun checkVersionRequirement(inspectionTarget: PsiElement, value: String): Diagnostic? { return try { RequirementHelper.RequirementChecker.processLine(RequirementParser.TokenReader(value)) null } catch (err: Throwable) { ILLEGAL_VERSION_REQUIREMENT.on(inspectionTarget, value, err.message ?: err.toString()) } } fun checkConsoleCommandOwner( context: CallCheckerContext, inspectionTarget: PsiElement, argument: ValueArgument ): Diagnostic? { val expr = argument.getArgumentExpression() ?: return null if (expr is KtReferenceExpression) { if (expr.getResolvedCall(context.bindingContext)?.isCalling(CONSOLE_COMMAND_OWNER_FQ_NAME) == true) { return RESTRICTED_CONSOLE_COMMAND_OWNER.on(inspectionTarget) } } return null } fun checkAbstractMessageKeys( context: CallCheckerContext, inspectionTarget: PsiElement, argument: ValueArgument ): Diagnostic? { val expr = argument.getArgumentExpression() ?: return null if (expr is KtReferenceExpression) { val call = expr.getResolvedCall(context.bindingContext) ?: return null if (PROHIBITED_MESSAGE_KEYS.any { call.isCalling(it) }) { return PROHIBITED_ABSTRACT_MESSAGE_KEYS.on(inspectionTarget) } } return null } } fun interface ElementChecker { operator fun invoke( context: CallCheckerContext, declaration: PsiElement, valueArgument: ValueArgument, value: String? ): Diagnostic? } @Suppress("unused") private val checkersMap: EnumMap<ResolveContextKind, ElementChecker> = EnumMap<ResolveContextKind, ElementChecker>(ResolveContextKind::class.java).apply { fun put(key: ResolveContextKind, value: KFunction2<PsiElement, String, Diagnostic?>): ElementChecker? { return put(key) { _, d, _, v -> if (v != null) value(d, v) else null } } fun put( key: ResolveContextKind, value: KFunction2<PsiElement, ValueArgument, Diagnostic?> ): ElementChecker? { return put(key) { _, d, v, _ -> value(d, v) } } fun put( key: ResolveContextKind, value: (CallCheckerContext, PsiElement, ValueArgument) -> Diagnostic? ): ElementChecker? { return put(key) { c, d, v, _ -> value(c, d, v) } } put(ResolveContextKind.PLUGIN_NAME, ::checkPluginName) put(ResolveContextKind.PLUGIN_ID, ::checkPluginId) put(ResolveContextKind.SEMANTIC_VERSION, ::checkPluginVersion) put(ResolveContextKind.COMMAND_NAME, ::checkCommandName) put(ResolveContextKind.PERMISSION_NAME, ::checkPermissionName) put(ResolveContextKind.PERMISSION_NAMESPACE, ::checkPermissionNamespace) put(ResolveContextKind.PERMISSION_ID, ::checkPermissionId) put(ResolveContextKind.VERSION_REQUIREMENT, ::checkVersionRequirement) put(ResolveContextKind.RESTRICTED_CONSOLE_COMMAND_OWNER, ::checkConsoleCommandOwner) put(ResolveContextKind.RESTRICTED_ABSTRACT_MESSAGE_KEYS, ::checkAbstractMessageKeys) } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/MessageChainGetCallChecker.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.psi.PsiElement import org.jetbrains.kotlin.idea.inspections.collections.isCalling import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall class MessageChainGetCallChecker : CallChecker { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { // if (resolvedCall.isCalling()) } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/PluginDataValuesChecker.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.psi.PsiElement import net.mamoe.mirai.console.compiler.common.SERIALIZABLE_FQ_NAME import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors import net.mamoe.mirai.console.compiler.common.resolve.* import net.mamoe.mirai.console.intellij.resolve.hasSuperType import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.inspections.collections.isCalling import org.jetbrains.kotlin.idea.project.builtIns import org.jetbrains.kotlin.idea.refactoring.fqName.fqName import org.jetbrains.kotlin.js.descriptorUtils.getJetTypeFqName import org.jetbrains.kotlin.js.descriptorUtils.getKotlinTypeFqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.resolve.calls.checkers.CallChecker import org.jetbrains.kotlin.resolve.calls.checkers.CallCheckerContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.SimpleType class PluginDataValuesChecker : CallChecker, DeclarationChecker { override fun check(resolvedCall: ResolvedCall<*>, reportOn: PsiElement, context: CallCheckerContext) { check( resolvedCall as ResolvedCall<out CallableDescriptor>, resolvedCall.call.callElement as? KtExpression ?: return, context ) } override fun check( declaration: KtDeclaration, descriptor: DeclarationDescriptor, context: DeclarationCheckerContext ) { if (declaration is KtProperty) { checkReadOnly(declaration, context) } } /** * Check `PluginData.value` calls */ fun check(call: ResolvedCall<out CallableDescriptor>, expr: KtExpression, context: CallCheckerContext) { if (!call.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME)) return if (expr is KtCallExpression) checkConstructableAndSerializable(call, expr, context) } private fun KtProperty.isInsideOrExtensionOfReadOnlyPluginData(): Boolean { return containingClassOrObject?.hasSuperType(READ_ONLY_PLUGIN_DATA_FQ_NAME) == true // inside || receiverTypeReference?.hasSuperType(READ_ONLY_PLUGIN_DATA_FQ_NAME) == true // extension } private fun checkReadOnly(property: KtProperty, context: DeclarationCheckerContext) { // first parent is KtPropertyDelegate, next is KtProperty if (property.isVar // var && property.delegateExpression?.getResolvedCall(context) ?.isCalling(PLUGIN_DATA_VALUE_FUNCTIONS_FQ_FQ_NAME) == true // by value() && property.isInsideOrExtensionOfReadOnlyPluginData() // extensionReceiver is ReadOnlyPluginData or null ) { context.report(MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR.on(property.valOrVarKeyword)) } } private fun checkConstructableAndSerializable( call: ResolvedCall<out CallableDescriptor>, expr: KtCallExpression, context: CallCheckerContext ) { if (call.resultingDescriptor.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) != true) return for ((entry, argument) in call.typeArguments.entries.zip(expr.typeArguments)) { val (parameter, kotlinType) = entry if ((parameter.isReified || parameter.resolveContextKinds?.contains(ResolveContextKind.RESTRICTED_NO_ARG_CONSTRUCTOR) == true) && kotlinType is SimpleType ) { checkConstructableAndSerializable(kotlinType, expr, argument, context) checkFixType(kotlinType, expr, argument, context) } } } private fun checkFixType( type: KotlinType, callExpr: KtCallExpression, inspectionTarget: KtTypeProjection, context: CallCheckerContext ) { val classDescriptor = type.classDescriptor() ?: return val jetTypeFqn = type.getKotlinTypeFqName(false) val builtIns = callExpr.builtIns val factory = when { jetTypeFqn == "java.util.concurrent.ConcurrentHashMap" -> MiraiConsoleErrors.USING_DERIVED_CONCURRENT_MAP_TYPE classDescriptor.fqNameSafe.asString() .startsWith("net.mamoe.mirai.message.data.") -> null // Don't report for MessageChain classDescriptor.isSubclassOf(builtIns.list) && jetTypeFqn != "kotlin.collections.List" -> { if (classDescriptor.isSubclassOf(builtIns.mutableList)) { if (jetTypeFqn != "kotlin.collections.MutableList" && jetTypeFqn != "java.util.List") { MiraiConsoleErrors.USING_DERIVED_MUTABLE_LIST_TYPE } else null } else MiraiConsoleErrors.USING_DERIVED_LIST_TYPE } classDescriptor.isSubclassOf(builtIns.map) && jetTypeFqn != "kotlin.collections.Map" -> { if (classDescriptor.isSubclassOf(builtIns.mutableMap)) { if (jetTypeFqn != "kotlin.collections.MutableMap" && jetTypeFqn != "java.util.Map") { MiraiConsoleErrors.USING_DERIVED_MUTABLE_MAP_TYPE } else null } else MiraiConsoleErrors.USING_DERIVED_MAP_TYPE } else -> return } ?: return context.trace.report(factory.on(inspectionTarget, callExpr, jetTypeFqn.substringAfterLast('.'))) } private fun checkConstructableAndSerializable( type: KotlinType, callExpr: KtCallExpression, inspectionTarget: KtTypeProjection, context: CallCheckerContext ) { val classDescriptor = type.classDescriptor() ?: return if (canBeSerializedInternally(classDescriptor)) return if (!classDescriptor.hasNoArgConstructor()) return context.trace.report( MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE.on( inspectionTarget, callExpr, type.fqName?.asString().toString() ) ) if (!classDescriptor.hasAnnotation(SERIALIZABLE_FQ_NAME)) return context.trace.report( MiraiConsoleErrors.UNSERIALIZABLE_TYPE.on( inspectionTarget, classDescriptor ) ) } private fun KotlinType.classDescriptor() = constructor.declarationDescriptor?.castOrNull<ClassDescriptor>() private fun retrieveInspectionTarget(type: KotlinType, callExpr: KtCallExpression): KtTypeProjection? { val fqName = type.fqName ?: return null return callExpr.typeArguments.find { it.typeReference?.isReferencing(fqName) == true } } } private fun canBeSerializedInternally(descriptor: ClassDescriptor): Boolean { @Suppress("UNUSED_VARIABLE") val name = when (descriptor.defaultType.getKotlinTypeFqName(false)) { // kotlinx.serialization "kotlin.Unit" -> "UnitSerializer" "Z", "kotlin.Boolean" -> "BooleanSerializer" "B", "kotlin.Byte" -> "ByteSerializer" "S", "kotlin.Short" -> "ShortSerializer" "I", "kotlin.Int" -> "IntSerializer" "J", "kotlin.Long" -> "LongSerializer" "F", "kotlin.Float" -> "FloatSerializer" "D", "kotlin.Double" -> "DoubleSerializer" "C", "kotlin.Char" -> "CharSerializer" "kotlin.String" -> "StringSerializer" "kotlin.Pair" -> "PairSerializer" "kotlin.Triple" -> "TripleSerializer" "kotlin.collections.Collection", "kotlin.collections.List", "kotlin.collections.ArrayList", "kotlin.collections.MutableList", -> "ArrayListSerializer" "kotlin.collections.Set", "kotlin.collections.LinkedHashSet", "kotlin.collections.MutableSet" -> "LinkedHashSetSerializer" "kotlin.collections.HashSet" -> "HashSetSerializer" "kotlin.collections.Map", "kotlin.collections.LinkedHashMap", "kotlin.collections.MutableMap" -> "LinkedHashMapSerializer" "kotlin.collections.HashMap" -> "HashMapSerializer" "kotlin.collections.Map.Entry" -> "MapEntrySerializer" "kotlin.ByteArray" -> "ByteArraySerializer" "kotlin.ShortArray" -> "ShortArraySerializer" "kotlin.IntArray" -> "IntArraySerializer" "kotlin.LongArray" -> "LongArraySerializer" "kotlin.CharArray" -> "CharArraySerializer" "kotlin.FloatArray" -> "FloatArraySerializer" "kotlin.DoubleArray" -> "DoubleArraySerializer" "kotlin.BooleanArray" -> "BooleanArraySerializer" "java.lang.Boolean" -> "BooleanSerializer" "java.lang.Byte" -> "ByteSerializer" "java.lang.Short" -> "ShortSerializer" "java.lang.Integer" -> "IntSerializer" "java.lang.Long" -> "LongSerializer" "java.lang.Float" -> "FloatSerializer" "java.lang.Double" -> "DoubleSerializer" "java.lang.Character" -> "CharSerializer" "java.lang.String" -> "StringSerializer" "java.util.Collection", "java.util.List", "java.util.ArrayList" -> "ArrayListSerializer" "java.util.Set", "java.util.LinkedHashSet" -> "LinkedHashSetSerializer" "java.util.HashSet" -> "HashSetSerializer" "java.util.Map", "java.util.LinkedHashMap" -> "LinkedHashMapSerializer" "java.util.HashMap" -> "HashMapSerializer" "java.util.Map.Entry" -> "MapEntrySerializer" // mirai "java.util.concurrent.ConcurrentMap", "java.util.concurrent.ConcurrentHashMap", -> "ConcurrentMap" // dummy name else -> return false } return true } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/PluginMainServiceNotConfiguredInspection.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.application.runReadAction import com.intellij.openapi.progress.impl.CancellationCheck.Companion.runWithCancellationCheck import com.intellij.openapi.project.rootManager import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiNameIdentifierOwner import net.mamoe.mirai.console.compiler.common.resolve.AUTO_SERVICE import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME import net.mamoe.mirai.console.intellij.diagnostics.fix.ConfigurePluginMainServiceFix import net.mamoe.mirai.console.intellij.resolve.allSuperNames import net.mamoe.mirai.console.intellij.resolve.hasAnnotation import org.jetbrains.kotlin.idea.base.util.module import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtObjectDeclaration import org.jetbrains.kotlin.psi.classOrObjectVisitor /* private val bundle by lazy { BundleUtil.loadLanguageBundle(PluginMainServiceNotConfiguredInspection::class.java.classLoader, "messages.InspectionGadgetsBundle")!! }*/ class PluginMainServiceNotConfiguredInspection : AbstractKotlinInspection() { companion object { private val SERVICE_FILE_NAMES = arrayOf( "net.mamoe.mirai.console.plugin.jvm.JvmPlugin", "net.mamoe.mirai.console.plugin.jvm.KotlinPlugin", "net.mamoe.mirai.console.plugin.jvm.JavaPlugin", ) } override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { val ktVisitor = classOrObjectVisitor visitor@{ element -> if (element !is KtObjectDeclaration) return@visitor if (element.allSuperNames.none { it == PLUGIN_FQ_NAME }) return@visitor val fqName = element.fqName?.asString() ?: return@visitor val found = isServiceConfiguredWithAutoService(element) || isServiceConfiguredWithResource(element, fqName) if (!found) { registerProblemImpl(holder, element, fqName) } } val javaVisitor = object : PsiElementVisitor() { override fun visitElement(element: PsiElement) { if (element !is PsiClass) return if (element.allSuperNames.none { it == PLUGIN_FQ_NAME }) return if (element.hasAnnotation(AUTO_SERVICE.asString())) return val fqName = element.qualifiedName ?: return if (isServiceConfiguredWithResource(element, fqName)) return registerProblemImpl(holder, element, fqName) } } return object : PsiElementVisitor() { override fun visitElement(element: PsiElement) { super.visitElement(element) if (element is KtClassOrObject) ktVisitor.visitClassOrObject(element) else javaVisitor.visitElement(element) } } } private fun registerProblemImpl(holder: ProblemsHolder, element: PsiNameIdentifierOwner, fqName: String) { holder.registerProblem( element.nameIdentifier ?: element.identifyingElement ?: element, @Suppress("DialogTitleCapitalization") "插件主类服务未配置", ProblemHighlightType.WARNING, ConfigurePluginMainServiceFix(element, fqName) ) } private fun isServiceConfiguredWithAutoService( ktClass: KtClassOrObject, ): Boolean { return ktClass.hasAnnotation(AUTO_SERVICE) } private fun isServiceConfiguredWithResource( psiOrKtClass: PsiElement, fqName: String, ): Boolean { return runWithCancellationCheck { val sourceRoots: Array<com.intellij.openapi.vfs.VirtualFile> = psiOrKtClass.module?.rootManager?.sourceRoots ?: return@runWithCancellationCheck false val services = sourceRoots.asSequence().flatMap { file -> SERVICE_FILE_NAMES.asSequence().mapNotNull { serviceFileName -> file.findFileByRelativePath("META-INF/services/$serviceFileName") } } return@runWithCancellationCheck services.any { serviceFile -> runReadAction { serviceFile.inputStream.bufferedReader() .use { reader -> reader.lineSequence().any { it == fqName } } } } } } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/QuickFixUtils.kt ================================================ /* * 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.console.intellij.diagnostics import com.intellij.codeInspection.LocalQuickFix import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction fun <T : PsiElement> LocalQuickFix( text: String, element: T, invokeAction: QuickFixInvoke<T>.() -> Unit ): LocalQuickFix { return object : KotlinCrossLanguageQuickFixAction<T>(element), KotlinUniversalQuickFix { @Suppress("DialogTitleCapitalization") override fun getFamilyName(): String = "Mirai console" override fun getText(): String = text override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { invokeAction(QuickFixInvoke(project, editor ?: return, file, this.element ?: return)) } } } class QuickFixInvoke<T>( val project: Project, val editor: Editor, val file: PsiFile, val element: T, ) ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/ResourceNotClosedInspection.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.codeInspection.LocalQuickFix import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.* import net.mamoe.mirai.console.intellij.resolve.* import org.jetbrains.kotlin.idea.base.psi.kotlinFqName import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor import org.jetbrains.kotlin.idea.util.ImportInsertHelper import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.referenceExpression import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import kotlin.contracts.contract /* private val bundle by lazy { BundleUtil.loadLanguageBundle(PluginMainServiceNotConfiguredInspection::class.java.classLoader, "messages.InspectionGadgetsBundle")!! }*/ /** * @since 2.4 */ class ResourceNotClosedInspection : AbstractKotlinInspection() { override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor { return object : KtVisitorVoid() { override fun visitCallExpression(callExpression: KtCallExpression) { for (processor in ResourceNotClosedInspectionProcessors.processors) { processor.visitKtExpr(holder, isOnTheFly, callExpression) } } override fun visitElement(element: PsiElement) { if (element is PsiCallExpression) { for (processor in ResourceNotClosedInspectionProcessors.processors) { processor.visitPsiExpr(holder, isOnTheFly, element) } } } } } } val CONTACT_FQ_NAME = FqName("net.mamoe.mirai.contact.Contact") val CONTACT_COMPANION_FQ_NAME = FqName("net.mamoe.mirai.contact.Contact.Companion") fun KtReferenceExpression.resolveCalleeFunction(): KtNamedFunction? { val originalCallee = getCalleeExpressionIfAny()?.referenceExpression()?.mainReference?.resolve() ?: return null if (originalCallee !is KtNamedFunction) return null return originalCallee } fun KtNamedFunction.isNamedMemberFunctionOf( className: String, functionName: String, extensionReceiver: String? = null ): Boolean { if (extensionReceiver != null) { if (this.receiverTypeReference?.resolveReferencedType()?.kotlinFqName ?.toString() != extensionReceiver ) return false } return this.name == functionName && this.containingClassOrObject?.allSuperTypes?.any { it.kotlinFqName?.toString() == className } == true } @Suppress("DialogTitleCapitalization") object ResourceNotClosedInspectionProcessors { val processors = arrayOf( FirstArgumentProcessor, KtExtensionProcessor ) interface Processor { fun visitKtExpr(holder: ProblemsHolder, isOnTheFly: Boolean, callExpr: KtCallExpression) fun visitPsiExpr(holder: ProblemsHolder, isOnTheFly: Boolean, expr: PsiCallExpression) } object KtExtensionProcessor : Processor { // net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImage(net.mamoe.mirai.utils.ExternalResource, C, kotlin.coroutines.Continuation<? super net.mamoe.mirai.message.MessageReceipt<? extends C>>) val SEND_AS_IMAGE_TO = FunctionSignature { name("sendAsImageTo") dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion") extensionReceiver("net.mamoe.mirai.utils.ExternalResource") } val UPLOAD_AS_IMAGE = FunctionSignature { name("uploadAsImage") dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion") extensionReceiver("net.mamoe.mirai.utils.ExternalResource") } override fun visitKtExpr(holder: ProblemsHolder, isOnTheFly: Boolean, callExpr: KtCallExpression) { val parent = callExpr.parent if (parent !is KtDotQualifiedExpression) return val callee = callExpr.resolveCalleeFunction() ?: return if (!parent.receiverExpression.isCallingExternalResourceCreators()) return class Fix(private val functionName: String) : KotlinCrossLanguageQuickFixAction<KtDotQualifiedExpression>(parent), KotlinUniversalQuickFix { override fun getFamilyName(): String = FAMILY_NAME override fun getText(): String = "修复 $functionName" override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { if (editor == null) return val uploadImageExpression = element ?: return val toExternalExpression = uploadImageExpression.receiverExpression val toExternalReceiverExpression = toExternalExpression.dotReceiverExpression() ?: return toExternalExpression.replace(toExternalReceiverExpression) } } when { callee.hasSignature(SEND_AS_IMAGE_TO) -> { // RECEIVER.sendAsImageTo holder.registerResourceNotClosedProblem( parent.receiverExpression, Fix("sendAsImageTo"), ) } callee.hasSignature(UPLOAD_AS_IMAGE) -> { holder.registerResourceNotClosedProblem( parent.receiverExpression, Fix("uploadAsImage"), ) } } } override fun visitPsiExpr(holder: ProblemsHolder, isOnTheFly: Boolean, expr: PsiCallExpression) { } } object FirstArgumentProcessor : Processor { val CONTACT_UPLOAD_IMAGE = FunctionSignature { name("uploadImage") dispatchReceiver(CONTACT_FQ_NAME) parameters("net.mamoe.mirai.utils.ExternalResource") } val CONTACT_UPLOAD_IMAGE_STATIC = FunctionSignature { name("uploadImage") extensionReceiver(CONTACT_FQ_NAME) dispatchReceiver(CONTACT_COMPANION_FQ_NAME) parameters("net.mamoe.mirai.utils.ExternalResource") } val CONTACT_COMPANION_UPLOAD_IMAGE = FunctionSignature { name("uploadImage") extensionReceiver(CONTACT_FQ_NAME) parameters("net.mamoe.mirai.utils.ExternalResource") } val CONTACT_COMPANION_SEND_IMAGE = FunctionSignature { name("sendImage") extensionReceiver(CONTACT_FQ_NAME) parameters("net.mamoe.mirai.utils.ExternalResource") } private val signatures = arrayOf( CONTACT_UPLOAD_IMAGE, CONTACT_COMPANION_UPLOAD_IMAGE, CONTACT_COMPANION_SEND_IMAGE ) override fun visitKtExpr(holder: ProblemsHolder, isOnTheFly: Boolean, callExpr: KtCallExpression) { val callee = callExpr.resolveCalleeFunction() ?: return if (signatures.none { callee.hasSignature(it) }) return val firstArgument = callExpr.valueArguments.firstOrNull() ?: return val firstArgumentExpr = firstArgument.getArgumentExpression() if (firstArgumentExpr?.isCallingExternalResourceCreators() != true) return holder.registerResourceNotClosedProblem( firstArgument, LocalQuickFix("修复", firstArgumentExpr) { fun tryAddImport() { if (file !is KtFile) return val companion = callee.descriptor?.containingDeclaration?.companionObjectDescriptor() ?: return val toImport = companion.findMemberFunction(callee.nameAsName ?: return) ?: return // net.mamoe.mirai.contact.Contact.Companion ImportInsertHelper.getInstance(project).importDescriptor(file, toImport) } val newArgumentText = element.dotReceiverExpression()?.text ?: return@LocalQuickFix callExpr.replace(KtPsiFactory(project).createExpression(buildString { append(callee.name) append('(') append(newArgumentText) append(')') })) tryAddImport() } ) } override fun visitPsiExpr(holder: ProblemsHolder, isOnTheFly: Boolean, expr: PsiCallExpression) { if (expr !is PsiMethodCallExpression) return val callee = expr.resolveMethod() ?: return val arguments = expr.argumentList.expressions when { callee.hasSignature(CONTACT_UPLOAD_IMAGE) -> { createFixImpl( expr = expr, holder = holder, argument = arguments.firstOrNull() ?: return, fileTypeArgument = arguments.getOrNull(1) ) { it.methodExpression.qualifierExpression?.text ?: "this" } } callee.hasSignature(CONTACT_UPLOAD_IMAGE_STATIC) -> { createFixImpl( expr = expr, holder = holder, argument = arguments.getOrNull(1) ?: return, fileTypeArgument = arguments.getOrNull(2) ) { arguments.getOrNull(0)?.text ?: "this" } } } } private fun createFixImpl( expr: PsiMethodCallExpression, holder: ProblemsHolder, argument: PsiExpression, fileTypeArgument: PsiExpression?, replaceForFirstArgument: (expr: PsiMethodCallExpression) -> String, ) { if (!argument.isCallingExternalResourceCreators()) return holder.registerResourceNotClosedProblem( argument, LocalQuickFix("修复", argument) { /* useImage(Contact.uploadImage(contact, ExternalResource.create(file))); // before useImage(Contact.uploadImage(contact, file)); // after */ val factory = project.psiElementFactory ?: return@LocalQuickFix val reference = factory.createExpressionFromText( if (fileTypeArgument == null) { "$CONTACT_FQ_NAME.uploadImage(${replaceForFirstArgument(expr)}, ${argument.argumentList?.expressions?.firstOrNull()?.text ?: ""})" } else { "$CONTACT_FQ_NAME.uploadImage(${replaceForFirstArgument(expr)}, ${argument.argumentList?.expressions?.firstOrNull()?.text ?: ""}, ${fileTypeArgument.text})" }, expr.context ) expr.replace(reference) } ) } } private fun ProblemsHolder.registerResourceNotClosedProblem(target: PsiElement, vararg fixes: LocalQuickFix) { registerProblem( target, @Suppress("DialogTitleCapitalization") "资源未关闭", ProblemHighlightType.WARNING, *fixes ) } } private val EXTERNAL_RESOURCE_CREATE = FunctionSignature { name("create") dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion") } private val TO_EXTERNAL_RESOURCE = FunctionSignature { name("toExternalResource") dispatchReceiver("net.mamoe.mirai.utils.ExternalResource.Companion") } fun KtExpression.isCallingExternalResourceCreators(): Boolean { val callExpr = resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor ?: return false return callExpr.hasSignature(EXTERNAL_RESOURCE_CREATE) || callExpr.hasSignature(TO_EXTERNAL_RESOURCE) } fun PsiExpression.isCallingExternalResourceCreators(): Boolean { contract { returns() implies (this@isCallingExternalResourceCreators is PsiCallExpression) } if (this !is PsiCallExpression) return false val callee = resolveMethod() ?: return false return callee.hasSignature(EXTERNAL_RESOURCE_CREATE) } private const val FAMILY_NAME = "Mirai console" ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/TaskUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.io.writeChild import org.intellij.lang.annotations.Language import java.nio.file.Path val Path.vfOrNull: VirtualFile? get() = LocalFileSystem.getInstance().refreshAndFindFileByPath(this.toAbsolutePath().toString()) val Path.vf: VirtualFile get() = vfOrNull ?: error("Failed to resolve VirtualFile ${this.toAbsolutePath()}") fun VirtualFile.readText(): String? = if (this.exists() && !this.isDirectory) String(inputStream.use { it.readBytes() }) else null fun VirtualFile.readChildText(relative: String): String? = this.resolve(relative)?.readText() fun VirtualFile.resolve(relative: String): VirtualFile? = VfsUtil.findRelativeFile( this, *relative.replace('\\', '/').split('/').toTypedArray() ) @PublishedApi internal inline fun <T> computeDelegated(executor: (setter: (T) -> Unit) -> Unit): T { var resultRef: T? = null executor { resultRef = it } @Suppress("UNCHECKED_CAST") return resultRef as T } internal fun VirtualFile.writeChild(path: String, content: String): Path { return toNioPath().writeChild(path, content) } @Language("RegExp") const val CLASS_NAME_PATTERN = "[a-zA-Z]+[0-9a-zA-Z_]*" // self written @Language("RegExp") const val PACKAGE_PATTERN = """[a-zA-Z]+[0-9a-zA-Z_]*(\.[a-zA-Z]+[0-9a-zA-Z_]*)*""" @Language("RegExp") const val QUALIFIED_CLASS_NAME_PATTERN = """($PACKAGE_PATTERN\.)?$CLASS_NAME_PATTERN""" // self written fun String.isValidQualifiedClassName(): Boolean = this matches Regex(QUALIFIED_CLASS_NAME_PATTERN) fun String.isValidPackageName(): Boolean = this matches Regex(PACKAGE_PATTERN) fun String.isValidSimpleClassName(): Boolean = this matches Regex(CLASS_NAME_PATTERN) fun String.adjustToClassName(): String? { val result = buildString { var doCapitalization = true fun Char.isAllowed() = isLetterOrDigit() || this in "_-" for (char in this@adjustToClassName) { if (!char.isAllowed()) continue if (doCapitalization) { when { char.isDigit() -> { if (this.isEmpty()) append('_') append(char) } char.isLetter() -> append(char.uppercase()) char == '-' -> append("_") else -> append(char) } doCapitalization = false } else { if (char in "_-") { doCapitalization = true } else { append(char) } } } } if (result.isValidSimpleClassName()) return result return null } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/UsingStringPlusMessageInspection.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.codeInspection.ProblemHighlightType import com.intellij.codeInspection.ProblemsHolder import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiElementVisitor import com.intellij.psi.PsiFile import net.mamoe.mirai.console.intellij.resolve.* import org.jetbrains.kotlin.idea.codeinsight.api.classic.inspections.AbstractKotlinInspection import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver import org.jetbrains.kotlin.psi.psiUtil.referenceExpression import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny /* private val bundle by lazy { BundleUtil.loadLanguageBundle(PluginMainServiceNotConfiguredInspection::class.java.classLoader, "messages.InspectionGadgetsBundle")!! }*/ class UsingStringPlusMessageInspection : AbstractKotlinInspection() { companion object { const val DESCRIPTION = "使用 String + Message 会导致 Message 被转换为 String 再相加" private const val MESSAGE_FQ_NAME_STR = "net.mamoe.mirai.message.data.Message" private const val CONVERT_TO_PLAIN_TEXT = "将 String 转换为 PlainText" fun KtReferenceExpression.isCallingStringPlus(): Boolean { val callee = this.referenceExpression()?.mainReference?.resolve() ?: return false if (callee !is KtNamedFunction) return false val className = callee.containingClassOrObject?.fqName?.asString() if (className != "kotlin.String") return false if (callee.name != "plus") return false return true } } private class Visitor( val holder: ProblemsHolder ) : KtVisitorVoid() { class BinaryExprFix(left: KtExpression) : ConvertToPlainTextFix<KtExpression>(left) { override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { if (editor == null || file !is KtFile) return val element = element ?: return val referenceExpr = element.referenceExpression() if (referenceExpr == null || element.parent is KtBinaryExpression) { // `+ operator`, e.g. `str + msg` // or // complex expressions, e.g. `str.toString().plus(msg)`, `"".also { }.plus(msg)` val replaced = element.replace(KtPsiFactory(project).createExpression("net.mamoe.mirai.message.data.PlainText(${element.text})")) as? KtElement ?: return ShortenReferences.DEFAULT.process(replaced) return } if (element is KtNameReferenceExpression) { val receiver = element.getQualifiedExpressionForReceiver() ?: return val replaced = receiver .replace(KtPsiFactory(project).createExpression("net.mamoe.mirai.message.data.PlainText(${receiver.text})")) as? KtElement ?: return ShortenReferences.DEFAULT.process(replaced) } } } override fun visitBinaryExpression(binaryExpression: KtBinaryExpression) { if (binaryExpression.operationToken != KtTokens.PLUS) return if (binaryExpression.left?.getCalleeExpressionIfAny()?.typeFqName()?.toString() != "kotlin.String") return val rightType = binaryExpression.right?.type() ?: return if (!rightType.hasSuperType(MESSAGE_FQ_NAME_STR)) return val left = binaryExpression.left ?: return holder.registerProblem( left, DESCRIPTION, ProblemHighlightType.WARNING, BinaryExprFix(left) ) } override fun visitCallExpression(expression: KtCallExpression) { if (!expression.isCallingStringPlus()) return val argumentType = expression.valueArguments.singleOrNull()?.type() ?: return if (!argumentType.hasSuperType(MESSAGE_FQ_NAME_STR)) return val explicitReceiverExpr = expression.siblingDotReceiverExpression() if (explicitReceiverExpr != null) { holder.registerProblem( explicitReceiverExpr, DESCRIPTION, ProblemHighlightType.WARNING, LocalQuickFix(CONVERT_TO_PLAIN_TEXT, explicitReceiverExpr) { element.replaceExpressionAndShortenReferences("net.mamoe.mirai.message.data.PlainText(${element.text})") } ) } else { val nameReferenceExpression = expression.findChild<KtNameReferenceExpression>() ?: expression.calleeExpression ?: expression holder.registerProblem( nameReferenceExpression, DESCRIPTION, ProblemHighlightType.WARNING, LocalQuickFix(CONVERT_TO_PLAIN_TEXT, expression) { val callExpression = this.element.calleeExpression ?: return@LocalQuickFix val implicitReceiverText = this.element.implicitExpressionText() ?: return@LocalQuickFix this.element.replaceExpressionAndShortenReferences( "net.mamoe.mirai.message.data.PlainText(${implicitReceiverText}).${callExpression.text}" ) } ) } } } override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor = Visitor(holder) abstract class ConvertToPlainTextFix<T : PsiElement>(element: T) : KotlinCrossLanguageQuickFixAction<T>(element), KotlinUniversalQuickFix { @Suppress("DialogTitleCapitalization") override fun getFamilyName(): String = "Mirai Console" @Suppress("DialogTitleCapitalization") override fun getText(): String = "将 String 转换为 PlainText" } } fun KtElement.replaceExpressionAndShortenReferences(expression: String) { val replaced = replace(KtPsiFactory(this.project).createExpression(expression)) as? KtElement ?: return ShortenReferences.DEFAULT.process(replaced) } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/diagnosticsUtil.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics import com.intellij.psi.PsiElement import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.resolve.READ_ONLY_PLUGIN_DATA_FQ_NAME import net.mamoe.mirai.console.intellij.resolve.getResolvedCall import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.base.psi.kotlinFqName import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtTypeParameter import org.jetbrains.kotlin.psi.KtTypeReference import org.jetbrains.kotlin.psi.KtUserType import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperClassifiers import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlinx.serialization.compiler.resolve.toClassDescriptor fun KotlinType.isSubtypeOfReadOnlyPluginData(): Boolean { return this.toClassDescriptor?.getAllSuperClassifiers() ?.any { it.fqNameOrNull() == READ_ONLY_PLUGIN_DATA_FQ_NAME } == true } fun DeclarationCheckerContext.report(diagnostic: Diagnostic) { return this.trace.report(diagnostic) } val DeclarationCheckerContext.bindingContext get() = this.trace.bindingContext fun KtElement.getResolvedCall( context: DeclarationCheckerContext, ): ResolvedCall<out CallableDescriptor>? { return this.getResolvedCall(context.bindingContext) } fun KtTypeReference.isReferencing(fqName: FqName): Boolean { return resolveReferencedType()?.kotlinFqName == fqName } val KtTypeReference.referencedUserType: KtUserType? get() = this.typeElement.castOrNull() fun KtTypeReference.resolveReferencedType(): PsiElement? { val resolved = referencedUserType?.referenceExpression?.mainReference?.resolve() if (resolved is KtTypeParameter) { val bound = resolved.extendsBound ?: return resolved if (bound.name == resolved.name) return null // <C: C> bad type, avoid infinite run return bound.resolveReferencedType() } return resolved } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/fix/AbuseYellowIntention.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics.fix /* import com.intellij.openapi.editor.Editor import net.mamoe.mirai.console.intellij.resolve.resolveStringConstantValues import org.jetbrains.kotlin.idea.intentions.SelfTargetingIntention import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtStringTemplateExpression @Suppress("IntentionDescriptionNotFoundInspection") // class AbuseYellowIntention : SelfTargetingIntention<KtStringTemplateExpression>(KtStringTemplateExpression::class.java, { "Abuse yellow" }, { "Abuse yellow" }) { override fun applyTo(element: KtStringTemplateExpression, editor: Editor?) { element.replace(KtPsiFactory(element).createExpression("\"弱智黄色\"")) } override fun isApplicableTo(element: KtStringTemplateExpression, caretOffset: Int): Boolean { return element.resolveStringConstantValues().firstOrNull() == "黄色" } }*/ ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/fix/AddSerializerFix.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics.fix import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import net.mamoe.mirai.console.compiler.common.SERIALIZABLE_FQ_NAME import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters1 import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.idea.util.addAnnotation import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi import org.jetbrains.kotlin.psi.KtClassOrObject import org.jetbrains.kotlin.psi.KtModifierListOwner /** * @see MiraiConsoleErrors.UNSERIALIZABLE_TYPE */ class AddSerializerFix( element: KtClassOrObject, ) : KotlinCrossLanguageQuickFixAction<KtModifierListOwner>(element), KotlinUniversalQuickFix { override fun getFamilyName(): String = "Mirai Console" override fun getText(): String = "添加 @Serializable" override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { element?.addAnnotation(SERIALIZABLE_FQ_NAME) ?: return } companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val classDescriptor = diagnostic.castOrNull<DiagnosticWithParameters1<*, *>>()?.a?.castOrNull<ClassDescriptor>() ?: return null val ktClassOrObject = classDescriptor.findPsi()?.castOrNull<KtClassOrObject>() ?: return null return AddSerializerFix(ktClassOrObject) } override fun isApplicableForCodeFragment(): Boolean = false } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/fix/ConfigurePluginMainServiceFix.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics.fix import com.intellij.codeInspection.LocalQuickFix import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.openapi.project.rootManager import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import net.mamoe.mirai.console.intellij.diagnostics.readChildText import net.mamoe.mirai.console.intellij.diagnostics.writeChild import org.jetbrains.kotlin.idea.base.util.isAndroidModule import org.jetbrains.kotlin.idea.base.util.module import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.util.application.executeWriteCommand class ConfigurePluginMainServiceFix( element: PsiElement, private val fqName: String, ) : KotlinCrossLanguageQuickFixAction<PsiElement>(element), KotlinUniversalQuickFix, LocalQuickFix { override fun getFamilyName(): String = "Mirai Console" override fun getText(): String = "配置插件主类服务" override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { val elementFqName = fqName val sourceRoots = file.module?.rootManager?.sourceRoots ?: return val sourceRoot = sourceRoots.find { it.name.endsWith("resources") } ?: sourceRoots.find { it.name.endsWith("res") } ?: sourceRoots.find { it.name.contains("resources") } ?: sourceRoots.find { it.name.contains("res") } ?: sourceRoots.last().run { project.executeWriteCommand(name, groupId = null) { parent.createChildDirectory( this@ConfigurePluginMainServiceFix, if (file.module?.isAndroidModule() == true) "res" else "resources" ) } } project.executeWriteCommand(name) { val filepath = "META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin" fun computeContent(): String { val origin = sourceRoot.readChildText(filepath) ?: "" return when { origin.isBlank() -> elementFqName origin.endsWith("\n") -> origin + elementFqName else -> "$origin\n$elementFqName" } } sourceRoot.writeChild(filepath, computeContent()) VfsUtil.markDirtyAndRefresh(true, false, false, sourceRoot.findChild(filepath)) } } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/fix/ConvertToValFix.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics.fix import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.SimpleDiagnostic import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.psi.KtPsiFactory /** * @see MiraiConsoleErrors.READ_ONLY_VALUE_CANNOT_BE_VAR */ class ConvertToValFix( element: PsiElement, ) : KotlinCrossLanguageQuickFixAction<PsiElement>(element), KotlinUniversalQuickFix { override fun getFamilyName(): String = "Mirai Console" override fun getText(): String = "转换为 val" override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { val element = element ?: return project.executeWriteCommand(name) { element.replace(KtPsiFactory(project).createValKeyword()) } } companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val diagnostic1 = diagnostic.castOrNull<SimpleDiagnostic<PsiElement>>() ?: return null return ConvertToValFix(diagnostic1.psiElement) } override fun isApplicableForCodeFragment(): Boolean = false } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/fix/ProvideDefaultValueFix.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics.fix import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import com.intellij.psi.SmartPointerManager import com.intellij.psi.SmartPsiElementPointer import net.mamoe.mirai.console.compiler.common.castOrNull import net.mamoe.mirai.console.compiler.common.diagnostics.MiraiConsoleErrors import net.mamoe.mirai.console.intellij.resolve.findChild import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.diagnostics.DiagnosticWithParameters2 import org.jetbrains.kotlin.idea.core.moveCaret import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.idea.util.application.executeWriteCommand import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.endOffset /** * @see MiraiConsoleErrors.NOT_CONSTRUCTABLE_TYPE */ class ProvideDefaultValueFix( element: KtCallExpression, private val typeProjection: SmartPsiElementPointer<KtTypeProjection>, ) : KotlinCrossLanguageQuickFixAction<KtCallExpression>(element), KotlinUniversalQuickFix { override fun getFamilyName(): String = "Mirai Console" override fun getText(): String = "添加默认值" override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { val element = element ?: return if (file !is KtFile) return /* val refereeFqName = element.resolve()?.getKotlinFqName() ?: return val referee = file.resolveImportReference(refereeFqName).singleOrNull { it is ClassDescriptor } ?: return ImportInsertHelper.getInstance(project).importDescriptor(file, referee) */ val typeName = typeProjection.element?.typeReference?.typeElement?.castOrNull<KtUserType>()?.referencedName ?: return val argumentList = element.findChild<KtValueArgumentList>() ?: return val offset = argumentList.leftParenthesis?.endOffset ?: return project.executeWriteCommand(name) { argumentList.addArgument(KtPsiFactory(project).createArgument("$typeName()")) editor?.moveCaret(offset + typeName.length + 1) } } companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { val diagnostic1 = diagnostic.castOrNull<DiagnosticWithParameters2<KtTypeProjection, KtCallExpression, *>>() ?: return null return ProvideDefaultValueFix(diagnostic1.a, SmartPointerManager.createPointer(diagnostic1.psiElement)) } override fun isApplicableForCodeFragment(): Boolean = false } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/fix/TypeProjectionFix.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.diagnostics.fix import com.intellij.codeInsight.intention.IntentionAction import com.intellij.openapi.editor.Editor import com.intellij.openapi.project.Project import com.intellij.psi.PsiFile import org.jetbrains.kotlin.diagnostics.Diagnostic import org.jetbrains.kotlin.idea.base.psi.replaced import org.jetbrains.kotlin.idea.core.ShortenReferences import org.jetbrains.kotlin.idea.inspections.KotlinUniversalQuickFix import org.jetbrains.kotlin.idea.quickfix.KotlinCrossLanguageQuickFixAction import org.jetbrains.kotlin.idea.quickfix.KotlinSingleIntentionActionFactory import org.jetbrains.kotlin.psi.KtPsiFactory import org.jetbrains.kotlin.psi.KtTypeProjection abstract class AbstractTypeProjectionFix( element: KtTypeProjection, private val newTypeFqn: String, ) : KotlinCrossLanguageQuickFixAction<KtTypeProjection>(element), KotlinUniversalQuickFix { override fun getFamilyName(): String = "Mirai console" override fun getText(): String = "转化为 ${newTypeFqn.substringAfterLast('.')}" override fun invokeImpl(project: Project, editor: Editor?, file: PsiFile) { val element = element ?: return val arguments = element.text.substringAfter('<', "") val newTypeElement = KtPsiFactory(project).createTypeArgument( if (arguments.isBlank()) { newTypeFqn } else "$newTypeFqn<$arguments" ) val e = element.replaced(newTypeElement) // ABI change ShortenReferences.DEFAULT.process(e) } } class ConvertToMutableMapFix( element: KtTypeProjection, ) : AbstractTypeProjectionFix(element, "kotlin.collections.MutableMap") { companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { return ConvertToMutableMapFix(diagnostic.psiElement as? KtTypeProjection ?: return null) } override fun isApplicableForCodeFragment(): Boolean = false } } class ConvertToMapFix( element: KtTypeProjection, ) : AbstractTypeProjectionFix(element, "kotlin.collections.Map") { companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { return ConvertToMapFix(diagnostic.psiElement as? KtTypeProjection ?: return null) } override fun isApplicableForCodeFragment(): Boolean = false } } class ConvertToConcurrentMapFix( element: KtTypeProjection, ) : AbstractTypeProjectionFix(element, "java.util.concurrent.ConcurrentHashMap") { companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { return ConvertToConcurrentMapFix(diagnostic.psiElement as? KtTypeProjection ?: return null) } override fun isApplicableForCodeFragment(): Boolean = false } } class ConvertToListFix( element: KtTypeProjection, ) : AbstractTypeProjectionFix(element, "kotlin.collections.List") { companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { return ConvertToListFix(diagnostic.psiElement as? KtTypeProjection ?: return null) } override fun isApplicableForCodeFragment(): Boolean = false } } class ConvertToMutableListFix( element: KtTypeProjection, ) : AbstractTypeProjectionFix(element, "kotlin.collections.MutableList") { companion object : KotlinSingleIntentionActionFactory() { override fun createAction(diagnostic: Diagnostic): IntentionAction? { return ConvertToListFix(diagnostic.psiElement as? KtTypeProjection ?: return null) } override fun isApplicableForCodeFragment(): Boolean = false } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/diagnostics/fix/WrapWithResourceUseCallIntention.kt ================================================ /* * 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.console.intellij.diagnostics.fix import com.intellij.openapi.editor.Editor import com.intellij.openapi.projectRoots.JavaSdkVersion import com.intellij.openapi.projectRoots.JavaVersionService import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethodCallExpression import net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspectionProcessors.KtExtensionProcessor.SEND_AS_IMAGE_TO import net.mamoe.mirai.console.intellij.diagnostics.ResourceNotClosedInspectionProcessors.KtExtensionProcessor.UPLOAD_AS_IMAGE import net.mamoe.mirai.console.intellij.diagnostics.replaceExpressionAndShortenReferences import net.mamoe.mirai.console.intellij.diagnostics.resolveCalleeFunction import net.mamoe.mirai.console.intellij.resolve.hasSignature import org.jetbrains.kotlin.idea.base.util.module import org.jetbrains.kotlin.idea.codeinsight.api.classic.intentions.SelfTargetingIntention import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.psiUtil.referenceExpression /** * @since 2.4 */ class WrapWithResourceUseCallIntention : SelfTargetingIntention<KtDotQualifiedExpression>(KtDotQualifiedExpression::class.java, { "转换为 .use" }) { override fun applyTo(element: KtDotQualifiedExpression, editor: Editor?) { val selectorExpression = element.selectorExpression ?: return selectorExpression.replaceExpressionAndShortenReferences("use { it.${selectorExpression.text} }") } override fun isApplicableTo(element: KtDotQualifiedExpression, caretOffset: Int): Boolean { val callee = element.selectorExpression?.referenceExpression()?.resolveCalleeFunction() ?: return false if (!callee.hasSignature(UPLOAD_AS_IMAGE) && !callee.hasSignature(SEND_AS_IMAGE_TO)) return false val receiver = element.receiverExpression return receiver is KtSimpleNameExpression } } // https://github.com/mamoe/mirai-console/issues/284 /** * * to be supported by 2.5 * @since 2.4 */ class WrapWithResourceUseCallJavaIntention : SelfTargetingIntention<PsiMethodCallExpression>(PsiMethodCallExpression::class.java, { "转换为 .use" }) { override fun applyTo(element: PsiMethodCallExpression, editor: Editor?) { // val selectorExpression = element.methodExpression } override fun isApplicableTo(element: PsiMethodCallExpression, caretOffset: Int): Boolean { return false // if (!element.isJavaVersionAtLeast(JavaSdkVersion.JDK_1_9)) return false } } fun PsiElement.isJavaVersionAtLeast(version: JavaSdkVersion): Boolean { return this.module?.getService(JavaVersionService::class.java)?.isAtLeast(this, version) == true } ================================================ FILE: mirai-console/tools/intellij-plugin/src/line/marker/CommandDeclarationLineMarkerProvider.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.line.marker import com.intellij.codeInsight.daemon.LineMarkerInfo import com.intellij.codeInsight.daemon.LineMarkerProvider import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod import net.mamoe.mirai.console.intellij.assets.Icons import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark import net.mamoe.mirai.console.intellij.resolve.isSimpleCommandHandlerOrCompositeCommandSubCommand import net.mamoe.mirai.console.intellij.util.runIgnoringErrors import org.jetbrains.kotlin.psi.KtNamedFunction class CommandDeclarationLineMarkerProvider : LineMarkerProvider { override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { when (element) { is KtNamedFunction -> { if (!element.isSimpleCommandHandlerOrCompositeCommandSubCommand()) return null runIgnoringErrors { // not showing icons is better than throwing exception every time doing inspection return Info(getElementForLineMark(element.funKeyword ?: element.identifyingElement ?: element)) } } is PsiMethod -> { if (!element.isSimpleCommandHandlerOrCompositeCommandSubCommand()) return null runIgnoringErrors { // not showing icons is better than throwing exception every time doing inspection return Info(getElementForLineMark(element.identifyingElement ?: element)) } } else -> return null } } class Info(callElement: PsiElement) : LineMarkerInfo<PsiElement>( callElement, callElement.textRange, Icons.CommandDeclaration, { "子指令定义" }, null, GutterIconRenderer.Alignment.RIGHT, { "子指令定义" } ) } ================================================ FILE: mirai-console/tools/intellij-plugin/src/line/marker/PluginMainLineMarkerProvider.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.line.marker import com.intellij.codeInsight.daemon.LineMarkerInfo import com.intellij.codeInsight.daemon.LineMarkerProvider import com.intellij.openapi.editor.markup.GutterIconRenderer import com.intellij.psi.PsiElement import net.mamoe.mirai.console.compiler.common.resolve.PLUGIN_FQ_NAME import net.mamoe.mirai.console.intellij.assets.Icons import net.mamoe.mirai.console.intellij.resolve.allSuperNames import net.mamoe.mirai.console.intellij.resolve.getElementForLineMark import net.mamoe.mirai.console.intellij.util.runIgnoringErrors class PluginMainLineMarkerProvider : LineMarkerProvider { override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? { runIgnoringErrors { // not showing icons is better than throwing exception every time doing inspection if (element.allSuperNames.any { it == PLUGIN_FQ_NAME }) return Info(getElementForLineMark(element)) } return null } class Info(callElement: PsiElement) : LineMarkerInfo<PsiElement>( callElement, callElement.textRange, Icons.PluginMainDeclaration, { "Mirai Console Plugin" }, null, GutterIconRenderer.Alignment.RIGHT, { "Mirai Console Plugin" } ) { } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/resolve/FunctionSignature.kt ================================================ /* * 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.console.intellij.resolve import com.intellij.psi.PsiMethod import com.intellij.psi.PsiModifier import net.mamoe.mirai.console.intellij.diagnostics.resolveReferencedType import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.idea.base.psi.kotlinFqName import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.quickfix.createFromUsage.callableBuilder.getReturnTypeReference import org.jetbrains.kotlin.idea.refactoring.fqName.fqName import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtFunction import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode inline fun FunctionSignature(builderAction: FunctionSignatureBuilder.() -> Unit): FunctionSignature { return FunctionSignatureBuilder().apply(builderAction).build() } data class FunctionSignature( val name: String? = null, val dispatchReceiver: FqName? = null, val extensionReceiver: FqName? = null, val parameters: List<FqName>? = null, val returnType: FqName? = null, ) class FunctionSignatureBuilder { private var name: String? = null private var dispatchReceiver: FqName? = null private var extensionReceiver: FqName? = null private var parameters: List<FqName>? = null private var returnType: FqName? = null fun name(name: String) { this.name = name } fun dispatchReceiver(dispatchReceiver: String) { this.dispatchReceiver = FqName(dispatchReceiver) } fun extensionReceiver(extensionReceiver: String) { this.extensionReceiver = FqName(extensionReceiver) } fun parameters(vararg parameters: String) { this.parameters = parameters.map { FqName(it) } } fun returnType(returnType: String) { this.returnType = FqName(returnType) } fun build(): FunctionSignature = FunctionSignature(name, dispatchReceiver, extensionReceiver, parameters, returnType) } fun FunctionSignatureBuilder.dispatchReceiver(dispatchReceiver: FqName) { dispatchReceiver(dispatchReceiver.toString()) } fun FunctionSignatureBuilder.extensionReceiver(extensionReceiver: FqName) { extensionReceiver(extensionReceiver.toString()) } fun KtFunction.hasSignature(functionSignature: FunctionSignature): Boolean { if (functionSignature.name != null) { if (this.name != functionSignature.name) return false } if (functionSignature.dispatchReceiver != null) { if (this.containingClassOrObject?.fqName != functionSignature.dispatchReceiver) return false } if (functionSignature.extensionReceiver != null) { if (this.receiverTypeReference?.resolveReferencedType() ?.kotlinFqName != functionSignature.extensionReceiver ) return false } if (functionSignature.parameters != null) { if (this.valueParameters.zip(functionSignature.parameters) .any { it.first.type()?.fqName != it.second } ) return false } if (functionSignature.returnType != null) { if (this.getReturnTypeReference()?.resolveReferencedType() ?.kotlinFqName != functionSignature.returnType ) return false } return true } fun KtDeclaration.type() = (resolveToDescriptorIfAny() as? CallableDescriptor)?.returnType fun PsiMethod.hasSignature(functionSignature: FunctionSignature): Boolean { if (functionSignature.name != null) { if (this.name != functionSignature.name) return false } val parameters = parameterList.parameters.toMutableList() if (functionSignature.dispatchReceiver != null) { val containingClass = this.containingClass ?: return false val kotlinContainingClassFqn = if (this is KtLightMethod) { if (this.modifierList.hasExplicitModifier(PsiModifier.STATIC)) { this.containingClass.kotlinOrigin?.companionObjects?.firstOrNull()?.fqName } else containingClass.kotlinFqName } else containingClass.kotlinFqName if (kotlinContainingClassFqn != functionSignature.dispatchReceiver) return false } if (functionSignature.extensionReceiver != null) { val receiver = parameters.removeFirstOrNull() ?: return false if (receiver.type.canonicalText != functionSignature.extensionReceiver.toString()) return false } if (functionSignature.parameters != null) { if (parameters.zip(functionSignature.parameters) .any { it.first.type.canonicalText != it.second.toString() } ) return false } if (functionSignature.returnType != null) { if (returnType?.canonicalText != functionSignature.returnType.toString()) return false } return true } fun KtExpression.isCalling(functionSignature: FunctionSignature): Boolean { val descriptor = resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor ?: return false return descriptor.hasSignature(functionSignature) } fun CallableDescriptor.hasSignature(functionSignature: FunctionSignature): Boolean { if (functionSignature.name != null) { if (this.name.toString() != functionSignature.name) return false } if (functionSignature.extensionReceiver != null) { if (this.extensionReceiverParameter?.fqNameUnsafe != functionSignature.extensionReceiver.toUnsafe()) return false } if (functionSignature.dispatchReceiver != null) { if (this.containingDeclaration.fqNameUnsafe != functionSignature.dispatchReceiver.toUnsafe()) return false } if (functionSignature.parameters != null) { if (this.valueParameters.zip(functionSignature.parameters) .any { it.first.type.fqName != it.second } ) return false } if (functionSignature.returnType != null) { if (this.returnType?.fqName != functionSignature.returnType) return false } return true } ================================================ FILE: mirai-console/tools/intellij-plugin/src/resolve/ReceiverExpression.kt ================================================ /* * 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.console.intellij.resolve import org.jetbrains.kotlin.idea.caches.resolve.analyze import org.jetbrains.kotlin.idea.util.getFactoryForImplicitReceiverWithSubtypeOf import org.jetbrains.kotlin.idea.util.getResolutionScope import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtDotQualifiedExpression import org.jetbrains.kotlin.psi.KtExpression import org.jetbrains.kotlin.psi.KtPsiFactory sealed class ReceiverExpression { abstract val receiverExpression: KtExpression abstract val receiverText: String operator fun component1(): KtExpression = receiverExpression operator fun component2(): String = receiverText class Explicit( override val receiverExpression: KtExpression ) : ReceiverExpression() { override val receiverText: String get() = receiverExpression.text } class Implicit( receiverExpression: Lazy<KtExpression>, override val receiverText: String, ) : ReceiverExpression() { override val receiverExpression: KtExpression by receiverExpression } } fun KtCallExpression.siblingDotReceiverExpression(): KtExpression? { val dotQualifiedExpression = parent if (dotQualifiedExpression is KtDotQualifiedExpression) { return dotQualifiedExpression.receiverExpression } return null } fun KtExpression.dotReceiverExpression(): KtExpression? { return if (this is KtDotQualifiedExpression) receiverExpression else null } /** * Find: * - explicit receiver: `a` for `a.foo()` * - implicit labeled receiver: `this@run` in `a.run { foo() }` * * @receiver identifier reference in a call. e.g. `foo` in `a.foo()` and `foo()` */ fun KtExpression.receiverExpression(psiFactory: KtPsiFactory): ReceiverExpression? { val dotQualifiedExpr = parent if (dotQualifiedExpr is KtDotQualifiedExpression) { return ReceiverExpression.Explicit(dotQualifiedExpr.receiverExpression) } else { val context = analyze() val scope = getResolutionScope(context) ?: return null val descriptor = getResolvedCall(context)?.resultingDescriptor ?: return null val receiverDescriptor = descriptor.extensionReceiverParameter ?: descriptor.dispatchReceiverParameter ?: return null val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverDescriptor.type) ?: return null val receiverText = if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText return ReceiverExpression.Implicit(lazy { expressionFactory.createExpression(psiFactory, true) }, receiverText) } } fun KtExpression.implicitExpressionText(): String? { val dotQualifiedExpr = parent if (dotQualifiedExpr is KtDotQualifiedExpression) { return null } else { val context = analyze() val scope = getResolutionScope(context) ?: return null val descriptor = getResolvedCall(context)?.resultingDescriptor ?: return null val receiverDescriptor = descriptor.extensionReceiverParameter ?: descriptor.dispatchReceiverParameter ?: return null val expressionFactory = scope.getFactoryForImplicitReceiverWithSubtypeOf(receiverDescriptor.type) ?: return null return if (expressionFactory.isImmediate) "this" else expressionFactory.expressionText } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/resolve/resolveIdea.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.resolve import com.intellij.openapi.project.Project import com.intellij.psi.* import net.mamoe.mirai.console.compiler.common.resolve.COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME import net.mamoe.mirai.console.compiler.common.resolve.findParent import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.idea.base.psi.kotlinFqName import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall import org.jetbrains.kotlin.idea.refactoring.fqName.fqName import org.jetbrains.kotlin.idea.references.KtSimpleNameReference import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.resolveToDescriptors import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.psiUtil.referenceExpression import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall import org.jetbrains.kotlin.resolve.calls.util.getCall import org.jetbrains.kotlin.resolve.calls.util.getResolvedCall import org.jetbrains.kotlin.resolve.constants.ArrayValue import org.jetbrains.kotlin.resolve.constants.ConstantValue import org.jetbrains.kotlin.resolve.constants.StringValue import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.TypeProjection import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance /** * For CompositeCommand.SubCommand */ fun KtNamedFunction.isCompositeCommandSubCommand(): Boolean = this.hasAnnotation(COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME) fun PsiModifierListOwner.isCompositeCommandSubCommand(): Boolean = this.hasAnnotation(COMPOSITE_COMMAND_SUB_COMMAND_FQ_NAME) internal fun PsiModifierListOwner.hasAnnotation(fqName: FqName): Boolean = this.hasAnnotation(fqName.asString()) /** * SimpleCommand.Handler */ fun KtNamedFunction.isSimpleCommandHandler(): Boolean = this.hasAnnotation(SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME) fun PsiModifierListOwner.isSimpleCommandHandler(): Boolean = this.hasAnnotation(SIMPLE_COMMAND_HANDLER_COMMAND_FQ_NAME) fun KtNamedFunction.isSimpleCommandHandlerOrCompositeCommandSubCommand(): Boolean = this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand() fun PsiModifierListOwner.isSimpleCommandHandlerOrCompositeCommandSubCommand(): Boolean = this.isSimpleCommandHandler() || this.isCompositeCommandSubCommand() val KtPureClassOrObject.allSuperTypes: Sequence<KtSuperTypeListEntry> get() = sequence { yieldAll(superTypeListEntries) for (list in superTypeListEntries.asSequence()) { yieldAll( (list.typeAsUserType?.referenceExpression?.mainReference?.resolve()?.parents(true) ?.filterIsInstance<KtClass>() ?.firstOrNull())?.allSuperTypes.orEmpty() ) } } fun PsiElement.parents(withSelf: Boolean): Sequence<PsiElement> { val seed = if (withSelf) this else parentWithoutWalkingDirectories(this) return generateSequence(seed, ::parentWithoutWalkingDirectories) } private fun parentWithoutWalkingDirectories(element: PsiElement): PsiElement? { return if (element is PsiFile) null else element.parent } val PsiClass.allSuperTypes: Sequence<PsiClass> get() = sequence { interfaces.forEach { yield(it) yieldAll(it.allSuperTypes) } val superClass = superClass if (superClass != null) { yield(superClass) yieldAll(superClass.allSuperTypes) } } fun KtConstructorCalleeExpression.getTypeAsUserType(): KtUserType? { val reference = typeReference if (reference != null) { val element = reference.typeElement if (element is KtUserType) { return element } } return null } fun KotlinType.hasSuperType(fqName: String, includeSelf: Boolean = true): Boolean { if (includeSelf && this.fqName?.asString() == fqName) return true return this.supertypes().any { it.hasSuperType("net.mamoe.mirai.message.data.Message", true) } } fun KtClassOrObject.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) fun KtClass.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @kotlin.internal.LowPriorityInOverloadResolution fun PsiElement.hasSuperType(fqName: FqName): Boolean = allSuperNames.contains(fqName) val KtClassOrObject.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { it.kotlinFqName } val PsiClass.allSuperNames: Sequence<FqName> get() = allSuperTypes.mapNotNull { clazz -> clazz.qualifiedName?.let { FqName( it ) } } @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @kotlin.internal.LowPriorityInOverloadResolution val PsiElement.allSuperNames: Sequence<FqName> get() { return when (this) { is KtClassOrObject -> allSuperNames is PsiClass -> allSuperNames else -> emptySequence() } } fun getElementForLineMark(callElement: PsiElement): PsiElement = when (callElement) { is KtSimpleNameExpression -> callElement.getReferencedNameElement() else -> // a fallback, //but who knows what to reference in KtArrayAccessExpression ? generateSequence(callElement, { it.firstChild }).last() } val KtAnnotationEntry.annotationClass: KtClass? get() = calleeExpression?.constructorReferenceExpression?.run { try { mainReference.resolve() } catch (e: Exception) { null // type inference with `by lazy {}` is unstable for now. I just ignore exceptions encountering with such issue. } }?.findParent<KtClass>() fun KtAnnotated.hasAnnotation(fqName: FqName): Boolean = this.annotationEntries.any { it.annotationClass?.kotlinFqName == fqName } fun ValueArgument.resolveStringConstantValues(bindingContext: BindingContext): Sequence<String>? { return this.getArgumentExpression()?.resolveStringConstantValues(bindingContext) } val PsiElement.allChildrenFlat: Sequence<PsiElement> get() { return sequence { for (child in children) { yield(child) yieldAll(child.allChildrenFlat) } } } inline fun <reified E> PsiElement.findChild(): E? = this.children.find { it is E } as E? fun KtValueArgument.type() = getArgumentExpression()?.referenceExpression()?.type() fun KtExpression.resultingDescriptor() = resolveToCall(BodyResolveMode.PARTIAL)?.resultingDescriptor fun KtExpression.type() = resultingDescriptor()?.returnType fun KtReferenceExpression.typeFqName() = type()?.fqName fun KtExpression.typeFqName() = referenceExpression()?.typeFqName() fun KtElement.getResolvedCall( context: BindingContext, ): ResolvedCall<out CallableDescriptor>? { return this.getCall(context)?.getResolvedCall(context) } val ResolvedCall<out CallableDescriptor>.valueParameters: List<ValueParameterDescriptor> get() = this.resultingDescriptor.valueParameters val Project.psiElementFactory: PsiElementFactory? get() = PsiElementFactory.getInstance(this) fun ConstantValue<*>.selfOrChildrenConstantStrings(): Sequence<String> { return when (this) { is StringValue -> sequenceOf(value) is ArrayValue -> sequence { yieldAll(this@selfOrChildrenConstantStrings.selfOrChildrenConstantStrings()) } else -> emptySequence() } } fun ClassDescriptor.findMemberFunction(name: Name, vararg typeProjection: TypeProjection): SimpleFunctionDescriptor? { return getMemberScope(typeProjection.toList()).getContributedFunctions(name, NoLookupLocation.FROM_IDE) .firstOrNull() } fun DeclarationDescriptor.companionObjectDescriptor(): ClassDescriptor? { if (this !is ClassDescriptor) { return null } return this.companionObjectDescriptor } fun KtExpression.resolveStringConstantValues(bindingContext: BindingContext): Sequence<String> { when (this) { is KtNameReferenceExpression -> { when (val descriptor = references.firstIsInstance<KtSimpleNameReference>().resolveToDescriptors(bindingContext) .singleOrNull()) { is VariableDescriptor -> { val compileTimeConstant = descriptor.compileTimeInitializer ?: return emptySequence() return compileTimeConstant.selfOrChildrenConstantStrings() } //is PsiDeclarationStatement -> { // // TODO: 2020/9/18 compile-time constants from Java //} } } is KtStringTemplateExpression -> { if (hasInterpolation()) return emptySequence() return sequenceOf(entries.joinToString("") { it.text }) } /* is KtCallExpression -> { val callee = this.calleeExpression?.getResolvedCallOrResolveToCall(bindingContext)?.resultingDescriptor if (callee is VariableDescriptor) { val compileTimeConstant = callee.compileTimeInitializer ?: return null return compileTimeConstant.castOrNull<StringValue>()?.value } return null }*/ is KtConstantExpression -> { // TODO: 2020/9/18 KtExpression.resolveStringConstantValue: KtConstantExpression } } return emptySequence() } ================================================ FILE: mirai-console/tools/intellij-plugin/src/util/RequirementHelper.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.util @Suppress("RegExpRedundantEscape") object RequirementHelper { private val directVersion = """^[0-9]+(\.[0-9]+)+(|[\-+].+)$""".toRegex() private val versionSelect = """^[0-9]+(\.[0-9]+)*\.x$""".toRegex() private val versionMathRange = """([\[\(])([0-9]+(\.[0-9]+)+(|[\-+].+))\s*\,\s*([0-9]+(\.[0-9]+)+(|[\-+].+))([\]\)])""".toRegex() private val versionRule = """^((\>\=)|(\<\=)|(\=)|(\!\=)|(\>)|(\<))\s*([0-9]+(\.[0-9]+)+(|[\-+].+))$""".toRegex() private val SEM_VERSION_REGEX = """^(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\.(0|[1-9]\d*))?(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$""".toRegex() fun isValid(rule: String): Boolean { return rule.trim().let { directVersion.matches(it) || versionSelect.matches(it) || versionMathRange.matches(it) || versionRule.matches(it) } } internal object RequirementChecker : RequirementParser.ProcessorBase<Unit>() { override fun processLogic(isAnd: Boolean, chunks: Iterable<Unit>) { } override fun processString(reader: RequirementParser.TokenReader, token: RequirementParser.Token.Content) { if (!isValid(token.content)) { token.ia(reader, "`${token.content}` 无效.") } } } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/util/RequirementParser.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.util import kotlin.math.max import kotlin.math.min internal class RequirementParser { sealed class Token { open var line: Int = -1 open var pos: Int = -1 open var sourcePos: Int = -1 open lateinit var content: String sealed class GroupBod : Token() { class Left : GroupBod() { override var content: String get() = "{" set(_) {} } class Right : GroupBod() { override var content: String get() = "}" set(_) {} } } sealed class Logic : Token() { class And : Logic() { override var content: String get() = "&&" set(_) {} } class Or : Logic() { override var content: String get() = "||" set(_) {} } } class Content : Token() class Ending : Token() { override var content: String get() = "" set(_) {} } object Begin : Token() { override var content: String get() = "" set(_) {} override var line: Int get() = 0 set(_) {} override var pos: Int get() = 0 set(_) {} override var sourcePos: Int get() = 0 set(_) {} } override fun toString(): String { return javaClass.canonicalName.substringAfterLast('.') + " - $content [$line, $pos]" } } companion object { const val END = '\u0000' } class TokenReader( @JvmField val content: String ) { @JvmField var pos: Int = 0 @JvmField var line: Int = 0 @JvmField var posi: Int = 0 @JvmField var latestToken: Token = Token.Begin @JvmField var insertToken: Token? = Token.Begin fun peekChar(): Char { if (pos < content.length) return content[pos] return END } fun peekNextChar(): Char { if (pos + 1 < content.length) return content[pos + 1] return END } fun nextChar(): Char { val char = peekChar() pos++ if (char == '\n') { line++ posi = 0 } else { posi++ } return char } fun nextToken(): Token { insertToken?.let { insertToken = null; return it } return nextToken0().also { latestToken = it } } private fun nextToken0(): Token { if (pos < content.length) { while (peekChar().isWhitespace()) { nextChar() } val startIndex = pos if (startIndex >= content.length) { return Token.Ending().also { it.line = line it.pos = posi it.sourcePos = content.length } } val pline = line val ppos = posi nextChar() when (content[startIndex]) { '&' -> { if (peekChar() == '&') { return Token.Logic.And().also { it.pos = ppos it.line = pline it.sourcePos = startIndex nextChar() } } } '|' -> { if (peekChar() == '|') { return Token.Logic.Or().also { nextChar() it.pos = ppos it.line = pline it.sourcePos = startIndex } } } '{' -> { return Token.GroupBod.Left().also { it.pos = ppos it.line = pline it.sourcePos = startIndex } } '}' -> { return Token.GroupBod.Right().also { it.pos = ppos it.line = pline it.sourcePos = startIndex } } } while (true) { when (val c = peekChar()) { '&', '|' -> { if (c == peekNextChar()) { break } nextChar() } '{', '}' -> { break } END -> break else -> nextChar() } } val endIndex = pos return Token.Content().also { it.content = content.substring(startIndex, endIndex) it.pos = ppos it.line = pline it.sourcePos = startIndex } } return Token.Ending().also { it.line = line it.pos = posi it.sourcePos = content.length } } } interface TokensProcessor<R> { fun process(reader: TokenReader): R fun processLine(reader: TokenReader): R fun processLogic(isAnd: Boolean, chunks: Iterable<R>): R } abstract class ProcessorBase<R> : TokensProcessor<R> { fun Token.ia(reader: TokenReader, msg: String, cause: Throwable? = null): Nothing { throw IllegalArgumentException("$msg (at [$line, $pos], ${cutSource(reader, sourcePos)})", cause) } fun cutSource(reader: TokenReader, index: Int): String { val content = reader.content val s = max(0, index - 10) val e = min(content.length, index + 10) return content.substring(s, e) } override fun process(reader: TokenReader): R { return when (val nextToken = reader.nextToken()) { is Token.Begin, is Token.GroupBod.Left -> { val first = when (val next = reader.nextToken()) { is Token.Content -> { processString(reader, next) } is Token.GroupBod.Right -> { nextToken.ia( reader, if (nextToken is Token.Begin) "无效的关键字 `}`" else "空规则组" ) } is Token.Logic -> { nextToken.ia(reader, "规则不允许以逻辑操作符开始") } is Token.Ending -> { nextToken.ia( reader, if (nextToken is Token.Begin) "规则为空" else "需要更多内容" ) } is Token.GroupBod.Left -> { reader.insertToken = next process(reader) } else -> { next.ia(reader, "Bad token $next") } } // null -> not set // true -> AND mode // false-> OR mode var mode: Boolean? = null val chunks = arrayListOf(first) while (true) { when (val next = reader.nextToken()) { is Token.Ending, is Token.GroupBod.Right -> { val isEndingOfGroup = next is Token.GroupBod.Right val isStartingOfGroup = nextToken is Token.GroupBod.Left if (isStartingOfGroup != isEndingOfGroup) { fun getType(type: Boolean) = if (type) "`}`" else "<结束>" next.ia( reader, "需要 ${getType(isStartingOfGroup)}, 但是找到了 ${getType(isEndingOfGroup)}" ) } else { // reader.insertToken = next break } } is Token.Logic -> { val stx = next is Token.Logic.And if (mode == null) mode = stx else if (mode != stx) { fun getMode(type: Boolean) = if (type) "`&&`" else "`||`" next.ia( reader, "为了避免语义混乱, 不允许在一层规则组混合使用 `&&` 和 `||`, 请显式使用 `{}` 分离. " + "需要 ${getMode(mode)}, 但是找到了 ${getMode(stx)}" ) } chunks.add(process(reader)) } else -> { next.ia( reader, "Except ${ when (mode) { null -> "`&&` or `||`" true -> "`&&`" false -> "`||`" } } but get `${next.content}`" ) } } } if (mode == null) { first } else { processLogic(mode, chunks) } } is Token.Content -> { processString(reader, nextToken) } is Token.Ending -> { nextToken.ia(reader, "需要更多值.") } else -> { nextToken.ia(reader, "Assert Error: $nextToken") } } } abstract fun processString(reader: TokenReader, token: Token.Content): R override fun processLine(reader: TokenReader): R { return process(reader).also { val tok = reader.nextToken() if (reader.nextToken() !is Token.Ending) { tok.ia(reader, "Token Reader 未完成解析") } } } } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/util/RunIgnoringErrors.kt ================================================ /* * 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.console.intellij.util import kotlin.contracts.InvocationKind import kotlin.contracts.contract internal val DEBUG_ENABLED = System.getenv("mirai.console.intellij.debug") == "true" internal inline fun runIgnoringErrors( block: () -> Unit ) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } try { block() } catch (e: Error) { // ignored } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution internal inline fun <R> R.runIgnoringErrors( block: R.() -> Unit ) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } try { block() } catch (e: Error) { // ignored } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/BuildSystemType.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard enum class BuildSystemType { GradleKt { override fun createBuildSystem(model: MiraiProjectModel): ProjectCreator = GradleKotlinProjectCreator(model) override fun toString(): String = "Gradle Kotlin DSL" }, GradleGroovy { override fun createBuildSystem(model: MiraiProjectModel): ProjectCreator = GradleGroovyProjectCreator(model) override fun toString(): String = "Gradle Groovy DSL" }, ; abstract fun createBuildSystem(model: MiraiProjectModel): ProjectCreator } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/KotlinStdlibVersion.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import org.jetbrains.kotlin.tools.projectWizard.Versions import org.jsoup.Jsoup import org.jsoup.nodes.Document import java.io.IOException typealias KotlinStdlibVersion = String object KotlinStdlibVersionFetcher { @Throws(IOException::class) fun getKotlinStdlibVersion(miraiVersion: MiraiVersion): KotlinStdlibVersion { fun download(url: String): Document { return Jsoup.connect(url) .followRedirects(true) .ignoreContentType(true) .ignoreHttpErrors(true) .get() } val path = "net/mamoe/mirai-core/$miraiVersion/mirai-core-$miraiVersion.pom" return kotlin.runCatching { download("https://maven.aliyun.com/repository/central/$path") }.recoverCatching { download("https://repo.maven.apache.org/maven2/$path") }.map { document -> val xml = document.toString() Regex("""<artifactid>\s*kotlin-stdlib-[A-Za-z0-9-]+\s*</artifactid>\s*<version>\s*(.*?)\s*</version>""") .findAll(xml) .mapNotNull { it.groupValues.getOrNull(1) } .firstOrNull() ?: Versions.KOTLIN.text }.getOrThrow() } } /* <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <!-- This module was also published with a richer model, Gradle metadata, --> <!-- which should be used instead. Do not delete the following line which --> <!-- is to indicate to Gradle or any Gradle module metadata file consumer --> <!-- that they should prefer consuming it instead. --> <!-- do_not_remove: published-with-gradle-metadata --> <modelVersion>4.0.0</modelVersion> <groupId>net.mamoe</groupId> <artifactId>mirai-core</artifactId> <version>2.12.2</version> <packaging>pom</packaging> <licenses> <license> <name>GNU AGPLv3</name> <url>https://github.com/mamoe/mirai/blob/master/LICENSE</url> </license> </licenses> <developers> <developer> <id>mamoe</id> <name>Mamoe Technologies</name> <email>support@mamoe.net</email> </developer> </developers> <scm> <connection>scm:https://github.com/mamoe/mirai.git</connection> <developerConnection>scm:git://github.com/mamoe/mirai.git</developerConnection> <url>https://github.com/mamoe/mirai</url> </scm> <dependencies> <dependency> <groupId>net.mamoe</groupId> <artifactId>mirai-core-api</artifactId> <version>2.12.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.jetbrains.kotlinx</groupId> <artifactId>kotlinx-serialization-core-jvm</artifactId> <version>1.3.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.jetbrains.kotlinx</groupId> <artifactId>kotlinx-serialization-json-jvm</artifactId> <version>1.3.2</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.jetbrains.kotlinx</groupId> <artifactId>kotlinx-coroutines-core-jvm</artifactId> <version>1.6.1</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib-jdk8</artifactId> <version>1.6.21</version> <scope>runtime</scope> </dependency> </dependencies> <description>Mirai Protocol implementation for QQ Android</description> <name>mirai-core</name> <url>https://github.com/mamoe/mirai</url> </project> */ ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/LanguageType.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.ide.fileTemplates.FileTemplate import net.mamoe.mirai.console.intellij.assets.FT data class NamedFile( val path: String, val template: FileTemplate ) interface ILanguageType { val sourceSetDirName: String fun pluginMainClassFile(creator: ProjectCreator): NamedFile } sealed class LanguageType : ILanguageType { @Suppress("UNCHECKED_CAST") fun <T : String?> escapeString(string: T): T { string ?: return null as T return string .replace("\\", "\\\\") .replace("\n", "\\n") .replace("\"", "\\\"") as T } abstract fun <T : String?> escapeRawString(string: T): T companion object { fun values() = arrayOf(Kotlin, Java) } object Kotlin : LanguageType() { override fun toString(): String = "Kotlin" // display in UI override val sourceSetDirName: String get() = "kotlin" override fun pluginMainClassFile(creator: ProjectCreator): NamedFile = creator.model.run { return NamedFile( path = "src/main/kotlin/$mainClassSimpleName.kt", template = creator.getTemplate(FT.PluginMainKt) ) } @Suppress("UNCHECKED_CAST") override fun <T : String?> escapeRawString(string: T): T { string ?: return null as T return string.replace("$", "\${'\$'}").replace("\n", "\\n") as T } } object Java : LanguageType() { override fun toString(): String = "Java" // display in UI override val sourceSetDirName: String get() = "java" override fun pluginMainClassFile(creator: ProjectCreator): NamedFile = creator.model.run { return NamedFile( path = "src/main/java/${packageName.replace('.', '/')}/$mainClassSimpleName.java", template = creator.getTemplate(FT.PluginMainJava) ) } override fun <T : String?> escapeRawString(string: T): T = escapeString(string) } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/MiraiModuleBuilder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.starters.local.* import com.intellij.ide.starters.local.wizard.StarterInitialStep import com.intellij.ide.starters.shared.* import com.intellij.ide.util.projectWizard.ModuleWizardStep import com.intellij.ide.util.projectWizard.WizardContext import com.intellij.openapi.module.Module import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.roots.ui.configuration.ModulesProvider import com.intellij.openapi.util.Key import com.intellij.pom.java.LanguageLevel import com.intellij.util.lang.JavaVersion import net.mamoe.mirai.console.intellij.assets.FT import net.mamoe.mirai.console.intellij.assets.Icons class MiraiModuleBuilder : StarterModuleBuilder() { companion object { val MIRAI_PROJECT_MODEL_KEY = Key.create<MiraiProjectModel>("mirai.project.model") val GRADLE_GROOVY_PROJECT: StarterProjectType = StarterProjectType("gradleGroovy", "Gradle Groovy DSL") val GRADLE_KTS_PROJECT: StarterProjectType = StarterProjectType("gradleKts", "Gradle Kotlin DSL") } override fun getBuilderId() = "MiraiModuleBuilder" override fun getPresentableName() = MiraiProjectWizardBundle.message("module.presentation.name") override fun getWeight() = KOTLIN_WEIGHT - 2 override fun getNodeIcon() = Icons.MainIcon override fun getDescription(): String = MiraiProjectWizardBundle.message("module.description") override fun getProjectTypes(): List<StarterProjectType> = listOf(GRADLE_GROOVY_PROJECT, GRADLE_KTS_PROJECT) override fun getTestFrameworks(): List<StarterTestRunner> = listOf(JUNIT_TEST_RUNNER) override fun getMinJavaVersion(): JavaVersion = LanguageLevel.JDK_1_8.toJavaVersion() override fun getStarterPack(): StarterPack { return StarterPack( "mirai", listOf( Starter("mirai", "Mirai Console", getDependencyConfig("/starters/compose.pom"), emptyList()) ) ) } override fun getLanguages(): List<StarterLanguage> = listOf(JAVA_STARTER_LANGUAGE, KOTLIN_STARTER_LANGUAGE) override fun createWizardSteps( context: WizardContext, modulesProvider: ModulesProvider ): Array<ModuleWizardStep> = emptyArray() override fun createOptionsStep(contextProvider: StarterContextProvider): StarterInitialStep { return MiraiProjectWizardInitialStep(contextProvider) } override fun setupModule(module: Module) { // manually set, we do not show the second page with libraries starterContext.starter = starterContext.starterPack.starters.first() starterContext.starterDependencyConfig = loadDependencyConfig()[starterContext.starter?.id] super.setupModule(module) } override fun getTemplateProperties(): Map<String, Any> { val model = starterContext.getUserData(MIRAI_PROJECT_MODEL_KEY)!! model.run { val projectCoordinates = projectCoordinates val pluginCoordinates = pluginCoordinates return mapOf<String, Any>( "KOTLIN_VERSION" to kotlinVersion, "MIRAI_VERSION" to miraiVersion, "GROUP_ID" to projectCoordinates.groupId, "VERSION" to projectCoordinates.version, "PROJECT_NAME" to starterContext, "USE_PROXY_REPO" to useProxyRepo, "ARTIFACT_ID" to projectCoordinates.artifactId, "MODULE_NAME" to projectCoordinates.moduleName, "PLUGIN_ID" to pluginCoordinates.id, "PLUGIN_NAME" to languageType.escapeString(pluginCoordinates.name), "PLUGIN_AUTHOR" to languageType.escapeString(pluginCoordinates.author), "PLUGIN_INFO" to languageType.escapeRawString(pluginCoordinates.info), "PLUGIN_DEPENDS_ON" to pluginCoordinates.dependsOn, "PLUGIN_VERSION" to projectCoordinates.version, "PACKAGE_NAME" to packageName, "CLASS_NAME" to mainClassSimpleName, "LANGUAGE_TYPE" to languageType.toString(), ) } } override fun getAssets(starter: Starter): List<GeneratorAsset> { val ftManager = FileTemplateManager.getInstance(ProjectManager.getInstance().defaultProject) val assets = mutableListOf<GeneratorAsset>() val model = starterContext.getUserData(MIRAI_PROJECT_MODEL_KEY)!! val standardAssetsProvider = StandardAssetsProvider() assets.add(GeneratorTemplateFile( standardAssetsProvider.gradleWrapperPropertiesLocation, ftManager.getCodeTemplate(FT.GradleWrapperProperties) )) assets.addAll(standardAssetsProvider.getGradlewAssets()) model.buildSystemType.createBuildSystem(model) .collectAssets { assets.add(it) } assets.add( GeneratorTemplateFile( "src/main/resources/META-INF/services/net.mamoe.mirai.console.plugin.jvm.JvmPlugin", ftManager.getCodeTemplate(FT.PluginMainService) ) ) assets.add(GeneratorEmptyDirectory("debug-sandbox")) assets.add(GeneratorEmptyDirectory("debug-sandbox/plugins")) assets.add(GeneratorEmptyDirectory("debug-sandbox/data")) assets.add(GeneratorEmptyDirectory("debug-sandbox/config")) assets.add( GeneratorTemplateFile( "debug-sandbox/account.properties", ftManager.getCodeTemplate(FT.AccountProperties) ) ) assets.add(GeneratorTemplateFile(".run/RunTerminal.run.xml", ftManager.getCodeTemplate(FT.RunTerminalRun))) return assets } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/MiraiModuleType.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.openapi.module.ModuleType import com.intellij.openapi.module.ModuleTypeManager import net.mamoe.mirai.console.intellij.assets.Icons class MiraiModuleType : ModuleType<MiraiModuleBuilder>("MIRAI_CONSOLE_PLUGIN_MODULE") { override fun createModuleBuilder() = MiraiModuleBuilder() override fun getIcon() = Icons.MainIcon override fun getNodeIcon(isOpened: Boolean) = Icons.MainIcon override fun getName() = NAME override fun getDescription() = "Modules used for developing plugins for <b>Mirai Console</b>" companion object { private const val ID = "MIRAI_CONSOLE_PLUGIN_MODULE" const val NAME = "Mirai" val instance: MiraiModuleType get() = ModuleTypeManager.getInstance().findByID(ID) as MiraiModuleType } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/MiraiProjectModel.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import net.mamoe.mirai.console.intellij.diagnostics.adjustToClassName import kotlin.contracts.contract data class ProjectCoordinates( val groupId: String, // already checked by pattern val artifactId: String, val version: String, val moduleName: String ) { val packageName: String get() = groupId } data class PluginCoordinates( val id: String, val name: String, val author: String, val info: String, val dependsOn: String, ) class MiraiProjectModel( val projectCoordinates: ProjectCoordinates, val pluginCoordinates: PluginCoordinates, val miraiVersion: String, val kotlinVersion: String, val buildSystemType: BuildSystemType, val languageType: LanguageType, val useProxyRepo: Boolean, val mainClassSimpleName: String = pluginCoordinates.run { name.adjustToClassName() ?: id.substringAfterLast('.').adjustToClassName() } ?: "PluginMain", val packageName: String = projectCoordinates.checkNotNull("projectCoordinates").groupId, ) fun <T : Any> T?.checkNotNull(name: String): T { contract { returns() implies (this@checkNotNull != null) } checkNotNull(this) { "$name is not yet initialized." } return this } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/MiraiProjectWizardInitialStep.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.ide.starters.local.StarterContextProvider import com.intellij.ide.starters.local.wizard.StarterInitialStep import com.intellij.ide.starters.shared.JAVA_STARTER_LANGUAGE import com.intellij.ide.starters.shared.KOTLIN_STARTER_LANGUAGE import com.intellij.ide.starters.shared.ValidationFunctions import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.observable.util.trim import com.intellij.openapi.ui.ComboBox import com.intellij.openapi.ui.MessageType import com.intellij.openapi.ui.popup.Balloon import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.ui.SimpleTextAttributes.GRAYED_ITALIC_ATTRIBUTES import com.intellij.ui.awt.RelativePoint import com.intellij.ui.dsl.builder.* import net.mamoe.mirai.console.intellij.wizard.MiraiProjectWizardBundle.message import org.jetbrains.concurrency.Promise import org.jetbrains.concurrency.runAsync import org.jetbrains.kotlin.tools.projectWizard.Versions private val log = logger<MiraiProjectWizardInitialStep>() class MiraiProjectWizardInitialStep(contextProvider: StarterContextProvider) : StarterInitialStep(contextProvider) { private val pluginVersionProperty = propertyGraph.property("0.1.0") private val pluginNameProperty = propertyGraph.lazyProperty { "" } private val pluginIdProperty = propertyGraph.lazyProperty { "" } private val pluginAuthorProperty = propertyGraph.lazyProperty { System.getProperty("user.name") } private val pluginDependenciesProperty = propertyGraph.lazyProperty { "" } private val pluginInfoProperty = propertyGraph.lazyProperty { "" } private val miraiVersionKindProperty = propertyGraph.property(MiraiVersionKind.Stable) private val miraiVersionProperty = propertyGraph.property("0.1.0") private val useProxyRepoProperty = propertyGraph.property(true) var pluginVersion by pluginVersionProperty.trim() var pluginName by pluginNameProperty.trim() var pluginId by pluginIdProperty.trim() var pluginAuthor by pluginAuthorProperty.trim() var pluginDependencies by pluginDependenciesProperty.trim() var pluginInfo by pluginInfoProperty.trim() var miraiVersionKind by miraiVersionKindProperty var miraiVersion by miraiVersionProperty var kotlinStdlibVersion = Versions.KOTLIN.text private lateinit var miraiVersionCell: Cell<ComboBox<String>> var useProxyRepo by useProxyRepoProperty override fun addFieldsAfter(layout: Panel) { layout.group(message("title.plugin.description")) { row(message("label.plugin.id")) { textField() .withSpecialValidation( ValidationFunctions.CHECK_NOT_EMPTY, ) .bindText(pluginIdProperty) rowComment(message("comment.plugin.id")) pluginIdProperty.dependsOn(groupIdProperty) { "$groupId.$artifactId" } pluginIdProperty.dependsOn(artifactIdProperty) { "$groupId.$artifactId" } } row(message("label.plugin.name")) { textField() .withSpecialValidation( ValidationFunctions.CHECK_NOT_EMPTY, MiraiValidations.CHECK_FORBIDDEN_PLUGIN_NAME, ) .bindText(pluginNameProperty) pluginNameProperty.dependsOn(artifactIdProperty) { artifactId.adjustToPresentationName() } rowComment(message("comment.plugin.name")) } row(message("label.plugin.version")) { textField() .withSpecialValidation( ValidationFunctions.CHECK_NOT_EMPTY, MiraiValidations.CHECK_ILLEGAL_VERSION_LINE ) .bindText(pluginVersionProperty) rowComment(message("comment.plugin.version")) } row(message("label.plugin.author")) { textField().bindText(pluginAuthorProperty) } row(message("label.plugin.dependencies")) { expandableTextField() .withSpecialValidation(MiraiValidations.CHECK_PLUGIN_DEPENDENCIES_SEGMENT) .bindText(pluginDependenciesProperty) .component.emptyText.setText(message("text.hint.plugin.dependencies"), GRAYED_ITALIC_ATTRIBUTES) rowComment(message("comment.plugin.dependencies")) } row(message("label.plugin.info")) { expandableTextField().bindText(pluginInfoProperty) .component.emptyText.setText(message("text.hint.plugin.info"), GRAYED_ITALIC_ATTRIBUTES) rowComment(message("comment.plugin.info")) } row(message("label.mirai.version")) { val miraiVersionKindCell = segmentedButton(MiraiVersionKind.values().toList()) { kind -> when (kind) { MiraiVersionKind.Stable -> message("label.version.stable") MiraiVersionKind.Prerelease -> message("label.version.prerelease") MiraiVersionKind.Nightly -> message("label.version.nightly") } }.bind(miraiVersionKindProperty) miraiVersionCell = comboBox(listOf<String>()) .enabled(false) .bindItem(miraiVersionProperty) miraiVersionProperty.afterChange { kotlinStdlibVersion = "Loading" runAsync { KotlinStdlibVersionFetcher.getKotlinStdlibVersion(miraiVersion) }.onError { log.error(it) kotlinStdlibVersion = Versions.KOTLIN.text }.onProcessed { kotlinStdlibVersion = it } } miraiVersionKindProperty.afterChange { if (!miraiVersionCell.component.isEnabled) return@afterChange updateVersionItems(miraiVersionKindCell, miraiVersionCell) } updateVersionItems(miraiVersionKindCell, miraiVersionCell) rowComment(message("comment.mirai.version")) } row { checkBox(message("text.use.proxy.repo")).enabled(true).bindSelected(useProxyRepoProperty) } } // Update default values languageProperty.set(KOTLIN_STARTER_LANGUAGE) projectTypeProperty.set(MiraiModuleBuilder.GRADLE_KTS_PROJECT) pluginIdProperty.set("$groupId.$artifactId") pluginNameProperty.set(artifactId.adjustToPresentationName()) } override fun updateDataModel() { super.updateDataModel() starterContext.putUserData( /* key = */ MiraiModuleBuilder.MIRAI_PROJECT_MODEL_KEY, /* value = */ MiraiProjectModel( projectCoordinates = ProjectCoordinates( groupId = groupId.trim(), artifactId = artifactId.trim(), version = pluginVersion.trim(), moduleName = entityName ), pluginCoordinates = PluginCoordinates( id = pluginId.trim(), name = pluginName.trim(), author = pluginAuthor.trim(), info = pluginInfo.trim(), dependsOn = pluginDependencies.trim() ), miraiVersion = miraiVersion, kotlinVersion = kotlinStdlibVersion, buildSystemType = when (val projectType = projectTypeProperty.get()) { MiraiModuleBuilder.GRADLE_KTS_PROJECT -> BuildSystemType.GradleKt MiraiModuleBuilder.GRADLE_GROOVY_PROJECT -> BuildSystemType.GradleGroovy else -> error("Unsupported project type: $projectType") }, languageType = when (val language = languageProperty.get()) { KOTLIN_STARTER_LANGUAGE -> LanguageType.Kotlin JAVA_STARTER_LANGUAGE -> LanguageType.Java else -> error("Unsupported language type: $language") }, useProxyRepo = useProxyRepo ) ) } override fun validate(): Boolean { if (miraiVersion == message("label.mirai.version.loading") || kotlinStdlibVersion == "Loading") { JBPopupFactory.getInstance() .createHtmlTextBalloonBuilder( message("error.please.wait.for.mirai.version"), MessageType.WARNING, null ) .setFadeoutTime(3000) .createBalloon() .show( RelativePoint.getSouthWestOf( miraiVersionCell.component ), Balloon.Position.atLeft ) return false } return super.validate() } private fun updateVersionItems( miraiVersionKindCell: SegmentedButton<MiraiVersionKind>, miraiVersionCell: Cell<ComboBox<MiraiVersion>> ): Promise<Set<MiraiVersion>?> { miraiVersionCell.component.isEditable = false miraiVersionKindCell.enabled(false) // disable the kind selector until the async operation finishes miraiVersionCell.enabled(false) miraiVersionCell.component.removeAllItems() miraiVersionCell.component.addItem(message("label.mirai.version.loading")) return runAsync { try { val list = MiraiVersionKind.getMiraiVersionList() miraiVersionCell.component.removeAllItems() list.filter { miraiVersionKind.isThatKind(it) } .forEach { v -> miraiVersionCell.component.addItem(v) } list } catch (e: Throwable) { JBPopupFactory.getInstance() .createHtmlTextBalloonBuilder( message("error.failed.to.download.mirai.version"), MessageType.ERROR, null ) .setFadeoutTime(2000) .createBalloon() .show( RelativePoint.getSouthOf( miraiVersionCell.component ), Balloon.Position.below ) null } }.onError { log.error(it) } .onProcessed { versions -> miraiVersionCell.component.isEditable = versions == null miraiVersionKindCell.enabled(true) miraiVersionCell.enabled(true) } } } private fun String.adjustToPresentationName(): String { val result = buildString { var doCapitalization = true fun Char.isAllowed() = isLetterOrDigit() || this in "_- " for (char in this@adjustToPresentationName) { if (!char.isAllowed()) continue if (doCapitalization) { when { char.isLetter() -> append(char.uppercase()) char == '_' -> {} char == '-' -> {} else -> append(char) } doCapitalization = false } else { if (char in "_- ") { doCapitalization = true append(' ') } else { append(char) } } } }.trim() return result } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/MiraiValidations.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.ide.starters.shared.TextValidationFunction import net.mamoe.mirai.console.compiler.common.CheckerConstants import net.mamoe.mirai.console.intellij.util.RequirementHelper import net.mamoe.mirai.console.intellij.util.RequirementParser import net.mamoe.mirai.console.intellij.wizard.MiraiProjectWizardBundle.message object MiraiValidations { val CHECK_FORBIDDEN_PLUGIN_NAME = TextValidationFunction { text -> val lowercaseName = text.lowercase().trim() val illegal = CheckerConstants.PLUGIN_FORBIDDEN_NAMES.firstOrNull { it == lowercaseName } if (illegal != null) { message("validation.plugin.name.forbidden.character", illegal) } else null } val CHECK_PLUGIN_ID = TextValidationFunction { text -> if (!CheckerConstants.PLUGIN_ID_REGEX.matches(text.trim())) { message("validation.illegal.plugin.id", text) } else null } val CHECK_ILLEGAL_VERSION_LINE = TextValidationFunction { text -> checkVersionLine(text)?.let { message("validation.illegal.version", text, it) } } val CHECK_PLUGIN_DEPENDENCIES_LINE = TextValidationFunction { text -> try { val trim = text.trim() val dep = PluginDependency.parseFromString(trim) if (!CheckerConstants.PLUGIN_ID_REGEX.matches(dep.id)) { return@TextValidationFunction message("validation.illegal.plugin.id", dep.id) } dep.versionRequirement?.let { checkVersionRequirementLine(it) }?.let { return@TextValidationFunction message("validation.illegal.version", it) } null // no error } catch (e: IllegalArgumentException) { message("validation.illegal.version", text, e.message ?: message("no.error.message")) } } val CHECK_PLUGIN_DEPENDENCIES_SEGMENT = TextValidationFunction { text -> text.lineSequence() .map { it.trim() } .filter { it.isNotEmpty() } .map { CHECK_PLUGIN_DEPENDENCIES_LINE.checkText(it) } .firstOrNull() } /** * Check multiple lines and returns information about the first illegal one. */ val CHECK_ILLEGAL_VERSION_SEGMENT = TextValidationFunction { text -> text.lineSequence() .map { it.trim() } .filter { it.isNotEmpty() } .mapNotNull { CHECK_ILLEGAL_VERSION_LINE.checkText(text) } .firstOrNull() } private class PluginDependency @JvmOverloads constructor( val id: String, val versionRequirement: String? = null, ) { companion object { /** * Frozen version of [net.mamoe.mirai.console.plugin.description.PluginDescription.Companion.parseFromString] from `2.11.0-M2.2`. */ @JvmStatic fun parseFromString(string: String): PluginDependency { // val optional = string.endsWith('?') val (id, version) = string.removeSuffix("?").let { rule -> if (rule.contains(':')) { rule.substringBeforeLast(':') to rule.substringAfterLast(':') } else { rule to null } } return PluginDependency(id, version) } } } private fun checkVersionLine(version: String): String? { return checkVersionRequirementLine(version) // for simplicity } private fun checkVersionRequirementLine(versionRequirement: String): String? { kotlin.runCatching { RequirementHelper.RequirementChecker.processLine( RequirementParser.TokenReader( versionRequirement ) ) }.onFailure { return it.message ?: message("no.error.message") } return null } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/MiraiVersion.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.openapi.diagnostic.Logger import com.intellij.util.text.SemVer import org.jsoup.Jsoup import org.jsoup.nodes.Document import java.io.IOException typealias MiraiVersion = String enum class MiraiVersionKind { Stable { override fun isThatKind(version: String): Boolean = version matches REGEX_STABLE }, Prerelease { override fun isThatKind(version: String): Boolean = Stable.isThatKind(version) || version.contains("-M") || version.contains("-RC") }, Nightly { override fun isThatKind(version: String): Boolean = true // version.contains("-dev") }, ; abstract fun isThatKind(version: String): Boolean companion object { val DEFAULT = Stable private val REGEX_STABLE = Regex("""^\d+\.\d+(?:\.\d+)?$""") private val LOG = Logger.getInstance(MiraiVersionKind::class.java) @Throws(IOException::class) fun getMiraiVersionList(): Set<MiraiVersion> { fun download(url: String): Document { return Jsoup.connect(url) .followRedirects(true) .ignoreContentType(true) .ignoreHttpErrors(true) .get() } return kotlin.runCatching { download("https://maven.aliyun.com/repository/central/net/mamoe/mirai-core/maven-metadata.xml") }.recoverCatching { download("https://repo.maven.apache.org/maven2/net/mamoe/mirai-core/maven-metadata.xml") }.map { document -> val xml = document.toString() Regex("""<version>\s*(.*?)\s*</version>""") .findAll(xml) .mapNotNull { it.groupValues.getOrNull(1) } .sortVersionsDescending() .toSet() }.getOrThrow() } // Kotlin version: not working because // Caused by: java.util.ServiceConfigurationError: kotlinx.coroutines.CoroutineExceptionHandler: com.intellij.openapi.application.impl.CoroutineExceptionHandlerImpl not a subtype // // private suspend fun getMiraiVersionList(): Set<MiraiVersion> { // suspend fun download(url: String): Document { // return Jsoup.connect(url) // .followRedirects(true) // .ignoreContentType(true) // .ignoreHttpErrors(true) // .run { runInterruptible(Dispatchers.IO) { get() } } // } // // val document = supervisorScope { // val jobs = mutableListOf<Deferred<Document>>() // jobs += async { // download("https://maven.aliyun.com/repository/central/net/mamoe/mirai-core/maven-metadata.xml") // } // jobs += async { // download("https://repo.maven.apache.org/maven2/net/mamoe/mirai-core/maven-metadata.xml") // } // val timeout = launch { // delay(10_000) // } // // select the faster one // select<Document> { // jobs.forEach { job -> job.onAwait { it } } // timeout.onJoin { // throw IllegalStateException("Timeout getMiraiVersionList").apply { // jobs.forEach { // if (it.isCompleted) { // try { // it.await() // } catch (e: Throwable) { // addSuppressed(e) // } // } // } // } // } // } // jobs.forEach { it.cancel() } // timeout.cancel() // } // // val xml = document.toString() // // return Regex("""<version>\s*(.*?)\s*</version>""").findAll(xml).mapNotNull { it.groupValues.getOrNull(1) }.toSet() // } // fun CoroutineScope.getMiraiVersionListAsync(): Deferred<Set<MiraiVersion>> { // return async(CoroutineName("getMiraiVersionListAsync")) { // getMiraiVersionList() // } // } } } internal fun Sequence<String>.sortVersionsDescending(): Sequence<String> { return this .mapNotNull { SemVer.parseFromText(it) } .sortedWith { o1, o2 -> o2.compareTo(o1) } .map { it.toString() } } /* <?xml version="1.0" encoding="UTF-8"?> <metadata> <groupId>net.mamoe</groupId> <artifactId>mirai-core</artifactId> <versioning> <latest>2.5.0-dev-2</latest> <release>2.5.0-dev-2</release> <versions> <version>2.4-RC</version> <version>2.4-M1-dev-publish-3</version> <version>2.4.0-dev-publish-2</version> <version>2.4.0</version> <version>2.4.1</version> <version>2.4.2</version> <version>2.5-RC-dev-1</version> <version>2.5-M1</version> <version>2.5-M2-dev-2</version> <version>2.5-M2</version> <version>2.5.0-dev-android-1</version> <version>2.5.0-dev-1</version> <version>2.5.0-dev-2</version> </versions> <lastUpdated>20210319014025</lastUpdated> </versioning> </metadata> */ ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/MiraiWizardBundle.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.AbstractBundle import org.jetbrains.annotations.Nls import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.PropertyKey import java.util.function.Supplier @NonNls private const val BUNDLE = "messages.MiraiProjectWizardBundle" object MiraiProjectWizardBundle : AbstractBundle(BUNDLE) { @Nls @JvmStatic fun message(@NonNls @PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): String = getMessage(key, *params) @JvmStatic fun messagePointer(@PropertyKey(resourceBundle = BUNDLE) key: String, vararg params: Any): Supplier<String> { return getLazyMessage(key, *params) } } ================================================ FILE: mirai-console/tools/intellij-plugin/src/wizard/ProjectAssetsProvider.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.wizard import com.intellij.ide.fileTemplates.FileTemplate import com.intellij.ide.fileTemplates.FileTemplateManager import com.intellij.ide.starters.local.GeneratorAsset import com.intellij.ide.starters.local.GeneratorEmptyDirectory import com.intellij.ide.starters.local.GeneratorTemplateFile import com.intellij.openapi.project.ProjectManager import net.mamoe.mirai.console.intellij.assets.FT sealed class ProjectCreator( val model: MiraiProjectModel, ) { private val manager get() = FileTemplateManager.getInstance(ProjectManager.getInstance().defaultProject) fun getTemplate(name: String): FileTemplate = manager.getCodeTemplate(name) abstract fun collectAssets( collect: (GeneratorAsset) -> Unit, ) } sealed class GradleProjectCreator( model: MiraiProjectModel, ) : ProjectCreator(model) { override fun collectAssets( collect: (GeneratorAsset) -> Unit ) { collect(GeneratorEmptyDirectory("src/main/${model.languageType.sourceSetDirName}")) collect(GeneratorEmptyDirectory("src/main/resources")) collect(GeneratorEmptyDirectory("src/test/${model.languageType.sourceSetDirName}")) collect(GeneratorEmptyDirectory("src/test/resources")) collect(GeneratorTemplateFile(model.languageType.pluginMainClassFile(this))) collect(GeneratorTemplateFile(".gitignore", getTemplate(FT.Gitignore))) collect(GeneratorTemplateFile("gradle.properties", getTemplate(FT.GradleProperties))) } } private fun GeneratorTemplateFile(targetFileName: NamedFile): GeneratorTemplateFile { return GeneratorTemplateFile(targetFileName.path, targetFileName.template) } class GradleKotlinProjectCreator( model: MiraiProjectModel, ) : GradleProjectCreator( model, ) { override fun collectAssets( collect: (GeneratorAsset) -> Unit ) { super.collectAssets(collect) collect(GeneratorTemplateFile("build.gradle.kts", getTemplate(FT.BuildGradleKts))) collect(GeneratorTemplateFile("settings.gradle.kts", getTemplate(FT.SettingsGradleKts))) } } class GradleGroovyProjectCreator( model: MiraiProjectModel, ) : GradleProjectCreator( model, ) { override fun collectAssets( collect: (GeneratorAsset) -> Unit ) { super.collectAssets(collect) collect(GeneratorTemplateFile("build.gradle", getTemplate(FT.BuildGradle))) collect(GeneratorTemplateFile("settings.gradle", getTemplate(FT.SettingsGradle))) } } ================================================ FILE: mirai-console/tools/intellij-plugin/test/creator/MiraiVersionKindTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package creator import net.mamoe.mirai.console.intellij.wizard.sortVersionsDescending import kotlin.test.Test import kotlin.test.assertEquals class MiraiVersionKindTest { @Test fun sortVersions() { assertEquals( "2.10.0, 2.10.0-RC, 2.10.0-M1, 2.9.0, 2.9.0-RC, 2.9.0-M2, 2.9.0-M1, 2.7.0, 2.7.0-RC" .split(",") .map { it.trim() }, sequenceOf( "2.9.0", "2.9.0-M1", "2.9.0-M2", "2.9.0-RC", "2.7.0", "2.7.0-RC", "2.10.0", "2.10.0-RC", "2.10.0-M1" ).sortVersionsDescending().toList() ) } } ================================================ FILE: mirai-console/tools/intellij-plugin/test/creator/tasks/TaskUtilsKtTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.console.intellij.creator.tasks import net.mamoe.mirai.console.intellij.diagnostics.adjustToClassName import net.mamoe.mirai.console.intellij.diagnostics.isValidPackageName import net.mamoe.mirai.console.intellij.diagnostics.isValidQualifiedClassName import net.mamoe.mirai.console.intellij.diagnostics.isValidSimpleClassName import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue internal class TaskUtilsKtTest { private fun useClassNameCases(mustBeTrue: (String) -> Boolean) { val success = listOf("A", "A_B", "A0", "A_0", "A_B0") val failure = listOf("", "0", "_", "-", ".", "/", "A/", "A.", "A.") success.forEach { assertEquals(true, mustBeTrue(it), it) } failure.forEach { assertEquals(false, mustBeTrue(it), it) } } @Test fun isValidPackageName() { useClassNameCases { it.isValidPackageName() } } @Test fun isValidClassName() { useClassNameCases { it.isValidSimpleClassName() } } @Test fun adjustToClassName() { assertEquals("Test", "Test".adjustToClassName()) assertEquals("TeSt", "Te_st".adjustToClassName()) assertEquals("TeSt", "Te_St".adjustToClassName()) assertEquals("TeSt", "Te-st".adjustToClassName()) assertEquals("TeSt", "Te-St".adjustToClassName()) assertEquals("TestAA", "Test//!@#$%^&*()AA".adjustToClassName()) assertEquals(null, "0".adjustToClassName()) assertEquals(null, "_0".adjustToClassName()) assertEquals(null, "_0A".adjustToClassName()) assertEquals("A1", "A1".adjustToClassName()) assertEquals("A1", "A_1".adjustToClassName()) assertEquals("A1", "A-1".adjustToClassName()) assertEquals("MiraiConsoleExample", "mirai-console-example".adjustToClassName()) } @Test fun qualifiedClassname() { useClassNameCases { it.isValidQualifiedClassName() } assertTrue { "a.b.c".isValidQualifiedClassName() } } } ================================================ FILE: mirai-console/tools/intellij-plugin/test/package.kt ================================================ /* * 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.console.intellij ================================================ FILE: mirai-core/.gitignore ================================================ src/jvmTest/kotlin/local test-sandbox/ ================================================ FILE: mirai-core/README.md ================================================ # mirai-core mirai 核心实现模块。首先阅读 [mirai-core-api/README.md](../mirai-core-api/README.md)。 本文仅介绍重要部分。 ## 架构 | 包名 | 描述 | |:---------------------------|:--------------------| | `net.mamoe.mirai.internal` | mirai 核心 API 的实现 | | `.contact` | 联系人实现 | | `.message` | 消息系统的实现 | | `.network` | 网络层实现 | | `.utils` | 工具类 | ## `net.mamoe.mirai.internal.contact` ### `AbstractContact` [AbstractContact.kt](src/commonMain/kotlin/contact/AbstractContact.kt#L22) 所有 `Contact` 实现的基类. 实现生命周期等. ### `SendMessageHandler` [SendMessageHandler.kt](src/commonMain/kotlin/contact/SendMessageHandler.kt#L35) 处理 mirai 消息系统 `Message` 到协议数据结构的转换, 并处理长消息上传, 音乐转发上传等. ## `net.mamoe.mirai.internal.message` ### `ReceiveMessageTransformer` [ReceiveMessageHandler.kt](src/commonMain/kotlin/message/ReceiveMessageHandler.kt#L29) 处理协议数据结构到 `Message` 的转换. 设有 `RefinableMessage` 处理长消息下载, 合并转发下载并展开等. ### `RefinableMessage` [RefinableMessage.kt](src/commonMain/kotlin/message/RefinableMessage.kt#L17) 支持处理长消息下载, 合并转发下载并展开等. ### `Image` 实现 [imagesImpl.kt](src/commonMain/kotlin/message/imagesImpl.kt#L30) 图片的实现的基类为 `AbstractImage`. 在底层协议, 群图片与私聊图片很不同. 因此图片实现可以是 `GroupImage` 或 `FriendImage`. 图片又细分了 `OnlineImage` 和 `OfflineImage`. 故有 `OnlineGroupImage` 等四个类型, 及他们分别的 `Impl`. `OnlineImage` 为通过 `Contact.uploadImage` 上传得到的, 或刚刚从服务器接收的图片对象. `OfflineImage` 则为反序列化得到的对象. ### `MessageSource` 实现 [MessageSourceInternal.kt](src/commonMain/kotlin/message/MessageSourceInternal.kt#L20) `MessageSource` 在协议底层十分复杂, ================================================ FILE: mirai-core/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UNUSED_VARIABLE") import BinaryCompatibilityConfigurator.configureBinaryValidators import shadow.relocateCompileOnly import shadow.relocateImplementation plugins { kotlin("multiplatform") id("kotlinx-atomicfu") kotlin("plugin.serialization") id("me.him188.kotlin-jvm-blocking-bridge") id("me.him188.kotlin-dynamic-delegation") // id("me.him188.maven-central-publish") `maven-publish` } description = "Mirai Protocol implementation for QQ Android" kotlin { explicitApi() apply(plugin = "explicit-api") configureJvmTargetsHierarchical("net.mamoe.mirai.internal") optInForAllSourceSets("net.mamoe.mirai.utils.MiraiExperimentalApi") optInForAllSourceSets("net.mamoe.mirai.utils.MiraiInternalApi") optInForAllSourceSets("net.mamoe.mirai.LowLevelApi") optInForAllSourceSets("kotlinx.serialization.ExperimentalSerializationApi") sourceSets.apply { val commonMain by getting { dependencies { api(project(":mirai-core-api")) api(`kotlinx-serialization-core`) api(`kotlinx-serialization-json`) api(`kotlinx-coroutines-core`) implementation(`kt-bignum`) implementation(project(":mirai-core-utils")) implementation(`kotlinx-serialization-protobuf`) implementation(`kotlinx-atomicfu`) // runtime from mirai-core-utils relocateCompileOnly(`ktor-io_relocated`) // relocateImplementation(`ktor-http_relocated`) // relocateImplementation(`ktor-serialization_relocated`) // relocateImplementation(`ktor-websocket-serialization_relocated`) } } commonTest { dependencies { implementation(kotlin("script-runtime")) implementation(`kotlinx-coroutines-test`) api(yamlkt) api(`junit-jupiter-api`) } } findByName("jvmBaseMain")?.apply { dependencies { implementation(`log4j-api`) implementation(`netty-handler`) api(`kotlinx-coroutines-jdk8`) // use -jvm modules for this magic target 'jvmBase' } } findByName("jvmBaseTest")?.apply { dependencies { implementation(`kotlinx-coroutines-debug`) } } findByName("androidMain")?.apply { dependencies { if (rootProject.property("mirai.android.target.api.level")!!.toString().toInt() < 23) { // Ship with BC if we are targeting 23 or lower where AndroidKeyStore is not stable enough. // For more info, read `net.mamoe.mirai.internal.utils.crypto.EcdhAndroidKt.create` in `androidMain`. implementation(bouncycastle) } } } // For Android with JDK findByName("androidTest")?.apply { dependencies { implementation(bouncycastle) } } // For Android with SDK findByName("androidUnitTest")?.apply { dependencies { implementation(bouncycastle) } } findByName("jvmMain")?.apply { dependencies { implementation(bouncycastle) // api(kotlinx("coroutines-debug", Versions.coroutines)) } } findByName("jvmTest")?.apply { dependencies { api(`kotlinx-coroutines-debug`) // implementation("net.mamoe:mirai-login-solver-selenium:1.0-dev-14") } } // Kt bignum findByName("jvmBaseMain")?.apply { dependencies { relocateImplementation(`kt-bignum_relocated`) } } // Ktor findByName("commonMain")?.apply { dependencies { compileOnly(`ktor-io`) implementation(`ktor-client-core`) } } findByName("jvmBaseMain")?.apply { // relocate for JVM like modules dependencies { relocateCompileOnly(`ktor-io_relocated`) // runtime from mirai-core-utils relocateImplementation(`ktor-client-core_relocated`) } } findByName("jvmBaseMain")?.apply { dependencies { relocateImplementation(`ktor-client-okhttp_relocated`) } } } } atomicfu { transformJvm = false } if (tasks.findByName("androidMainClasses") != null) { tasks.register("checkAndroidApiLevel") { doFirst { analyzes.AndroidApiLevelCheck.check( buildDir.resolve("classes/kotlin/android/main"), project.property("mirai.android.target.api.level")!!.toString().toInt(), project ) } group = "verification" this.mustRunAfter("androidMainClasses") } tasks.findByName("androidTest")?.dependsOn("checkAndroidApiLevel") } configureMppPublishing() configureBinaryValidators(setOf("jvm", "android").filterTargets()) tasks.register("compileJava") { description = "Dummy task to allow IntelliJ IDEA to run main functions from jvmTest" dependsOn(tasks.getByName("compileKotlinJvm")) } tasks.register("testClasses") { description = "Dummy task to allow IntelliJ IDEA to run main functions from jvmTest" dependsOn(tasks.getByName("compileTestKotlinJvm")) } //mavenCentralPublish { // artifactId = "mirai-core" // githubProject("mamoe", "mirai") // developer("Mamoe Technologies", email = "support@mamoe.net", url = "https://github.com/mamoe") // licenseFromGitHubProject("AGPLv3", "dev") // publishPlatformArtifactsInRootModule = "jvm" //} ================================================ FILE: mirai-core/compatibility-validation/android/api/android.api ================================================ ================================================ FILE: mirai-core/compatibility-validation/jvm/api/jvm.api ================================================ ================================================ FILE: mirai-core/src/androidInstrumentedTest/kotlin/package.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal ================================================ FILE: mirai-core/src/androidInstrumentedTest/kotlin/test/initializeTestJvm.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.test internal actual fun initializeTestPlatformBeforeCommon() { // nop } ================================================ FILE: mirai-core/src/androidInstrumentedTest/kotlin/testFramework/currentPlatform.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework actual fun currentPlatform(): Platform = Platform.AndroidInstrumentedTest ================================================ FILE: mirai-core/src/androidMain/AndroidManifest.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright 2019-2023 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/dev/LICENSE --> <manifest package="net.mamoe.mirai.internal" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> </manifest> ================================================ FILE: mirai-core/src/androidMain/kotlin/package.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal ================================================ FILE: mirai-core/src/androidMain/kotlin/utils/crypto/EcdhAndroid.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import java.security.Provider import java.security.Security internal actual fun Ecdh.Companion.create(): Ecdh<*, *> = // WARNING: If you change the SDK version checks here, // search for usages of `mirai.android.target.api.level` and see if you need to change elsewhere! // Especially in mirai-core/build.gradle.kts (configuring bouncy-castle dependency) if (kotlin.runCatching { // When running tests on JVM desktop, `ClassNotFoundException` will be got android.os.Build.VERSION.SDK_INT >= 23 }.getOrDefault(false)) { // For newer Android, BC is deprecated, but AndroidKeyStore (default) handles ECDH well // Do not specify a provider as Google recommends JceEcdh() } else { // For older Android, AndroidKeyStore (default) is buggy and cannot handle EC key generation without tricks // See https://developer.android.com/training/articles/keystore#SupportedKeyPairGenerators for details // Let's use BC instead, BC is bundled into older Android JceEcdhWithProvider( Security.getProvider("BC") ?: Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider") .getConstructor().newInstance() as Provider // in tests ) } ================================================ FILE: mirai-core/src/androidUnitTest/kotlin/package.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal ================================================ FILE: mirai-core/src/androidUnitTest/kotlin/test/Logger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.test import android.util.Log import net.mamoe.mirai.utils.MiraiLogger /** * Delegate logs to stdout, since android [Log] is not mocked. */ class JvmLoggerFactory : MiraiLogger.Factory { override fun create(requester: Class<*>, identity: String?): MiraiLogger { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return net.mamoe.mirai.internal.utils.StdoutLogger(identity ?: requester.simpleName) } } ================================================ FILE: mirai-core/src/androidUnitTest/kotlin/test/initializeTestJvm.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.test import net.mamoe.mirai.internal.utils.StructureToStringTransformerNew import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.PlatformLogger import net.mamoe.mirai.utils.Services import org.junit.jupiter.api.Test import kotlin.test.assertIsNot internal actual fun initializeTestPlatformBeforeCommon() { Services.register( net.mamoe.mirai.utils.StructureToStringTransformer::class.qualifiedName!!, StructureToStringTransformerNew::class.qualifiedName!!, ::StructureToStringTransformerNew ) Services.registerAsOverride( MiraiLogger.Factory::class.qualifiedName!!, "net.mamoe.mirai.utils.MiraiLogger.Factory" ) { JvmLoggerFactory() } // force override @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge.setInstance(JvmLoggerFactory()) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge.freeze() println("[testFramework] Initialized loggers using JvmLoggerFactory") } internal class AndroidUnitTestPlatformTest : AbstractTest() { @Test fun usesStdoutLogger() { // PlatformLogger uses android.util.Log and will fail assertIsNot<PlatformLogger>(MiraiLogger.Factory.create(this::class)) } } ================================================ FILE: mirai-core/src/androidUnitTest/kotlin/testFramework/currentPlatform.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework import net.mamoe.mirai.internal.test.AbstractTest import org.junit.jupiter.api.Test import kotlin.test.assertIs actual fun currentPlatform(): Platform = when (System.getenv("mirai.android.sdk.kind")) { "jdk" -> Platform.AndroidUnitTestWithJdk "adk" -> Platform.AndroidUnitTestWithAdk else -> throw IllegalStateException("`mirai.android.sdk.kind` must be `jdk` or `adk`. Ensure you are running tests using Gradle test tasks.") } internal class AndroidUnitTestPlatformTest : AbstractTest() { @Test fun currentPlatformIsAvailable() { assertIs<Platform.AndroidUnitTest>(currentPlatform()) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/AbstractBot.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(LowLevelApi::class) package net.mamoe.mirai.internal import kotlinx.coroutines.* import net.mamoe.mirai.Bot import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.ContactList import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.contact.info.FriendInfoImpl import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.asCoroutineExceptionHandler import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.supervisorJob import net.mamoe.mirai.utils.* import kotlin.collections.set import kotlin.coroutines.CoroutineContext import kotlin.jvm.Volatile /** * Protocol-irrelevant implementations */ internal abstract class AbstractBot( final override val configuration: BotConfiguration, final override val id: Long, ) : Bot, CoroutineScope { /////////////////////////////////////////////////////////////////////////// // lifecycle /////////////////////////////////////////////////////////////////////////// // FASTEST INIT @Suppress("LeakingThis") final override val logger: MiraiLogger = configuration.botLoggerSupplier(this) final override val coroutineContext: CoroutineContext = CoroutineName("Bot.$id") .plus(logger.asCoroutineExceptionHandler()) .childScopeContext(configuration.parentCoroutineContext) .apply { job.invokeOnCompletion { throwable -> logger.info { "Bot cancelled" + throwable?.message?.let { ": $it" }.orEmpty() } kotlin.runCatching { val bot = bot if (bot is AbstractBot && bot.networkInitialized) { bot.network.close(throwable) } }.onFailure { if (it !is CancellationException) logger.error(it) } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") Bot._instances.remove(id) // help GC release instances groups.forEach { it.members.delegate.clear() } groups.delegate.clear() // job is cancelled, so child jobs are to be cancelled friends.delegate.clear() strangers.delegate.clear() } } init { @Suppress("LeakingThis", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") Bot._instances[this.id] = this } /////////////////////////////////////////////////////////////////////////// // overrides /////////////////////////////////////////////////////////////////////////// /** * Bot level components plus network level components. */ abstract val components: ComponentStorage final override val isOnline: Boolean get() = if (!networkInitialized) false else network.state == State.OK final override val eventChannel: EventChannel<BotEvent> = GlobalEventChannel.filterIsInstance<BotEvent>().filter { it.bot === this@AbstractBot } final override val otherClients: ContactList<OtherClientImpl> = ContactList() final override val friends: ContactList<FriendImpl> = ContactList() final override val groups: ContactList<GroupImpl> = ContactList() final override val strangers: ContactList<StrangerImpl> = ContactList() final override val asFriend: FriendImpl by lazy { Mirai.newFriend(this, FriendInfoImpl(uin, "", "", 0)).cast() } // nick is initialized later on login final override val asStranger: StrangerImpl by lazy { Mirai.newStranger(this, StrangerInfoImpl(bot.id, bot.nick)).cast() } final override var nick: String by asFriend.info::nick override fun close(cause: Throwable?) { if (!this.isActive) return try { if (networkInitialized) { network.close(cause) } } finally { // ensure CoroutineScope is always closed if (cause == null) { supervisorJob.cancel() } else { supervisorJob.cancel(CancellationException("Bot closed", cause)) } } } final override fun toString(): String = "Bot($id)" /////////////////////////////////////////////////////////////////////////// // network /////////////////////////////////////////////////////////////////////////// @Volatile var networkInitialized = false val network: NetworkHandler by lazy { createNetworkHandler().also { it.context // ensure components available networkInitialized = true } } // the selector handles renewal of [NetworkHandler] final override suspend fun login() { if (!isActive) error("Bot is already closed and cannot relogin. Please create a new Bot instance then do login.") try { network.resumeConnection() } catch (e: Throwable) { // failed to init // lift cause to the top of the exception chain. e.g. LoginFailedException val cause = if (e is NetworkException) { e.unwrapForPublicApi() } else e try { // close bot if it hadn't been done during `resumeConnection()` if (!components[SsoProcessor].firstLoginSucceed) { close(cause) // failed to do first login, close bot } else if (cause is LoginFailedException && cause.killBot) { close(cause) // re-login failed and has caused bot being somehow killed by server } } catch (errorInClose: Throwable) { errorInClose.addSuppressed(cause) throw errorInClose } throw cause } logger.info { "Bot login successful." } } protected abstract fun createNetworkHandler(): NetworkHandler } ================================================ FILE: mirai-core/src/commonMain/kotlin/BotAccount.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR") package net.mamoe.mirai.internal import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.utils.SecretsProtection import net.mamoe.mirai.utils.TestOnly internal class BotAccount( internal val id: Long, authorization: BotAuthorization, ) { var authorization: BotAuthorization = authorization // FIXME: Making this mutable is very bad. // But I had to do this because the current test framework is bad, and I don't have time to do a major rewrite. @TestOnly set @TestOnly // to be compatible with your local tests :) constructor( id: Long, pwd: String ) : this(id, BotAuthorization.byPassword(pwd)) var accountSecretsKeyBuffer: SecretsProtection.EscapedByteBuffer? = null val accountSecretsKey: ByteArray get() { accountSecretsKeyBuffer?.let { return it.asByteArray } error("accountSecretsKey not yet available") } } ================================================ FILE: mirai-core/src/commonMain/kotlin/BotFactory.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress( "FunctionName", "INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR", "DeprecatedCallableAddReplaceWith", "OverridingDeprecatedMember" ) package net.mamoe.mirai.internal import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.DeprecatedSinceMirai /** * QQ for Android */ @DeprecatedSinceMirai(errorSince = "2.10", internalSince = "2.11") internal object BotFactoryImpl : BotFactory { /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ override fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot { return QQAndroidBot(BotAccount(qq, BotAuthorization.byPassword(password)), configuration) } /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ override fun newBot( qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration ): Bot = QQAndroidBot(BotAccount(qq, BotAuthorization.byPassword(passwordMd5)), configuration) override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot { return QQAndroidBot(BotAccount(qq, authorization), configuration) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/MiraiImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("MiraiImplKt_common") package net.mamoe.mirai.internal import io.ktor.client.* import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.utils.io.core.* import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.long import net.mamoe.mirai.* import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.* import net.mamoe.mirai.event.Event 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.FriendInfoImpl.Companion.impl import net.mamoe.mirai.internal.contact.info.MemberInfoImpl import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl.Companion.impl import net.mamoe.mirai.internal.event.EventChannelToEventDispatcherAdapter import net.mamoe.mirai.internal.event.InternalEventMechanism import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.EmptyRefineContext import net.mamoe.mirai.internal.message.RefineContext import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.message.data.* import net.mamoe.mirai.internal.message.image.* import net.mamoe.mirai.internal.message.source.* import net.mamoe.mirai.internal.message.toMessageChainNoSource import net.mamoe.mirai.internal.network.components.EventDispatcher import net.mamoe.mirai.internal.network.highway.ChannelKind import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.highway.tryDownload import net.mamoe.mirai.internal.network.highway.tryServersDownload import net.mamoe.mirai.internal.network.protocol.data.jce.SvcDevLoginInfo import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit import net.mamoe.mirai.internal.network.protocol.packet.chat.MultiMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact import net.mamoe.mirai.internal.network.protocol.packet.chat.NudgePacket import net.mamoe.mirai.internal.network.protocol.packet.chat.PbMessageSvc 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.summarycard.SummaryCard import net.mamoe.mirai.internal.network.psKey import net.mamoe.mirai.internal.network.sKey import net.mamoe.mirai.internal.utils.MiraiProtocolInternal import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* import kotlin.jvm.JvmName internal fun getMiraiImpl() = Mirai as MiraiImpl internal expect fun createDefaultHttpClient(): HttpClient // used by `net.mamoe.mirai.deps.test.CoreDependencyResolutionTest` in mirai-deps-test module. Do not change signature. @Suppress("unused") @TestOnly internal fun testHttpClient() { createDefaultHttpClient().close() } @Suppress("FunctionName") internal expect fun _MiraiImpl_static_init() @OptIn(LowLevelApi::class) // not object for ServiceLoader. internal open class MiraiImpl : IMirai, LowLevelApiAccessor { init { _MiraiImpl_static_init() // companion object is lazily initialized on native } companion object { init { _MiraiImpl_static_init() } } override val BotFactory: BotFactory get() = BotFactoryImpl override var FileCacheStrategy: FileCacheStrategy = net.mamoe.mirai.utils.FileCacheStrategy.PlatformDefault private val httpClient: HttpClient = createDefaultHttpClient() override suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) { check(!event.bot.friends.contains(event.fromId)) { "the request $event is outdated: You had already responded it on another device." } solveNewFriendRequestEvent( event.bot, eventId = event.eventId, fromId = event.fromId, fromNick = event.fromNick, accept = true, blackList = false ) event.bot.getFriend(event.fromId)?.let { friend -> FriendAddEvent(friend).broadcast() } } override suspend fun refreshKeys(bot: Bot) { // TODO: 2021/4/14 MiraiImpl.refreshKeysNow } override suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean) { check(!event.bot.friends.contains(event.fromId)) { "the request $event is outdated: You had already responded it on another device." } solveNewFriendRequestEvent( event.bot, eventId = event.eventId, fromId = event.fromId, fromNick = event.fromNick, accept = false, blackList = blackList ) } override suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent) { @Suppress("DuplicatedCode") checkGroupPermission(event.bot, event.groupId) { event::class.simpleName ?: "<anonymous class>" } if (event.group?.contains(event.fromId) == true) return solveMemberJoinRequestEvent( bot = event.bot, eventId = event.eventId, fromId = event.fromId, fromNick = event.fromNick, groupId = event.groupId, accept = true, blackList = false ) } @Suppress("DuplicatedCode") override suspend fun rejectMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean, message: String) { checkGroupPermission(event.bot, event.groupId) { event::class.simpleName ?: "<anonymous class>" } if (event.group?.contains(event.fromId) == true) return solveMemberJoinRequestEvent( bot = event.bot, eventId = event.eventId, fromId = event.fromId, fromNick = event.fromNick, groupId = event.groupId, accept = false, blackList = blackList, message = message ) } private inline fun checkGroupPermission(eventBot: Bot, groupId: Long, eventName: () -> String) { val group = eventBot.getGroup(groupId) ?: kotlin.run { error( "A ${eventName()} is outdated. Group $groupId not found for bot ${eventBot.id}. " + "This is because bot isn't in the group anymore" ) } group.checkBotPermission(MemberPermission.ADMINISTRATOR) } override suspend fun getOnlineOtherClientsList(bot: Bot, mayIncludeSelf: Boolean): List<OtherClientInfo> { bot.asQQAndroidBot() val response = bot.network.run { bot.network.sendAndExpect(StatSvc.GetDevLoginInfo(bot.client)) } fun SvcDevLoginInfo.toOtherClientInfo() = OtherClientInfo( iAppId.toInt(), Platform.getByTerminalId(iTerType?.toInt() ?: 0), deviceName.orEmpty(), deviceTypeInfo.orEmpty() ) return response.deviceList.map { it.toOtherClientInfo() }.let { result -> if (mayIncludeSelf) result else result.filterNot { it.appId == MiraiProtocolInternal[bot.configuration.protocol].id.toInt() } } } override suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean) { checkGroupPermission(event.bot, event.groupId) { event::class.simpleName ?: "<anonymous class>" } solveMemberJoinRequestEvent( bot = event.bot, eventId = event.eventId, fromId = event.fromId, fromNick = event.fromNick, groupId = event.groupId, accept = null, blackList = blackList ) } override suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) = solveInvitedJoinGroupRequest(event, accept = true) override suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) = solveInvitedJoinGroupRequest(event, accept = false) @OptIn(InternalEventMechanism::class) override suspend fun broadcastEvent(event: Event) { if (event is BotEvent) { val bot = event.bot if (bot is AbstractBot) { bot.components[EventDispatcher].broadcast(event) } } else { EventChannelToEventDispatcherAdapter.instance.broadcastEventImpl(event) } } private suspend fun solveInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent, accept: Boolean) { check(!event.bot.groups.contains(event.groupId)) { "the request $this is outdated: Bot has been already in the group." } solveBotInvitedJoinGroupRequestEvent( bot = event.bot, eventId = event.eventId, invitorId = event.invitorId, groupId = event.groupId, accept = accept ) } @LowLevelApi override fun newFriend(bot: Bot, friendInfo: FriendInfo): FriendImpl { return FriendImpl( bot.asQQAndroidBot(), bot.coroutineContext, friendInfo.impl(), ) } @LowLevelApi override fun newStranger(bot: Bot, strangerInfo: StrangerInfo): StrangerImpl { return StrangerImpl( bot.asQQAndroidBot(), bot.coroutineContext, strangerInfo.impl(), ) } @OptIn(LowLevelApi::class) override suspend fun getRawGroupList(bot: Bot): Sequence<Long> { bot.asQQAndroidBot() return bot.network.run { bot.network.sendAndExpect(FriendList.GetTroopListSimplify(bot.client)) }.groups.asSequence().map { it.groupUin.shl(32) and it.groupCode } } @OptIn(LowLevelApi::class) override suspend fun getRawGroupMemberList( bot: Bot, groupUin: Long, groupCode: Long, ownerId: Long ): Sequence<MemberInfo> { var nextUin = 0L var sequence = sequenceOf<MemberInfoImpl>() while (true) { val data = bot.asQQAndroidBot().network.sendAndExpect( FriendList.GetTroopMemberList( client = bot.client, targetGroupUin = groupUin, targetGroupCode = groupCode, nextUin = nextUin ), 5000, 3 ) sequence += data.members.asSequence().map { troopMemberInfo -> MemberInfoImpl(bot.client, troopMemberInfo, ownerId) } nextUin = data.nextUin if (nextUin == 0L) { break } } return sequence } override suspend fun recallGroupMessageRaw( bot: Bot, groupCode: Long, messageIds: IntArray, messageInternalIds: IntArray, ): Boolean { val response = bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForGroupMessage( bot.client, groupCode, messageIds, messageInternalIds ), 5000, 2 ) return response is PbMessageSvc.PbMsgWithDraw.Response.Success } override suspend fun recallFriendMessageRaw( bot: Bot, targetId: Long, messageIds: IntArray, messageInternalIds: IntArray, time: Int, ): Boolean { val response = bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForFriendMessage( bot.client, targetId, messageIds, messageInternalIds, time, ), 5000, 2 ) return response is PbMessageSvc.PbMsgWithDraw.Response.Success } override suspend fun recallGroupTempMessageRaw( bot: Bot, groupUin: Long, targetId: Long, messageIds: IntArray, messageInternalIds: IntArray, time: Int ): Boolean { val response = bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage( bot.client, groupUin, targetId, messageIds, messageInternalIds, time, ), 5000, 2 ) return response is PbMessageSvc.PbMsgWithDraw.Response.Success } override suspend fun recallMessage(bot: Bot, source: MessageSource) = bot.asQQAndroidBot().run { check(source is MessageSourceInternal) source.ensureSequenceIdAvailable() check(!source.isRecalledOrPlanned && source.setRecalled()) { "$source had already been recalled." } val response: PbMessageSvc.PbMsgWithDraw.Response = when (source) { is OnlineMessageSourceToGroupImpl, is OnlineMessageSourceFromGroupImpl -> { val group: Group = when (source) { is OnlineMessageSourceToGroupImpl -> source.subject is OnlineMessageSourceFromGroupImpl -> source.subject else -> error("stub") } if (bot.id != source.fromId) { // if member leave, messageSource will throw exception(#1661) when (group[source.fromId]?.permission ?: MemberPermission.MEMBER) { MemberPermission.MEMBER -> group.checkBotPermission(MemberPermission.ADMINISTRATOR) MemberPermission.ADMINISTRATOR -> group.checkBotPermission(MemberPermission.OWNER) // bot cannot be owner MemberPermission.OWNER -> throw PermissionDeniedException("Permission denied: cannot recall message from owner") } } bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForGroupMessage( bot.asQQAndroidBot().client, group.id, source.sequenceIds, source.internalIds ), 5000, 2 ) } is OnlineMessageSourceFromFriendImpl, is OnlineMessageSourceToFriendImpl, is OnlineMessageSourceFromStrangerImpl, is OnlineMessageSourceToStrangerImpl, -> { check(source.fromId == bot.id) { "can only recall a message sent by bot" } bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForFriendMessage( bot.client, source.targetId, source.sequenceIds, source.internalIds, source.time ), 5000, 2 ) } is OnlineMessageSourceFromTempImpl, is OnlineMessageSourceToTempImpl -> { check(source.fromId == bot.id) { "can only recall a message sent by bot" } source as OnlineMessageSourceToTempImpl bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage( bot.client, (source.target.group as GroupImpl).uin, source.targetId, source.sequenceIds, source.internalIds, source.time ), 5000, 2 ) } is OfflineMessageSource -> { when (source.kind) { MessageSourceKind.FRIEND, MessageSourceKind.STRANGER -> { check(source.fromId == bot.id) { "can only recall a message sent by bot" } bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForFriendMessage( bot.client, source.targetId, source.sequenceIds, source.internalIds, source.time ), 5000, 2 ) } MessageSourceKind.TEMP -> { check(source.fromId == bot.id) { "can only recall a message sent by bot" } bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForGroupTempMessage( bot.client, source.targetId, // groupUin source.targetId, // memberUin source.sequenceIds, source.internalIds, source.time ), 5000, 2 ) } MessageSourceKind.GROUP -> { bot.asQQAndroidBot().network.sendAndExpect( PbMessageSvc.PbMsgWithDraw.createForGroupMessage( bot.client, source.targetId, source.sequenceIds, source.internalIds ), 5000, 2 ) } } } else -> error("stub!") } // 1001: No message meets the requirements (实际上是没权限, 管理员在尝试撤回群主的消息) // 154: timeout // 3: <no message> check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.ids.contentToString()}: $response" } } private val json = Json { isLenient = true ignoreUnknownKeys = true } override suspend fun solveNewFriendRequestEvent( bot: Bot, eventId: Long, fromId: Long, fromNick: String, accept: Boolean, blackList: Boolean ): Unit = bot.asQQAndroidBot().run { network.sendWithoutExpect( NewContact.SystemMsgNewFriend.Action( bot.client, eventId = eventId, fromId = fromId, accept = accept, blackList = blackList ) ) if (!accept) return @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") bot.friends.delegate.add(newFriend(bot, FriendInfoImpl(fromId, fromNick, "", 0))) } override suspend fun solveBotInvitedJoinGroupRequestEvent( bot: Bot, eventId: Long, invitorId: Long, groupId: Long, accept: Boolean ) { bot.asQQAndroidBot().network.sendWithoutExpect( NewContact.SystemMsgNewGroup.Action( bot.client, eventId = eventId, fromId = invitorId, groupId = groupId, isInvited = true, accept = accept ) ) } override suspend fun solveMemberJoinRequestEvent( bot: Bot, eventId: Long, fromId: Long, fromNick: String, groupId: Long, accept: Boolean?, blackList: Boolean, message: String ) { bot.asQQAndroidBot().network.sendWithoutExpect( NewContact.SystemMsgNewGroup.Action( bot.client, eventId = eventId, fromId = fromId, groupId = groupId, isInvited = false, accept = accept, blackList = blackList, message = message ) ) // Add member in MsgOnlinePush.PbPushMsg } @LowLevelApi override suspend fun getGroupVoiceDownloadUrl( bot: Bot, md5: ByteArray, groupId: Long, dstUin: Long ): String { val response = bot.asQQAndroidBot().network.sendAndExpect( PttStore.GroupPttDown(bot.client, groupId, dstUin, md5), 5000, 2 ) return "http://${response.strDomain}${response.downPara.decodeToString()}" } override suspend fun muteAnonymousMember( bot: Bot, anonymousId: String, anonymousNick: String, groupId: Long, seconds: Int ) { bot as QQAndroidBot val response = httpClient.post { url("https://qqweb.qq.com/c/anonymoustalk/blacklist") setBody( MultiPartFormDataContent(formData { append("anony_id", anonymousId) append("group_code", groupId) append("seconds", seconds) append("anony_nick", anonymousNick) append("bkn", bot.client.wLoginSigInfo.bkn) }) ) headers { // ktor bug append( "cookie", "uin=o${bot.id}; skey=${bot.sKey}; p_uin=o${bot.id}; p_skey=${bot.psKey(host)};" ) } }.bodyAsText() val jsonObj = Json.decodeFromString(JsonObject.serializer(), response) if ((jsonObj["retcode"] ?: jsonObj["cgicode"] ?: error("missing response code")).jsonPrimitive.long != 0L) { throw IllegalStateException(response) } } override fun createFileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage { return FileMessageImpl(id, internalId, name, size) } override fun createUnsupportedMessage(struct: ByteArray): UnsupportedMessage = UnsupportedMessageImpl(struct.loadAs(ImMsgBody.Elem.serializer())) @Suppress("OverridingDeprecatedMember") override suspend fun queryImageUrl(bot: Bot, image: Image): String = when (image) { is ConstOriginUrlAware -> image.originUrl is DeferredOriginUrlAware -> image.getUrl(bot) is SuspendDeferredOriginUrlAware -> image.getUrl(bot) else -> error("Internal error: unsupported image class: ${image::class.simpleName}") } override suspend fun queryProfile(bot: Bot, targetId: Long): UserProfile { return bot.asQQAndroidBot().network.sendAndExpect( SummaryCard.ReqSummaryCard(bot.client, targetId), 5000, 2 ) } override suspend fun sendNudge(bot: Bot, nudge: Nudge, receiver: Contact): Boolean { if (!bot.configuration.protocol.isNudgeSupported) { throw UnsupportedOperationException("nudge is supported only with protocol ${ MiraiProtocolInternal.protocols.filter { it.value.supportsNudge }.map { it.key } }") } bot.asQQAndroidBot() bot.network.run { return if (receiver is Group) { receiver.checkIsGroupImpl() bot.network.sendAndExpect( NudgePacket.troopInvoke( client = bot.client, messageReceiverGroupCode = receiver.id, nudgeTargetId = nudge.target.id, ) ).success } else { bot.network.sendAndExpect( NudgePacket.friendInvoke( client = bot.client, messageReceiverUin = receiver.id, nudgeTargetId = nudge.target.id, ) ).success } } } override fun getUin(contactOrBot: ContactOrBot): Long { return when (contactOrBot) { is Group -> contactOrBot.uin is User -> contactOrBot.uin is Bot -> contactOrBot.uin else -> contactOrBot.id } } override fun constructMessageSource( botId: Long, kind: MessageSourceKind, fromId: Long, targetId: Long, ids: IntArray, time: Int, internalIds: IntArray, originalMessage: MessageChain ): OfflineMessageSource = OfflineMessageSourceImplData( kind, ids, botId, time, fromId, targetId, originalMessage, internalIds ) override suspend fun downloadLongMessage(bot: Bot, resourceId: String): MessageChain { try { return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.LONG_MESSAGE).msg .toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP) .refineDeep(bot) } catch (error: Throwable) { throw IllegalStateException("Failed to download long message `$resourceId`", error) } } override suspend fun downloadForwardMessage(bot: Bot, resourceId: String): List<ForwardMessage.Node> { try { return downloadMultiMsgTransmit(bot, resourceId, ResourceKind.FORWARD_MESSAGE).toForwardMessageNodes(bot) } catch (error: Throwable) { throw IllegalStateException("Failed to download forward message `$resourceId`", error) } } internal open suspend fun MsgTransmit.PbMultiMsgNew.toForwardMessageNodes( bot: Bot, context: RefineContext ): List<ForwardMessage.Node> { return msg.map { it.toNode(bot, context) } } internal open suspend fun MsgTransmit.PbMultiMsgTransmit.toForwardMessageNodes(bot: Bot): List<ForwardMessage.Node> { val pbs = this.pbItemList.associate { it.fileName to it.buffer.loadAs(MsgTransmit.PbMultiMsgNew.serializer()) } val main = pbs["MultiMsg"] ?: return this.msg.map { it.toNode(bot, EmptyRefineContext) } return main.toForwardMessageNodes(bot, SimpleRefineContext(ForwardMessageInternal.MsgTransmits to pbs)) } private suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node { val msg = this @Suppress("USELESS_CAST") // compiler bug, do not remove val senderName = (msg.msgHead.groupInfo?.groupCard ?: msg.msgHead.fromNick.takeIf { it.isNotEmpty() } ?: msg.msgHead.fromUin.toString()) as String val chain = listOf(msg) .toMessageChainNoSource(bot, 0, MessageSourceKind.GROUP) .refineDeep(bot, refineContext) return ForwardMessage.Node( senderId = msg.msgHead.fromUin, time = msg.msgHead.msgTime, senderName = senderName, messageChain = chain ) } private suspend fun downloadMultiMsgTransmit( bot: Bot, resourceId: String, resourceKind: ResourceKind, ): MsgTransmit.PbMultiMsgTransmit { bot.asQQAndroidBot() when (val resp = bot.network.sendAndExpect(MultiMsg.ApplyDown(bot.client, 2, resourceId, 1))) { is MultiMsg.ApplyDown.Response.RequireDownload -> { 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, _ -> httpClient.get("$host${origin.thumbDownPara}") }.readBytes() } else tryServersDownload( bot = bot, servers = origin.uint32DownIp.zip(origin.uint32DownPort), resourceKind = resourceKind, channelKind = ChannelKind.HTTP ) { ip, port -> httpClient.get("http://$ip:$port${origin.thumbDownPara}") }.readBytes() 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.decodeToString()}, 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") } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/QQAndroidBot.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal import kotlinx.atomicfu.atomic import kotlinx.coroutines.* import net.mamoe.mirai.Bot import net.mamoe.mirai.auth.AuthReason import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotReloginEvent import net.mamoe.mirai.internal.contact.friendgroup.FriendGroupsImpl import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ComponentStorageDelegate import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.component.withFallback import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.BaseStateImpl import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler import net.mamoe.mirai.internal.network.handler.state.CombinedStateObserver.Companion.plus import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver import net.mamoe.mirai.internal.network.handler.state.StateChangedObserver import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.internal.network.handler.state.safe import net.mamoe.mirai.internal.network.impl.ForceOfflineException import net.mamoe.mirai.internal.network.notice.TraceLoggingNoticeProcessor import net.mamoe.mirai.internal.network.notice.UnconsumedNoticesAlerter import net.mamoe.mirai.internal.network.notice.decoders.GroupNotificationDecoder import net.mamoe.mirai.internal.network.notice.decoders.MsgInfoDecoder import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor import net.mamoe.mirai.internal.network.notice.group.GroupNotificationProcessor import net.mamoe.mirai.internal.network.notice.group.GroupOrMemberListNoticeProcessor import net.mamoe.mirai.internal.network.notice.group.GroupRecallProcessor import net.mamoe.mirai.internal.network.notice.priv.FriendGroupNoticeProcessor import net.mamoe.mirai.internal.network.notice.priv.FriendNoticeProcessor import net.mamoe.mirai.internal.network.notice.priv.OtherClientNoticeProcessor import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.utils.ImagePatcher import net.mamoe.mirai.internal.utils.ImagePatcherImpl import net.mamoe.mirai.internal.utils.actualCacheDir import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.lateinitMutableProperty import kotlin.contracts.contract import kotlin.time.Duration.Companion.seconds internal fun Bot.asQQAndroidBot(): QQAndroidBot { contract { returns() implies (this@asQQAndroidBot is QQAndroidBot) } return this as QQAndroidBot } @Suppress("INVISIBLE_MEMBER", "BooleanLiteralArgument", "OverridingDeprecatedMember") internal open class QQAndroidBot constructor( internal val account: BotAccount, configuration: BotConfiguration, ) : AbstractBot(configuration, account.id) { override val bot: QQAndroidBot get() = this override val friendGroups: FriendGroupsImpl by lazy { FriendGroupsImpl(this) } val client get() = components[SsoProcessor].client private val closing = atomic(false) override fun close(cause: Throwable?) { if (!this.isActive) return if (!closing.compareAndSet(false, true)) return if (networkInitialized) { runBlocking { try { // this may not be very good but withTimeoutOrNull(5.seconds) { components[SsoProcessor].logout(network) } } catch (ignored: Exception) { } } } super.close(cause) } /////////////////////////////////////////////////////////////////////////// // network /////////////////////////////////////////////////////////////////////////// // also called by tests. fun ComponentStorage.stateObserverChain(): StateObserver { val components = this val eventDispatcher = this[EventDispatcher] return StateObserver.chainOfNotNull( components[BotInitProcessor].asObserver(), object : StateChangedObserver(State.OK) { private val shouldBroadcastRelogin = atomic(false) override fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: BaseStateImpl, new: BaseStateImpl, ) { eventDispatcher.broadcastAsync(BotOnlineEvent(bot)).thenBroadcast(eventDispatcher) { if (!shouldBroadcastRelogin.compareAndSet(false, true)) { BotReloginEvent(bot, new.getCause()) } else null } } override fun toString(): String = "StateChangedObserver(BotOnlineEventBroadcaster)" }, StateChangedObserver("LastConnectedAddressUpdater", State.OK) { components[ServerList].run { lastConnectedIP = getLastPolledIP() } }, StateChangedObserver("LastDisconnectedAddressUpdater", State.CLOSED) { components[ServerList].run { lastDisconnectedIP = lastConnectedIP } }, StateChangedObserver("BotOfflineEventBroadcasterAfter", State.OK, State.CLOSED) { new -> // logging performed by BotOfflineEventMonitor val cause = new.getCause() when { cause is ForceOfflineException -> { eventDispatcher.broadcastAsync(BotOfflineEvent.Force(bot, cause.title, cause.message)) } cause is StatSvc.ReqMSFOffline.MsfOfflineToken -> { eventDispatcher.broadcastAsync(BotOfflineEvent.MsfOffline(bot, cause)) } cause is NetworkException && cause.recoverable -> { eventDispatcher.broadcastAsync(BotOfflineEvent.Dropped(bot, cause)) } cause is BotClosedByEvent -> { } else -> { // any other unexpected exceptions considered as an error // When bot is closed, eventDispatcher.isActive will be false. // While in TestEventDispatcherImpl, eventDispatcher.isActive will always be true to enable catching the event. if (eventDispatcher.isActive) { eventDispatcher.broadcastAsync { BotOfflineEvent.Active(bot, cause) } } else { @OptIn(DelicateCoroutinesApi::class) GlobalScope.launch { BotOfflineEvent.Active(bot, cause).broadcast() } } } } }, StateChangedObserver("ReLoginCauseCatcher", State.OK, State.CLOSED) { new -> get(SsoProcessor).authReason = when (val cause = new.getCause()) { is ForceOfflineException -> AuthReason.ForceOffline(bot, cause.message) is StatSvc.ReqMSFOffline.MsfOfflineToken -> AuthReason.MsfOffline(bot, cause.message) is NetworkException -> AuthReason.NetworkError(bot, cause.message) else -> AuthReason.Unknown(bot, cause) } }, StateChangedObserver("FirstLoginObserver", State.OK) { get(SsoProcessor).isFirstLogin = false } ).safe(logger.subLogger("StateObserver")) + LoggingStateObserver.createLoggingIfEnabled() } private val networkLogger: MiraiLogger by lazy { configuration.networkLoggerSupplier(this) } final override val components: ComponentStorage get() = network.context private val defaultBotLevelComponents: ComponentStorage by lateinitMutableProperty { createBotLevelComponents().apply { set(StateObserver, stateObserverChain()) }.also { components -> components[BotOfflineEventMonitor].attachJob(bot, this) } } open fun createBotLevelComponents(): ConcurrentComponentStorage = ConcurrentComponentStorage { val components = ComponentStorageDelegate { this@QQAndroidBot.components } // There's no need to interrupt a broadcasting event when network handler closed. set(EventDispatcher, EventDispatcherImpl(bot.coroutineContext, logger.subLogger("EventDispatcher"))) val pipelineLogger = networkLogger.subLogger("NoticeProcessor") // shorten name set( NoticeProcessorPipeline, NoticeProcessorPipelineImpl.create( bot, MsgInfoDecoder(pipelineLogger.subLogger("MsgInfoDecoder")), GroupNotificationDecoder(), FriendNoticeProcessor(pipelineLogger.subLogger("FriendNoticeProcessor")), GroupOrMemberListNoticeProcessor(pipelineLogger.subLogger("GroupOrMemberListNoticeProcessor")), GroupMessageProcessor(pipelineLogger.subLogger("GroupMessageProcessor")), GroupNotificationProcessor(pipelineLogger.subLogger("GroupNotificationProcessor")), FriendGroupNoticeProcessor(pipelineLogger.subLogger("FriendGroupNoticeProcessor")), PrivateMessageProcessor(), OtherClientNoticeProcessor(), GroupRecallProcessor(), UnconsumedNoticesAlerter(pipelineLogger.subLogger("UnconsumedNoticesAlerter")), TraceLoggingNoticeProcessor(pipelineLogger.subLogger("TraceLoggingNoticeProcessor")) ) ) set(SsoProcessorContext, SsoProcessorContextImpl(bot)) set(SsoProcessor, SsoProcessorImpl(get(SsoProcessorContext))) set( QRCodeLoginProcessor, QRCodeLoginProcessor.parse(get(SsoProcessorContext), networkLogger.subLogger("QRCodeLoginProcessor")) ) val cacheValidator = CacheValidatorImpl( get(SsoProcessorContext), configuration.actualCacheDir().resolve("validator.bin"), networkLogger.subLogger("CacheValidator"), ) set(CacheValidator, cacheValidator) set(HeartbeatProcessor, HeartbeatProcessorImpl()) set(HeartbeatScheduler, TimeBasedHeartbeatSchedulerImpl(networkLogger.subLogger("HeartbeatScheduler"))) set(HttpClientProvider, HttpClientProviderImpl()) set(KeyRefreshProcessor, KeyRefreshProcessorImpl(networkLogger.subLogger("KeyRefreshProcessor"))) set(ConfigPushProcessor, ConfigPushProcessorImpl(networkLogger.subLogger("ConfigPushProcessor"))) set(BotOfflineEventMonitor, BotOfflineEventMonitorImpl()) set(BotInitProcessor, BotInitProcessorImpl(bot, components, networkLogger.subLogger("BotInitProcessor"))) set(ContactCacheService, ContactCacheServiceImpl(bot, networkLogger.subLogger("ContactCacheService"))) set(ContactUpdater, ContactUpdaterImpl(bot, components, networkLogger.subLogger("ContactUpdater"))) set( BdhSessionSyncer, BdhSessionSyncerImpl(configuration, components, networkLogger.subLogger("BotSessionSyncer")), ) set( MessageSvcSyncer, MessageSvcSyncerImpl(bot, bot.coroutineContext, networkLogger.subLogger("MessageSvcSyncer")), ) set( EcdhInitialPublicKeyUpdater, EcdhInitialPublicKeyUpdaterImpl(bot, networkLogger.subLogger("ECDHInitialPublicKeyUpdater")), ) set(ServerList, ServerListImpl(networkLogger.subLogger("ServerList"))) set(PacketLoggingStrategy, PacketLoggingStrategyImpl(bot)) set( PacketHandler, PacketHandlerChain( EventBroadcasterPacketHandler(components), CallPacketFactoryPacketHandler(bot), LoggingPacketHandlerAdapter(get(PacketLoggingStrategy), networkLogger), ), ) set(PacketCodec, PacketCodecImpl()) set( OtherClientUpdater, OtherClientUpdaterImpl(bot, components, networkLogger.subLogger("OtherClientUpdater")), ) set(ConfigPushSyncer, ConfigPushSyncerImpl()) set( AccountSecretsManager, configuration.createAccountsSecretsManager(bot.logger.subLogger("AccountSecretsManager")), ) set(ImagePatcher, ImagePatcherImpl()) cacheValidator.register(get(AccountSecretsManager)) cacheValidator.register(get(BdhSessionSyncer)) set( EncryptServiceHolder, EncryptServiceHolderImpl(this@QQAndroidBot, get(SsoProcessorContext)) ) } /** * This would overrides those from [createBotLevelComponents] */ open fun createNetworkLevelComponents(): ComponentStorage { return ConcurrentComponentStorage { set(BotClientHolder, BotClientHolderImpl(bot, networkLogger.subLogger("BotClientHolder"))) set(SyncController, SyncControllerImpl()) set(ClockHolder, ClockHolder()) }.withFallback(defaultBotLevelComponents) } override fun createNetworkHandler(): NetworkHandler { return SelectorNetworkHandler( KeepAliveNetworkHandlerSelector( maxAttempts = configuration.reconnectionRetryTimes.coerceIn(1, Int.MAX_VALUE), logger = networkLogger.subLogger("Selector") ) { val context = NetworkHandlerContextImpl( bot, networkLogger, createNetworkLevelComponents(), ) NetworkHandlerFactory.getPlatformDefault().create( context, context[ServerList].pollAny().toSocketAddress(), ) }, ) // We can move the factory to configuration but this is not necessary for now. } } internal fun QQAndroidBot.getGroupByUinOrFail(uin: Long) = getGroupByUin(uin) ?: throw NoSuchElementException("group.uin=$uin") internal fun QQAndroidBot.getGroupByUin(uin: Long) = groups.firstOrNull { it.uin == uin } /** * uin first */ internal fun QQAndroidBot.getGroupByUinOrCode(uinOrCode: Long) = groups.firstOrNull { it.uin == uinOrCode } ?: groups.firstOrNull { it.id == uinOrCode } /** * uin first */ internal fun QQAndroidBot.getGroupByUinOrCodeOrFail(uinOrCode: Long) = getGroupByUinOrCode(uinOrCode) ?: throw NoSuchElementException("group.code or uin=$uinOrCode") /** * code first */ internal fun QQAndroidBot.getGroupByCodeOrUin(uinOrCode: Long) = groups.firstOrNull { it.id == uinOrCode } ?: groups.firstOrNull { it.uin == uinOrCode } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/AbstractContact.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact import io.ktor.utils.io.core.* import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BeforeShortVideoUploadEvent import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.ShortVideoUploadEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.message.data.OfflineShortVideoImpl import net.mamoe.mirai.internal.message.data.ShortVideoThumbnail import net.mamoe.mirai.internal.message.image.calculateImageInfo import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.protocol.data.proto.PttShortVideo import net.mamoe.mirai.internal.network.protocol.packet.chat.video.PttCenterSvr import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.message.data.ShortVideo import net.mamoe.mirai.internal.utils.CombinedExternalResource import net.mamoe.mirai.utils.* import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext internal abstract class AbstractContact( final override val bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, ) : Contact { final override val coroutineContext: CoroutineContext = parentCoroutineContext.childScopeContext() override suspend fun uploadShortVideo( thumbnail: ExternalResource, video: ExternalResource, fileName: String? ): ShortVideo = thumbnail.withAutoClose { video.withAutoClose { if (this !is Group && this !is Friend) { throw UnsupportedOperationException("short video can only upload to friend or group.") } if (video.formatName != "mp4") { throw UnsupportedOperationException("video format ${video.formatName} is not supported.") } if (BeforeShortVideoUploadEvent(this, thumbnail, video).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeShortVideoUploadEvent") } // local uploaded offline short video uses video file md5 as its file name by default val videoName = fileName ?: video.md5.toUHexString("") val uploadResp = bot.network.sendAndExpect( PttCenterSvr.GroupShortVideoUpReq( client = bot.client, contact = this, thumbnailFileMd5 = thumbnail.md5, thumbnailFileSize = thumbnail.size, videoFileName = videoName, videoFileMd5 = video.md5, videoFileSize = video.size, videoFileFormat = video.formatName ) ) // get thumbnail image width and height val thumbnailInfo = thumbnail.calculateImageInfo() // fast path if (uploadResp is PttCenterSvr.GroupShortVideoUpReq.Response.FileExists) { return OfflineShortVideoImpl( uploadResp.fileId, videoName, video.md5, video.size, video.formatName, ShortVideoThumbnail( thumbnail.md5, thumbnail.size, thumbnailInfo.width, thumbnailInfo.height ) ).also { ShortVideoUploadEvent.Succeed(this, thumbnail, video, it).broadcast() } } val highwayRespExt = CombinedExternalResource(thumbnail, video).use { resource -> Highway.uploadResourceBdh( bot = bot, resource = resource, kind = ResourceKind.SHORT_VIDEO, commandId = 25, extendInfo = buildPacket { writeProtoBuf( PttShortVideo.PttShortVideoUploadReq.serializer(), PttCenterSvr.GroupShortVideoUpReq.buildShortVideoFileInfo( client = bot.client, contact = this@AbstractContact, thumbnailFileMd5 = thumbnail.md5, thumbnailFileSize = thumbnail.size, videoFileName = videoName, videoFileMd5 = video.md5, videoFileSize = video.size, videoFileFormat = video.formatName ) ) }.readBytes(), encrypt = true ).extendInfo } if (highwayRespExt == null) { ShortVideoUploadEvent.Failed( this, thumbnail, video, -1, "highway upload short video failed, extendInfo is null." ).broadcast() error("highway upload short video failed, extendInfo is null.") } val highwayUploadResp = highwayRespExt.loadAs(PttShortVideo.PttShortVideoUploadResp.serializer()) OfflineShortVideoImpl( highwayUploadResp.fileid, videoName, video.md5, video.size, video.formatName, ShortVideoThumbnail( thumbnail.md5, thumbnail.size, thumbnailInfo.width, thumbnailInfo.height ) ).also { ShortVideoUploadEvent.Succeed(this, thumbnail, video, it).broadcast() } } } } internal val Contact.userIdOrNull: Long? get() = if (this is User) this.id else null internal val Contact.groupIdOrNull: Long? get() = if (this is Group) this.id else null internal val Contact.groupUinOrNull: Long? get() = if (this is Group) this.uin else null internal val ContactOrBot.uin: Long get() = when (this) { is Group -> uin is User -> uin is OtherClient -> bot.uin is Bot -> id else -> this.id } internal fun Contact.impl(): AbstractContact { contract { returns() implies (this@impl is AbstractContact) } return this as AbstractContact } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/AbstractMember.kt ================================================ /* * 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 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 kotlin.coroutines.CoroutineContext internal sealed class AbstractMember( final override val group: GroupImpl, parentCoroutineContext: CoroutineContext, memberInfo: MemberInfo, ) : AbstractUser(group.bot, parentCoroutineContext, memberInfo), Member { final override val info: MemberInfoImpl = memberInfo.cast() override var nick: String by info::nick override val remark: String by info::remark override val nameCard: String get() = info.nameCard override val specialTitle: String get() = info.specialTitle override val active: MemberActiveImpl = MemberActiveImpl(info, group) override var permission: MemberPermission by info::permission } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/AbstractUser.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.User import net.mamoe.mirai.data.UserInfo import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.flags.SkipEventBroadcast import net.mamoe.mirai.internal.message.image.* import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.component.buildComponentStorage import net.mamoe.mirai.internal.network.components.BdhSession import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.internal.network.components.HttpClientProvider 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.tryServersUpload import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 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.utils.AtomicIntSeq import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext internal val User.info: UserInfo? get() = this.castOrNull<AbstractUser>()?.info @Suppress("NOTHING_TO_INLINE") internal inline fun User.impl(): AbstractUser { contract { returns() implies (this@impl is AbstractUser) } check(this is AbstractUser) return this } internal val User.correspondingMessageSourceKind get() = when (this) { is Friend -> MessageSourceKind.FRIEND is Member -> MessageSourceKind.TEMP is Stranger -> MessageSourceKind.STRANGER else -> error("Unknown user: ${this::class.qualifiedName}") } internal sealed class AbstractUser( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, userInfo: UserInfo, ) : User, AbstractContact(bot, parentCoroutineContext) { final override val id: Long = userInfo.uin abstract override val nick: String abstract override val remark: String val messageSeq = AtomicIntSeq.forMessageSeq() val fragmentedMessageMerger = C2CPkgMsgParsingCache() open val info: UserInfo = userInfo @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") override suspend fun uploadImage(resource: ExternalResource): Image = resource.withAutoClose { if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } val imageInfo = runBIO { resource.calculateImageInfo() } val resp = bot.network.sendAndExpect( LongConn.OffPicUp( bot.client, Cmd0x352.TryUpImgReq( buType = 1, srcUin = bot.id, dstUin = this@AbstractUser.id, fileMd5 = resource.md5, fileSize = resource.size, imgWidth = imageInfo.width, imgHeight = imageInfo.height, imgType = getIdByImageType(imageInfo.imageType), fileName = "${resource.md5.toUHexString("")}.${resource.formatName}", //For gif, using not original imgOriginal = (imageInfo.imageType != ImageType.GIF), buildVer = bot.client.buildVer, ), ), 5000, 2 ) return when (resp) { is LongConn.OffPicUp.Response.FileExists -> { val imageType = getImageType(resp.imageInfo.fileType).takeIf { it != ExternalResource.DEFAULT_FORMAT_NAME } ?: resource.formatName resp.imageInfo.run { OfflineFriendImage( imageId = generateImageIdFromResourceId( resourceId = resp.resourceId, format = imageType ) ?: kotlin.run { if (resp.imageInfo.fileMd5.size == 16) { generateImageId(resp.imageInfo.fileMd5, imageType) } else { throw contextualBugReportException( "Failed to compute friend image image from resourceId: ${resp.resourceId}", resp.structureToString(), additional = "并附加此时正在上传的文件" ) } }, width = fileWidth, height = fileHeight, imageType = getImageTypeById(fileType) ?: ImageType.UNKNOWN, size = resource.size ) }.also { ImageUploadEvent.Succeed(this, resource, it).broadcast() } } is LongConn.OffPicUp.Response.RequireUpload -> { kotlin.runCatching { // try once upload by private bdh Highway.uploadResourceBdh( bot = bot, resource = resource, kind = PRIVATE_IMAGE, commandId = 1, initialTicket = resp.uKey, tryOnce = true ) }.recoverCatchingSuppressed { // try upload as group image val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect( ImgStore.GroupPicUp( bot.client, uin = bot.id, groupCode = id, md5 = resource.md5, size = resource.size, picWidth = imageInfo.width, picHeight = imageInfo.height, picType = getIdByImageType(imageInfo.imageType), buType = 2, // not group ) ) when (response) { is ImgStore.GroupPicUp.Response.Failed -> { error("upload private image as group image failed with reason ${response.message}") } is ImgStore.GroupPicUp.Response.FileExists -> { // success } is ImgStore.GroupPicUp.Response.RequireUpload -> { // val servers = response.uploadIpList.zip(response.uploadPortList) Highway.uploadResourceBdh(bot = bot, resource = resource, kind = PRIVATE_IMAGE, commandId = 2, initialTicket = response.uKey, fallbackSession = { BdhSession( EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, ssoAddresses = response.uploadIpList.zip(response.uploadPortList) .toMutableSet(), ) }) } } }.recoverCatchingSuppressed { // try upload by private bdh on other servers Highway.uploadResourceBdh( bot = bot, resource = resource, kind = PRIVATE_IMAGE, commandId = 1, initialTicket = resp.uKey, ) }.recoverCatchingSuppressed { // try upload by http on provided servers tryServersUpload( bot = bot, servers = resp.serverIp.zip(resp.serverPort), resourceSize = resource.size, resourceKind = PRIVATE_IMAGE, channelKind = ChannelKind.HTTP ) { ip, port -> bot.components[HttpClientProvider].getHttpClient().postImage( serverIp = ip, serverPort = port, htcmd = "0x6ff0070", uin = bot.id, groupcode = null, imageInput = resource, uKeyHex = resp.uKey.toUHexString("") ) } }.recoverCatchingSuppressed { // try upload by http on fallback server bot.components[HttpClientProvider].getHttpClient().postImage( serverIp = "htdata2.qq.com", htcmd = "0x6ff0070", uin = bot.id, groupcode = null, imageInput = resource, uKeyHex = resp.uKey.toUHexString("") ) }.getOrThrow() imageInfo.run { OfflineFriendImage( imageId = generateImageIdFromResourceId(resp.resourceId, resource.formatName) ?: resp.resourceId, width = width, height = height, imageType = imageType, size = resource.size ) }.also { ImageUploadEvent.Succeed(this, resource, it).broadcast() } } is LongConn.OffPicUp.Response.Failed -> { ImageUploadEvent.Failed(this, resource, -1, resp.message).broadcast() error(resp.message) } } } } internal suspend fun <C : AbstractContact> C.sendMessageImpl( message: Message, messageProtocolStrategy: MessageProtocolStrategy<C>, preSendEventConstructor: (C, Message) -> MessagePreSendEvent, postSendEventConstructor: (C, MessageChain, Throwable?, MessageReceipt<C>?) -> MessagePostSendEvent<C>, ): MessageReceipt<C> { val skipEvent = if (message is MessageChain) { message.anyIsInstance<SkipEventBroadcast>() } else false require(!message.isContentEmpty()) { "message is empty" } val chain = broadcastMessagePreSendEvent(message, skipEvent, preSendEventConstructor) val result = kotlin.runCatching { MessageProtocolFacade.preprocessAndSendOutgoing(this, chain, buildComponentStorage { set(MessageProtocolStrategy, messageProtocolStrategy) set(HighwayUploader, HighwayUploader.Default) set(ClockHolder, bot.components[ClockHolder]) }) } if (result.isSuccess) { // logMessageSent(result.getOrNull()?.source?.plus(chain) ?: chain) // log with source bot.logger.verbose("$this <- $chain".replaceMagicCodes()) } if (!skipEvent) { postSendEventConstructor(this, chain, result.exceptionOrNull(), result.getOrNull()).broadcast() } return result.getOrThrow() } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/AnonymousMemberImpl.kt ================================================ /* * 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 import net.mamoe.mirai.contact.AnonymousMember import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.internal.MiraiImpl import net.mamoe.mirai.internal.getMiraiImpl import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.utils.ExternalResource import kotlin.coroutines.CoroutineContext internal class AnonymousMemberImpl( group: GroupImpl, parentCoroutineContext: CoroutineContext, memberInfo: MemberInfo, ) : AnonymousMember, AbstractMember(group, parentCoroutineContext, memberInfo) { init { requireNotNull(memberInfo.anonymousId) { "anonymousId must not be null" } } override val anonymousId: String get() = info.anonymousId!! override suspend fun mute(durationSeconds: Int) { checkBotPermissionHigherThanThis("mute") getMiraiImpl().muteAnonymousMember(bot, anonymousId, nameCard, group.uin, durationSeconds) } override fun toString(): String = "AnonymousMember($nameCard, $anonymousId)" override suspend fun uploadImage(resource: ExternalResource): Image = throw UnsupportedOperationException("Cannot upload image to AnonymousMember") } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/ContactAware.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.internal.asQQAndroidBot internal interface ContactAware { val contact: Contact val bot get() = contact.bot.asQQAndroidBot() val client get() = bot.client } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/FriendImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(LowLevelApi::class) @file:Suppress( "NOTHING_TO_INLINE", ) package net.mamoe.mirai.internal.contact import io.ktor.utils.io.core.* import kotlinx.coroutines.launch import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.FriendMessagePostSendEvent import net.mamoe.mirai.event.events.FriendMessagePreSendEvent import net.mamoe.mirai.event.events.FriendRemarkChangeEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.info.FriendInfoImpl import net.mamoe.mirai.internal.contact.roaming.RoamingMessagesImplFriend import net.mamoe.mirai.internal.message.data.OfflineAudioImpl import net.mamoe.mirai.internal.message.protocol.outgoing.FriendMessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.highway.* import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x346 import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList import net.mamoe.mirai.internal.network.protocol.packet.summarycard.ChangeFriendRemark import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.OfflineAudio import net.mamoe.mirai.spi.AudioToSilkService import net.mamoe.mirai.utils.* import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext internal fun net.mamoe.mirai.internal.network.protocol.data.jce.FriendInfo.toMiraiFriendInfo(): FriendInfoImpl = FriendInfoImpl( friendUin, nick, remark, groupId.toInt(), ) @OptIn(ExperimentalContracts::class) internal inline fun Friend.impl(): FriendImpl { contract { returns() implies (this@impl is FriendImpl) } check(this is FriendImpl) { "A Friend instance is not instance of FriendImpl. Your instance: ${this::class.qualifiedName}" } return this } internal class FriendImpl( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, override val info: FriendInfoImpl, ) : Friend, AbstractUser(bot, parentCoroutineContext, info) { override var nick: String by info::nick override var remark: String get() = info.remark set(value) { val old = info.remark info.remark = value launch { bot.network.sendWithoutExpect(ChangeFriendRemark(bot.client, this@FriendImpl.id, value)) FriendRemarkChangeEvent(this@FriendImpl, old, value).broadcast() } } override val friendGroup: FriendGroup get() = bot.friendGroups[info.friendGroupId] ?: bot.friendGroups[0]!! private val messageProtocolStrategy: MessageProtocolStrategy<FriendImpl> = FriendMessageProtocolStrategy(this) override suspend fun delete() { check(bot.friends[id] != null) { "Friend $id had already been deleted" } bot.network.sendAndExpect(FriendList.DelFriend.invoke(bot.client, this@FriendImpl), 5000, 2).let { check(it.isSuccess) { "delete friend failed: ${it.resultCode}" } } } override suspend fun sendMessage(message: Message): MessageReceipt<Friend> { return sendMessageImpl( message, messageProtocolStrategy, ::FriendMessagePreSendEvent, ::FriendMessagePostSendEvent.cast() ) } override fun toString(): String = "Friend($id)" override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = AudioToSilkService.instance.convert( resource ).useAutoClose { res -> var audio: OfflineAudioImpl? = null kotlin.runCatching { val resp = Highway.uploadResourceBdh( bot = bot, resource = res, kind = ResourceKind.PRIVATE_AUDIO, commandId = 26, extendInfo = PttStore.C2C.createC2CPttStoreBDHExt(bot, this@FriendImpl.uin, res) .toByteArray(Cmd0x346.ReqBody.serializer()) ) // resp._miraiContentToString("UV resp") val c346resp = resp.extendInfo!!.loadAs(Cmd0x346.RspBody.serializer()) if (c346resp.msgApplyUploadRsp == null) { error("Upload failed") } audio = OfflineAudioImpl( filename = "${res.md5.toUHexString("")}.amr", fileMd5 = res.md5, fileSize = res.size, codec = res.audioCodec, originalPtt = ImMsgBody.Ptt( fileType = 4, srcUin = bot.uin, fileUuid = c346resp.msgApplyUploadRsp.uuid, fileMd5 = res.md5, fileName = res.md5 + ".amr".toByteArray(), fileSize = res.size.toInt(), boolValid = true, ) ) }.recoverCatchingSuppressed { when (val resp = bot.network.sendAndExpect(PttStore.GroupPttUp(bot.client, bot.id, id, res))) { is PttStore.GroupPttUp.Response.RequireUpload -> { tryServersUpload( bot, resp.uploadIpList.zip(resp.uploadPortList), res.size, ResourceKind.GROUP_AUDIO, ChannelKind.HTTP ) { ip, port -> bot.components[HttpClientProvider].getHttpClient() .postPtt(ip, port, res, resp.uKey, resp.fileKey) } audio = OfflineAudioImpl( filename = "${res.md5.toUHexString("")}.amr", fileMd5 = res.md5, fileSize = res.size, codec = res.audioCodec, originalPtt = ImMsgBody.Ptt( fileType = 4, srcUin = bot.uin, fileUuid = resp.fileId.toByteArray(), fileMd5 = res.md5, fileName = res.md5 + ".amr".toByteArray(), fileSize = res.size.toInt(), boolValid = true, ) ) } } }.getOrThrow() return audio!! } override val roamingMessages: RoamingMessages by lazy { RoamingMessagesImplFriend(this) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @file:OptIn(LowLevelApi::class) package net.mamoe.mirai.internal.contact import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import net.mamoe.mirai.Bot import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.active.GroupActive import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.contact.essence.Essences import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.data.GroupInfo 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.active.GroupActiveImpl import net.mamoe.mirai.internal.contact.announcement.AnnouncementsImpl import net.mamoe.mirai.internal.contact.essence.EssencesImpl import net.mamoe.mirai.internal.contact.file.RemoteFilesImpl import net.mamoe.mirai.internal.contact.info.MemberInfoImpl import net.mamoe.mirai.internal.contact.roaming.RoamingMessagesImplGroup import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.data.OfflineAudioImpl import net.mamoe.mirai.internal.message.image.OfflineGroupImage import net.mamoe.mirai.internal.message.image.calculateImageInfo import net.mamoe.mirai.internal.message.image.getIdByImageType import net.mamoe.mirai.internal.message.image.getImageTypeById import net.mamoe.mirai.internal.message.protocol.outgoing.GroupMessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.components.BdhSession import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.highway.ChannelKind import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_AUDIO import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_IMAGE import net.mamoe.mirai.internal.network.highway.postPtt import net.mamoe.mirai.internal.network.highway.tryServersUpload import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388 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 import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache import net.mamoe.mirai.internal.utils.ImagePatcher import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.spi.AudioToSilkService import net.mamoe.mirai.utils.* import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext internal fun GroupImpl.Companion.checkIsInstance(instance: Group) { contract { returns() implies (instance is GroupImpl) } check(instance is GroupImpl) { "group is not an instanceof GroupImpl!! DO NOT interlace two or more protocol implementations!!" } } internal fun Group.checkIsGroupImpl(): GroupImpl { contract { returns() implies (this@checkIsGroupImpl is GroupImpl) } GroupImpl.checkIsInstance(this) return this } internal fun GroupImpl( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, id: Long, groupInfo: GroupInfo, members: Sequence<MemberInfo>, ): GroupImpl { return GroupImpl(bot, parentCoroutineContext, id, groupInfo, ContactList(ConcurrentLinkedDeque())).apply Group@{ members.forEach { info -> if (info.uin == bot.id) { botAsMember = newNormalMember(info) if (info.permission == MemberPermission.OWNER) { owner = botAsMember } } else newNormalMember(info).let { member -> if (member.permission == MemberPermission.OWNER) { owner = member } this@Group.members.delegate.add(member) } } }.apply { if (!botAsMemberInitialized) { logger.error( contextualBugReportException("GroupImpl", """ groupId: ${groupInfo.groupCode.takeIf { it != 0L } ?: id} groupUin: ${groupInfo.uin} membersCount: ${members.count()} botId: ${bot.id} owner: ${kotlin.runCatching { owner }.getOrNull()?.id} """.trimIndent(), additional = "并告知此时 Bot 是否为群管理员或群主, 和是否刚刚加入或离开这个群" ) ) } } } private val logger by lazy { MiraiLogger.Factory.create(GroupImpl::class, "Group") } internal fun Bot.nickIn(context: Contact): String = if (context is Group) context.botAsMember.nameCardOrNick else bot.nick internal expect class GroupImpl constructor( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, id: Long, groupInfo: GroupInfo, members: ContactList<NormalMemberImpl>, ) : Group, CommonGroupImpl { companion object } @Suppress("PropertyName") internal abstract class CommonGroupImpl constructor( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, override val id: Long, groupInfo: GroupInfo, final override val members: ContactList<NormalMemberImpl>, ) : Group, AbstractContact(bot, parentCoroutineContext) { companion object val uin: Long = groupInfo.uin final override val settings: GroupSettingsImpl = GroupSettingsImpl(this.cast(), groupInfo) final override var name: String by settings::name final override lateinit var owner: NormalMemberImpl final override lateinit var botAsMember: NormalMemberImpl internal val botAsMemberInitialized get() = ::botAsMember.isInitialized final override val files: RemoteFiles by lazy { RemoteFilesImpl(this) } private val _lastTalkative: AtomicRef<NormalMemberImpl?> = atomic(null) init { // Cannot move to argument of `atomic`, compiler error. val value = members.find { GroupHonorType.TALKATIVE in it.active.honors } _lastTalkative.value = value } val lastTalkative get() = _lastTalkative.value fun casLastTalkative(expect: NormalMemberImpl?, update: NormalMemberImpl?): Boolean = _lastTalkative.compareAndSet(expect, update) final override val announcements: Announcements by lazy { AnnouncementsImpl( this as GroupImpl, bot.network.logger.subLogger("Group $id") ) } final override val active: GroupActive by lazy { GroupActiveImpl( this as GroupImpl, bot.network.logger.subLogger("Group $id"), groupInfo ) } val groupPkgMsgParsingCache = GroupPkgMsgParsingCache() private val messageProtocolStrategy: MessageProtocolStrategy<GroupImpl> = GroupMessageProtocolStrategy(this.cast()) override suspend fun quit(): Boolean { check(botPermission != MemberPermission.OWNER) { "Failed to quit $id because bot is the owner" } if (!bot.groups.delegate.remove(this)) { return false } val response: ProfileService.GroupMngReq.GroupMngReqResponse = bot.network.sendAndExpect( ProfileService.GroupMngReq(bot.client, this@CommonGroupImpl.id), 5000, 2 ) check(response.errorCode == 0) { "Group($id).quit failed: $response".also { bot.groups.delegate.add(this@CommonGroupImpl.castUp()) } } BotLeaveEvent.Active(this).broadcast() return true } override operator fun get(id: Long): NormalMemberImpl? { if (id == bot.id) return botAsMember return members.firstOrNull { it.id == id } } override fun contains(id: Long): Boolean { return bot.id == id || members.firstOrNull { it.id == id } != null } override suspend fun sendMessage(message: Message): MessageReceipt<Group> { check(!isBotMuted) { throw BotIsBeingMutedException(this, message) } return sendMessageImpl( message, messageProtocolStrategy.castUp(), ::GroupMessagePreSendEvent, ::GroupMessagePostSendEvent.cast() ) } override suspend fun uploadImage(resource: ExternalResource): Image = resource.withAutoClose { if (BeforeImageUploadEvent(this, resource).broadcast().isCancelled) { throw EventCancelledException("cancelled by BeforeImageUploadEvent.ToGroup") } fun OfflineGroupImage.putIntoCache() { // We can't understand wny Image(group.uploadImage().imageId) bot.components[ImagePatcher].putCache(this) } val imageInfo = runBIO { resource.calculateImageInfo() } val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect( ImgStore.GroupPicUp( bot.client, uin = bot.id, groupCode = id, md5 = resource.md5, size = resource.size, filename = "${resource.md5.toUHexString("")}.${resource.formatName}", picWidth = imageInfo.width, picHeight = imageInfo.height, picType = getIdByImageType(imageInfo.imageType), ), 5000, 2 ) when (response) { is ImgStore.GroupPicUp.Response.Failed -> { ImageUploadEvent.Failed(this@CommonGroupImpl, resource, response.resultCode, response.message) .broadcast() if (response.message == "over file size max") throw OverFileSizeMaxException() error("upload group image failed with reason ${response.message}") } is ImgStore.GroupPicUp.Response.FileExists -> { val resourceId = resource.calculateResourceId() return response.fileInfo.run { OfflineGroupImage( imageId = resourceId, height = fileHeight, width = fileWidth, imageType = getImageTypeById(fileType) ?: ImageType.UNKNOWN, size = resource.size ) } .also { it.fileId = response.fileId.toInt() } .also { it.putIntoCache() } .also { ImageUploadEvent.Succeed(this@CommonGroupImpl, resource, it).broadcast() } } is ImgStore.GroupPicUp.Response.RequireUpload -> { // val servers = response.uploadIpList.zip(response.uploadPortList) Highway.uploadResourceBdh( bot = bot, resource = resource, kind = GROUP_IMAGE, commandId = 2, initialTicket = response.uKey, noBdhAwait = true, fallbackSession = { BdhSession( EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, ssoAddresses = response.uploadIpList.zip(response.uploadPortList).toMutableSet(), ) }, ) return imageInfo.run { OfflineGroupImage( imageId = resource.calculateResourceId(), width = width, height = height, imageType = imageType, size = resource.size ) }.also { it.fileId = response.fileId.toInt() } .also { it.putIntoCache() } .also { ImageUploadEvent.Succeed(this@CommonGroupImpl, resource, it).broadcast() } } } } @Deprecated("use uploadAudio", replaceWith = ReplaceWith("uploadAudio(resource)"), level = DeprecationLevel.HIDDEN) @Suppress("OverridingDeprecatedMember", "DEPRECATION", "DEPRECATION_ERROR") override suspend fun uploadVoice(resource: ExternalResource): net.mamoe.mirai.message.data.Voice = AudioToSilkService.instance.convert( resource ).useAutoClose { res -> return bot.network.run { uploadAudioResource(res) // val body = resp?.loadAs(Cmd0x388.RspBody.serializer()) // ?.msgTryupPttRsp // ?.singleOrNull()?.fileKey ?: error("Group voice highway transfer succeed but failed to find fileKey") net.mamoe.mirai.message.data.Voice( "${res.md5.toUHexString("")}.amr", res.md5, res.size, res.voiceCodec, "" ) } } private suspend fun uploadAudioResource(resource: ExternalResource) { kotlin.runCatching { val (_) = Highway.uploadResourceBdh( bot = bot, resource = resource, kind = GROUP_AUDIO, commandId = 29, extendInfo = PttStore.GroupPttUp.createTryUpPttPack(bot.id, id, resource) .toByteArray(Cmd0x388.ReqBody.serializer()), ) }.recoverCatchingSuppressed { when (val resp = bot.network.sendAndExpect(PttStore.GroupPttUp(bot.client, bot.id, id, resource))) { is PttStore.GroupPttUp.Response.RequireUpload -> { tryServersUpload( bot, resp.uploadIpList.zip(resp.uploadPortList), resource.size, GROUP_AUDIO, ChannelKind.HTTP ) { ip, port -> bot.components[HttpClientProvider].getHttpClient() .postPtt(ip, port, resource, resp.uKey, resp.fileKey) } } } }.getOrThrow() } override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = AudioToSilkService.instance.convert( resource ).useAutoClose { res -> return bot.network.run { uploadAudioResource(res) // val body = resp?.loadAs(Cmd0x388.RspBody.serializer()) // ?.msgTryupPttRsp // ?.singleOrNull()?.fileKey ?: error("Group voice highway transfer succeed but failed to find fileKey") OfflineAudioImpl( filename = "${res.md5.toUHexString("")}.amr", fileMd5 = res.md5, fileSize = res.size, codec = res.audioCodec, originalPtt = null, ) } } override suspend fun setEssenceMessage(source: MessageSource): Boolean { checkBotPermission(MemberPermission.ADMINISTRATOR) val result = bot.network.sendAndExpect( TroopEssenceMsgManager.SetEssence( bot.client, this@CommonGroupImpl.uin, source.internalIds.first(), source.ids.first() ), 5000, 2 ) return result.success } override val roamingMessages: RoamingMessages by lazy { RoamingMessagesImplGroup(this) } // 鉴于在 [essences] 中 有相同的功能的 Web API 所以此方法移除 // override suspend fun removeEssenceMessage(source: MessageSource): Boolean { // checkBotPermission(MemberPermission.ADMINISTRATOR) // val result = bot.network.sendAndExpect( // TroopEssenceMsgManager.RemoveEssence( // bot.client, // this@CommonGroupImpl.uin, // source.internalIds.first(), // source.ids.first() // ), 5000, 2 // ) // return result.success // } override val essences: Essences by lazy { EssencesImpl( this as GroupImpl, bot.network.logger.subLogger("Group $id"), ) } override fun toString(): String = "Group($id)" } internal fun Group.addNewNormalMember(memberInfo: MemberInfo): NormalMemberImpl? { if (members.contains(memberInfo.uin)) return null return newNormalMember(memberInfo).also { members.delegate.add(it) } } internal fun Group.newNormalMember(memberInfo: MemberInfo): NormalMemberImpl { this.checkIsGroupImpl() return NormalMemberImpl( this, this.coroutineContext, memberInfo ) } internal fun GroupImpl.newAnonymous(name: String, id: String): AnonymousMemberImpl { return AnonymousMemberImpl( this, this.coroutineContext, MemberInfoImpl( uin = 80000000L, nick = name, permission = MemberPermission.MEMBER, remark = "匿名", nameCard = name, specialTitle = "匿名", muteTimestamp = 0, anonymousId = id, ) ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/GroupSendMessageImpl.kt ================================================ /* * 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("RESULT_CLASS_IN_RETURN_TYPE") // inline ABI not stable but we don't care about internal ABI package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.MessagePreSendEvent import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.toMessageChain /** * Called only in 'public' apis. */ internal suspend fun <C : Contact> C.broadcastMessagePreSendEvent( message: Message, skipEvent: Boolean, eventConstructor: (C, Message) -> MessagePreSendEvent, ): MessageChain { if (skipEvent) return message.toMessageChain() var eventName: String? = null return kotlin.runCatching { eventConstructor(this, message).also { eventName = it::class.simpleName }.broadcast() }.onSuccess { check(!it.isCancelled) { throw EventCancelledException("cancelled by $eventName") } }.getOrElse { eventName = eventName ?: (this@broadcastMessagePreSendEvent::class.simpleName + "MessagePreSendEvent") throw EventCancelledException("exception thrown when broadcasting $eventName", it) }.message.toMessageChain() } internal enum class SendMessageStep( val allowMultiplePackets: Boolean ) { /** * 尝试单包直接发送全部消息 */ FIRST(false) { override fun nextStepOrNull(): SendMessageStep { return LONG_MESSAGE } }, /** * 尝试通过长消息通道上传长消息取得 resId 后再通过普通消息通道发送长消息标识 */ LONG_MESSAGE(false) { override fun nextStepOrNull(): SendMessageStep { return FRAGMENTED } }, /** * 发送分片多包发送 */ FRAGMENTED(true) { override fun nextStepOrNull(): SendMessageStep? { return null } }; abstract fun nextStepOrNull(): SendMessageStep? } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/GroupSettingsImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact import kotlinx.coroutines.launch import net.mamoe.mirai.contact.GroupSettings import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.checkBotPermission import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.GroupAllowMemberInviteEvent import net.mamoe.mirai.event.events.GroupMuteAllEvent import net.mamoe.mirai.event.events.GroupNameChangeEvent import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement.GroupOperation import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement.SwitchAnonymousChat @Suppress("SetterBackingFieldAssignment") internal class GroupSettingsImpl( private val group: GroupImpl, groupInfo: GroupInfo, ) : GroupSettings { private inline fun <T> GroupImpl.setImpl( newValue: T, getter: () -> T, setter: (T) -> Unit, crossinline packetConstructor: (client: QQAndroidClient, groupCode: Long, newValue: T) -> OutgoingPacket, crossinline eventConstructor: (old: T) -> Event?, ) { checkBotPermission(MemberPermission.ADMINISTRATOR) val oldValue = getter() setter(newValue) launch { bot.network.sendWithoutExpect(packetConstructor(bot.client, id, newValue)) eventConstructor(oldValue)?.broadcast() } } internal var nameField: String = groupInfo.name var name: String get() = nameField set(newValue) { group.setImpl(newValue, { nameField }, { nameField = it }, GroupOperation::name) { GroupNameChangeEvent(it, newValue, group, null) } } private var _entranceAnnouncement: String = groupInfo.memo @Deprecated("Don't use public var internally", level = DeprecationLevel.HIDDEN) override var entranceAnnouncement: String get() = _entranceAnnouncement set(newValue) { group.setImpl(newValue, { _entranceAnnouncement }, { _entranceAnnouncement = it }, GroupOperation::memo) { null } } private var isAllowMemberInviteField: Boolean = groupInfo.allowMemberInvite @Deprecated("Don't use public var internally", level = DeprecationLevel.HIDDEN) override var isAllowMemberInvite: Boolean get() = isAllowMemberInviteField set(newValue) { group.setImpl( newValue, { isAllowMemberInviteField }, { isAllowMemberInviteField = it }, GroupOperation::allowMemberInvite ) { GroupAllowMemberInviteEvent(it, newValue, group, null) } } internal var isAnonymousChatEnabledField: Boolean = groupInfo.allowAnonymousChat @Deprecated("Don't use public var internally", level = DeprecationLevel.HIDDEN) override var isAnonymousChatEnabled: Boolean get() = isAnonymousChatEnabledField set(newValue) { group.run { checkBotPermission(MemberPermission.ADMINISTRATOR) launch { //Handle it in NoticePipelineContext#processAllowAnonymousChat bot.network.sendAndExpect(SwitchAnonymousChat(bot.client, id, newValue)) } } } @Deprecated("Don't use public var internally", level = DeprecationLevel.HIDDEN) override var isAutoApproveEnabled: Boolean = groupInfo.autoApprove @Suppress("UNUSED_PARAMETER") set(newValue) { throw UnsupportedOperationException() } internal var isMuteAllField: Boolean = groupInfo.muteAll @Deprecated("Don't use public var internally", level = DeprecationLevel.HIDDEN) override var isMuteAll: Boolean get() = isMuteAllField set(newValue) { group.setImpl(newValue, { isMuteAllField }, { isMuteAllField = it }, GroupOperation::muteAll) { GroupMuteAllEvent(it, newValue, group, null) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/MemberActiveImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.active.MemberActive import net.mamoe.mirai.contact.active.MemberMedalInfo import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.internal.contact.active.GroupActiveImpl import net.mamoe.mirai.internal.contact.info.MemberInfoImpl internal class MemberActiveImpl(private val info: MemberInfoImpl, private val group: GroupImpl) : MemberActive { override val rank: Int get() = info.rank override val point: Int get() = info.point override val honors: Set<GroupHonorType> get() = info.honors override val temperature: Int get() = info.temperature override suspend fun queryMedal(): MemberMedalInfo { return (group.active as GroupActiveImpl).queryMemberMedal(uid = info.uin) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/NormalMemberImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.internal.contact import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.message.protocol.outgoing.GroupTempMessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToTempImpl import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext @OptIn(LowLevelApi::class) @Suppress("MemberVisibilityCanBePrivate") internal class NormalMemberImpl constructor( group: GroupImpl, parentCoroutineContext: CoroutineContext, memberInfo: MemberInfo, ) : NormalMember, AbstractMember(group, parentCoroutineContext, memberInfo) { override val joinTimestamp: Int get() = info.joinTimestamp override val lastSpeakTimestamp: Int get() = info.lastSpeakTimestamp private val messageProtocolStrategy: MessageProtocolStrategy<NormalMemberImpl> = GroupTempMessageProtocolStrategy override fun toString(): String = "NormalMember($id)" @Suppress("DuplicatedCode") override suspend fun sendMessage(message: Message): MessageReceipt<NormalMember> { return asFriendOrNull()?.sendMessage(message)?.convert() ?: asStrangerOrNull()?.sendMessage(message)?.convert() ?: sendMessageImpl( message = message, preSendEventConstructor = ::GroupTempMessagePreSendEvent, postSendEventConstructor = ::GroupTempMessagePostSendEvent.cast(), messageProtocolStrategy = messageProtocolStrategy ) } private fun MessageReceipt<User>.convert(): MessageReceipt<NormalMemberImpl> { return OnlineMessageSourceToTempImpl(source, this@NormalMemberImpl).createMessageReceipt( this@NormalMemberImpl, doLightRefine = false //we've already did ) } @Suppress("PropertyName") internal var _nameCard: String = memberInfo.nameCard @Suppress("PropertyName") internal var _specialTitle: String = memberInfo.specialTitle @Suppress("PropertyName") var _muteTimestamp: Int = memberInfo.muteTimestamp @Suppress("PropertyName") var _nudgeTimestamp: Long = 0L override val muteTimeRemaining: Int get() = if (_muteTimestamp == 0 || _muteTimestamp == 0xFFFFFFFF.toInt()) { 0 } else { (_muteTimestamp - currentTimeSeconds().toInt()).coerceAtLeast(0) } override var nameCard: String get() = _nameCard set(newValue) { if (id != bot.id) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) } if (_nameCard != newValue) { val oldValue = _nameCard _nameCard = newValue launch { bot.network.sendWithoutExpect( TroopManagement.EditGroupNametag( bot.client, this@NormalMemberImpl, newValue, ) ) MemberCardChangeEvent(oldValue, newValue, this@NormalMemberImpl).broadcast() } } } override var specialTitle: String get() = _specialTitle set(newValue) { group.checkBotPermission(MemberPermission.OWNER) if (_specialTitle != newValue) { val oldValue = _specialTitle _specialTitle = newValue launch { bot.network.sendWithoutExpect( TroopManagement.EditSpecialTitle( bot.client, this@NormalMemberImpl, newValue, ) ) MemberSpecialTitleChangeEvent(oldValue, newValue, this@NormalMemberImpl, null).broadcast() } } } override suspend fun mute(durationSeconds: Int) { check(this.id != bot.id) { "A bot can't mute itself." } require(durationSeconds > 0) { "durationSeconds must greater than zero" } checkBotPermissionHigherThanThis("mute") bot.network.sendAndExpect( TroopManagement.Mute( client = bot.client, groupCode = group.id, memberUin = this@NormalMemberImpl.id, timeInSecond = durationSeconds, ), 5000, 2 ) @Suppress("RemoveRedundantQualifierName") // or unresolved reference (net.mamoe.mirai.event.events.MemberMuteEvent(this@NormalMemberImpl, durationSeconds, null).broadcast()) this._muteTimestamp = currentTimeSeconds().toInt() + durationSeconds } override suspend fun unmute() { checkBotPermissionHigherThanThis("unmute") bot.network.sendAndExpect( TroopManagement.Mute( client = bot.client, groupCode = group.id, memberUin = this@NormalMemberImpl.id, timeInSecond = 0, ), 5000, 2 ) @Suppress("RemoveRedundantQualifierName") // or unresolved reference (net.mamoe.mirai.event.events.MemberUnmuteEvent(this@NormalMemberImpl, null).broadcast()) this._muteTimestamp = 0 } override suspend fun kick(message: String, block: Boolean) { checkBotPermissionHigherThanThis("kick") check(group.members[this.id] != null) { "Member ${this.id} had already been kicked from group ${group.id}" } val response: TroopManagement.Kick.Response = bot.network.sendAndExpect( TroopManagement.Kick( client = bot.client, groupCode = group.groupCode, memberId = id, message = message, ban = block ), 5000, 2 ) // Note: when member not found, result is still true. if (response.ret == 255) error("Operation too fast") // https://github.com/mamoe/mirai/issues/1503 check(response.success) { "kick member $id of group ${group.id} failed: ${response.ret}" } group.members.delegate.removeAll { it.id == this@NormalMemberImpl.id } this@NormalMemberImpl.cancel(CancellationException("Kicked by bot")) MemberLeaveEvent.Kick(this@NormalMemberImpl, null).broadcast() } override suspend fun modifyAdmin(operation: Boolean) { checkBotPermissionHighest("modifyAdmin") val origin = this@NormalMemberImpl.permission val new = if (operation) { MemberPermission.ADMINISTRATOR } else { MemberPermission.MEMBER } if (origin == new) return val resp: TroopManagement.ModifyAdmin.Response = bot.network.sendAndExpect( TroopManagement.ModifyAdmin( client = bot.client, member = this@NormalMemberImpl, operation = operation, ), 5000, 2 ) as TroopManagement.ModifyAdmin.Response check(resp.success) { buildString { append("Failed to ") append(if (operation) "grant" else "revoke") append(" administrator privileges ") append(if (operation) "to" else "from") append(" member ") append(id).append(" in group ").append(group.id) append(": code=").append(resp.code) append(", msg=").append(resp.msg) } } this@NormalMemberImpl.permission = new MemberPermissionChangeEvent(this@NormalMemberImpl, origin, new).broadcast() } } internal fun Member.checkBotPermissionHighest(operationName: String) { check(group.botPermission == MemberPermission.OWNER) { throw PermissionDeniedException( "`$operationName` operation for member $id requires the OWNER permission, while bot has ${group.botPermission} in group ${group.id}", ) } } internal fun Member.checkBotPermissionHigherThanThis(operationName: String) { check(group.botPermission > this.permission) { throw PermissionDeniedException( "`$operationName` operation for member $id of group ${group.id} requires a higher permission, while " + "${group.botPermission} < ${this.permission}", ) } } @OptIn(ExperimentalContracts::class) internal fun Member.checkIsMemberImpl(): NormalMemberImpl { contract { returns() implies (this@checkIsMemberImpl is NormalMemberImpl) } check(this is NormalMemberImpl) { "A Member instance is not instance of MemberImpl. Don't interlace two protocol implementations together!" } return this } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/OnlineAnnouncementImpl.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.announcement.AnnouncementParameters import net.mamoe.mirai.contact.announcement.OnlineAnnouncement /** * Note: Online is not designed to be serializable * * @since 2.7 */ //@SerialName(OnlineAnnouncementImpl.SERIAL_NAME) //@Serializable(OnlineAnnouncementImpl.Serializer::class) internal data class OnlineAnnouncementImpl( override val group: Group, override val senderId: Long, override val sender: NormalMember?, override val content: String, override val parameters: AnnouncementParameters, override val fid: String, override val allConfirmed: Boolean, override val confirmedMembersCount: Int, override val publicationTime: Long, ) : OnlineAnnouncement { // // @Serializable // private data class SerialData( // val botId: Long, // val groupId: Long, // val memberId: Long, // val title: String, // val body: String, // val parameters: AnnouncementParameters, // val fid: String, // val isAllRead: Boolean, // val readMemberNumber: Int, // val publishTime: Long, // ) // // internal object Serializer : KSerializer<OnlineAnnouncementImpl> by SerialData.serializer().map( // SerialData.serializer().descriptor.copy(SERIAL_NAME), // { // OnlineAnnouncementImpl( // sender = Bot.getInstance(botId).getGroupOrFail(groupId).getMemberOrFail(memberId), // title = title, // body = body, // parameters = parameters, // fid = Fid(fid), // isAllRead = isAllRead, // readMemberNumber = readMemberNumber, // publishTime = publishTime // ) // }, // { // SerialData( // botId = sender.bot.id, // groupId = sender.group.id, // memberId = sender.id, // title = title, // body = body, // parameters = parameters, // fid = fid.toString(), // isAllRead = isAllRead, // readMemberNumber = readMemberNumber, // publishTime = publishTime // ) // } // ) // companion object { // const val SERIAL_NAME: String = "ReceiveAnnouncement" // } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/OtherClientImpl.kt ================================================ /* * 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 import net.mamoe.mirai.contact.OtherClient import net.mamoe.mirai.contact.OtherClientInfo import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.ExternalResource import kotlin.coroutines.CoroutineContext internal inline val OtherClient.appId: Int get() = info.appId internal fun QQAndroidBot.createOtherClient( info: OtherClientInfo, ): OtherClientImpl { return OtherClientImpl(this, coroutineContext, info) } internal class OtherClientImpl( bot: QQAndroidBot, coroutineContext: CoroutineContext, override val info: OtherClientInfo, ) : OtherClient, AbstractContact(bot, coroutineContext) { override suspend fun sendMessage(message: Message): MessageReceipt<OtherClient> { throw UnsupportedOperationException("OtherClientImpl.sendMessage is not yet supported.") } override suspend fun uploadImage(resource: ExternalResource): Image { throw UnsupportedOperationException("OtherClientImpl.uploadImage is not yet supported.") } override fun toString(): String { return "OtherClient(bot=${bot.id},deviceName=${info.deviceName},platform=${info.platform})" } } /* contentHead=ContentHead#522561765 { } msgBody=MsgBody#-1622349855 { msgContent=08 04 12 1E 08 E9 07 10 B7 F7 8B 80 02 18 E9 07 20 00 28 DD F1 92 B7 07 30 DD F1 92 B7 07 48 02 50 03 32 1E 08 88 80 F8 92 CD 84 80 80 10 10 01 18 00 20 01 2A 0C 0A 0A 08 01 12 06 E5 95 8A E5 95 8A richText=RichText#-184909407 { elems=[] } } msgHead=MsgHead#1128220129 { authUin=0x0000000000000000(0) c2cCmd=0x00000007(7) cpid=0x0000000000000000(0) fromUin=0x0000000076E4B8DD(1994701021) isSrcMsg=false msgInstCtrl=InstCtrl#1220180502 { msgExcludeInst=[] msgFromInst=InstInfo#-1165404375 { apppid=0x000003E9(1001) enumDeviceType=0x00000002(2) instid=0x2002FBB7(537066423) } msgSendToInst=[InstInfo#-1165404375 { apppid=0x000003E9(1001) enumDeviceType=0x00000003(3) }] } msgSeq=0x000073C8(29640) msgTime=0x5FE34926(1608730918) msgType=0x00000211(529) msgUid=0x0100000076360F0E(72057596021182222) toUin=0x0000000076E4B8DD(1994701021) } */ ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/SendMessageHandler.kt ================================================ ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/StrangerImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(LowLevelApi::class) @file:Suppress( "EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR", "NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE" ) package net.mamoe.mirai.internal.contact import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.User import net.mamoe.mirai.contact.asFriendOrNull 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.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.StrangerMessageProtocolStrategy import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToStrangerImpl import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.network.protocol.packet.list.StrangerList import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.cast import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext @OptIn(ExperimentalContracts::class) internal inline fun Stranger.impl(): StrangerImpl { contract { returns() implies (this@impl is StrangerImpl) } check(this is StrangerImpl) { "A Stranger instance is not instance of StrangerImpl. Your instance: ${this::class.qualifiedName}" } return this } internal class StrangerImpl( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, override val info: StrangerInfo, ) : Stranger, AbstractUser(bot, parentCoroutineContext, info) { override val nick: String by info::nick override val remark: String by info::remark override suspend fun delete() { check(bot.strangers[this.id] != null) { "Stranger ${this.id} had already been deleted" } bot.network.sendAndExpect(StrangerList.DelStranger(bot.client, this@StrangerImpl), 5000, 2).also { check(it.isSuccess) { "delete Stranger $id failed: ${it.result}" } } } private val messageProtocolStrategy: MessageProtocolStrategy<StrangerImpl> = StrangerMessageProtocolStrategy @Suppress("DuplicatedCode") override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> { return asFriendOrNull()?.sendMessage(message)?.convert() ?: sendMessageImpl( message = message, messageProtocolStrategy = messageProtocolStrategy, preSendEventConstructor = ::StrangerMessagePreSendEvent, postSendEventConstructor = ::StrangerMessagePostSendEvent.cast() ) } private fun MessageReceipt<User>.convert(): MessageReceipt<StrangerImpl> { return OnlineMessageSourceToStrangerImpl(source, this@StrangerImpl).createMessageReceipt( this@StrangerImpl, doLightRefine = false //we've already did ) } override fun toString(): String = "Stranger($id)" } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/active/GroupActiveImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.active import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.* import kotlinx.coroutines.isActive import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.active.* import net.mamoe.mirai.contact.checkBotPermission import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.groupCode import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.check internal expect class GroupActiveImpl( group: GroupImpl, logger: MiraiLogger, groupInfo: GroupInfo, ) : CommonGroupActiveImpl internal abstract class CommonGroupActiveImpl( protected val group: GroupImpl, protected val logger: MiraiLogger, groupInfo: GroupInfo, ) : GroupActive { private var _isHonorVisible: Boolean = groupInfo.isHonorVisible private var _isTitleVisible: Boolean = groupInfo.isTitleVisible private var _isTemperatureVisible: Boolean = groupInfo.isTemperatureVisible private var _isRankVisible: Map<Int, String> = groupInfo.rankTitles private var _temperatureTitles: Map<Int, String> = groupInfo.temperatureTitles private suspend fun getGroupLevelInfo(): GroupLevelInfo { return group.bot.getRawGroupLevelInfo(groupCode = group.groupCode).check() } private suspend fun refreshRank() { val info = getGroupLevelInfo() _isTitleVisible = info.levelFlag == 1 _isTemperatureVisible = info.levelNewFlag == 1 _isRankVisible = info.levelName.mapKeys { (level, _) -> level.removePrefix("lvln").toInt() } _temperatureTitles = info.levelNewName.mapKeys { (level, _) -> level.removePrefix("lvln").toInt() } } override val isHonorVisible: Boolean get() = _isHonorVisible override suspend fun setHonorVisible(newValue: Boolean) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) group.bot.setGroupHonourFlag(groupCode = group.groupCode, flag = newValue).check() _isHonorVisible = newValue } override val rankTitles: Map<Int, String> get() = _isRankVisible override suspend fun setRankTitles(newValue: Map<Int, String>) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) group.bot.setGroupLevelInfo(groupCode = group.groupCode, new = false, titles = newValue).check() refreshRank() } override val isTitleVisible: Boolean get() = _isTitleVisible override suspend fun setTitleVisible(newValue: Boolean) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) group.bot.setGroupSetting(groupCode = group.groupCode, new = false, show = newValue).check() refreshRank() } override val temperatureTitles: Map<Int, String> get() = _temperatureTitles override suspend fun setTemperatureTitles(newValue: Map<Int, String>) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) group.bot.setGroupLevelInfo(groupCode = group.groupCode, new = true, titles = newValue).check() refreshRank() } override val isTemperatureVisible: Boolean get() = _isTemperatureVisible override suspend fun setTemperatureVisible(newValue: Boolean) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) group.bot.setGroupSetting(groupCode = group.groupCode, new = true, show = newValue).check() refreshRank() } override suspend fun refresh() { val info = group.bot.getRawMemberLevelInfo(groupCode = group.groupCode).check() refreshRank() _isHonorVisible = info.honourFlag == 1 _isTitleVisible = info.levelFlag == 1 _isRankVisible = info.levelName.mapKeys { (level, _) -> level.removePrefix("lvln").toInt() } for (member in group.members) { val (_, _, point, rank) = info.lv[member.id] ?: continue member.info.point = point member.info.rank = rank } } protected suspend fun getGroupActiveData(page: Int?): GroupActiveData { return group.bot.getRawGroupActiveData(group.id, page).check() } override fun asFlow(): Flow<ActiveRecord> { return flow { var page = 0 while (currentCoroutineContext().isActive) { val result = getGroupActiveData(page = page) val most = result.info.mostAct ?: break for (active in most) emit(active.toActiveRecord(group)) if (result.info.isEnd == 1) break page++ } } } override suspend fun queryChart(): ActiveChart { return getGroupActiveData(page = null).info.toActiveChart() } private suspend fun getHonorInfo(type: GroupHonorType): MemberHonorList { return when (type) { GroupHonorType.TALKATIVE -> group.bot.getRawTalkativeInfo(group.id) GroupHonorType.PERFORMER -> group.bot.getRawContinuousInfo(group.id, type.id) GroupHonorType.LEGEND -> group.bot.getRawContinuousInfo(group.id, type.id) GroupHonorType.STRONG_NEWBIE -> group.bot.getRawContinuousInfo(group.id, type.id) GroupHonorType.EMOTION -> group.bot.getRawEmotionInfo(group.id) GroupHonorType.BRONZE -> group.bot.getRawHomeworkExcellentInfo(group.id, 1) GroupHonorType.SILVER -> group.bot.getRawHomeworkExcellentInfo(group.id, 2) GroupHonorType.GOLDEN -> group.bot.getRawHomeworkExcellentInfo(group.id, 3) GroupHonorType.WHIRLWIND -> group.bot.getRawHomeworkActiveInfo(group.id) GroupHonorType.RICHER -> group.bot.getRawRicherHonorInfo(group.id) GroupHonorType.RED_PACKET -> group.bot.getRawRedPacketInfo(group.id) else -> group.bot.getRawContinuousInfo(group.id, type.id) } } override suspend fun queryHonorHistory(type: GroupHonorType): ActiveHonorList { val data = getHonorInfo(type) when (type) { GroupHonorType.TALKATIVE, GroupHonorType.RICHER, GroupHonorType.RED_PACKET -> { val current = data.current?.uin for (member in group.members) { if (member.id != current) { member.info.honors += type } else { member.info.honors -= type } } data.current?.let { group.members[it.uin] }?.let { it.info.honors += type } } GroupHonorType.LEGEND -> { val current = data.list.mapTo(HashSet()) { it.uin } for (member in group.members) { if (member.id in current) { member.info.honors += GroupHonorType.LEGEND member.info.honors -= GroupHonorType.PERFORMER } else { member.info.honors -= GroupHonorType.LEGEND } } } else -> { val current = data.list.mapTo(HashSet()) { it.uin } for (member in group.members) { if (member.id in current) { member.info.honors += type } else { member.info.honors -= type } } } } @Suppress("INVISIBLE_MEMBER") return ActiveHonorList( type = type, current = data.current?.toActiveHonorInfo(group), records = data.list.map { it.toActiveHonorInfo(group) }, ) } private suspend fun getMemberScoreData(): MemberScoreData { return group.bot.getRawMemberTitleList(group.id).check() } override suspend fun queryActiveRank(): List<ActiveRankRecord> { val data = getMemberScoreData() @Suppress("INVISIBLE_MEMBER") return data.members.map { ActiveRankRecord( memberId = it.uin, memberName = it.nickName, member = group.get(id = it.uin), temperature = it.levelId, score = it.score ) } } private suspend fun getMemberMedalInfo(uid: Long): MemberMedalData { return group.bot.getRawMemberMedalInfo(group.id, uid) } suspend fun queryMemberMedal(uid: Long): MemberMedalInfo { val info = getMemberMedalInfo(uid = uid) val medals: MutableSet<MemberMedalType> = HashSet() var worn: MemberMedalType = MemberMedalType.ACTIVE for (item in info.list) { if (item.achieveTs == 0) continue val type = when (item.mask) { MemberMedalType.OWNER.mask -> MemberMedalType.OWNER MemberMedalType.ADMIN.mask -> MemberMedalType.ADMIN MemberMedalType.SPECIAL.mask -> MemberMedalType.SPECIAL MemberMedalType.ACTIVE.mask -> MemberMedalType.ACTIVE else -> continue } medals.add(type) if (item.wearTs != 0) worn = type } @Suppress("INVISIBLE_MEMBER") return MemberMedalInfo( title = info.weared, color = info.wearedColor, wearing = worn, medals = medals ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/active/GroupActiveProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.active import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.active.* import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.psKey import net.mamoe.mirai.internal.network.sKey import net.mamoe.mirai.utils.* @Serializable internal data class SetResult( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, @SerialName("errcode") val errCode: Int? ) : CheckableResponseA(), JsonStruct /** * 群等级信息 */ @Serializable internal data class GroupLevelInfo( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, @SerialName("errcode") val errCode: Int?, @SerialName("levelflag") val levelFlag: Int = 0, @SerialName("levelname") val levelName: Map<String, String> = emptyMap(), @SerialName("levelnewflag") val levelNewFlag: Int = 0, @SerialName("levelnewname") val levelNewName: Map<String, String> = emptyMap() ) : CheckableResponseA(), JsonStruct @Serializable internal data class MemberLevelInfo( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, @SerialName("errcode") val errCode: Int?, @SerialName("role") val role: Int = 0, @SerialName("mems") val mems: Map<Long, MemberInfo> = emptyMap(), @SerialName("lv") val lv: Map<Long, LevelInfo> = emptyMap(), @SerialName("levelflag") val levelFlag: Int = 0, @SerialName("levelname") val levelName: Map<String, String> = emptyMap(), @SerialName("honourflag") val honourFlag: Int = 0 ) : CheckableResponseA(), JsonStruct { @Serializable data class MemberInfo( @SerialName("u") val u: Long = 0, @SerialName("g") val g: Int = 0, @SerialName("n") val n: String = "" ) @Serializable data class LevelInfo( @SerialName("u") val u: Long = 0, @SerialName("d") val d: Int = 0, @SerialName("p") val p: Int = 0, @SerialName("l") val l: Int = 1 ) } /** * 群统计信息 */ @Serializable internal data class GroupActiveData( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, @SerialName("errcode") val errCode: Int?, @SerialName("ginfo") val info: ActiveInfo, @SerialName("query") val query: Int? = 0, @SerialName("role") val role: Int? = 0 ) : CheckableResponseA(), JsonStruct { @Serializable data class Situation( @SerialName("date") val date: String, @SerialName("num") val num: Int ) @Serializable data class MostActive( @SerialName("name") val name: String, // 名称 不完整 @SerialName("sentences_num") val sentencesNum: Int, // 发言数 @SerialName("sta") val sta: Int = 0, @SerialName("uin") val uin: Long = 0 ) @Serializable data class ActiveInfo( @SerialName("g_act_num") val actNum: List<Situation>? = null, //发言人数列表 @SerialName("g_createtime") val createTime: Int? = 0, @SerialName("g_exit_num") val exitNum: List<Situation>? = null, //退群人数列表 @SerialName("g_join_num") val joinNum: List<Situation>? = null, @SerialName("g_mem_num") val memNum: List<Situation>? = null, //人数变化 @SerialName("g_most_act") val mostAct: List<MostActive>? = null, //发言排行 @SerialName("g_sentences") val sentences: List<Situation>? = null, @SerialName("gc") val gc: Int? = null, @SerialName("gn") val gn: String? = null, @SerialName("gowner") val owner: String? = null, @SerialName("isEnd") val isEnd: Int ) } @Serializable internal data class CgiData( @SerialName("cgicode") val cgicode: Int, @SerialName("data") val `data`: JsonElement, @SerialName("msg") override val errorMessage: String, @SerialName("retcode") override val errorCode: Int ) : CheckableResponseA(), JsonStruct @Serializable internal data class MemberMedalData( @SerialName("avatar") val avatar: String, @SerialName("face_flag") val faceFlag: Int, @SerialName("last_view_ts") val lastViewTs: Int, @SerialName("list") val list: List<MemberMedalItem>, // 头衔详情 @SerialName("nick") val nick: String, @SerialName("role") val role: Int, // 身份/权限 @SerialName("weared") val weared: String, // 目前显示头衔 @SerialName("weared_color") val wearedColor: String // 头衔颜色 ) @Serializable internal data class MemberMedalItem( @SerialName("achieve_ts") val achieveTs: Int, // 是否拥有 @SerialName("category_id") val categoryId: Int, @SerialName("color") val color: String, @SerialName("is_mystery") val isMystery: Int, @SerialName("mask") val mask: Int, // 群主 300 管理员 301 特殊 302 活跃 315 @SerialName("medal_desc") val medalDesc: String, @SerialName("name") val name: String, @SerialName("order") val order: Int, @SerialName("pic") val pic: String, @SerialName("rule") val rule: Int, @SerialName("rule_desc") val ruleDesc: String, // 来源 @SerialName("wear_ts") val wearTs: Int // 是否佩戴 ) @Serializable internal data class MemberHonorInfo( @SerialName("add_friend") val addFriend: Int = 0, @SerialName("avatar") val avatar: String, @SerialName("avatar_size") val avatarSize: Int, @SerialName("day_count") val dayCount: Int, @SerialName("day_count_history") val dayCountHistory: Int = 1, @SerialName("day_count_max") val dayCountMax: Int = 1, @SerialName("honor_ids") val honorIds: List<Int> = emptyList(), @SerialName("nick") val nick: String, @SerialName("uin") val uin: Long, @SerialName("update_ymd") val updated: Long = 0, // 格式为 yyyyMMdd 的 数字,表示最后更新时间 ) internal interface MemberHonorList : JsonStruct { val current: MemberHonorInfo? get() = null val total: Int val list: List<MemberHonorInfo> } @Serializable internal data class MemberTalkativeInfo( @SerialName("current_talkative") val currentTalkative: MemberHonorInfo? = null, @SerialName("talkative_amount") val talkativeAmount: Int, @SerialName("talkative_list") val talkativeList: List<MemberHonorInfo> ) : MemberHonorList { override val current: MemberHonorInfo? get() = currentTalkative override val total: Int get() = talkativeAmount override val list: List<MemberHonorInfo> get() = talkativeList } @Serializable internal data class MemberEmotionInfo( @SerialName("emotion_list") val emotionList: List<MemberHonorInfo>, @SerialName("total") override val total: Int ) : MemberHonorList { override val list: List<MemberHonorInfo> get() = emotionList } @Serializable internal data class MemberHomeworkExcellentInfo( @SerialName("hwexcellent_list") val excellentList: List<MemberHonorInfo>, @SerialName("total") override val total: Int ) : MemberHonorList { override val list: List<MemberHonorInfo> get() = excellentList } @Serializable internal data class MemberHomeworkActiveInfo( @SerialName("hwactive_list") val activeList: List<MemberHonorInfo>, @SerialName("total") override val total: Int ) : MemberHonorList { override val list: List<MemberHonorInfo> get() = activeList } @Serializable internal data class MemberContinuousInfo( @SerialName("continuous_list") val continuousList: List<MemberHonorInfo>, @SerialName("total") override val total: Int ) : MemberHonorList { override val list: List<MemberHonorInfo> get() = continuousList } @Serializable internal data class MemberRicherHonorInfo( @SerialName("current_richer_honor") val currentRicherHonor: MemberHonorInfo? = null, @SerialName("richer_amount") val richerAmount: Int, @SerialName("richer_honor_list") val richerHonorList: List<MemberHonorInfo> ) : MemberHonorList { override val current: MemberHonorInfo? get() = currentRicherHonor override val total: Int get() = richerAmount override val list: List<MemberHonorInfo> get() = richerHonorList } @Serializable internal data class MemberRedPacketInfo( @SerialName("current_redpacket_honor") val currentRedPacketHonor: MemberHonorInfo? = null, @SerialName("redpacket_amount") val redPacketAmount: Int, @SerialName("redpacket_honor_list") val redPacketHonorList: List<MemberHonorInfo> ) : MemberHonorList { override val current: MemberHonorInfo? get() = currentRedPacketHonor override val total: Int get() = redPacketAmount override val list: List<MemberHonorInfo> get() = redPacketHonorList } @Serializable internal data class MemberScoreData( @SerialName("level_list") val levels: List<Level>, @SerialName("member_level_list") val mapping: List<MemberLevel>, @SerialName("member_title_info") val self: MemberScoreInfo, @SerialName("members_list") val members: List<MemberScoreInfo>, @SerialName("msg") override val errorMessage: String, @SerialName("retcode") override val errorCode: Int ) : CheckableResponseA(), JsonStruct { @Serializable data class Level( @SerialName("level") val level: String, @SerialName("name") val name: String ) @Serializable data class MemberLevel( @SerialName("level") val level: Int, @SerialName("lower_limit") val lowerLimit: Int, @SerialName("mapping_level") val mappingLevel: Int, @SerialName("name") val name: String ) @Serializable data class MemberScoreInfo( @SerialName("level_id") val levelId: Int, @SerialName("nf") val nf: Int = 0, @SerialName("nick_name") val nickName: String, @SerialName("role") val role: Int, @SerialName("score") val score: Int, @SerialName("uin") val uin: Long ) } internal suspend fun QQAndroidBot.getRawGroupLevelInfo( groupCode: Long ): GroupLevelInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qinfo.clt.qq.com/cgi-bin/qun_info/get_group_level_new_info") parameter("gc", groupCode) parameter("bkn", client.wLoginSigInfo.bkn) parameter("src", "qinfo_v3") headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}" ) } }.bodyAsText() .loadAs(GroupLevelInfo.serializer()) } internal suspend fun QQAndroidBot.getRawMemberLevelInfo( groupCode: Long ): MemberLevelInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qinfo.clt.qq.com/cgi-bin/qun_info/get_group_members_lite") parameter("gc", groupCode) parameter("bkn", client.wLoginSigInfo.bkn) parameter("src", "qinfo_v3") headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}" ) } }.bodyAsText() .loadAs(MemberLevelInfo.serializer()) } internal suspend fun QQAndroidBot.getRawMemberMedalInfo( groupCode: Long, uid: Long ): MemberMedalData { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/qunwelcome/medal2/list") parameter("gc", groupCode) parameter("uin", uid) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberMedalData.serializer()) } internal suspend fun QQAndroidBot.getRawTalkativeInfo( groupCode: Long ): MemberTalkativeInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/qunapp/honor_talkative") parameter("gc", groupCode) parameter("num", 3000) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberTalkativeInfo.serializer()) } internal suspend fun QQAndroidBot.getRawEmotionInfo( groupCode: Long ): MemberEmotionInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/qunapp/honor_emotion") parameter("gc", groupCode) parameter("num", 3000) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberEmotionInfo.serializer()) } @PublishedApi internal val defaultJson: Json = Json { isLenient = true ignoreUnknownKeys = true } private fun <T> CgiData.loadData(serializer: KSerializer<T>): T = defaultJson.decodeFromJsonElement(serializer, this.data) /** * @param type 取值 1 2 3 分别对应 学术新星 顶尖学霸 至尊学神 */ internal suspend fun QQAndroidBot.getRawHomeworkExcellentInfo( groupCode: Long, type: Int ): MemberHomeworkExcellentInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/qunapp/honor_hwexcellent") parameter("gc", groupCode) parameter("req_type", type) parameter("num", 3000) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberHomeworkExcellentInfo.serializer()) } internal suspend fun QQAndroidBot.getRawHomeworkActiveInfo( groupCode: Long ): MemberHomeworkActiveInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/qunapp/honor_hwactive") parameter("gc", groupCode) parameter("num", 3000) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberHomeworkActiveInfo.serializer()) } /** * @param type 取值 2 3 5 分别对应 群聊之火 群聊炽焰 冒尖小春笋 */ internal suspend fun QQAndroidBot.getRawContinuousInfo( groupCode: Long, type: Int ): MemberContinuousInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/qunapp/honor_continuous") parameter("gc", groupCode) parameter("num", 3000) parameter("continuous_type", type) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberContinuousInfo.serializer()) } internal suspend fun QQAndroidBot.getRawRicherHonorInfo( groupCode: Long ): MemberRicherHonorInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/new_honor/list_honor/list_richer_honor") parameter("group_code", groupCode) parameter("num", 3000) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberRicherHonorInfo.serializer()) } internal suspend fun QQAndroidBot.getRawRedPacketInfo( groupCode: Long ): MemberRedPacketInfo { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/new_honor/list_honor/list_redpacket_honor") parameter("group_code", groupCode) parameter("num", 3000) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(CgiData.serializer()) .loadData(MemberRedPacketInfo.serializer()) } /** * 只有前 50 名的数据 */ internal suspend fun QQAndroidBot.getRawMemberTitleList( groupCode: Long ): MemberScoreData { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/honorv2/honor_title_list") parameter("group_code", groupCode) parameter("request_type", "2") parameter("g_tk", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(MemberScoreData.serializer()) } internal suspend fun QQAndroidBot.setGroupLevelInfo( groupCode: Long, new: Boolean, titles: Map<Int, String> ): SetResult { return components[HttpClientProvider].getHttpClient().post { url("https://qinfo.clt.qq.com/cgi-bin/qun_info/set_group_level_info") setBody(FormDataContent(Parameters.build { titles.forEach { (index, name) -> append("lvln$index", name) } append("new", if (new) "1" else "0") append("gc", groupCode.toString()) append("src", "qinfo_v3") append("bkn", client.wLoginSigInfo.bkn.toString()) })) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}" ) } }.bodyAsText() .loadAs(SetResult.serializer()) } internal suspend fun QQAndroidBot.setGroupSetting( groupCode: Long, new: Boolean, show: Boolean ): SetResult { return components[HttpClientProvider].getHttpClient().post { url("https://qinfo.clt.qq.com/cgi-bin/qun_info/set_group_setting") setBody(FormDataContent(Parameters.build { append(if (new) "levelnewflag" else "levelflag", if (show) "1" else "0") append("gc", groupCode.toString()) append("src", "qinfo_v3") append("bkn", client.wLoginSigInfo.bkn.toString()) })) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}" ) } }.bodyAsText() .loadAs(SetResult.serializer()) } internal suspend fun QQAndroidBot.setGroupHonourFlag( groupCode: Long, flag: Boolean ): SetResult { return components[HttpClientProvider].getHttpClient().post { url("https://qinfo.clt.qq.com/cgi-bin/qun_info/set_honour_flag") setBody(FormDataContent(Parameters.build { append("gc", groupCode.toString()) append("bkn", client.wLoginSigInfo.bkn.toString()) append("src", "qinfo_v3") append("flag", if (flag) "0" else "1") })) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}" ) } }.bodyAsText() .loadAs(SetResult.serializer()) } internal suspend fun QQAndroidBot.getRawGroupActiveData( groupCode: Long, page: Int? = null ): GroupActiveData { return components[HttpClientProvider].getHttpClient().get { url("https://qqweb.qq.com/c/activedata/get_mygroup_data") parameter("bkn", client.wLoginSigInfo.bkn) parameter("gc", groupCode) parameter("page", page) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText() .loadAs(GroupActiveData.serializer()) } @Suppress("INVISIBLE_MEMBER") internal fun GroupActiveData.MostActive.toActiveRecord(group: Group): ActiveRecord { return ActiveRecord( memberId = uin, memberName = name, periodDays = sentencesNum, messagesCount = sta, member = group.get(id = uin) ) } @Suppress("INVISIBLE_MEMBER") internal fun GroupActiveData.ActiveInfo.toActiveChart(): ActiveChart { return ActiveChart( actives = actNum?.associate { it.date to it.num }.orEmpty(), sentences = sentences?.associate { it.date to it.num }.orEmpty(), members = memNum?.associate { it.date to it.num }.orEmpty(), join = joinNum?.associate { it.date to it.num }.orEmpty(), exit = exitNum?.associate { it.date to it.num }.orEmpty() ) } @Suppress("INVISIBLE_MEMBER") internal fun MemberHonorInfo.toActiveHonorInfo(group: GroupImpl): ActiveHonorInfo { return ActiveHonorInfo( memberName = nick, memberId = uin, avatar = avatar + avatarSize, member = group.get(id = uin), termDays = dayCount, historyDays = dayCountHistory, maxTermDays = dayCountMax ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.announcement import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.coroutines.flow.* import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.announcement.* import net.mamoe.mirai.contact.checkBotPermission import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.OnlineAnnouncementImpl import net.mamoe.mirai.internal.contact.active.defaultJson import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.deleteGroupAnnouncement import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.getGroupAnnouncement import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.getRawGroupAnnouncements import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.getReadDetail import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.sendGroupAnnouncement import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.sendRemind import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toAnnouncement import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toGroupAnnouncement import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.client import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.highway.ChannelKind import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.highway.tryServersUpload import net.mamoe.mirai.internal.network.psKey import net.mamoe.mirai.internal.network.sKey import net.mamoe.mirai.internal.utils.io.writeResource import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.Either.Companion.onLeft import net.mamoe.mirai.utils.Either.Companion.rightOrNull internal class AnnouncementsImpl( protected val group: GroupImpl, protected val logger: MiraiLogger, ) : Announcements { inline val bot get() = group.bot protected suspend fun getGroupAnnouncementList(i: Int): GroupAnnouncementList? { return bot.getRawGroupAnnouncements(group.id, i).onLeft { if (logger.isEnabled) { // createException logger.warning( { "Failed to load announcement for group ${group.id}" }, it.createException() ) } }.rightOrNull } override fun asFlow(): Flow<OnlineAnnouncement> { return flow { var i = 1 while (true) { val result = getGroupAnnouncementList(i++) ?: break if (result.inst.isNullOrEmpty() && result.feeds.isNullOrEmpty()) break result.inst?.let { emitAll(it.asFlow()) } result.feeds?.let { emitAll(it.asFlow()) } } }.map { it.toAnnouncement(group) } } override suspend fun delete(fid: String): Boolean { group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to delete group announcement" } return bot.deleteGroupAnnouncement(group.id, fid) } override suspend fun get(fid: String): OnlineAnnouncement { return bot.getGroupAnnouncement(group.id, fid).toAnnouncement(group) } private fun Announcement.describe(): String = "'${content.truncated(10)}' ${parameters.describe()}" private fun AnnouncementParameters.describe(): String { return mutableListOf<String>().apply { if (image != null) add("with image") if (sendToNewMember) add("sendToNewMember") if (isPinned) add("pinned") if (showEditCard) add("showEditCard") if (showPopup) add("popup") if (requireConfirmation) add("needConfirm") }.joinToString() } override suspend fun publish(announcement: Announcement): OnlineAnnouncement = announcement.run { val id = announcement.hashCode() logger.verbose { "Publishing announcement #$id: ${announcement.describe()}" } val bot = group.bot group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to send group announcement" } val image = parameters.image val fid = bot.sendGroupAnnouncement(group.id, toGroupAnnouncement(bot.id), image) return OnlineAnnouncementImpl( group = group, senderId = bot.id, sender = group.botAsMember, content = content, parameters = parameters, fid = fid, allConfirmed = false, confirmedMembersCount = 0, publicationTime = currentTimeSeconds() ).also { logger.verbose { "Publishing announcement #$id: success." } } } override suspend fun uploadImage(resource: ExternalResource): AnnouncementImage { return tryServersUpload( bot, serversStub, resource.size, ResourceKind.ANNOUNCEMENT_IMAGE, ChannelKind.HTTP ) { _, _ -> // use common logging AnnouncementProtocol.uploadGroupAnnouncementImage(bot, resource) } } override suspend fun members(fid: String, confirmed: Boolean): List<NormalMember> { group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission see announcement confirmed detail" } val flow = flow<NormalMember> { var offset = 0 while (true) { val detail = bot.getReadDetail( groupId = group.id, fid = fid, read = confirmed, offset = offset, limit = 50 ) if (detail.users.isEmpty()) break for (user in detail.users) { val member = group[user.uin] ?: continue emit(member) } offset += detail.users.size val target = if (confirmed) detail.readTotal else detail.unreadTotal if (offset == target) break } } return flow.toList() } override suspend fun remind(fid: String) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission send announcement remind" } bot.sendRemind(groupId = group.id, fid = fid) } } private val serversStub = listOf("web.qun.qq.com" to 80) internal object AnnouncementProtocol { @Serializable data class UploadImageResp( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, @SerialName("id") val id: String, ) : CheckableResponseA(), JsonStruct suspend fun uploadGroupAnnouncementImage( bot: AbstractBot, resource: ExternalResource ): AnnouncementImage = bot.run { val resp = bot.components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/upload_img") setBody( MultiPartFormDataContent(formData { append("\"bkn\"", client.wLoginSigInfo.bkn) append("\"source\"", "troopNotice") append("m", "0") append( "\"pic_up\"", headers = Headers.build { append(HttpHeaders.ContentType, ContentType.Image.PNG) append(HttpHeaders.ContentDisposition, "filename=\"temp_uploadFile.png\"") } ) { writeResource(resource) } }) ) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) }.bodyAsText().loadSafelyAs(UploadImageResp.serializer()).check() return resp.id.replace("&quot;", "\"").loadSafelyAs(GroupAnnouncementImage.serializer()).check().toPublic() } @Serializable data class SendGroupAnnouncementResp( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, @SerialName("new_fid") val fid: String? = null, ) : CheckableResponseA(), JsonStruct suspend fun QQAndroidBot.sendGroupAnnouncement( groupId: Long, announcement: GroupAnnouncement, image: AnnouncementImage?, ): String { val body = bot.components[HttpClientProvider].getHttpClient().post { url( "https://web.qun.qq.com/cgi-bin/announce/add_qun_" + if (announcement.type == 20) { "instruction" } else { "notice" } ) setBody( MultiPartFormDataContent(formData { append("qid", groupId) append("bkn", client.wLoginSigInfo.bkn) append("text", announcement.msg.text) append("pinned", announcement.pinned) image?.let { append("pic", image.id) append("imgWidth", image.width) append("imgHeight", image.height) } append( "settings", announcement.settings.toJsonString(GroupAnnouncementSettings.serializer()), ) append("format", "json") // append("type", announcement.type.toString()) }) ) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) }.bodyAsText() val resp = body.loadSafelyAs(SendGroupAnnouncementResp.serializer()).check() // check: deserialization errors resp.check() // check: server response resp.fid?.let { return it } // '{"ec":1,"em":"no login [errcode:1:0]","ltsm":1653791033,"srv_code":0}' throw contextualBugReportException("No fid found, but this should have be handled before.", forDebug = body) } suspend fun QQAndroidBot.getRawGroupAnnouncements( groupId: Long, page: Int, amount: Int = 10 ): Either<DeserializationFailure, GroupAnnouncementList> { return bot.components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/list_announce") setBody( MultiPartFormDataContent(formData { append("qid", groupId) append("bkn", client.wLoginSigInfo.bkn) append("ft", 23) //好像是一个用来识别应用的参数 append("s", if (page == 1) 0 else -(page * amount + 1)) // 第一页这里的参数应该是-1 append("n", amount) append("ni", if (page == 1) 1 else 0) append("format", "json") }) ) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) }.bodyAsText().loadSafelyAs(GroupAnnouncementList.serializer()) } @Serializable data class DeleteResp( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, ) : CheckableResponseA(), JsonStruct suspend fun QQAndroidBot.deleteGroupAnnouncement(groupId: Long, fid: String): Boolean { components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/del_feed") setBody(feedBody(groupId, fid)) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) }.bodyAsText().loadSafelyAs(DeleteResp.serializer()).check() return true } suspend fun QQAndroidBot.getGroupAnnouncement(groupId: Long, fid: String): GroupAnnouncement { return bot.components[HttpClientProvider].getHttpClient().post { url("https://web.qun.qq.com/cgi-bin/announce/get_feed") setBody(feedBody(groupId, fid)) cookie("uin", "o$id") cookie("p_uin", "o$id") cookie("skey", sKey) cookie("p_skey", psKey("qun.qq.com")) }.bodyAsText().loadAs(GroupAnnouncement.serializer()) } private fun QQAndroidBot.feedBody( groupId: Long, fid: String ) = MultiPartFormDataContent(formData { append("qid", groupId) append("bkn", client.wLoginSigInfo.bkn) append("fid", fid) append("format", "json") }) private fun <T> CgiData.loadData(serializer: KSerializer<T>): T = defaultJson.decodeFromJsonElement(serializer, this.data) suspend fun QQAndroidBot.getReadDetail( groupId: Long, fid: String, read: Boolean, offset: Int, limit: Int ): GroupAnnouncementReadDetail { val cgi = bot.components[HttpClientProvider].getHttpClient().post { url("https://qun.qq.com/cgi-bin/qunapp/announce_unread") parameter("gc", groupId) parameter("start", offset) parameter("num", limit) parameter("feed_id", fid) parameter("type", if (read) 1 else 0) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey("qun.qq.com")};" ) } }.bodyAsText().loadAs(CgiData.serializer()) check(cgi.cgicode == 0) { cgi.errorMessage } return cgi.loadData(GroupAnnouncementReadDetail.serializer()) } suspend fun QQAndroidBot.sendRemind(groupId: Long, fid: String) { val cgi = bot.components[HttpClientProvider].getHttpClient().post { url("https://qun.qq.com/cgi-bin/qunapp/announce_remindread") parameter("gc", groupId) parameter("feed_id", fid) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey("qun.qq.com")};" ) } }.bodyAsText().loadAs(CgiData.serializer()) // 14 "该公告不存在" // 54016 "该公告已提醒多次,不可再提醒。" // 54010 "提醒太频繁,请稍后再试" check(cgi.cgicode == 0) { cgi.errorMessage } } fun Announcement.toGroupAnnouncement(senderId: Long): GroupAnnouncement { return GroupAnnouncement( sender = senderId, msg = GroupAnnouncementMsg(text = content), // 实际测试中发布一个新公告的时候不需要进行 html 转码 type = if (parameters.sendToNewMember) 20 else 6, settings = GroupAnnouncementSettings( isShowEditCard = if (parameters.showEditCard) 1 else 0, tipWindowType = if (parameters.showPopup) 0 else 1, confirmRequired = if (parameters.requireConfirmation) 1 else 0, ), pinned = if (parameters.isPinned) 1 else 0, ) } fun GroupAnnouncement.toAnnouncement(group: Group): OnlineAnnouncementImpl { val fid = this.fid ?: "" return OnlineAnnouncementImpl( group = group, senderId = sender, sender = group[sender], content = msg.text.decodeHtmlEscape(), parameters = buildAnnouncementParameters { isPinned = this@toAnnouncement.pinned == 1 sendToNewMember = type == 20 showPopup = settings.tipWindowType == 0 requireConfirmation = settings.confirmRequired == 1 showEditCard = settings.isShowEditCard == 1 image = msg.images.firstOrNull()?.toPublic() }, fid = fid, allConfirmed = isAllConfirm != 0, confirmedMembersCount = readNum, publicationTime = time ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/announcement/GroupAnnouncement.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("DEPRECATION") package net.mamoe.mirai.internal.contact.announcement import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import net.mamoe.mirai.contact.announcement.AnnouncementImage import net.mamoe.mirai.utils.CheckableResponseA import net.mamoe.mirai.utils.JsonStruct import net.mamoe.mirai.utils.MiraiInternalApi @Serializable internal data class GroupAnnouncementList( @SerialName("ec") override val errorCode: Int = 0, @SerialName("em") override val errorMessage: String? = null, val feeds: List<GroupAnnouncement>? = null, //群公告列表 val inst: List<GroupAnnouncement>? = null //置顶列表? 应该是发送给新成员的 ) : CheckableResponseA(), JsonStruct { /* // notes from original implementor, luo123, on 2020/3/13 * 群公告数据类 * getGroupAnnouncementList时,如果page=1,那么你可以在inst里拿到一些置顶公告 * * 发公告时只需要填写text,其他参数可为默认值 * */ } @Serializable internal data class GroupAnnouncement( @SerialName("u") val sender: Long = 0, //发送者id val msg: GroupAnnouncementMsg, val type: Int = 0, //20 为inst , 6 为feeds val settings: GroupAnnouncementSettings = GroupAnnouncementSettings.DEFAULT, @SerialName("pubt") val time: Long = 0, //发布时间 @SerialName("read_num") val readNum: Int = 0, //如果需要确认,则为确认收到的人数,反之则为已经阅读的人数 @SerialName("is_read") val isRead: Int = 0, //好像没用 @SerialName("is_all_confirm") val isAllConfirm: Int = 0, //为0 则未全部收到 val pinned: Int = 0, //1为置顶, 0为默认 val fid: String? = null, //公告的id ) : JsonStruct @Serializable internal class GroupAnnouncementImage @MiraiInternalApi constructor( @SerialName("h") val height: Int, @SerialName("w") val width: Int, @SerialName("id") val id: String ) : JsonStruct { fun toPublic(): AnnouncementImage = AnnouncementImage.create(id, height, width) } @Serializable internal data class GroupAnnouncementMsg( val text: String, @SerialName("text_face") val textFace: String? = null, @SerialName("pics") val images: List<GroupAnnouncementImage> = emptyList(), // val title: String? = null // no title any more ) @Serializable internal data class GroupAnnouncementSettings( @SerialName("is_show_edit_card") val isShowEditCard: Int = 0, //引导群成员修改该昵称 1 引导 @SerialName("remind_ts") val remindTs: Int = 0, @SerialName("tip_window_type") val tipWindowType: Int = 0, //是否用弹窗展示 1 不使用 @SerialName("confirm_required") val confirmRequired: Int = 0 // 是否需要确认收到 1 需要 ) : JsonStruct { companion object { val DEFAULT = GroupAnnouncementSettings() } } @Serializable internal data class CgiData( @SerialName("cgicode") val cgicode: Int, @SerialName("data") val `data`: JsonElement, @SerialName("msg") override val errorMessage: String, @SerialName("retcode") override val errorCode: Int ) : CheckableResponseA(), JsonStruct @Serializable internal data class GroupAnnouncementReadDetail( @SerialName("read_total") val readTotal: Int = 0, @SerialName("unread_total") val unreadTotal: Int = 0, @SerialName("users") val users: List<User> = emptyList() ) { @Serializable data class User( @SerialName("avatar") val avatar: String, @SerialName("display_name") val displayName: String, @SerialName("face_flag") val faceFlag: Int, @SerialName("uin") val uin: Long ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/essence/EssencesImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.essence import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.isActive import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.int import kotlinx.serialization.json.intOrNull import kotlinx.serialization.json.jsonPrimitive import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.checkBotPermission import net.mamoe.mirai.contact.essence.EssenceMessageRecord import net.mamoe.mirai.contact.essence.Essences import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopEssenceMsgManager import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource internal class EssencesImpl( internal val group: GroupImpl, internal val logger: MiraiLogger, ) : Essences { private suspend fun parse(content: JsonObject): Message { return when (content.getValue("msg_type").jsonPrimitive.intOrNull) { 1 -> PlainText(content = content.getValue("text").jsonPrimitive.content) 2 -> Face(id = content.getValue("face_index").jsonPrimitive.int) 3 -> { val url = content.getValue("image_url").jsonPrimitive.content try { // url -> bytes -> group.upload val bytes = group.bot.downloadEssenceMessageImage(url) bytes.toExternalResource().use { group.uploadImage(it) } } catch (cause: Exception) { logger.debug({ "essence message image $url download fail." }, cause) val match = IMAGE_MD5_REGEX.find(url) ?: return emptyMessageChain() val (md5, ext) = match.destructured val imageId = buildString { append(md5) insert(8,"-") insert(13,"-") insert(18,"-") insert(23,"-") insert(0, "{") append("}.") append(ext.replace("jpeg", "jpg")) } Image(imageId) } } else -> { // XXX: unknown message type logger.warning { "unknown digest message type for $content" } emptyMessageChain() } } } private fun plain(content: JsonObject): String { return when (content.getValue("msg_type").jsonPrimitive.intOrNull) { 1 -> content.getValue("text").jsonPrimitive.content 2 -> Face(id = content.getValue("face_index").jsonPrimitive.int).content 3 -> "[图片]" else -> "" } } private suspend fun source(digests: DigestMessage, parse: Boolean): MessageSource { return group.bot.buildMessageSource(MessageSourceKind.GROUP) { ids = intArrayOf(digests.msgSeq.toInt()) internalIds = intArrayOf(digests.msgRandom.toInt()) time = digests.senderTime fromId = digests.senderUin targetId = group.id if (parse) { messages(digests.msgContent.map { content -> parse(content) }) } else { messages(digests.msgContent.joinToString { content -> plain(content) }.toPlainText()) } } } private fun record(digests: DigestMessage): EssenceMessageRecord { return EssenceMessageRecord( group = group, sender = group[digests.senderUin], senderId = digests.senderUin, senderNick = digests.senderNick, senderTime = digests.senderTime, operator = group[digests.addDigestUin], operatorId = digests.addDigestUin, operatorNick = digests.addDigestNick, operatorTime = digests.addDigestTime, loadMessageSource = { source(digests = digests, parse = false) } ) } override suspend fun getPage(start: Int, limit: Int): List<EssenceMessageRecord> { val page = group.bot.getDigestList( groupCode = group.id, pageStart = start, pageLimit = limit ) return page.messages.map(this::record) } override suspend fun share(source: MessageSource): String { val share = group.bot.shareDigest( groupCode = group.id, msgSeq = source.ids.first().toLong().and(0xFFFF_FFFF), msgRandom = source.internalIds.first().toLong().and(0xFFFF_FFFF), targetGroupCode = 0 ) return "https://qun.qq.com/essence/share?_wv=3&_wwv=128&_wvx=2&sharekey=${share.shareKey}" } override suspend fun remove(source: MessageSource) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) val result = group.bot.network.sendAndExpect( TroopEssenceMsgManager.RemoveEssence( group.bot.client, group.uin, source.internalIds.first(), source.ids.first() ), 5000, 2 ) if (result.success.not()) { try { group.bot.cancelDigest( groupCode = group.id, msgSeq = source.ids.first().toLong().and(0xFFFF_FFFF), msgRandom = source.internalIds.first().toLong().and(0xFFFF_FFFF) ) } catch (cause: IllegalStateException) { cause.addSuppressed(IllegalStateException(result.msg)) throw cause } } } override fun asFlow(): Flow<EssenceMessageRecord> { return flow { var offset = 0 while (currentCoroutineContext().isActive) { val page = group.bot.getDigestList( groupCode = group.id, pageStart = offset, pageLimit = 30 ) for (message in page.messages) { emit(record(message)) } if (page.isEnd) break if (page.messages.isEmpty()) break offset += 30 } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/essence/GroupDigestProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.essence import io.ktor.client.call.* import io.ktor.client.request.* import io.ktor.client.statement.* import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonNull import kotlinx.serialization.json.JsonObject import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.active.defaultJson import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.psKey import net.mamoe.mirai.internal.network.sKey import net.mamoe.mirai.utils.CheckableResponseA import net.mamoe.mirai.utils.JsonStruct import net.mamoe.mirai.utils.loadAs @Serializable internal data class DigestData( @SerialName("data") val `data`: JsonElement = JsonNull, @SerialName("wording") val reason: String = "", @SerialName("retmsg") override val errorMessage: String, @SerialName("retcode") override val errorCode: Int ) : CheckableResponseA(), JsonStruct @Serializable internal data class DigestList( @SerialName("group_role") val role: Int = 0, @SerialName("is_end") val isEnd: Boolean = false, @SerialName("msg_list") val messages: List<DigestMessage> = emptyList(), @SerialName("show_tips") val showTips: Boolean = false ) @Serializable internal data class DigestMessage( @SerialName("add_digest_nick") val addDigestNick: String = "", @SerialName("add_digest_time") val addDigestTime: Int = 0, @SerialName("add_digest_uin") val addDigestUin: Long = 0, @SerialName("group_code") val groupCode: String = "", @SerialName("msg_content") val msgContent: List<JsonObject> = emptyList(), @SerialName("msg_random") val msgRandom: Long = 0, @SerialName("msg_seq") val msgSeq: Long = 0, @SerialName("sender_nick") val senderNick: String = "", @SerialName("sender_time") val senderTime: Int = 0, @SerialName("sender_uin") val senderUin: Long = 0 ) internal val IMAGE_MD5_REGEX: Regex = """([0-9a-fA-F]{32})\.([0-9a-zA-Z]+)""".toRegex() @Serializable internal data class DigestShare( @SerialName("share_key") val shareKey: String = "" ) private fun <T> DigestData.loadData(serializer: KSerializer<T>): T { return try { defaultJson.decodeFromJsonElement(serializer, this.data) } catch (cause: Exception) { throw IllegalStateException("parse digest data error, status: $errorCode - $errorMessage", cause) } } internal suspend fun QQAndroidBot.getDigestList( groupCode: Long, pageStart: Int, pageLimit: Int ): DigestList { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/group_digest/digest_list") parameter("group_code", groupCode) parameter("page_start", pageStart) parameter("page_limit", pageLimit) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText().loadAs(DigestData.serializer()).loadData(DigestList.serializer()) } internal suspend fun QQAndroidBot.cancelDigest( groupCode: Long, msgSeq: Long, msgRandom: Long ) { val data = components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/group_digest/cancel_digest") parameter("group_code", groupCode) parameter("msg_seq", msgSeq) parameter("msg_random", msgRandom) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText().loadAs(DigestData.serializer()) when (data.errorCode) { 0, 11007, 11001 -> Unit else -> throw IllegalStateException("cancel digest error, status: ${data.errorCode} - ${data.errorMessage}, reason: ${data.reason}") } } internal suspend fun QQAndroidBot.shareDigest( groupCode: Long, msgSeq: Long, msgRandom: Long, targetGroupCode: Long ): DigestShare { return components[HttpClientProvider].getHttpClient().get { url("https://qun.qq.com/cgi-bin/group_digest/share_digest") parameter("group_code", groupCode) parameter("msg_seq", msgSeq) parameter("msg_random", msgRandom) parameter("target_group_code", targetGroupCode) parameter("bkn", client.wLoginSigInfo.bkn) headers { // ktor bug append( "cookie", "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey(host)};" ) } }.bodyAsText().loadAs(DigestData.serializer()).loadData(DigestShare.serializer()) } internal suspend fun QQAndroidBot.downloadEssenceMessageImage(urlString: String): ByteArray { return components[HttpClientProvider].getHttpClient().get { url(urlString) }.body() } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFileImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.file import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.internal.message.data.FileMessageImpl import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.utils.isSameClass import net.mamoe.mirai.utils.toUHexString internal class AbsoluteFileImpl( contact: FileSupported, parent: AbsoluteFolder?, id: String, name: String, uploadTime: Long, lastModifiedTime: Long, uploaderId: Long, override var expiryTime: Long, override val size: Long, // when file is changed, its id will also be changed, so no need to be var override val sha1: ByteArray, override val md5: ByteArray, busId: Int, ) : AbsoluteFile, AbstractAbsoluteFileFolder( contact, parent, id, name, uploadTime, uploaderId, lastModifiedTime, busId ) { override fun checkPermission(operationHint: String) { // TODO: 30/10/2021 checkPermission: 群可以设置允许任何人上传而目前没有检测这个属性, 因此不能实现权限判定 // if (uploaderId == bot.id) return // if (contact is GroupImpl && !contact.botPermission.isOperator()) throwPermissionDeniedException(operationHint) // return } override val isFile: Boolean get() = true override val isFolder: Boolean get() = false override val absolutePath: String get() { val parent = parent return when { parent == null || parent.name == "/" -> "/$name" else -> "${parent.absolutePath}/$name" } } override suspend fun exists(): Boolean { return bot.network.sendAndExpect( FileManagement.GetFileInfo( client, groupCode = contact.id, busId = busId, fileId = id ) ).toResult("AbsoluteFileImpl.exists", checkResp = false) .getOrThrow() .fileInfo != null } override suspend fun moveTo(folder: AbsoluteFolder): Boolean { if (folder.contact != this.contact) { error("Cross-group file operation is not yet supported.") } if (folder.absolutePath == this.parentOrRoot.absolutePath) return true checkPermission("moveTo") val result = bot.network.sendAndExpect( FileManagement.MoveFile( client, contact.id, busId, id, parent.idOrRoot, folder.idOrRoot ) ) .toResult("AbsoluteFileImpl.moveTo", checkResp = false) .getOrThrow() return when (result.int32RetCode) { -36 -> throwPermissionDeniedException("moveTo") 0 -> { parent = folder true } else -> { false } } // } else { // return FileManagement.RenameFolder(client, contact.id, id, name).sendAndExpect(bot) // .toResult("RemoteFile.moveTo", checkResp = false).getOrThrow().int32RetCode == 0 // } } override suspend fun getUrl(): String? { // Known error // java.lang.IllegalStateException: Failed AbsoluteFileImpl.getUrl, result=-303, msg=param error: bus_id // java.lang.IllegalStateException: Failed AbsoluteFileImpl.getUrl, result=-103, msg=GetFileAttrAction file not exist val resp = bot.network.sendAndExpect( FileManagement.RequestDownload( client, groupCode = contact.id, busId = busId, fileId = id ) ).toResult("AbsoluteFileImpl.getUrl").getOrElse { return null } return "http://${resp.downloadIp}/ftn_handler/${resp.downloadUrl.toUHexString("")}/?fname=" + id.toByteArray().toUHexString("") } override fun toMessage(): FileMessage { return FileMessageImpl(id, busId, name, size) } override suspend fun refresh(): Boolean { val new = refreshed() ?: return false this.parent = new.parent this.expiryTime = new.expiryTime this.name = new.name this.lastModifiedTime = new.lastModifiedTime return true } override fun toString(): String = "AbsoluteFile(name=$name, absolutePath=$absolutePath, id=$id)" override suspend fun refreshed(): AbsoluteFile? { val result = bot.network.sendAndExpect(FileManagement.GetFileInfo(client, contact.id, id, busId)) .toResult("AbsoluteFile.refreshed") .getOrNull()?.fileInfo ?: return null return if (result.parentFolderId == this.parentOrRoot.id) { this.parentOrRoot.impl().createChildFile(result) } else { null } } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is AbsoluteFileImpl || !isSameClass(this, other)) return false if (!super.equals(other)) return false if (expiryTime != other.expiryTime) return false if (size != other.size) return false if (!sha1.contentEquals(other.sha1)) return false if (!md5.contentEquals(other.md5)) return false return true } override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + expiryTime.hashCode() result = 31 * result + size.hashCode() result = 31 * result + sha1.contentHashCode() result = 31 * result + md5.contentHashCode() return result } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/file/AbsoluteFolderImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.file import io.ktor.utils.io.core.* import kotlinx.coroutines.flow.* import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFileFolder import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.file.RemoteFilesImpl.Companion.findFileByPath import net.mamoe.mirai.internal.message.flags.AllowSendFileMessage import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.protocol import net.mamoe.mirai.internal.network.protocol.data.proto.* import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.internal.utils.FileSystem import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.utils.* internal fun Oidb0x6d8.GetFileListRspBody.Item.resolved(parent: CommonAbsoluteFolderImpl): AbsoluteFileFolder? { val item = this return when { item.fileInfo != null -> { parent.createChildFile(item.fileInfo) } item.folderInfo != null -> { parent.createChildFolder(item.folderInfo) } else -> null } } internal fun CommonAbsoluteFolderImpl.createChildFolder( folderInfo: GroupFileCommon.FolderInfo ): AbsoluteFolderImpl = AbsoluteFolderImpl( contact = contact, parent = this, id = folderInfo.folderId, name = folderInfo.folderName, uploadTime = folderInfo.createTime.toLongUnsigned(), uploaderId = folderInfo.createUin, lastModifiedTime = folderInfo.modifyTime.toLongUnsigned(), contentsCount = folderInfo.totalFileCount ) internal fun CommonAbsoluteFolderImpl.createChildFile( info: GroupFileCommon.FileInfo ): AbsoluteFileImpl = AbsoluteFileImpl( contact = contact, parent = this, id = info.fileId, name = info.fileName, uploadTime = info.uploadTime.toLongUnsigned(), lastModifiedTime = info.modifyTime.toLongUnsigned(), uploaderId = info.uploaderUin, expiryTime = info.deadTime.toLongUnsigned(), size = info.fileSize, sha1 = info.sha, md5 = info.md5, busId = info.busId ) internal expect class AbsoluteFolderImpl( contact: FileSupported, parent: AbsoluteFolder?, id: String, name: String, uploadTime: Long, uploaderId: Long, lastModifiedTime: Long, contentsCount: Int, ) : CommonAbsoluteFolderImpl internal abstract class CommonAbsoluteFolderImpl( contact: FileSupported, parent: AbsoluteFolder?, id: String, name: String, uploadTime: Long, uploaderId: Long, lastModifiedTime: Long, override var contentsCount: Int, ) : AbstractAbsoluteFileFolder( contact, parent, id, name, uploadTime, uploaderId, lastModifiedTime, 0 ), AbsoluteFolder { override fun checkPermission(operationHint: String) { // 目录权限不受 '允许任何人上传' 设置的影响 if (contact is GroupImpl && !contact.botPermission.isOperator()) throwPermissionDeniedException(operationHint) return } override val isFile: Boolean get() = false override val isFolder: Boolean get() = true override val absolutePath: String get() { val parent = parent return when { parent == null || this.id == "/" -> "/" parent.parent == null || parent.id == "/" -> "/$name" else -> "${parent.absolutePath}/$name" } } companion object { suspend fun getItemsFlow( client: QQAndroidClient, contact: FileSupported, folderId: String ): Flow<Oidb0x6d8.GetFileListRspBody.Item> { return flow { var index = 0 while (true) { val list = client.bot.network.sendAndExpect( FileManagement.GetFileList( client, groupCode = contact.id, folderId = folderId, startIndex = index ) ).toResult("AbsoluteFolderImpl.getFilesFlow").getOrThrow() index += list.itemList.size if (list.int32RetCode != 0) return@flow if (list.itemList.isEmpty()) return@flow emitAll(list.itemList.asFlow()) } } } suspend fun uploadNewFileImpl( folder: AbsoluteFolderImpl, filepath: String, content: ExternalResource, callback: ProgressionCallback<AbsoluteFile, Long>? ): AbsoluteFile { if (filepath.isBlank()) throw IllegalArgumentException("filename cannot be blank.") // TODO: 12/10/2021 checkPermission for AbsoluteFolderImpl.upload content.withAutoClose { val resp = folder.bot.network.sendAndExpect( FileManagement.RequestUpload( folder.client, groupCode = folder.contact.id, folderId = folder.id, resource = content, filename = filepath ) ).toResult("AbsoluteFolderImpl.upload").getOrThrow() when (resp.int32RetCode) { -36 -> folder.throwPermissionDeniedException("uploadNewFile") } val file = AbsoluteFileImpl( contact = folder.contact, parent = folder, id = resp.fileId, name = filepath, uploadTime = folder.bot.clock.server.currentTimeSeconds(), lastModifiedTime = folder.bot.clock.server.currentTimeSeconds(), expiryTime = 0, uploaderId = folder.bot.id, size = content.size, sha1 = content.sha1, md5 = content.md5, busId = resp.busId ) if (resp.boolFileExist) { // resp.boolFileExist: // 服务器是否存在相同的内容, 只是用来判断可不可以跳过上传 // 当为 true 时跳过上传, 但仍然需要完成 `sendMessage(FileMessage)` 才是正常逻辑 callback?.onBegin(file, content) val result = kotlin.runCatching { folder.contact.sendMessage(AllowSendFileMessage + file.toMessage()) }.map { content.size } callback?.onFinished(file, content, result) return file } val ext = GroupFileUploadExt( u1 = 100, u2 = 1, entry = GroupFileUploadEntry( business = ExcitingBusiInfo( busId = resp.busId, senderUin = folder.bot.id, receiverUin = folder.contact.id, // TODO: 2021/3/1 code or uin? groupCode = folder.contact.id, ), fileEntry = ExcitingFileEntry( fileSize = content.size, md5 = content.md5, sha1 = content.sha1, fileId = resp.fileId.toByteArray(), uploadKey = resp.checkKey, ), clientInfo = ExcitingClientInfo( clientType = 2, appId = folder.client.protocol.id.toString(), terminalType = 2, clientVer = "9e9c09dc", unknown = 4, ), fileNameInfo = ExcitingFileNameInfo(filepath), host = ExcitingHostConfig( hosts = listOf( ExcitingHostInfo( url = ExcitingUrlInfo( unknown = 1, host = resp.uploadIpLanV4.firstOrNull() ?: resp.uploadIpLanV6.firstOrNull() ?: resp.uploadIp, ), port = resp.uploadPort, ), ), ), ), u3 = 0, ).toByteArray(GroupFileUploadExt.serializer()) callback?.onBegin(file, content) kotlin.runCatching { Highway.uploadResourceBdh( bot = folder.bot, resource = content, kind = ResourceKind.GROUP_FILE, commandId = 71, extendInfo = ext, dataFlag = 0, callback = if (callback == null) null else fun(it: Long) { callback.onProgression(file, content, it) } ) }.let { result0 -> val result = result0.onSuccessCatching { folder.contact.sendMessage(AllowSendFileMessage + file.toMessage()) } callback?.onFinished(file, content, result.map { content.size }) } return file } } } suspend fun getItemsFlow(): Flow<Oidb0x6d8.GetFileListRspBody.Item> = Companion.getItemsFlow(client, contact, id) protected fun Oidb0x6d8.GetFileListRspBody.Item.resolve(): AbsoluteFileFolder? = resolved(this@CommonAbsoluteFolderImpl) override suspend fun folders(): Flow<AbsoluteFolder> { return getItemsFlow().filter { it.folderInfo != null }.map { it.resolve() as AbsoluteFolder } } override suspend fun files(): Flow<AbsoluteFile> { return getItemsFlow().filter { it.fileInfo != null }.map { it.resolve() as AbsoluteFile } } override suspend fun children(): Flow<AbsoluteFileFolder> { return getItemsFlow().mapNotNull { it.resolve() } } override suspend fun createFolder(name: String): AbsoluteFolder { if (name.isBlank()) throw IllegalArgumentException("folder name cannot be blank.") checkPermission("createFolder") FileSystem.checkLegitimacy(name) // server only support nesting depth level of 1 so we don't need to check the name val result = bot.network.sendAndExpect(FileManagement.CreateFolder(client, contact.id, this.id, name)) .toResult("AbsoluteFolderImpl.mkdir", checkResp = false) .getOrThrow() // throw protocol errors /* 2021-10-30 13:06:33 D/soutv: unnamed = CreateFolderRspBody#-941698272 { folderInfo=FolderInfo#1879610684 { createTime=0x617D3548(1635595592) createUin=xxx folderId=/49a18e46-cf24-4362-b0d0-13235c0e7862 folderName=myFolder modifyTime=0x617D3548(1635595592) modifyUin=xxx parentFolderId=/ usedSpace=0x0000000000000000(0) } retMsg=ok } */ /* 2021-10-30 13:03:44 D/soutv: unnamed = CreateFolderRspBody#-941698272 { clientWording=只允许群主和管理员操作 int32RetCode=0xFFFFFFDC(-36) retMsg=not group admin } */ /* 2021-10-30 13:10:32 D/soutv: unnamed = CreateFolderRspBody#-941698272 { clientWording=同名文件夹已存在 int32RetCode=0xFFFFFEC7(-313) retMsg=folder name has exist } */ return when (result.int32RetCode) { -36 -> throwPermissionDeniedException("createFolder") -313 -> this.resolveFolder(name) // already exists 0 -> { if (result.folderInfo != null) { this.createChildFolder(result.folderInfo) } else { this.resolveFolder(name) } } else -> { // unexpected errors error("Failed to create folder '$name': ${result.int32RetCode} ${result.clientWording}.") } } ?: error("Failed to create folder '$name': server returned success but failed to find folder.") } override suspend fun resolveFolder(name: String): AbsoluteFolder? { if (name.isBlank()) throw IllegalArgumentException("folder name cannot be blank.") if (!FileSystem.isLegal(name)) return null if (name[0] == '/') { return root.resolveFolder(name.substring(1)) } return getItemsFlow().firstOrNull { it.folderInfo?.folderName == name }?.resolve() as AbsoluteFolder? } override suspend fun resolveFolderById(id: String): AbsoluteFolder? { if (name.isBlank()) throw IllegalArgumentException("folder id cannot be blank.") if (!FileSystem.isLegal(id)) return null if (id == AbsoluteFolder.ROOT_FOLDER_ID) return root // special case, not ambiguous — '/' always refers to root. if (this.id != AbsoluteFolder.ROOT_FOLDER_ID) return null // reserved for future // All folder ids start with '/'. // Currently, only root folders can have children folders, // and we don't know how the folderIds can be changed, // so we force the receiver to be root for now, to provide forward-compatibility. return root.impl().getItemsFlow().firstOrNull { it.folderInfo?.folderId == id }?.resolve() as AbsoluteFolder? } override suspend fun resolveFileById(id: String, deep: Boolean): AbsoluteFile? { if (id == "/" || id.isEmpty()) throw IllegalArgumentException("Illegal file id: $id") getItemsFlow().filter { it.fileInfo?.fileId == id }.map { it.resolve() as AbsoluteFile }.firstOrNull() ?.let { return it } if (!deep) return null return folders().map { it.resolveFileById(id, deep) }.firstOrNull { it != null } } override suspend fun resolveFiles(path: String): Flow<AbsoluteFile> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") if (!FileSystem.isLegal(path)) return emptyFlow() if (path[0] == '/') { return root.resolveFiles(path.substring(1)) } if (!path.contains('/')) { return getItemsFlow().filter { it.fileInfo?.fileName == path }.map { it.resolve() as AbsoluteFile } } return resolveFolder(path.substringBefore('/'))?.resolveFiles(path.substringAfter('/')) ?: emptyFlow() } override suspend fun resolveAll(path: String): Flow<AbsoluteFileFolder> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") if (!FileSystem.isLegal(path)) return emptyFlow() if (path[0] == '/') { return root.resolveAll(path.substring(1)) } if (!path.contains('/')) { return getItemsFlow().mapNotNull { it.resolve() } } return resolveFolder(path.substringBefore('/'))?.resolveAll(path.substringAfter('/')) ?: emptyFlow() } override suspend fun uploadNewFile( filepath: String, content: ExternalResource, callback: ProgressionCallback<AbsoluteFile, Long>? ): AbsoluteFile { val (actualFolder, actualFilename) = findFileByPath(filepath) return uploadNewFileImpl(actualFolder.impl(), actualFilename, content, callback) } override suspend fun exists(): Boolean { return parentOrFail().folders().firstOrNull { it.id == this.id } != null } override suspend fun refresh(): Boolean { val new = refreshed() ?: return false this.name = new.name this.lastModifiedTime = new.lastModifiedTime this.contentsCount = new.contentsCount return true } override fun toString(): String = "AbsoluteFolder(name=$name, absolutePath=$absolutePath, id=$id)" override suspend fun refreshed(): AbsoluteFolder? = parentOrRoot.folders().firstOrNull { it.id == this.id } override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (!super.equals(other)) return false if (contentsCount != other.contentsCount) return false return true } override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + contentsCount.hashCode() return result } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/file/AbstractAbsoluteFileFolder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.internal.contact.file import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFileFolder import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.internal.utils.FileSystem import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.isSameType internal fun AbstractAbsoluteFileFolder.api(): AbsoluteFileFolder = this.cast() internal fun AbsoluteFileFolder.impl(): AbstractAbsoluteFileFolder = this.cast() internal fun AbsoluteFile.impl(): AbsoluteFileImpl = this.cast() internal fun AbsoluteFolder.impl(): AbsoluteFolderImpl = this.cast() internal val AbsoluteFolder?.idOrRoot get() = this?.id ?: AbsoluteFolder.ROOT_FOLDER_ID internal val AbstractAbsoluteFileFolder.parentOrRoot get() = parent ?: contact.files.root internal val AbstractAbsoluteFileFolder.root get() = contact.files.root /** * @see AbsoluteFileFolder */ internal abstract class AbstractAbsoluteFileFolder( // overriding AbsFileFolder val contact: FileSupported, var parent: AbsoluteFolder?, val id: String, // uuid-like var name: String, val uploadTime: Long, val uploaderId: Long, var lastModifiedTime: Long, // end val busId: Int, // protocol internal ) { protected inline val bot get() = contact.bot.asQQAndroidBot() protected inline val client get() = bot.client protected abstract fun checkPermission(operationHint: String) fun throwPermissionDeniedException(operationHint: String): Nothing { throw PermissionDeniedException("Permission denied: '$operationHint' on file '${this.api().absolutePath}' requires an operator permission.") } protected fun parentOrFail() = parent ?: error("Cannot rename the root folder.") /////////////////////////////////////////////////////////////////////////// // overriding AbsFileFolder /////////////////////////////////////////////////////////////////////////// protected abstract val isFile: Boolean protected abstract val isFolder: Boolean suspend fun renameTo(newName: String): Boolean { FileSystem.checkLegitimacy(newName) parentOrFail() checkPermission("renameTo") val result = bot.network.sendAndExpect( if (isFile) { FileManagement.RenameFile(client, contact.id, busId, id, parent.idOrRoot, newName) } else { FileManagement.RenameFolder(client, contact.id, id, newName) } ) result.toResult("AbstractAbsoluteFileFolder.renameTo") { when (it) { 0 -> { name = newName return true } 1 -> return false else -> false } }.getOrThrow() error("unreachable") } suspend fun delete(): Boolean { checkPermission("delete") val result = if (isFile) { bot.network.sendAndExpect(FileManagement.DeleteFile(client, contact.id, busId, id, parent.idOrRoot)) } else { // natively 'recursive' bot.network.sendAndExpect(FileManagement.DeleteFolder(client, contact.id, id)) }.toResult("AbstractAbsoluteFileFolder.delete", checkResp = false).getOrThrow() return when (result.int32RetCode) { -36 -> throwPermissionDeniedException("delete") 0 -> true else -> { // files not exists or other errors. false } } } @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (contact != other.contact) return false if (parent != other.parent) return false if (id != other.id) return false if (name != other.name) return false if (uploadTime != other.uploadTime) return false if (uploaderId != other.uploaderId) return false if (lastModifiedTime != other.lastModifiedTime) return false if (busId != other.busId) return false if (isFile != other.isFile) return false if (isFolder != other.isFolder) return false return true } override fun hashCode(): Int { var result = contact.hashCode() result = 31 * result + (parent?.hashCode() ?: 0) result = 31 * result + id.hashCode() result = 31 * result + name.hashCode() result = 31 * result + uploadTime.hashCode() result = 31 * result + uploaderId.hashCode() result = 31 * result + lastModifiedTime.hashCode() result = 31 * result + busId result = 31 * result + isFile.hashCode() result = 31 * result + isFolder.hashCode() return result } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/file/FileProtocol.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.file import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x6d6 import net.mamoe.mirai.internal.network.protocol.packet.chat.CommonOidbResponse import net.mamoe.mirai.internal.utils.FileSystem /** * Abstract protocol bridge for file management. */ internal interface FileProtocol { val fs: FileSystem get() = FileSystem fun renameFile( client: QQAndroidClient, groupCode: Long, busId: Int, fileId: String, parentFolderId: String, newName: String, ): CommonOidbResponse<Oidb0x6d6.RenameFileRspBody> } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/file/RemoteFilesImpl.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.file import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.internal.contact.ContactAware import net.mamoe.mirai.internal.utils.FileSystem internal class RemoteFilesImpl( override val contact: FileSupported, override val root: AbsoluteFolder = AbsoluteFolderImpl( contact, null, AbsoluteFolder.ROOT_FOLDER_ID, "/", 0, 0, 0, 0 ), ) : RemoteFiles, ContactAware { companion object { suspend fun AbsoluteFolder.findFileByPath(path: String): Pair<AbsoluteFolder, String> { if (path.isBlank()) throw IllegalArgumentException("absolutePath cannot be blank.") val normalized = FileSystem.normalize(path) // if (!normalized.contains('/')) { // throw IllegalArgumentException("Invalid absolutePath: '$path'. If you wanted to upload file to root directory, please add a leading '/'.") // } val folder = when (normalized.count { it == '/' }) { 0, 1 -> this else -> this.createFolder(normalized.substringBeforeLast("/")) } val filename = normalized.substringAfterLast('/') return folder to filename } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.friendgroup import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.FriendImpl import net.mamoe.mirai.internal.contact.impl import net.mamoe.mirai.internal.contact.info.FriendGroupInfo import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract @OptIn(ExperimentalContracts::class) internal fun FriendGroup.impl(): FriendGroupImpl { contract { returns() implies (this@impl is FriendGroupImpl) } check(this is FriendGroupImpl) { "A FriendGroup instance is not instance of FriendGroupImpl. Your instance: ${this::class.qualifiedName}" } return this } internal class FriendGroupImpl constructor( val bot: QQAndroidBot, val info: FriendGroupInfo ) : FriendGroup { override val id: Int by info::groupId override var name: String by info::groupName override val count: Int get() = friends.size override val friends: Collection<Friend> = object : AbstractCollection<Friend>() { override val size: Int get() = bot.friends.count { it.impl().info.friendGroupId == id } private val delegateSequence = sequence<Friend> { bot.friends.forEach { friend -> friend.impl() if (friend.info.friendGroupId == id) { yield(friend) } } } override fun iterator(): Iterator<Friend> = delegateSequence.iterator() override fun isEmpty(): Boolean { return bot.friends.none { it.impl().info.friendGroupId == id } } override fun contains(element: Friend): Boolean { if (element !is FriendImpl) return false return element.info.friendGroupId == id } } override suspend fun renameTo(newName: String): Boolean { bot.network.sendAndExpect(FriendList.SetGroupReqPack.Rename(bot.client, newName, id)).let { if (it.result.toInt() == 1) { return false } check(it.isSuccess) { "Cannot rename friendGroup(id=$id) to $newName, code=${it.result.toInt()}, errStr=${it.errStr}" } } info.groupName = newName return true } override suspend fun moveIn(friend: Friend): Boolean { bot.network.sendAndExpect(FriendList.MoveGroupMemReqPack(bot.client, friend.id, id)).let { check(it.isSuccess) { "Cannot move friend to $this, code=${it.result.toInt()}, errStr=${it.errStr}" } } // 因为 MoveGroupMemReqPack 协议在测试里如果移动到不存在的分组,他会自动移动好友到 id = 0 的默认好友分组然后返回 result = 0 val id = friend.queryProfile().friendGroupId friend.impl().info.friendGroupId = id if (id != this.id && id == 0) return false return true } override suspend fun delete(): Boolean { bot.network.sendAndExpect(FriendList.SetGroupReqPack.Delete(bot.client, id)).let { if (it.result.toInt() == 1) { return false } check(it.isSuccess) { "Cannot delete friendGroup, code=${it.result.toInt()}, errStr=${it.errStr}" } } friends.forEach { it.impl().info.friendGroupId = 0 } bot.friendGroups.friendGroups.remove(this) return true } override fun toString(): String { return "FriendGroup(id=$id, name=$name)" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/friendgroup/FriendGroupsImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.friendgroup import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.friendgroup.FriendGroups import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.info.FriendGroupInfo import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList import net.mamoe.mirai.utils.ConcurrentLinkedDeque import net.mamoe.mirai.utils.asImmutable internal class FriendGroupsImpl( val bot: QQAndroidBot ) : FriendGroups { val friendGroups = ConcurrentLinkedDeque<FriendGroupImpl>() private val friendGroupsImmutable by lazy { friendGroups.asImmutable() } override suspend fun create(name: String): FriendGroup { val resp = bot.network.sendAndExpect(FriendList.SetGroupReqPack.New(bot.client, name)) check(resp.isSuccess) { "Cannot create friendGroup, code=${resp.result.toInt()}, errStr=${resp.errStr}" } return FriendGroupImpl(bot, FriendGroupInfo(resp.groupId, name)).apply { friendGroups.add(this) } } override fun get(id: Int): FriendGroup? = friendGroups.firstOrNull { it.id == id } override fun asCollection(): Collection<FriendGroup> = friendGroupsImmutable } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/info/FriendGroupInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.info import kotlinx.serialization.Serializable @Serializable internal data class FriendGroupInfo( val groupId: Int, var groupName: String, // val friendCount: Int, // val onlineFriendCount: Int ) ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/info/FriendInfoImpl.kt ================================================ /* * 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, override var friendGroupId: Int = 0 ) : FriendInfo { companion object { fun FriendInfo.impl() = if (this is FriendInfoImpl) this else FriendInfoImpl(uin, nick, remark, friendGroupId) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/info/GroupInfoImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/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.StGroupRankInfo 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, override val isHonorVisible: Boolean, override val isTitleVisible: Boolean, override val isTemperatureVisible: Boolean, override val rankTitles: Map<Int, String>, override val temperatureTitles: Map<Int, String> ) : GroupInfo, Packet, Packet.NoLog { constructor(stTroopNum: StTroopNum, stGroupRankInfo: StGroupRankInfo?) : 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, isHonorVisible = stGroupRankInfo?.groupRankSysFlag?.toInt() == 1, isTitleVisible = stGroupRankInfo?.groupRankUserFlag?.toInt() == 1, isTemperatureVisible = stGroupRankInfo?.groupRankUserFlagNew?.toInt() == 1, rankTitles = buildMap { for (pair in stGroupRankInfo?.vecRankMap.orEmpty()) { val level = pair.dwLevel?.toInt() ?: continue val title = pair.rank.orEmpty() put(level, title) } }, temperatureTitles = buildMap { for (pair in stGroupRankInfo?.vecRankMapNew.orEmpty()) { val level = pair.dwLevel?.toInt() ?: continue val title = pair.rank.orEmpty() put(level, title) } } ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/info/MemberInfoImpl.kt ================================================ /* * 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.contact.MemberPermission import net.mamoe.mirai.data.GroupHonorType 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.internal.network.protocol.packet.login.wtlogin.orEmpty import net.mamoe.mirai.utils.ConcurrentSet import net.mamoe.mirai.utils.currentTimeSeconds @Serializable internal data class MemberInfoImpl( override val uin: Long, override var nick: String, override var permission: MemberPermission, override var remark: String = "", override val nameCard: String = "", override val specialTitle: String = "", override val muteTimestamp: Int = 0, override val anonymousId: String? = null, override val joinTimestamp: Int = currentTimeSeconds().toInt(), override var lastSpeakTimestamp: Int = 0, override val isOfficialBot: Boolean = false, override var rank: Int = 1, override var point: Int = 0, override val honors: MutableSet<GroupHonorType> = ConcurrentSet(), override var temperature: Int = 1 ) : MemberInfo { constructor( client: QQAndroidClient, jceInfo: StTroopMemberInfo, groupOwnerId: Long, ) : this( uin = jceInfo.memberUin, nick = jceInfo.nick, permission = when { jceInfo.memberUin == groupOwnerId -> MemberPermission.OWNER jceInfo.dwFlag?.takeLowestOneBit() == 1L -> MemberPermission.ADMINISTRATOR else -> MemberPermission.MEMBER }, remark = jceInfo.autoRemark.orEmpty(), nameCard = jceInfo.sName.orEmpty(), specialTitle = jceInfo.sSpecialTitle.orEmpty(), muteTimestamp = jceInfo.dwShutupTimestap?.toInt() ?: 0, anonymousId = null, joinTimestamp = jceInfo.dwJoinTime?.toInt() ?: 0, lastSpeakTimestamp = jceInfo.dwLastSpeakTime?.toInt() ?: 0, isOfficialBot = client.groupConfig.isOfficialRobot(jceInfo.memberUin), rank = jceInfo.dwMemberLevel?.toInt() ?: 1, point = jceInfo.dwPoint?.toInt() ?: 0, honors = ConcurrentSet<GroupHonorType>().apply { /** * vecGroupHonor 的 结构是 * [ * (type, value), * (type, value), * (type, value), * ] * 构成的 ByteArray * type = 8 时,表示群荣誉头衔标志 * type = 16 时,表示群荣誉活跃度 */ val bytes = jceInfo.vecGroupHonor.orEmpty() for (index in bytes.indices step 2) { val type = bytes[index] if (type.toInt() == 8) { val value = bytes.getOrNull(index + 1) ?: break try { @Suppress("INVISIBLE_MEMBER") add(GroupHonorType(value.toInt())) } catch (_: Exception) { } } } }, temperature = jceInfo.vecGroupHonor?.let { bytes -> for (index in bytes.indices step 2) { val type = bytes[index] if (type.toInt() == 16) { val value = bytes.getOrNull(index + 1) ?: break return@let value.toInt() } } null } ?: 0 ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/info/StrangerInfoImpl.kt ================================================ /* * 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 { companion object { fun StrangerInfo.impl() = if (this is StrangerInfoImpl) this else StrangerInfoImpl(uin, nick, fromGroup, remark) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/roaming/AbstractRoamingMessages.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.roaming import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.roaming.RoamingMessage import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.source.decodeRandom import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.utils.mapToIntArray import net.mamoe.mirai.utils.toLongUnsigned internal abstract class AbstractRoamingMessages : RoamingMessages { abstract val contact: AbstractContact protected fun createRoamingMessage( message: MsgComm.Msg, messages: List<MsgComm.Msg> ) = object : RoamingMessage { override val contact: Contact get() = this@AbstractRoamingMessages.contact override val sender: Long get() = message.msgHead.fromUin override val target: Long get() = message.msgHead.groupInfo?.groupCode ?: message.msgHead.toUin override val time: Long get() = message.msgHead.msgTime.toLongUnsigned() override val ids: IntArray by lazy { messages.mapToIntArray { it.msgHead.msgSeq } } override val internalIds: IntArray by lazy { messages.mapToIntArray { it.decodeRandom() } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImplFriend.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.roaming import net.mamoe.mirai.internal.contact.FriendImpl import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq import net.mamoe.mirai.utils.check internal class RoamingMessagesImplFriend( override val contact: FriendImpl ) : TimeBasedRoamingMessagesImpl() { override suspend fun requestRoamMsg( timeStart: Long, lastMessageTime: Long, random: Long ): MessageSvcPbGetRoamMsgReq.Response { return contact.bot.network.sendAndExpect( MessageSvcPbGetRoamMsgReq.createForFriend( client = contact.bot.client, uin = contact.id, timeStart = timeStart, lastMsgTime = lastMessageTime, random = random, maxCount = 1000, sig = byteArrayOf(), pwd = byteArrayOf() ) ).value.check() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/roaming/RoamingMessagesImplGroup.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.roaming import kotlinx.coroutines.flow.* import net.mamoe.mirai.contact.roaming.RoamingMessageFilter import net.mamoe.mirai.internal.contact.CommonGroupImpl import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetGroupMsg import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSourceKind internal class RoamingMessagesImplGroup( override val contact: CommonGroupImpl ) : AbstractRoamingMessages() { private val bot get() = contact.bot override suspend fun getMessagesIn( timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter? ): Flow<MessageChain> { var currentSeq: Int = getLastMsgSeq() ?: return emptyFlow() var lastOfferedSeq = -1 return flow { while (true) { val resp = contact.bot.network.sendAndExpect( MessageSvcPbGetGroupMsg( client = contact.bot.client, groupUin = contact.uin, messageSequence = currentSeq.toLong(), count = 20 // maximum 20 ) ) if (resp is MessageSvcPbGetGroupMsg.Failed) break resp as MessageSvcPbGetGroupMsg.Success // stupid smart cast if (resp.msgElem.isEmpty()) break // the message may be sorted increasing by message time, // if so, additional sortBy will not take cost. val messageTimeSequence = resp.msgElem.asSequence().map { it.time } val maxTime = messageTimeSequence.max() // we have fetched all messages // note: maxTime = 0 means all fetched messages were recalled if (maxTime < timeStart && maxTime != 0) break emitAll( resp.msgElem.asSequence() .filter { lastOfferedSeq == -1 || it.msgHead.msgSeq < lastOfferedSeq } .filter { it.time in timeStart..timeEnd } .sortedByDescending { it.msgHead.msgSeq } // Ensure caller receives newer messages first .filter { filter.apply(it) } // Call filter after sort .asFlow() .map { listOf(it).toMessageChainOnline( bot, contact.id, MessageSourceKind.GROUP, SimpleRefineContext( RefineContextKey.MessageSourceKind to MessageSourceKind.GROUP, RefineContextKey.FromId to it.msgHead.fromUin, RefineContextKey.GroupIdOrZero to contact.id, ) ) } ) currentSeq = resp.msgElem.first().msgHead.msgSeq lastOfferedSeq = currentSeq } } } private val MsgComm.Msg.time get() = msgHead.msgTime private fun RoamingMessageFilter?.apply( it: MsgComm.Msg ) = this?.invoke(createRoamingMessage(it, listOf())) != false private suspend fun getLastMsgSeq(): Int? { // Iterate from the newest message to find messages within [timeStart] and [timeEnd] val lastMsgSeqResp = bot.network.sendAndExpect( TroopManagement.GetGroupLastMsgSeq( client = bot.client, groupUin = contact.uin ) ) return when (lastMsgSeqResp) { TroopManagement.GetGroupLastMsgSeq.Response.Failed -> null is TroopManagement.GetGroupLastMsgSeq.Response.Success -> lastMsgSeqResp.seq } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/roaming/TimeBasedRoamingMessagesImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package net.mamoe.mirai.internal.contact.roaming import kotlinx.coroutines.currentCoroutineContext import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.isActive import net.mamoe.mirai.contact.roaming.RoamingMessageFilter import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq import net.mamoe.mirai.message.data.MessageChain internal sealed class TimeBasedRoamingMessagesImpl : AbstractRoamingMessages() { override suspend fun getMessagesIn( timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter? ): Flow<MessageChain> { return flow { var lastMessageTime = timeEnd.coerceAtLeast(timeStart).coerceAtLeast(1) var random = 0L while (currentCoroutineContext().isActive) { val resp = requestRoamMsg(timeStart, lastMessageTime, random) val messages = resp.messages ?: break if (filter == null || filter === RoamingMessageFilter.ANY) { // fast path messages.forEach { msg -> val context = SimpleRefineContext(RefineContextKey.FromId to msg.msgHead.fromUin) emit(msg.toMessageChainOnline(contact.bot, context)) } } else { for (message in messages) { if (filter.invoke(createRoamingMessage(message, messages))) { val context = SimpleRefineContext(RefineContextKey.FromId to message.msgHead.fromUin) emit(message.toMessageChainOnline(contact.bot, context)) } } } lastMessageTime = resp.lastMessageTime random = resp.random } } } abstract suspend fun requestRoamMsg( timeStart: Long, lastMessageTime: Long, random: Long ): MessageSvcPbGetRoamMsgReq.Response } ================================================ FILE: mirai-core/src/commonMain/kotlin/contact/util.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.internal.contact import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.utils.verbose internal inline val Group.uin: Long get() = this.cast<GroupImpl>().uin internal inline val Group.groupCode: Long get() = this.id internal inline val User.uin: Long get() = this.id internal inline val Bot.uin: Long get() = this.id @Suppress("RemoveRedundantQualifierName") // compiler bug internal fun net.mamoe.mirai.event.events.MessageEvent.logMessageReceived() { fun renderMessage(message: MessageChain): String { return message.filterNot { it is MessageSource }.joinToString("") } fun renderGroupMessage(group: Group, senderName: String, sender: Member, message: MessageChain): String { val displayId = if (sender is AnonymousMember) "匿名" else sender.id.toString() return "[${group.name}(${group.id})] ${senderName}($displayId) -> ${renderMessage(message)}" } fun renderGroupMessageSync(group: Group, message: MessageChain): String { return "[${group.name}(${group.id})][SYNC] <- ${renderMessage(message)}" } fun renderGroupTempMessage(group: Group, senderName: String, sender: Member, message: MessageChain): String { return "[${group.name}(${group.id})] $senderName(Temp ${sender.id}) -> ${renderMessage(message)}" } fun renderGroupTempMessageSync(group: Group, subjectName: String, subject: Member, message: MessageChain): String { return "[${group.name}(${group.id})] $subjectName(Temp ${subject.id})[SYNC] <- ${renderMessage(message)}" } fun renderStrangerMessage(senderName: String, sender: User, message: MessageChain): String { return "[$senderName(Stranger ${sender.id}) -> ${renderMessage(message)}" } fun renderStrangerMessageSync(subjectName: String, subject: User, message: MessageChain): String { return "[$subjectName(Stranger ${subject.id})[SYNC] <- ${renderMessage(message)}" } fun renderFriendMessage(sender: User, message: MessageChain): String { return "${sender.nick}(${sender.id}) -> ${renderMessage(message)}" } fun renderFriendMessageSync(subject: User, message: MessageChain): String { return "${subject.nick}(${subject.id})[SYNC] <- ${renderMessage(message)}" } fun renderOtherClientMessage(client: OtherClient): String { return "${client.platform} -> ${renderMessage(message)}" } bot.logger.verbose { when (this) { is net.mamoe.mirai.event.events.GroupMessageEvent -> renderGroupMessage(group, senderName, sender, message) is net.mamoe.mirai.event.events.GroupMessageSyncEvent -> renderGroupMessageSync(group, message) is net.mamoe.mirai.event.events.GroupTempMessageEvent -> renderGroupTempMessage(group, senderName, sender, message) is net.mamoe.mirai.event.events.GroupTempMessageSyncEvent -> renderGroupTempMessageSync(group, subject.nameCardOrNick, subject, message) is net.mamoe.mirai.event.events.StrangerMessageEvent -> renderStrangerMessage(senderName, sender, message) is net.mamoe.mirai.event.events.StrangerMessageSyncEvent -> renderStrangerMessageSync(subject.nick, subject, message) is net.mamoe.mirai.event.events.FriendMessageEvent -> renderFriendMessage(sender, message) is net.mamoe.mirai.event.events.FriendMessageSyncEvent -> renderFriendMessageSync(subject, message) is net.mamoe.mirai.event.events.OtherClientMessageEvent -> renderOtherClientMessage(client) else -> toString() }.replaceMagicCodes() // group name & sender nick & message } } @Suppress("SpellCheckingInspection") private val charMappings = mapOf( '\n' to """\n""", '\r' to "", // region Control Characters https://en.wikipedia.org/wiki/Control_character https://en.wikipedia.org/wiki/Unicode_control_characters // ASCII '\u0000' to "<NUL>", '\u0001' to "<SOH>", '\u0002' to "<STX>", '\u0003' to "<ETX>", '\u0004' to "<EOT>", '\u0005' to "<ENQ>", '\u0006' to "<ACK>", '\u0007' to "<BEL>", '\u0008' to "<BS>", '\u0009' to "<HT>", // '\u000a' to "<LF>", // \n '\u000b' to "<VT>", '\u000c' to "<FF>", // '\u000d' to "<CR>", // \r '\u000e' to "<SO>", '\u000F' to "<SI>", '\u0010' to "<DLE>", '\u0011' to "<DC1>", '\u0012' to "<DC2>", '\u0013' to "<DC3>", '\u0014' to "<DC4>", '\u0015' to "<NAK>", '\u0016' to "<SYN>", '\u0017' to "<ETB>", '\u0018' to "<CAN>", '\u0019' to "<EM>", '\u001a' to "<SUB>", '\u001b' to "<ESC>", '\u001c' to "<FS>", '\u001d' to "<GS>", '\u001e' to "<RS>", '\u001f' to "<US>", '\u007F' to "<DEL>", '\u0085' to "<NEL>", // Unicode Control Characters - Bidirectional text control // https://en.wikipedia.org/wiki/Unicode_control_characters#Bidirectional_text_control '\u061C' to "<ALM>", '\u200E' to "<LTRM>", '\u200F' to "<RTLM>", '\u202A' to "<LTRE>", '\u202B' to "<RTLE>", '\u202C' to "<PDF>", '\u202D' to "<LTR>", '\u202E' to "<RTL>", '\u2066' to "<LTRI>", '\u2067' to "<RTLI>", '\u2068' to "<FSI>", '\u2069' to "<PDI>", // endregion ) private val regionMappings: Map<IntRange, StringBuilder.(Char) -> Unit> = mapOf( 0x0080..0x009F to { // https://en.wikipedia.org/wiki/Control_character#In_Unicode append("<control-").append(it.code.toUHexString()).append(">") }, ) internal fun String.applyCharMapping() = buildString(capacity = this.length) { this@applyCharMapping.forEach { char -> charMappings[char]?.let { append(char); return@forEach } regionMappings.entries.find { char.code in it.key }?.let { mapping -> mapping.value.invoke(this@buildString, char) return@forEach } append(char) } } internal fun String.replaceMagicCodes(): String = this .applyCharMapping() internal fun Message.takeContent(length: Int): String = this.toMessageChain().joinToString("", limit = length) { it.content } ================================================ FILE: mirai-core/src/commonMain/kotlin/event/EventChannelImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.Job import kotlinx.coroutines.channels.BufferOverflow import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.event.* import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.EventDispatcher import net.mamoe.mirai.internal.network.components.SHOW_VERBOSE_EVENT import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.verbose import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass // See docs below @RequiresOptIn( "You must not use this API unless you are writing Event infrastructure.", level = RequiresOptIn.Level.ERROR ) internal annotation class InternalEventMechanism // Note: You probably should only use EventChannelToEventDispatcherAdapter.instance, or just use EventDispatchers. Event.broadcast is also good to use internally! /** * Implementation of [EventChannel]. Every [EventChannelImpl] holds its own list of listeners, so one [EventChannelImpl] instance, [EventChannelToEventDispatcherAdapter] is shared in mirai. * * ## Broadcasting an event * * You should first consider using [EventDispatcher.broadcast] or [EventDispatcher.broadcastAsync]. * Use [Event.broadcast] if you have no access to [EventDispatcher] (though you should have). * * Note: using [Event.broadcast] for [BotEvent] is the same as using [EventDispatcher.broadcast] but the former is slower. So it's recommended to use [EventDispatcher]. * * **Never ever** use [EventChannelToEventDispatcherAdapter.instance] directly. */ internal sealed class EventChannelImpl<E : Event>( baseEventClass: KClass<out E>, defaultCoroutineContext: CoroutineContext = EmptyCoroutineContext ) : EventChannel<E>(baseEventClass, defaultCoroutineContext) { private val eventListeners = EventListeners() // drop any unsubscribed events private val flow = MutableSharedFlow<Event>(extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST) companion object { private val logger by lazy { MiraiLogger.Factory.create(EventChannelImpl::class, "EventChannelImpl") } } /** * Basic entrance for broadcasting an event. */ @InternalEventMechanism suspend fun <E : Event> broadcastEventImpl(event: E): E { require(event is AbstractEvent) { "Events must extend AbstractEvent" } if (event is BroadcastControllable && !event.shouldBroadcast) { return event } event.broadCastLock.withLock { event._intercepted = false callListeners(event) } return event } @InternalEventMechanism private suspend fun callListeners(event: Event) { event as AbstractEvent logEvent(event) eventListeners.callListeners(event) flow.emit(event) } override fun asFlow(): Flow<E> { @Suppress("UNCHECKED_CAST") return flow.asSharedFlow().filter { baseEventClass.isInstance(it) } as Flow<E> } override fun <E : Event> registerListener(eventClass: KClass<out E>, listener: Listener<E>) { eventListeners.addListener(eventClass, listener) } override fun <E : Event> createListener( coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority, listenerBlock: suspend (E) -> ListeningStatus ): Listener<E> { val context = this.defaultCoroutineContext + coroutineContext return SafeListener( parentJob = context[Job], subscriberContext = context, listenerBlock = listenerBlock, concurrencyKind = concurrencyKind, priority = priority ) } private fun isVerboseEvent(event: Event): Boolean { if (SHOW_VERBOSE_EVENT) return false if (event is VerboseEvent) { if (event is BotEvent) { return !event.bot.configuration.isShowingVerboseEventLog } return true } return false } private fun logEvent(event: Event) { if (event is Packet.NoEventLog) return if (event is Packet.NoLog) return if (event is MessageEvent) return // specially handled in [LoggingPacketHandlerAdapter] // if (this is Packet) return@withLock // all [Packet]s are logged in [LoggingPacketHandlerAdapter] if (isVerboseEvent(event)) return if (event is BotEvent) { event.bot.logger.verbose { "Event: $event" } } else { logger.verbose { "Event: $event" } } } override fun context(vararg coroutineContexts: CoroutineContext): EventChannel<E> { val newDefaultContext = coroutineContexts.fold(defaultCoroutineContext) { acc, coroutineContext -> acc + coroutineContext } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return object : DelegateEventChannel<E>(this) { override fun <E : Event> createListener( coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority, listenerBlock: suspend (E) -> ListeningStatus ): Listener<E> { return super.createListener( newDefaultContext + coroutineContext, concurrencyKind, priority, listenerBlock ) } override fun context(vararg coroutineContexts: CoroutineContext): EventChannel<E> { return delegate.context(newDefaultContext, *coroutineContexts) } } } } internal abstract class DelegateEventChannel<BaseEvent : Event>( protected val delegate: EventChannel<BaseEvent>, ) : EventChannel<BaseEvent>(delegate.baseEventClass, delegate.defaultCoroutineContext) { override fun asFlow(): Flow<BaseEvent> = delegate.asFlow() @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") override fun <E : Event> registerListener(eventClass: KClass<out E>, listener: Listener<E>) { delegate.registerListener0(eventClass, listener) } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") override fun <E : Event> createListener( coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority, listenerBlock: suspend (E) -> ListeningStatus ): Listener<E> = delegate.createListener0(coroutineContext, concurrencyKind, priority, listenerBlock) override fun context(vararg coroutineContexts: CoroutineContext): EventChannel<BaseEvent> { return delegate.context(*coroutineContexts) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/event/EventChannelToEventDispatcherAdapter.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import net.mamoe.mirai.event.Event import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass internal class EventChannelToEventDispatcherAdapter<E : Event> private constructor( baseEventClass: KClass<out E>, defaultCoroutineContext: CoroutineContext = EmptyCoroutineContext ) : EventChannelImpl<E>(baseEventClass, defaultCoroutineContext) { companion object { @InternalEventMechanism val instance by lazy { EventChannelToEventDispatcherAdapter(Event::class, EmptyCoroutineContext) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/event/EventListeners.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.launch import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.event.* import net.mamoe.mirai.internal.network.components.EVENT_LAUNCH_UNDISPATCHED import net.mamoe.mirai.utils.* import kotlin.reflect.KClass internal class ListenerRegistry( val listener: Listener<Event>, val type: KClass<out Event>, ) internal class EventListeners { companion object { private val logger by lazy { MiraiLogger.Factory.create(EventListeners::class).withSwitch(systemProp("mirai.event.trace", false)) } } private val map: Map<EventPriority, MutableCollection<ListenerRegistry>> init { val map = EnumMap<EventPriority, MutableCollection<ListenerRegistry>>(EventPriority::class) EventPriority.values().forEach { map[it] = ConcurrentLinkedDeque() } this.map = map } fun clear() { map.forEach { (_, u) -> u.clear() } } operator fun get(priority: EventPriority): MutableCollection<ListenerRegistry> = map[priority] ?: error("Internal error: map[$priority] == null") private val prioritiesExcludedMonitor: Array<EventPriority> = run { EventPriority.values().filter { it != EventPriority.MONITOR }.toTypedArray() } internal suspend fun <E : AbstractEvent> callListeners(event: E) { for (p in prioritiesExcludedMonitor) { val container = get(p) for (registry in container) { if (event.isIntercepted) return if (!registry.type.isInstance(event)) continue val listener = registry.listener process(container, registry, listener, event) } } if (event.isIntercepted) return val container = get(EventPriority.MONITOR) when (container.size) { 0 -> return 1 -> { val registry = container.firstOrNull() ?: return if (!registry.type.isInstance(event)) return process(container, registry, registry.listener, event) } else -> supervisorScope { for (registry in get(EventPriority.MONITOR)) { if (!registry.type.isInstance(event)) continue launch(start = if (EVENT_LAUNCH_UNDISPATCHED) CoroutineStart.UNDISPATCHED else CoroutineStart.DEFAULT) { process(container, registry, registry.listener, event) } } } } } internal fun <E : Event> addListener(eventClass: KClass<E>, listener: Listener<E>) { logger.info { "Add listener: $listener for $eventClass" } val listeners = get(listener.priority) @Suppress("UNCHECKED_CAST") val node = ListenerRegistry(listener as Listener<Event>, eventClass) listeners.add(node) listener.invokeOnCompletion { listeners.remove(node) } } private suspend fun <E : AbstractEvent> process( container: MutableCollection<ListenerRegistry>, registry: ListenerRegistry, listener: Listener<Event>, event: E, ) { logger.info { "Invoke listener: $listener" } when (listener.concurrencyKind) { ConcurrencyKind.LOCKED -> { (listener as SafeListener).lock!!.withLock { if (listener.onEvent(event) == ListeningStatus.STOPPED) { container.remove(registry) } } } ConcurrencyKind.CONCURRENT -> { if (listener.onEvent(event) == ListeningStatus.STOPPED) { container.remove(registry) } } } logger.info { "Finished listener: $listener" } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/event/GlobalEventChannelProviderImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.InternalGlobalEventChannelProvider @InternalEventMechanism internal class GlobalEventChannelProviderImpl : InternalGlobalEventChannelProvider { @InternalEventMechanism val instance = EventChannelToEventDispatcherAdapter.instance @OptIn(InternalEventMechanism::class) override fun getInstance(): EventChannel<Event> = instance } ================================================ FILE: mirai-core/src/commonMain/kotlin/event/SafeListener.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import net.mamoe.mirai.event.* import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.systemProp import kotlin.coroutines.CoroutineContext /** * 包装用户的 [Listener], 增加异常处理, 实现 [ConcurrencyKind]. */ internal class SafeListener<in E : Event> internal constructor( parentJob: Job?, subscriberContext: CoroutineContext, private val listenerBlock: suspend (E) -> ListeningStatus, override val concurrencyKind: ConcurrencyKind, override val priority: EventPriority, private val creationStacktrace: Exception? = if (traceEnabled) Exception() else null ) : Listener<E>, CompletableJob by SupervisorJob(parentJob) { // avoid being cancelled on handling event private val subscriberContext: CoroutineContext = subscriberContext + this // override Job. val lock: Mutex? = when (concurrencyKind) { ConcurrencyKind.LOCKED -> Mutex() else -> null } override fun toString(): String { return if (creationStacktrace != null) { "SafeListener(concurrency=${concurrencyKind}" + ", priority=$priority" + ", subscriberContext=${subscriberContext.minusKey(Job)}" + ", trace=${creationStacktrace.stackTraceToString()})" } else { return "SafeListener(concurrency=${concurrencyKind}" + ", priority=$priority" + ", subscriberContext=${subscriberContext.minusKey(Job)})" // remove this } } @Suppress("unused") override suspend fun onEvent(event: E): ListeningStatus { if (isCompleted || isCancelled) return ListeningStatus.STOPPED if (!isActive) return ListeningStatus.LISTENING return try { // Inherit context. withContext(subscriberContext) { listenerBlock.invoke(event) }.also { if (it == ListeningStatus.STOPPED) this.complete() } } catch (e: Throwable) { // 若监听方使用了 EventChannel.exceptionHandler, 那么它就能处理异常, 否则将只记录异常. val subscriberExceptionHandler = subscriberContext[CoroutineExceptionHandler] if (subscriberExceptionHandler == null) { val logger = if (event is BotEvent) event.bot.logger else logger val subscriberName = subscriberContext[CoroutineName]?.name ?: "<unnamed>" // Bot 协程域有 CoroutineName, mirai-console 也会给插件域加入. val broadcasterName = currentCoroutineContext()[CoroutineName]?.name ?: "<unnamed>" val message = "An exception occurred when processing event. " + "Subscriber scope: '$subscriberName'. " + "Broadcaster scope: '$broadcasterName'" logger.warning(message, e) } else { subscriberExceptionHandler.handleException(subscriberContext, e) } ListeningStatus.LISTENING } } companion object { private val logger by lazy { MiraiLogger.Factory.create(SafeListener::class) } } } private val traceEnabled by lazy { systemProp("mirai.event.trace", true) } ================================================ FILE: mirai-core/src/commonMain/kotlin/event/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event ================================================ FILE: mirai-core/src/commonMain/kotlin/message/ReceiveMessageHandler.kt ================================================ /* * 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 io.ktor.utils.io.core.* import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.OnlineAudioImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol.Companion.UNSUPPORTED_POKE_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol.Companion.UNSUPPORTED_MERGED_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.source.* import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.structureToString import net.mamoe.mirai.utils.toLongUnsigned import net.mamoe.mirai.utils.warning /** * 只在手动构造 [OfflineMessageSource] 时调用 */ internal fun ImMsgBody.SourceMsg.toMessageChainNoSource( bot: Bot, messageSourceKind: MessageSourceKind, groupIdOrZero: Long, refineContext: RefineContext = EmptyRefineContext, facade: MessageProtocolFacade = MessageProtocolFacade ): MessageChain { val elements = this.elems return buildMessageChain(elements.size + 1) { facade.decode(elements, groupIdOrZero, messageSourceKind, bot, this, null) }.cleanupRubbishMessageElements().refineLight(bot, refineContext) } internal suspend fun List<MsgComm.Msg>.toMessageChainOnline( bot: Bot, groupIdOrZero: Long, messageSourceKind: MessageSourceKind, refineContext: RefineContext = EmptyRefineContext, facade: MessageProtocolFacade = MessageProtocolFacade ): MessageChain { return toMessageChain(bot, groupIdOrZero, true, messageSourceKind, facade).refineDeep(bot, refineContext) } internal fun getMessageSourceKindFromC2cCmdOrNull(c2cCmd: Int): MessageSourceKind? { return when (c2cCmd) { 11 -> MessageSourceKind.FRIEND // bot 给其他人发消息 4 -> MessageSourceKind.FRIEND // bot 给自己作为好友发消息 (非 other client) 1 -> MessageSourceKind.GROUP else -> null } } internal fun getMessageSourceKindFromC2cCmd(c2cCmd: Int): MessageSourceKind { return getMessageSourceKindFromC2cCmdOrNull(c2cCmd) ?: error("Could not get source kind from c2cCmd: $c2cCmd") } internal suspend fun MsgComm.Msg.toMessageChainOnline( bot: Bot, refineContext: RefineContext = EmptyRefineContext, facade: MessageProtocolFacade = MessageProtocolFacade, ): MessageChain { val kind = getMessageSourceKindFromC2cCmd(msgHead.c2cCmd) val groupId = when (kind) { MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0 else -> 0 } return listOf(this).toMessageChainOnline( bot, groupId, kind, refineContext.merge(SimpleRefineContext( RefineContextKey.MessageSourceKind to kind, RefineContextKey.GroupIdOrZero to groupId ), false), facade ) } //internal fun List<MsgComm.Msg>.toMessageChainOffline( // bot: Bot, // groupIdOrZero: Long, // messageSourceKind: MessageSourceKind //): MessageChain { // return toMessageChain(bot, groupIdOrZero, false, messageSourceKind).refineLight(bot) //} internal fun List<MsgComm.Msg>.toMessageChainNoSource( bot: Bot, groupIdOrZero: Long, messageSourceKind: MessageSourceKind, refineContext: RefineContext = EmptyRefineContext, ): MessageChain { return toMessageChain(bot, groupIdOrZero, null, messageSourceKind).refineLight(bot, refineContext) } private fun List<MsgComm.Msg>.toMessageChain( bot: Bot, groupIdOrZero: Long, onlineSource: Boolean?, messageSourceKind: MessageSourceKind, facade: MessageProtocolFacade = MessageProtocolFacade, ): MessageChain { try { return toMessageChainImpl(bot, groupIdOrZero, onlineSource, messageSourceKind, facade) } catch (e: Exception) { throw IllegalStateException( "Failed to transform internal message to facade message, msg=${this@toMessageChain.structureToString()}", e ) } } private fun List<MsgComm.Msg>.toMessageChainImpl( bot: Bot, groupIdOrZero: Long, onlineSource: Boolean?, messageSourceKind: MessageSourceKind, facade: MessageProtocolFacade = MessageProtocolFacade, ): MessageChain { val messageList = this val builder = MessageChainBuilder(messageList.sumOf { it.msgBody.richText.elems.size }) val source = if (onlineSource != null) { ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList) } else null if (source != null) builder.add(source) val fromId = source?.fromId ?: firstOrNull()?.msgHead?.fromUin if (fromId == null) { bot.logger.warning { "Cannot determine fromId from message source and msg elements, " + "source: $source, elements: ${this.joinToString(", ")}" } } messageList.forEach { msg -> facade.decode( msg.msgBody.richText.elems, groupIdOrZero, messageSourceKind, bot, builder, msg ) } for (msg in messageList) { msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) } } return builder.build().cleanupRubbishMessageElements() } /** * 接收消息的解析器. 将 [MsgComm.Msg] 转换为对应的 [SingleMessage] * @see joinToMessageChain */ internal 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 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++ } // delete empty plain text removeAll { it is PlainText && it.content.isEmpty() } } fun MessageChain.cleanupRubbishMessageElements(): MessageChain { val builder = MessageChainBuilder(initialSize = count()).also { it.addAll(this) } kotlin.run moveQuoteReply@{ // Move QuoteReply after MessageSource val exceptedQuoteReplyIndex = builder.indexOfFirst { it is MessageSource } + 1 val quoteReplyIndex = builder.indexOfFirst { it is QuoteReply } if (quoteReplyIndex < 1) return@moveQuoteReply if (quoteReplyIndex != exceptedQuoteReplyIndex) { val qr = builder[quoteReplyIndex] builder.removeAt(quoteReplyIndex) builder.add(exceptedQuoteReplyIndex, qr) } } kotlin.run quote@{ val quoteReplyIndex = builder.indexOfFirst { it is QuoteReply } if (quoteReplyIndex >= 0) { // QuoteReply + At + PlainText(space 1) if (quoteReplyIndex < builder.size - 1) { if (builder[quoteReplyIndex + 1] is At) { builder.removeAt(quoteReplyIndex + 1) } if (quoteReplyIndex < builder.size - 1) { val elm = builder[quoteReplyIndex + 1] if (elm is PlainText && elm.content.startsWith(' ')) { if (elm.content.length == 1) { builder.removeAt(quoteReplyIndex + 1) } else { builder[quoteReplyIndex + 1] = PlainText(elm.content.substring(1)) } } } return@quote } } } // TIM audios if (builder.any { it is Audio }) { builder.remove(UNSUPPORTED_VOICE_MESSAGE_PLAIN) } kotlin.run { // VipFace val vipFaceIndex = builder.indexOfFirst { it is VipFace } if (vipFaceIndex >= 0 && vipFaceIndex < builder.size - 1) { val l = builder[vipFaceIndex] as VipFace val text = builder[vipFaceIndex + 1] if (text is PlainText) { if (text.content.length == 4 + (l.count / 10) + l.kind.name.length) { builder.removeAt(vipFaceIndex + 1) } } } } fun removeSuffixText(index: Int, text: PlainText) { if (index >= 0 && index < builder.size - 1) { if (builder[index + 1] == text) { builder.removeAt(index + 1) } } } removeSuffixText(builder.indexOfFirst { it is LongMessageInternal }, UNSUPPORTED_MERGED_MESSAGE_PLAIN) removeSuffixText(builder.indexOfFirst { it is PokeMessage }, UNSUPPORTED_POKE_MESSAGE_PLAIN) builder.compressContinuousPlainText() return builder.asMessageChain() } fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl( filename = fileName.decodeToString(), fileMd5 = fileMd5, fileSize = fileSize.toLongUnsigned(), codec = AudioCodec.fromId(format), url = downPara.decodeToString(), length = time.toLongUnsigned(), originalPtt = this, ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/RefinableMessage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.LightMessageRefiner.refineMessageSource import net.mamoe.mirai.internal.message.flags.InternalFlagOnlyMessage import net.mamoe.mirai.internal.message.source.IncomingMessageSourceInternal import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.safeCast /** * 在接收解析消息后会经过一层转换的消息. * * @see DeepMessageRefiner.refineDeep * @see LightMessageRefiner.refineLight */ internal interface RefinableMessage : SingleMessage { /** * Refine if possible (without suspension), returns self otherwise. * @since 2.6 */ // see #1157 fun tryRefine( bot: Bot, context: MessageChain, refineContext: RefineContext = EmptyRefineContext, ): Message? = this /** * This message [RefinableMessage] will be replaced by return value of [refineLight] */ suspend fun refine( bot: Bot, context: MessageChain, refineContext: RefineContext = EmptyRefineContext, ): Message? = tryRefine(bot, context, refineContext) } internal sealed class MessageRefiner { protected inline fun MessageChain.refineImpl( bot: Bot, refineAction: (message: RefinableMessage) -> Message?, ): MessageChain { val convertLineSeparator = bot.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 = refineAction(singleMessage) 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() } } @Suppress("unused") internal class RefineContextKey<T : Any>( val name: String?, ) { override fun toString(): String { return buildString { append("Key(") name?.also(this@buildString::append) ?: kotlin.run { append('#').append(this@RefineContextKey.hashCode()) } append(')') } } internal companion object { val MessageSourceKind = RefineContextKey<MessageSourceKind>("MessageSourceKind") val FromId = RefineContextKey<Long>("FromId") val GroupIdOrZero = RefineContextKey<Long>("GroupIdOrZero") } } /** * 转换消息时的上下文 */ internal interface RefineContext { operator fun contains(key: RefineContextKey<*>): Boolean operator fun <T : Any> get(key: RefineContextKey<T>): T? fun <T : Any> getNotNull(key: RefineContextKey<T>): T = get(key) ?: error("No such value of `$key`") fun merge(other: RefineContext, override: Boolean): RefineContext fun entries(): Set<Pair<RefineContextKey<*>, Any>> } internal interface MutableRefineContext : RefineContext { operator fun <T : Any> set(key: RefineContextKey<T>, value: T) fun remove(key: RefineContextKey<*>) } internal object EmptyRefineContext : RefineContext { override fun contains(key: RefineContextKey<*>): Boolean = false override fun <T : Any> get(key: RefineContextKey<T>): T? = null override fun merge(other: RefineContext, override: Boolean): RefineContext { return other } override fun entries(): Set<Pair<RefineContextKey<*>, Any>> { return emptySet() } override fun toString(): String { return "EmptyRefineContext" } override fun equals(other: Any?): Boolean { return other === EmptyRefineContext } } @Suppress("UNCHECKED_CAST") internal class SimpleRefineContext( private val delegate: MutableMap<RefineContextKey<*>, Any> = mutableMapOf(), ) : MutableRefineContext { override fun contains(key: RefineContextKey<*>): Boolean = delegate.containsKey(key) override fun <T : Any> get(key: RefineContextKey<T>): T? { return (delegate[key] ?: return null) as T } override fun <T : Any> set(key: RefineContextKey<T>, value: T) { delegate[key] = value } override fun remove(key: RefineContextKey<*>) { delegate.remove(key) } override fun entries(): Set<Pair<RefineContextKey<*>, Any>> { return delegate.entries.map { (k, v) -> k to v }.toSet() } override fun merge(other: RefineContext, override: Boolean): RefineContext { val new = SimpleRefineContext(*entries().toTypedArray()) other.entries().forEach { (key, value) -> if (new[key] == null || override) { new[key as RefineContextKey<Any>] = value } } return new } override fun equals(other: Any?): Boolean { if (other !is RefineContext) return false if (other === this) return true return other.entries() == entries() } } internal fun SimpleRefineContext(vararg elements: Pair<RefineContextKey<*>, Any>): SimpleRefineContext = SimpleRefineContext(elements.toMap().toMutableMap()) /** * 执行不需要 `suspend` 的 refine. 用于 [MessageSource.originalMessage]. */ internal object LightMessageRefiner : MessageRefiner() { /* note: * 不在 refineLight 中处理的原因是 refineMessageSource * 需要的是 **最终处理完成后** 的 MessageChain (即 refineDeep 后的 MessageChain) * * 在 refineLight/RefinableMessage.(try)refine 中直接处理将导致获取不到最终结果导致逻辑错误 */ fun MessageChain.refineMessageSource(): MessageChain { val source = this.sourceOrNull?.safeCast<IncomingMessageSourceInternal>() ?: return this val originalMessage = this source.originalMessageLazy = lazy { originalMessage.filterNot { it is MessageSource }.toMessageChain() // @Suppress("INVISIBLE_MEMBER") // createMessageChainImplOptimized(originalMessage.filterNot { it is MessageSource }) } return this } fun MessageChain.refineLight( bot: Bot, refineContext: RefineContext = EmptyRefineContext, ): MessageChain { return refineImpl(bot) { it.tryRefine(bot, this, refineContext) } } /** * 去除 [MessageChain] 携带的内部标识 * * 用于 [createMessageReceipt] <- `RemoteFile.uploadAndSend` (文件操作API v1) */ fun MessageChain.dropMiraiInternalFlags(): MessageChain { return asSequence().filterNot { it is InternalFlagOnlyMessage }.toMessageChain() } } /** * 执行需要 `suspend` 的 refine. 用于解析到的消息. */ internal object DeepMessageRefiner : MessageRefiner() { suspend fun MessageChain.refineDeep( bot: Bot, refineContext: RefineContext = EmptyRefineContext, ): MessageChain { return refineImpl(bot) { it.refine(bot, this, refineContext) } .refineMessageSource() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/atImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ ================================================ FILE: mirai-core/src/commonMain/kotlin/message/contextualBugReportException.kt ================================================ /* * 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.InvocationKind import kotlin.contracts.contract internal data class ContextualBugReportException( override val message: String, override val cause: Throwable?, ) : IllegalStateException() internal fun contextualBugReportException( context: String, forDebug: String?, e: Throwable? = null, additional: String = "", ): ContextualBugReportException { return ContextualBugReportException( "在 $context 时遇到了意料之中的问题. 请完整复制此日志提交给 mirai: https://github.com/mamoe/mirai/issues/new/choose $additional 调试信息: $forDebug", e, ) } 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) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/FileMessageImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.onEach import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.contact.file.* import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x6d8.GetFileListRspBody import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.utils.cast import kotlin.contracts.contract internal fun FileMessage.checkIsImpl(): FileMessageImpl { contract { returns() implies (this@checkIsImpl is FileMessageImpl) } return this as? FileMessageImpl ?: error("FileMessage must not be implemented manually.") } @Serializable @Suppress("ANNOTATION_ARGUMENT_MUST_BE_CONST") // bug @SerialName(FileMessage.SERIAL_NAME) internal data class FileMessageImpl( override val id: String, @SerialName("internalId") val busId: Int, override val name: String, override val size: Long, @Transient val allowSend: Boolean = false, ) : FileMessage { override val internalId: Int get() = busId override suspend fun toAbsoluteFile(contact: FileSupported): AbsoluteFile? { val result = contact.bot.asQQAndroidBot().network .sendAndExpect(FileManagement.GetFileInfo(contact.bot.asQQAndroidBot().client, contact.id, id, busId)) .toResult("FileMessage.toAbsoluteFile").getOrThrow() if (result.fileInfo == null) return null // Get its parent AbsoluteFolder // This is necessary for properties like creationTime. // Maybe we can optimize it in the future (i.e. make it lazy?) val root = contact.files.root.impl() val folder = if (result.fileInfo.parentFolderId == AbsoluteFolder.ROOT_FOLDER_ID) { root } else { val folders = ArrayList<GetFileListRspBody.Item>() root.impl().getItemsFlow() .filter { it.folderInfo != null } .onEach { folders.add(it) } .firstOrNull { it.folderInfo?.folderId == result.fileInfo.parentFolderId } ?.resolved(root) as AbsoluteFolderImpl? ?: kotlin.run { for (folder in folders) { CommonAbsoluteFolderImpl.getItemsFlow( (contact.bot as QQAndroidBot).client, contact, folder.folderInfo!!.folderId ).firstOrNull { it.folderInfo?.folderId == result.fileInfo.parentFolderId } ?.resolved(root)?.cast<AbsoluteFolderImpl?>()?.let { return@run it } } root } } return folder.createChildFile(result.fileInfo) } override fun toString(): String = "[mirai:file:$name, id=$id, internalId=$busId, size=$size]" } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/LongMessageInternal.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.getMiraiImpl import net.mamoe.mirai.internal.message.RefinableMessage import net.mamoe.mirai.internal.message.RefineContext import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.visitor.ex import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.encodeHtmlEscape 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(), RefinableMessage { override val serviceId: Int get() = 35 override suspend fun refine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message { bot.asQQAndroidBot() val long = Mirai.downloadLongMessage(bot, resId) return MessageOrigin(SimpleServiceMessage(serviceId, content), resId, MessageOriginKind.LONG) + long } override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitLongMessageInternal(this, data) ?: super<AbstractServiceMessage>.accept(visitor, data) } companion object Key : AbstractPolymorphicMessageKey<ServiceMessage, LongMessageInternal>(ServiceMessage, { it.safeCast() }) } // internal runtime value, not serializable @Suppress("RegExpRedundantEscape", "UnnecessaryVariable") internal data class ForwardMessageInternal( override val content: String, val resId: String?, /** * null means top-level. * not null means nested and need [ForwardMessageInternal.MsgTransmits] in [RefineContext] */ val fileName: String?, /** * For light refine before constructing [MessageReceipt]. * See [OutgoingMessageSourceInternal] for more details. */ val origin: ForwardMessage? = null, ) : AbstractServiceMessage(), RefinableMessage { override val serviceId: Int get() = 35 override fun tryRefine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message { return origin ?: this } override suspend fun refine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message { 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") val resId = resId?.takeIf { it.isNotEmpty() } if (fileName != null) kotlin.run nested@{ // nested val transmits = refineContext[MsgTransmits]?.get(fileName) ?: return@nested // Refine failed return MessageOrigin( SimpleServiceMessage(serviceId, content), resId, MessageOriginKind.FORWARD, ) + ForwardMessage( preview = preview, title = title, brief = brief, source = source, summary = summary.trim(), nodeList = getMiraiImpl().run { transmits.toForwardMessageNodes(bot, refineContext) }, ) } // No id and no fileName if (resId == null) { return SimpleServiceMessage(serviceId, content) } return MessageOrigin( SimpleServiceMessage(serviceId, content), resId, MessageOriginKind.FORWARD, ) + ForwardMessage( preview = preview, title = title, brief = brief, source = source, summary = summary.trim(), nodeList = Mirai.downloadForwardMessage(bot, resId), ) } override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitForwardMessageInternal(this, data) ?: super<AbstractServiceMessage>.accept( visitor, data ) } companion object Key : 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("\"", "") } val MsgTransmits = RefineContextKey<Map<String, MsgTransmit.PbMultiMsgNew>>("MsgTransmit") } } private fun String.xmlEnc(): String = encodeHtmlEscape() internal fun RichMessage.Key.forwardMessage( resId: String, fileName: String, forwardMessage: ForwardMessage, ): ForwardMessageInternal = with(forwardMessage) { val template = """ <?xml version="1.0" encoding="utf-8"?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="${brief.take(30).xmlEnc()}" m_resid="$resId" m_fileName="$fileName" tSum="3" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="0"> <item layout="1" advertiser_id="0" aid="0"> <title size="34" maxLines="2" lineSpace="12">${title.take(50).xmlEnc()}</title> ${ when { preview.size > 4 -> { preview.take(3).joinToString("") { """<title size="26" color="#777777" maxLines="2" lineSpace="12">${it.take(50).xmlEnc()}</title>""" } + """<title size="26" color="#777777" maxLines="2" lineSpace="12">...</title>""" } else -> { preview.joinToString("") { """<title size="26" color="#777777" maxLines="2" lineSpace="12">${it.take(50).xmlEnc()}</title>""" } } } } <hr hidden="false" style="0"/> <summary size="26" color="#777777">${summary.take(50).xmlEnc()}</summary> </item> <source name="${source.take(50).xmlEnc()}" icon="" action="" appid="-1"/> </msg> """.trimIndent().replace("\n", " ").trim() return ForwardMessageInternal(template, resId, null, forwardMessage) } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/MarketFaceImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.internal.message.visitor.ex import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.MarketFace import net.mamoe.mirai.message.data.visitor.MessageVisitor @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]" override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitMarketFaceImpl(this, data) ?: super.accept(visitor, data) } companion object { const val SERIAL_NAME = MarketFace.SERIAL_NAME } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/MessageChainBuilderExt.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChainBuilder import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.message.data.visitor.MessageVisitorUnit import net.mamoe.mirai.message.data.visitor.accept import net.mamoe.mirai.utils.replaceAllKotlin internal fun MessageChainBuilder.acceptChildren(visitor: MessageVisitorUnit) { forEach { it.accept(visitor) } } internal fun MessageChainBuilder.transformChildren(visitor: MessageVisitor<MessageChainBuilder, SingleMessage>) { replaceAllKotlin { it.accept(visitor, this) } } internal inline fun MessageChain.transform(trans: (SingleMessage) -> SingleMessage?): MessageChain { return this.mapNotNull(trans).toMessageChain() } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/MessageSourceExt.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.MessageSourceKind internal fun ContactOrBot.inferMessageSourceKind(): MessageSourceKind { return when (this) { is Group -> MessageSourceKind.GROUP is Member -> MessageSourceKind.TEMP is Friend -> MessageSourceKind.FRIEND is Stranger -> MessageSourceKind.STRANGER is Bot -> MessageSourceKind.FRIEND else -> error("Unsupported contact: $this") } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/MultiMsgUploader.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.internal.contact.impl import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.source.MessageSourceInternal import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.LongMsg import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgTransmit import net.mamoe.mirai.internal.network.protocol.packet.chat.MessageValidationData import net.mamoe.mirai.internal.network.protocol.packet.chat.MultiMsg import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.concatAsLong import net.mamoe.mirai.utils.gzip import net.mamoe.mirai.utils.toLongUnsigned import net.mamoe.mirai.utils.use import kotlin.math.absoluteValue import kotlin.random.Random internal open class MultiMsgUploader( val client: QQAndroidClient, val isLong: Boolean, val random: Random, val contact: Contact, val components: ComponentStorage, val senderName: String ) { protected open fun newUploader(): MultiMsgUploader = MultiMsgUploader( client = client, isLong = isLong, random = random, contact = contact, components = components, senderName = senderName ) val mainMsg = mutableListOf<MsgComm.Msg>() val nestedMsgs = mutableMapOf<String, MutableList<MsgComm.Msg>>() init { nestedMsgs["MultiMsg"] = mainMsg } protected open fun newNid(): String { var nid: String do { nid = "${random.nextInt().absoluteValue}" } while (nestedMsgs.containsKey(nid)) return nid } open suspend fun emitMain( nodes: Collection<ForwardMessage.INode>, ) { emit("MultiMsg", nodes) } open suspend fun convertNestedForwardMessage(nestedForward: ForwardMessage, msgChain: MessageChain): MessageChain { suspend fun convertByMessageOrigin(origin: MessageOrigin): MessageChain? { if (origin.kind != MessageOriginKind.FORWARD) return null val resId = origin.resourceId if (resId != null) { val nid = newNid() emit(nid, nestedForward.nodeList) return messageChainOf( RichMessage.forwardMessage( resId = resId, fileName = nid, forwardMessage = nestedForward, ) ) } return null } suspend fun convertByReUpload(): MessageChain { // Upload nested and refine to service msg val nestedMMUploader = newUploader() nestedMMUploader.emitMain(nestedForward.nodeList) val resId = nestedMMUploader.uploadAndReturnResId() val mirror = nestedMMUploader.nestedMsgs mirror.remove("MultiMsg") nestedMsgs.putAll(mirror) val nid = newNid() nestedMsgs[nid] = nestedMMUploader.mainMsg return messageChainOf( RichMessage.forwardMessage( resId = resId, fileName = nid, forwardMessage = nestedForward, ) ) } msgChain.firstIsInstanceOrNull<MessageOrigin>()?.let { origin -> convertByMessageOrigin(origin)?.let { return it } } return convertByReUpload() } open suspend fun emit(id: String, msgs: Collection<ForwardMessage.INode>) { val nds = mutableListOf<MsgComm.Msg>().let { tmp -> nestedMsgs.getOrPut(id) { tmp } } val existsIds = mutableSetOf<Long>() val existsSeqs = mutableSetOf<Int>() class PendingMessage( var seq: Int, var uid: Int, var convertedMessageChain: MessageChain, val msg: ForwardMessage.INode, ) val pendingMessages = mutableListOf<PendingMessage>() var hasMsgSource = false // Step1: Convert message & Get message ids msgs.forEach { msg -> var msgChain = msg.messageChain msgChain[ForwardMessage]?.let { nestedForward -> msgChain = convertNestedForwardMessage(nestedForward, msgChain) } msgChain = components[MessageProtocolFacade].preprocess(contact.impl(), msgChain, components) var seq: Int = -1 var uid: Int = -1 msg.messageChain.sourceOrNull?.let { source -> source as MessageSourceInternal hasMsgSource = true seq = source.sequenceIds.first() uid = source.internalIds.first() } pendingMessages.add( PendingMessage( seq = seq, uid = uid, convertedMessageChain = msgChain, msg = msg ) ) } // Step2: Fix duplicated messages if (hasMsgSource) { pendingMessages.forEach { pm -> if (pm.seq == -1 && pm.uid == -1) return@forEach while (true) { if (existsSeqs.add(pm.seq)) return@forEach pm.seq++ pm.uid = random.nextInt().absoluteValue } } } // Step3: Fill custom messages..... val randSeqStart = random.nextInt().absoluteValue.coerceAtMost( Int.MAX_VALUE - pendingMessages.size - 15405 ).coerceAtLeast(141225) var seqStart = if (hasMsgSource) { val idx = pendingMessages.indexOfFirst { it.seq != -1 } // Assertion: idx != -1 pendingMessages[idx].seq - idx } else randSeqStart pendingMessages.forEach { pm -> if (pm.seq != -1 && pm.uid != -1) { seqStart = pm.seq + 1 } else { pm.seq = seqStart seqStart++ do { // For patch: no duplicated id pm.uid = random.nextInt().absoluteValue } while (!existsIds.add(pm.seq.concatAsLong(pm.uid))) } } // Step4: Verify sequence existsSeqs.clear() var lastSeq = 0 var needPatch = false for (pm in pendingMessages) { if (pm.seq <= lastSeq) { needPatch = true break } lastSeq = pm.seq if (!existsSeqs.add(lastSeq)) { needPatch = true break } } // Step 5: Patch if (needPatch) { existsIds.clear() existsSeqs.clear() var ranSeqStart = randSeqStart for (pm in pendingMessages) { val oldSeq = pm.seq val oldUid = pm.uid pm.seq = ranSeqStart ranSeqStart++ pm.uid = random.nextInt().absoluteValue for (otherpms in pendingMessages) { val quoteReply = otherpms.convertedMessageChain[QuoteReply] ?: continue val srco = quoteReply.source val src = srco as? MessageSourceInternal ?: continue if (src.sequenceIds.first() == oldSeq && src.internalIds.first() == oldUid) { val newSrc = MessageSourceBuilder() .allFrom(srco) .id(pm.seq) .internalId(pm.uid) .time(srco.time) .build(botId = client.uin, kind = srco.kind) otherpms.convertedMessageChain = otherpms.convertedMessageChain + newSrc } } } } val isToGroup = contact is Group // Step6: Convert pendingMessages.forEach { pm -> val msg0 = MsgComm.Msg( msgHead = MsgComm.MsgHead( fromUin = pm.msg.senderId, fromNick = pm.msg.senderName, msgSeq = pm.seq, msgTime = pm.msg.time, msgUid = 0x0100000000000000L or pm.uid.toLongUnsigned(), mutiltransHead = MsgComm.MutilTransHead( status = 0, msgId = 1, ), msgType = if (isToGroup) { 82 // troop } else { 9 // c2c }, c2cCmd = if (isToGroup) 0 else 11, groupInfo = if (isToGroup) MsgComm.GroupInfo( groupCode = contact.id, groupCard = pm.msg.senderName, // Cinnamon ) else null, isSrcMsg = false, ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = MessageProtocolFacade.encode( pm.convertedMessageChain, messageTarget = contact, withGeneralFlags = false, isForward = true ) ) ) ) nds.add(msg0) } } open fun toMessageValidationData(): MessageValidationData { val msgTransmit = MsgTransmit.PbMultiMsgTransmit(msg = mainMsg, pbItemList = nestedMsgs.asSequence().map { (name, msgList) -> MsgTransmit.PbMultiMsgItem( fileName = name, buffer = MsgTransmit.PbMultiMsgNew(msgList).toByteArray(MsgTransmit.PbMultiMsgNew.serializer()) ) }.toList()) val bytes = msgTransmit.toByteArray(MsgTransmit.PbMultiMsgTransmit.serializer()) return MessageValidationData(bytes.gzip()) } open suspend fun uploadAndReturnResId(): String { val data = toMessageValidationData() val response = client.bot.network.sendAndExpect( MultiMsg.ApplyUp.createForGroup( buType = if (isLong) 1 else 2, client = client, messageData = data, dstUin = contact.uin ) ) lateinit var resId: String when (response) { is MultiMsg.ApplyUp.Response.MessageTooLarge -> error( "Internal error: message is too large, but this should be handled before sending. " ) is MultiMsg.ApplyUp.Response.RequireUpload -> { resId = response.proto.msgResid val body = LongMsg.ReqBody( subcmd = 1, platformType = 9, termType = 5, msgUpReq = listOf( LongMsg.MsgUpReq( msgType = 3, // group dstUin = contact.uin, msgId = 0, msgUkey = response.proto.msgUkey, needCache = 0, storeType = 2, msgContent = data.data ) ) ).toByteArray(LongMsg.ReqBody.serializer()) body.toExternalResource().use { resource -> Highway.uploadResourceBdh( bot = client.bot, resource = resource, kind = when (isLong) { true -> ResourceKind.LONG_MESSAGE false -> ResourceKind.FORWARD_MESSAGE }, commandId = 27, initialTicket = response.proto.msgSig ) } } } return resId // this must be initialized, 'lateinit' due to IDE complaint } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/UnsupportedMessageImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder 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.UnsupportedMessage import net.mamoe.mirai.message.data.content import net.mamoe.mirai.utils.copy @SerialName(UnsupportedMessage.SERIAL_NAME) @Serializable(UnsupportedMessageImpl.Serializer::class) internal data class UnsupportedMessageImpl( val structElem: ImMsgBody.Elem, ) : UnsupportedMessage { override val struct: ByteArray by lazy { structElem.toByteArray(ImMsgBody.Elem.serializer()) } override fun toString(): String = content override fun hashCode(): Int { return struct.contentHashCode() } override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is UnsupportedMessageImpl) return false if (other.structElem == this.structElem) return true return other.struct.contentEquals(this.struct) } object Serializer : KSerializer<UnsupportedMessageImpl> { override val descriptor: SerialDescriptor = UnsupportedMessage.Serializer.descriptor.copy(UnsupportedMessage.SERIAL_NAME) override fun deserialize(decoder: Decoder): UnsupportedMessageImpl = UnsupportedMessage.Serializer.deserialize(decoder) as UnsupportedMessageImpl override fun serialize(encoder: Encoder, value: UnsupportedMessageImpl) = UnsupportedMessage.Serializer.serialize(encoder, value) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/audio.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import io.ktor.utils.io.core.* import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.utils.io.ProtoBuf 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.copy import net.mamoe.mirai.utils.isSameType import net.mamoe.mirai.utils.map /** * ## Audio Implementation Overview * * ``` * (api)Audio * | * /------------------\ * (api)OnlineAudio (api)OfflineAudio * | | * | | * (core)OnlineAudioImpl (core)OfflineAudioImpl * ``` * * - [OnlineAudioImpl]: 实现从 [ImMsgBody.Ptt] 解析 * - [OfflineAudioImpl]: 支持用户手动构造 * * ## Equality * * - [OnlineAudio] != [OfflineAudio] * * ## Converting [Audio] to [ImMsgBody.Ptt] * * Always call [Audio.toPtt] */ internal interface AudioPttSupport : MessageContent { // Audio is sealed in mirai-core-api /** * 原协议数据. 用于在接受到其他用户发送的语音时能按照原样发回. */ val originalPtt: ImMsgBody.Ptt? } @Serializable internal class AudioExtraData( @ProtoNumber(1) val ptt: ImMsgBody.Ptt?, ) : ProtoBuf { fun toByteArray(): ByteArray { return Wrapper(CURRENT_VERSION, this).toByteArray(Wrapper.serializer()) } companion object { @Serializable class Wrapper( @ProtoNumber(1) val version: Int, @ProtoNumber(2) val v1: AudioExtraData? = null, ) : ProtoBuf private const val CURRENT_VERSION = 1 fun loadFrom(byteArray: ByteArray?): AudioExtraData? { byteArray ?: return null return kotlin.runCatching { byteArray.loadAs(Wrapper.serializer()).v1 // in this version we only support v1 }.getOrNull() } } } internal fun Audio.toPtt(): ImMsgBody.Ptt { if (this is AudioPttSupport) { this.originalPtt?.let { return it } } return ImMsgBody.Ptt( fileName = this.filename.toByteArray(), fileMd5 = this.fileMd5, boolValid = true, fileSize = this.fileSize.toInt(), fileType = 4, pbReserve = byteArrayOf(0), format = this.codec.id ) } @SerialName(OnlineAudio.SERIAL_NAME) @Serializable(OnlineAudioImpl.Serializer::class) internal class OnlineAudioImpl( override val filename: String, override val fileMd5: ByteArray, override val fileSize: Long, override val codec: AudioCodec, url: String, override val length: Long, override val originalPtt: ImMsgBody.Ptt?, ) : OnlineAudio, AudioPttSupport { private val _url = refineUrl(url) override val extraData: ByteArray? by lazy { AudioExtraData(originalPtt).toByteArray() } override val urlForDownload: String get() = _url.takeIf { it.isNotBlank() } ?: throw UnsupportedOperationException("Could not fetch URL for audio $filename") private val _stringValue: String by lazy { "[mirai:audio:${filename}]" } override fun toString(): String = _stringValue @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (filename != other.filename) return false if (!fileMd5.contentEquals(other.fileMd5)) return false if (fileSize != other.fileSize) return false if (_url != other._url) return false if (codec != other.codec) return false if (length != other.length) return false if (originalPtt != other.originalPtt) return false return true } override fun hashCode(): Int { var result = super.hashCode() result = 31 * result + filename.hashCode() result = 31 * result + fileMd5.contentHashCode() result = 31 * result + fileSize.hashCode() result = 31 * result + _url.hashCode() result = 31 * result + codec.hashCode() result = 31 * result + length.hashCode() result = 31 * result + originalPtt.hashCode() return result } companion object { fun refineUrl(url: String) = when { url.isBlank() -> "" url.startsWith("http") -> url url.startsWith("/") -> "$DOWNLOAD_URL$url" else -> "$DOWNLOAD_URL/$url" } @Suppress("HttpUrlsUsage") const val DOWNLOAD_URL = "http://grouptalk.c2c.qq.com" } object Serializer : KSerializer<OnlineAudioImpl> by Surrogate.serializer().map( resultantDescriptor = Surrogate.serializer().descriptor, deserialize = { OnlineAudioImpl( filename = filename, fileMd5 = fileMd5, fileSize = fileSize, url = urlForDownload, codec = codec, length = length, originalPtt = AudioExtraData.loadFrom(extraData)?.ptt ) }, serialize = { Surrogate( filename = filename, fileMd5 = fileMd5, fileSize = fileSize, urlForDownload = urlForDownload, codec = codec, length = length, extraData = extraData ) } ) { @Serializable @SerialName(OnlineAudio.SERIAL_NAME) private class Surrogate( override val filename: String, override val fileMd5: ByteArray, override val fileSize: Long, override val codec: AudioCodec, override val length: Long, override val extraData: ByteArray?, override val urlForDownload: String, ) : OnlineAudio { override fun toString(): String { return "Surrogate(filename='$filename', fileMd5=${fileMd5.contentToString()}, fileSize=$fileSize, codec=$codec, length=$length, extraData=${extraData.contentToString()}, urlForDownload='$urlForDownload')" } } } } @SerialName(OfflineAudio.SERIAL_NAME) @Serializable(OfflineAudioImpl.Serializer::class) internal class OfflineAudioImpl( override val filename: String, override val fileMd5: ByteArray, override val fileSize: Long, override val codec: AudioCodec, override val originalPtt: ImMsgBody.Ptt?, ) : OfflineAudio, AudioPttSupport { constructor( filename: String, fileMd5: ByteArray, fileSize: Long, codec: AudioCodec, extraData: ByteArray?, ) : this(filename, fileMd5, fileSize, codec, AudioExtraData.loadFrom(extraData)?.ptt) override val extraData: ByteArray? by lazy { AudioExtraData(originalPtt).toByteArray() } private val _stringValue: String by lazy { "[mirai:audio:${filename}]" } override fun toString(): String = _stringValue override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (filename != other.filename) return false if (!fileMd5.contentEquals(other.fileMd5)) return false if (fileSize != other.fileSize) return false if (codec != other.codec) return false if (originalPtt != other.originalPtt) return false return true } override fun hashCode(): Int { var result = filename.hashCode() result = 31 * result + fileMd5.contentHashCode() result = 31 * result + fileSize.hashCode() result = 31 * result + codec.hashCode() result = 31 * result + originalPtt.hashCode() return result } object Serializer : KSerializer<OfflineAudioImpl> by Surrogate.serializer().map( resultantDescriptor = Surrogate.serializer().descriptor, deserialize = { OfflineAudioImpl( filename = filename, fileMd5 = fileMd5, fileSize = fileSize, codec = codec, extraData = extraData, ) }, serialize = { Surrogate( filename = filename, fileMd5 = fileMd5, fileSize = fileSize, codec = codec, extraData = extraData, ) } ) { @Serializable @SerialName(OfflineAudio.SERIAL_NAME) private class Surrogate( override val filename: String, override val fileMd5: ByteArray, override val fileSize: Long, override val codec: AudioCodec, override val extraData: ByteArray?, ) : OfflineAudio { override fun toString(): String { return "OfflineAudio(filename='$filename', fileMd5=${fileMd5.contentToString()}, fileSize=$fileSize, codec=$codec, extraData=${extraData.contentToString()})" } } } } @PublishedApi internal class OfflineAudioFactoryImpl : OfflineAudio.Factory { override fun create( filename: String, fileMd5: ByteArray, fileSize: Long, codec: AudioCodec, extraData: ByteArray? ): OfflineAudio = OfflineAudioImpl(filename, fileMd5, fileSize, codec, extraData) } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/lightApp.kt ================================================ /* * 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.data import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.* import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.internal.message.RefinableMessage import net.mamoe.mirai.internal.message.RefineContext import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.map import net.mamoe.mirai.utils.safeCast internal data class LightAppInternal( override val content: String, ) : RichMessage, RefinableMessage { companion object Key : AbstractPolymorphicMessageKey<RichMessage, LightAppInternal>(RichMessage, { it.safeCast() }) override fun tryRefine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message { val contentJson = runCatching { json.parseToJsonElement(content).jsonObject }.getOrNull() ?: return LightApp(content) val struct = contentJson.tryDeserialize() ?: return LightApp(content) return lightRefine(struct, contentJson) ?: LightApp(content) } private fun lightRefine(struct: LightAppStruct, contentJson: JsonObject): Message? { struct.run { if (meta.music != null) { MusicKind.values().find { it.appId == meta.music.appid }?.let { musicType -> meta.music.run { return MessageOrigin( LightApp(content), null, MessageOriginKind.MUSIC_SHARE ) + MusicShare( kind = musicType, title = title, summary = desc, jumpUrl = jumpUrl, pictureUrl = preview, musicUrl = musicUrl, brief = prompt ) } } } } return null } override suspend fun refine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message? { val contentJson = runCatching { json.parseToJsonElement(content).jsonObject }.getOrNull() ?: return LightApp(content) val struct = contentJson.tryDeserialize() ?: return LightApp(content) if (struct.app == "com.tencent.multimsg") { runCatching { json.decodeFromJsonElement( LightAppStruct.Meta.MultiMsgDetail.serializer(), contentJson["meta"]!!.jsonObject["detail"]!! ) }.onSuccess { detail -> return MessageOrigin( LightApp(content), detail.resId, MessageOriginKind.FORWARD, ) + ForwardMessage( preview = listOf(), // FIXME preview with LightApp title = detail.source.trim(), brief = struct.prompt.trim(), source = detail.source.trim(), summary = detail.summary.trim(), nodeList = Mirai.downloadForwardMessage(bot, detail.resId), ) }.onFailure { err -> bot.logger.warning("Exception when refining forward message", err) } } return lightRefine(struct, contentJson) ?: LightApp(content) } } private val json = Json { ignoreUnknownKeys = true isLenient = true } private fun JsonElement.tryDeserialize(): LightAppStruct? { return kotlin.runCatching { json.decodeFromJsonElement(LightAppStruct.serializer(), this) }.getOrNull() } /* EXAMPLE LightAppStruct for MusicShare { "app": "com.tencent.structmsg", "config": { "autosize": true, "ctime": 1611339208, "forward": true, "token": "1f27c2b5687e0320549992a4652c8465", "type": "normal" }, "desc": "音乐", "extra": { "app_type": 1, "appid": 100495085, // NeteaseCloudMusic "uin": 123456789 // qq uin }, "meta": { "music": { "action": "", "android_pkg_name": "", "app_type": 1, "appid": 100495085, "desc": "rinahamu/Yunomi", "jumpUrl": "http://music.163.com/song/1338728297/?userid=324076307", "musicUrl": "http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307", "preview": "http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg", "sourceMsgId": "0", "source_icon": "", "source_url": "", "tag": "网易云音乐", "title": "ファッション" } }, "prompt": "[分享]ファッション", "ver": "0.0.0.1", "view": "music" } */ /* EXAMPLE LightAppStruct for ForwardMessage { "app": "com.tencent.multimsg", "desc": "[聊天记录]", "bizsrc": "", "view": "contact", "ver": "0.0.0.5", "prompt": "[聊天记录]", "appID": "", "sourceName": "", "actionData": "", "actionData_A": "", "sourceUrl": "", "meta": { "detail": { "news": [ { "text": "纤绫·洛雨: [动画表情]" }, { "text": "纤绫·洛雨: [图片]" } ], "uniseq": "7238251206428406430", "resid": "...", "summary": "查看2条转发消息", "source": "群聊的聊天记录" } }, "config": { "round": 1, "forward": 1, "autosize": 1, "type": "normal", "width": 300 }, "text": "", "sourceAd": "", "extra": "{\"tsum\":2,\"filename\":\"7238251206428406430\"}" } */ @Serializable internal data class LightAppStruct( @SerialName("app") val app: String = "", @SerialName("config") val config: Config = Config(), @SerialName("desc") val desc: String = "", // @SerialName("extra") // val extra: Extra = Extra(), @SerialName("meta") val meta: Meta = Meta(), @SerialName("prompt") val prompt: String = "", @SerialName("ver") val ver: String = "", @SerialName("view") val view: String = "", ) { @Serializable data class Config( @SerialName("autosize") @Serializable(BadBooleanSerializer::class) val autosize: Boolean = false, @SerialName("ctime") val ctime: Long = 0, @SerialName("forward") @Serializable(BadBooleanSerializer::class) val forward: Boolean = false, @SerialName("token") val token: String = "", @SerialName("type") val type: String = "", ) @Serializable data class Extra( @SerialName("app_type") val appType: Long = 0, @SerialName("appid") val appid: Long = 0, @SerialName("uin") val uin: Long = 0, ) @Serializable data class Meta( @SerialName("music") val music: Music? = null, ) { @Serializable data class Music( @SerialName("action") val action: String = "", @SerialName("android_pkg_name") val androidPkgName: String = "", @SerialName("app_type") val appType: Long = 0, @SerialName("appid") val appid: Long = 0, @SerialName("desc") val desc: String = "", @SerialName("jumpUrl") val jumpUrl: String = "", @SerialName("musicUrl") val musicUrl: String = "", @SerialName("preview") val preview: String = "", @SerialName("source_icon") val sourceIcon: String = "", @SerialName("sourceMsgId") val sourceMsgId: String = "", @SerialName("source_url") val sourceUrl: String = "", @SerialName("tag") val tag: String = "", @SerialName("title") val title: String = "", ) @Serializable data class MultiMsgDetail( @SerialName("uniseq") val fileName: String, @SerialName("resid") val resId: String, @SerialName("summary") val summary: String = "", @SerialName("source") val source: String = "", ) } } private object BadBooleanSerializer : KSerializer<Boolean> by JsonPrimitive.serializer().map( JsonPrimitive.serializer().descriptor, deserialize = { prime -> prime.booleanOrNull?.let { return@map it } prime.intOrNull?.let { return@map it != 0 } return@map prime.content.toBoolean() }, serialize = { JsonPrimitive(it) } ) ================================================ FILE: mirai-core/src/commonMain/kotlin/message/data/shortVideo.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.getMember import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.message.RefinableMessage import net.mamoe.mirai.internal.message.RefineContext import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.packet.chat.video.PttCenterSvr import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.toUHexString /** * receive from pipeline and refine to [OnlineShortVideoImpl] */ internal class OnlineShortVideoMsgInternal( private val videoFile: ImMsgBody.VideoFile ) : RefinableMessage { override fun tryRefine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message? { return null } override suspend fun refine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message? { bot.asQQAndroidBot() val sourceKind = refineContext[RefineContextKey.MessageSourceKind] ?: return null val fromId = refineContext[RefineContextKey.FromId] ?: return null val groupId = refineContext[RefineContextKey.GroupIdOrZero] ?: return null val contact = when (sourceKind) { MessageSourceKind.FRIEND -> bot.getFriend(fromId) ?: error("Cannot find friend $fromId.") MessageSourceKind.GROUP -> bot.getGroup(groupId) ?: error("Cannot find group $groupId.") else -> return null // ignore processing stranger's video message } val sender = when (sourceKind) { MessageSourceKind.FRIEND -> bot.getFriend(fromId) ?: error("Cannot find friend $fromId.") MessageSourceKind.GROUP -> { val group = bot.getGroup(groupId) ?: error("Cannot find group $groupId.") group.getMember(fromId) ?: error("Cannot find member $fromId of group $groupId.") } else -> return null // ignore processing stranger's video message } val shortVideoDownloadReq = bot.network.sendAndExpect( PttCenterSvr.ShortVideoDownReq( bot.client, contact, sender, videoFile.fileUuid.decodeToString(), videoFile.fileMd5 ) ) if (shortVideoDownloadReq !is PttCenterSvr.ShortVideoDownReq.Response.Success) throw IllegalStateException("Failed to query short video download attributes.") if (!shortVideoDownloadReq.fileMd5.contentEquals(videoFile.fileMd5)) throw IllegalStateException( "Queried short video download attributes doesn't match the requests. " + "message provides: ${videoFile.fileMd5.toUHexString("")}, " + "queried result: ${shortVideoDownloadReq.fileMd5.toUHexString("")}" ) val format = ShortVideoProtocol.FORMAT .firstOrNull { it.second == videoFile.fileFormat }?.first ?: ExternalResource.DEFAULT_FORMAT_NAME return OnlineShortVideoImpl( videoFile.fileUuid.decodeToString(), shortVideoDownloadReq.fileMd5, videoFile.fileName.decodeToString(), videoFile.fileSize.toLong(), format, shortVideoDownloadReq.urlV4, ShortVideoThumbnail( videoFile.thumbFileMd5, videoFile.thumbFileSize.toLong(), videoFile.thumbWidth, videoFile.thumbHeight ) ) } override fun toString(): String { return "OnlineShortVideoMsgInternal(videoElem=$videoFile)" } override fun contentToString(): String { return "[视频元数据]" } } @Serializable internal data class ShortVideoThumbnail( val md5: ByteArray, val size: Long, val width: Int?, val height: Int?, ) { override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as ShortVideoThumbnail if (!md5.contentEquals(other.md5)) return false if (size != other.size) return false if (width != other.width) return false if (height != other.height) return false return true } override fun hashCode(): Int { var result = md5.contentHashCode() result = 31 * result + size.hashCode() result = 31 * result + (width ?: 0) result = 31 * result + (height ?: 0) return result } } internal abstract class AbstractShortVideoWithThumbnail : ShortVideo { abstract val thumbnail: ShortVideoThumbnail } @Suppress("DuplicatedCode") @SerialName(OnlineShortVideo.SERIAL_NAME) @Serializable internal class OnlineShortVideoImpl( override val videoId: String, override val fileMd5: ByteArray, override val filename: String, override val fileSize: Long, override val fileFormat: String, override val urlForDownload: String, override val thumbnail: ShortVideoThumbnail ) : OnlineShortVideo, AbstractShortVideoWithThumbnail() { override fun toString(): String { return "[mirai:shortvideo:$videoId, videoName=$filename.$fileFormat, videoMd5=${fileMd5.toUHexString("")}, " + "videoSize=${fileSize}, thumbnailMd5=${thumbnail.md5.toUHexString("")}, thumbnailSize=${thumbnail.size}]" } override fun contentToString(): String { return "[视频]" } override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as OnlineShortVideoImpl if (videoId != other.videoId) return false if (!fileMd5.contentEquals(other.fileMd5)) return false if (filename != other.filename) return false if (fileSize != other.fileSize) return false if (fileFormat != other.fileFormat) return false if (urlForDownload != other.urlForDownload) return false if (thumbnail != other.thumbnail) return false return true } override fun hashCode(): Int { var result = videoId.hashCode() result = 31 * result + fileMd5.contentHashCode() result = 31 * result + filename.hashCode() result = 31 * result + fileSize.hashCode() result = 31 * result + fileFormat.hashCode() result = 31 * result + urlForDownload.hashCode() result = 31 * result + thumbnail.hashCode() return result } } @Serializable internal class OfflineShortVideoImpl( override val videoId: String, override val filename: String, override val fileMd5: ByteArray, override val fileSize: Long, override val fileFormat: String, override val thumbnail: ShortVideoThumbnail ) : OfflineShortVideo, AbstractShortVideoWithThumbnail() { /** * offline short video uses */ override fun toString(): String { return "[mirai:shortvideo:$videoId, videoName=$filename.$fileFormat, videoMd5=${fileMd5.toUHexString("")}, " + "videoSize=${fileSize}, thumbnailMd5=${thumbnail.md5.toUHexString("")}, thumbnailSize=${thumbnail.size}]" } override fun contentToString(): String { return "[视频]" } override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false other as OfflineShortVideoImpl if (videoId != other.videoId) return false if (filename != other.filename) return false if (!fileMd5.contentEquals(other.fileMd5)) return false if (fileSize != other.fileSize) return false if (fileFormat != other.fileFormat) return false if (thumbnail != other.thumbnail) return false return true } override fun hashCode(): Int { var result = videoId.hashCode() result = 31 * result + filename.hashCode() result = 31 * result + fileMd5.contentHashCode() result = 31 * result + fileSize.hashCode() result = 31 * result + fileFormat.hashCode() result = 31 * result + thumbnail.hashCode() return result } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/faceImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ ================================================ FILE: mirai-core/src/commonMain/kotlin/message/flags/InternalFlagOnlyMessage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.internal.message.flags import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.internal.message.visitor.ex import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.safeCast /** * Ignore on transformation */ internal sealed interface InternalFlagOnlyMessage : MessageMetadata internal sealed interface ForceAs : InternalFlagOnlyMessage, ConstrainSingle { companion object Key : AbstractMessageKey<ForceAs>({ it.safeCast() }) } /** * 内部 flag, 放入 chain 强制作为 long 发送 */ internal object ForceAsLongMessage : ForceAs, AbstractPolymorphicMessageKey<ForceAs, ForceAsLongMessage>(ForceAs, { it.safeCast() }) { override val key: MessageKey<ForceAsLongMessage> get() = this override fun toString(): String = "" override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitForceAsLongMessage(this, data) ?: super.accept(visitor, data) } } /** * 内部 flag, 放入 chain 强制作为 fragmented 发送 */ internal object ForceAsFragmentedMessage : ForceAs, AbstractPolymorphicMessageKey<ForceAs, ForceAsFragmentedMessage>(ForceAs, { it.safeCast() }) { override val key: MessageKey<ForceAsFragmentedMessage> get() = this override fun toString(): String = "" override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitForceAsFragmentedMessage(this, data) ?: super.accept(visitor, data) } } /** * 强制不发 long */ internal object DontAsLongMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, AbstractMessageKey<DontAsLongMessage>({ it.safeCast() }) { override val key: MessageKey<DontAsLongMessage> get() = this override fun toString(): String = "" override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitDontAsLongMessage(this, data) ?: super<InternalFlagOnlyMessage>.accept(visitor, data) } } internal object IgnoreLengthCheck : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, AbstractMessageKey<IgnoreLengthCheck>({ it.safeCast() }) { override val key: MessageKey<IgnoreLengthCheck> get() = this override fun toString(): String = "" override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitIgnoreLengthCheck(this, data) ?: super<InternalFlagOnlyMessage>.accept(visitor, data) } } /** * Skip broadcasting events. Used for [Contact.sendMessage] */ internal sealed interface SkipEventBroadcast : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage { override val key: MessageKey<SkipEventBroadcast> get() = Companion override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitSkipEventBroadcast(this, data) ?: super<InternalFlagOnlyMessage>.accept( visitor, data ) } companion object : AbstractMessageKey<SkipEventBroadcast>({ it.safeCast() }), SkipEventBroadcast { // instance override fun toString(): String = "" } } internal sealed interface AllowSendFileMessage : MessageMetadata, ConstrainSingle, InternalFlagOnlyMessage, SkipEventBroadcast { override val key: MessageKey<AllowSendFileMessage> get() = Companion override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitAllowSendFileMessage(this, data) ?: super<InternalFlagOnlyMessage>.accept( visitor, data ) } companion object : AbstractMessageKey<AllowSendFileMessage>({ it.safeCast() }), AllowSendFileMessage { override fun toString(): String = "" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/AbstractImage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.utils.MiraiLogger /** * 所有 [Image] 实现的基类. */ // moved from mirai-core-api since 2.11 internal sealed class AbstractImage : Image { private val _stringValue: String? by lazy(LazyThreadSafetyMode.NONE) { "[mirai:image:$imageId, width=$width, height=$height, size=$size, type=$imageType, isEmoji=$isEmoji]" } override val size: Long get() = 0L override val width: Int get() = 0 override val height: Int get() = 0 final override fun toString(): String = _stringValue!! final override fun contentToString(): String = if (isEmoji) { "[动画表情]" } else { "[图片]" } override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:image:").append(imageId).append("]") } final override fun hashCode(): Int = imageId.hashCode() final override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is Image) return false return this.imageId == other.imageId && this.width == other.width && this.height == other.height && this.isEmoji == other.isEmoji && this.imageType == other.imageType && this.size == other.size } } /** * 好友图片 * * [imageId] 形如 `/f8f1ab55-bf8e-4236-b55e-955848d7069f` (37 长度) 或 `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` (54 长度) */ // NotOnlineImage // moved from mirai-core-api since 2.11 internal sealed class FriendImage : AbstractImage() /** * 群图片. * * @property imageId 形如 `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.ext` (ext系扩展名) * @see Image 查看更多说明 */ // CustomFace // moved from mirai-core-api since 2.11 internal sealed class GroupImage : AbstractImage() // NT Image internal sealed class NewTechImage : AbstractImage() private val imageLogger: MiraiLogger by lazy { MiraiLogger.Factory.create(Image::class, "Image") } internal val Image.Key.logger get() = imageLogger ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/ImageDecoder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import io.ktor.utils.io.core.* import io.ktor.utils.io.errors.* import net.mamoe.mirai.message.data.ImageType import net.mamoe.mirai.utils.* //SOF0-SOF3 SOF5-SOF7 SOF9-SOF11 SOF13-SOF15 Segment // (0xC4, 0xC8 and 0xCC not included due to is not an SOF) private val JPG_SOF_RANGE = listOf( 0xC0..0xC3, 0xC5..0xC7, 0xC9..0xCB, 0xCD..0xCF ) // https://docs.fileformat.com/image/jpeg/ // http://www.vip.sugovica.hu/Sardi/kepnezo/JPEG%20File%20Layout%20and%20Format.htm private fun Input.getJPGImageInfo(): ImageInfo { require(readBytes(2).contentEquals(byteArrayOf(0xFF.toByte(), 0xD8.toByte()))) { "It's not a valid jpg file" } //0xFF Segment Start while (readByte() == 0xFF.toByte()) { val type = readByte().toIntUnsigned() //Find SOF if (JPG_SOF_RANGE.any { it.contains(type) }) { //Length discardExact(2) //Data precision discardExact(1) val height = readShort().toInt() val width = readShort().toInt() return ImageInfo(width = width, height = height, imageType = ImageType.JPG) } else { when (type) { //SOS Segment, header is ended 0xDA -> break //0x00 (Byte alignment) and 0x01 (TEM) in 0x00..0x01 -> continue //RST[0-7] no length and content, skip in 0xD0..0xD7 -> continue //Normal segment, Skipped size=Segment Length - 2 (Length data itself) else -> discardExact(readShort().toIntUnsigned() - 2) } } } throw IllegalArgumentException("It's not a valid jpg file, failed to find an SOF segment") } private fun Input.getBMPImageInfo(): ImageInfo { require(readString(2) == "BM") { "It's not a valid bmp file" } //========== //FILE HEADER //========== //Size discardExact(4) //Reserve 2*2bytes discardExact(4) //Offset for image data discardExact(4) //========== //INFO HEADER //========== //Size discardExact(4) return ImageInfo( width = readIntLittleEndian(), height = readIntLittleEndian(), imageType = ImageType.BMP ) } private fun Input.getPNGImageInfo(): ImageInfo { require( readBytes(8).contentEquals( byteArrayOf( 0x89.toByte(), 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ) ) ) { "It's not a valid png file" } //Chunk length discardExact(4) //Chunk type var type = readString(4) //First chunk must be IHDR require(type == "IHDR") { "It's not a valid png file, First chunk must be IHDR" } val width = readInt() val height = readInt() //Skip to next chunk //Bit depth (1 byte) + color type (1 byte) // + compression method (1 byte) + filter method (1 byte) // + interlace method (1 byte) + CRC(4 bytes) = 9 bytes discardExact(9) //Chunk length discardExact(4) //Chunk type type = readString(4) return ImageInfo( width = width, height = height, //Correct the image type //If is apng, it has to be an acTL chunk imageType = if (type == "acTL") { ImageType.APNG } else { ImageType.PNG } ) } private fun Input.getGIFImageInfo(): ImageInfo { require(readString(6).run { startsWith("GIF") && endsWith("a") }) { "It's not a valid gif file" } return ImageInfo( width = readShortLittleEndian().toInt(), height = readShortLittleEndian().toInt(), imageType = ImageType.GIF ) } @Throws(IOException::class, IllegalArgumentException::class) internal fun ExternalResource.calculateImageInfo(): ImageInfo { //Preload val imageType = ImageType.match(formatName) return input().withUse { when (imageType) { ImageType.JPG -> getJPGImageInfo() ImageType.BMP -> getBMPImageInfo() ImageType.GIF -> getGIFImageInfo() ImageType.PNG, ImageType.APNG -> getPNGImageInfo() else -> { throw IllegalArgumentException( "Unsupported image type (${formatName}) for ExternalResource ${this@calculateImageInfo}, " + "considering use gif/png/bmp/jpg format. " + "image header: ${readBytesOf(max = 30).toUHexString()}" ) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/ImageInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.ImageType import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.systemProp internal data class ImageInfo(val width: Int = 0, val height: Int = 0, val imageType: ImageType = ImageType.UNKNOWN) /* * ImgType: * JPG: 1000 * PNG: 1001 * WEBP: 1002 * BMP: 1005 * GIG: 2000 // gig? gif? * APNG: 2001 * SHARPP: 1004 */ internal val UNKNOWN_IMAGE_TYPE_PROMPT_ENABLED = systemProp("mirai.unknown.image.type.logging", false) internal fun getImageTypeById(id: Int): ImageType? { return if (id == 2001) { ImageType.APNG } else { ImageType.matchOrNull(getImageType(id)) } } internal fun getIdByImageType(imageType: ImageType): Int { return when (imageType) { ImageType.JPG -> 1000 ImageType.PNG -> 1001 //ImageType.WEBP -> 1002 //Unsupported by pc client ImageType.BMP -> 1005 ImageType.GIF -> 2000 ImageType.APNG -> 2001 //default to jpg else -> 1000 } } internal fun getImageType(id: Int): String { return when (id) { 1000 -> "jpg" 1001 -> "png" //1002 -> "webp" //Unsupported by pc client 1005 -> "bmp" 2000, 3, 4 -> "gif" //apng 2001 -> "png" else -> { if (UNKNOWN_IMAGE_TYPE_PROMPT_ENABLED) { Image.logger.debug( "Unknown image id: $id. Stacktrace:", Exception() ) } ExternalResource.DEFAULT_FORMAT_NAME } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/ImageUrlAware.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import net.mamoe.mirai.Bot import net.mamoe.mirai.message.data.Image internal sealed interface ImageUrlAware : Image internal interface ConstOriginUrlAware : ImageUrlAware { val originUrl: String } internal interface DeferredOriginUrlAware : ImageUrlAware { fun getUrl(bot: Bot): String } internal interface SuspendDeferredOriginUrlAware : ImageUrlAware { suspend fun getUrl(bot: Bot): String } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/InternalImageProtocolImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.User import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 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.utils.ImagePatcher import net.mamoe.mirai.internal.utils.withCache import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.ImageType import net.mamoe.mirai.message.data.InternalImageProtocol import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.toUHexString internal class InternalImageProtocolImpl : InternalImageProtocol { /** * Test Notes: * * - 查图片只需要 md5 和 size * - 上传给群的图片可以通过 GroupPicUp(groupCode=user.id) 或 OffPicUp(dstUin=user.id) 查询 * - 上传给好友的图片可以通过 GroupPicUp(groupCode=group.id) 或 OffPicUp(dstUin=group.id) 查询 */ interface ImageUploadedChecker<C : Contact?> { suspend fun isUploaded( bot: QQAndroidBot, context: C, md5: ByteArray, type: ImageType, size: Long, width: Int, height: Int ): Boolean companion object { val checkers = mapOf( Group::class to ImageUploadedCheckerGroup(), User::class to ImageUploadedCheckerUser(), null to ImageUploadedCheckerFallback() ) } } class ImageUploadedCheckerGroup : ImageUploadedChecker<Group> { override suspend fun isUploaded( bot: QQAndroidBot, context: Group, md5: ByteArray, type: ImageType, size: Long, width: Int, height: Int ): Boolean { val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect( ImgStore.GroupPicUp( bot.client, uin = bot.id, groupCode = context.id, md5 = md5, size = size, filename = "${md5.toUHexString("")}.${type.formatName}", picWidth = width, picHeight = height, picType = getIdByImageType(type), ) ) return response is ImgStore.GroupPicUp.Response.FileExists } } class ImageUploadedCheckerUser : ImageUploadedChecker<User> { override suspend fun isUploaded( bot: QQAndroidBot, context: User, md5: ByteArray, type: ImageType, size: Long, width: Int, height: Int ): Boolean { val resp = bot.network.sendAndExpect( LongConn.OffPicUp( bot.client, Cmd0x352.TryUpImgReq( buType = 1, srcUin = bot.id, dstUin = context.id, fileMd5 = md5, fileSize = size, imgWidth = width, imgHeight = height, imgType = getIdByImageType(type), fileName = "${md5.toUHexString("")}.${type.formatName}", //For gif, using not original imgOriginal = (type != ImageType.GIF), buildVer = bot.client.buildVer, ), ) ) return resp is LongConn.OffPicUp.Response.FileExists } } class ImageUploadedCheckerFallback : ImageUploadedChecker<Nothing?> { override suspend fun isUploaded( bot: QQAndroidBot, context: Nothing?, md5: ByteArray, type: ImageType, size: Long, width: Int, height: Int ): Boolean { val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect( ImgStore.GroupPicUp( bot.client, uin = bot.id, groupCode = 1, md5 = md5, size = size, filename = "${md5.toUHexString("")}.${type.formatName}", picWidth = width, picHeight = height, picType = getIdByImageType(type), ) ) return response is ImgStore.GroupPicUp.Response.FileExists } } fun findExistImageByCache(imageId: String): Image? { Bot.instancesSequence.forEach { existsBot -> runCatching { val patcher = existsBot.asQQAndroidBot().components[ImagePatcher] patcher.findCacheByImageId(imageId)?.withCache { cache -> val rsp = cache.cacheOGI.value0 if (rsp != null) return rsp } } } return null } override fun createImage( imageId: String, size: Long, type: ImageType, width: Int, height: Int, isEmoji: Boolean ): Image { return when { imageId matches Image.IMAGE_ID_REGEX -> { if (size == 0L && width == 0 && height == 0) { findExistImageByCache(imageId)?.let { return it } } OfflineGroupImage(imageId, width, height, size, type, isEmoji) } imageId matches Image.IMAGE_RESOURCE_ID_REGEX_1 -> { OfflineFriendImage(imageId, width, height, size, type, isEmoji) } imageId matches Image.IMAGE_RESOURCE_ID_REGEX_2 -> { OfflineFriendImage(imageId, width, height, size, type, isEmoji) } else -> @Suppress("INVISIBLE_MEMBER") throw IllegalArgumentException("Illegal imageId: $imageId. ${net.mamoe.mirai.message.data.ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE}") } } override suspend fun isUploaded( bot: Bot, md5: ByteArray, size: Long, context: Contact?, type: ImageType, width: Int, height: Int ): Boolean { val checker = findChecker(context) ?: return false checker.cast<ImageUploadedChecker<Contact?>>() return checker.isUploaded(bot.asQQAndroidBot(), context.cast<Contact?>(), md5, type, size, width, height) } fun findChecker(context: Contact?) = ImageUploadedChecker.checkers.asSequence() .find { bothNull(it.key, context) || it.key?.isInstance(context) == true }?.value private fun bothNull(a: Any?, b: Any?) = a == null && b == null } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/InternalShortVideoProtocolImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import net.mamoe.mirai.internal.message.data.OfflineShortVideoImpl import net.mamoe.mirai.internal.message.data.ShortVideoThumbnail import net.mamoe.mirai.message.data.InternalShortVideoProtocol import net.mamoe.mirai.message.data.OfflineShortVideo internal class InternalShortVideoProtocolImpl : InternalShortVideoProtocol { override fun createOfflineShortVideo( videoId: String, fileMd5: ByteArray, fileSize: Long, fileFormat: String, fileName: String, thumbnailMd5: ByteArray, thumbnailSize: Long ): OfflineShortVideo { return OfflineShortVideoImpl( videoId, fileName, fileMd5, fileSize, fileFormat, ShortVideoThumbnail( thumbnailMd5, thumbnailSize, 0, 0 ) ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/OfflineImage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.Bot import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.ImageType /** * 离线的图片, 即为客户端主动上传到服务器而获得的 [Image] 实例. * 不能直接获取它在服务器上的链接. 需要通过 [IMirai.queryImageUrl] 查询 * * 一般由 [Contact.uploadImage] 得到 */ internal sealed interface OfflineImage : Image /** * 通过 [Group.uploadImage] 上传得到的 [GroupImage]. 它的链接需要查询 [IMirai.queryImageUrl] * * @param imageId 参考 [Image.imageId] */ @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(with = OfflineFriendImage.Serializer::class) internal data class OfflineFriendImage( override val imageId: String, override val width: Int = 0, override val height: Int = 0, override val size: Long = 0L, override val imageType: ImageType = ImageType.UNKNOWN, override val isEmoji: Boolean = false, ) : FriendImage(), OfflineImage, DeferredOriginUrlAware { object Serializer : Image.FallbackSerializer("OfflineFriendImage") override fun getUrl(bot: Bot): String { return "http://c2cpicdw.qpic.cn/offpic_new/${bot.id}${this.friendImageId}/0?term=2" } } /** * @param imageId 参考 [Image.imageId] */ @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(with = OfflineGroupImage.Serializer::class) internal data class OfflineGroupImage( override val imageId: String, override val width: Int = 0, override val height: Int = 0, override val size: Long = 0L, override val imageType: ImageType = ImageType.UNKNOWN, override val isEmoji: Boolean = false, ) : GroupImage(), OfflineImage, DeferredOriginUrlAware { @Transient internal var fileId: Int? = null object Serializer : Image.FallbackSerializer("OfflineGroupImage") override fun getUrl(bot: Bot): String { return "http://gchat.qpic.cn/gchatpic_new/${bot.id}/0-0-${ imageId.substring(1..36) .replace("-", "") }/0?term=2" } init { @Suppress("DEPRECATION") require(imageId matches Image.IMAGE_ID_REGEX) { "Illegal imageId. It must matches GROUP_IMAGE_ID_REGEX" } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/OnlineImage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.protocol.data.proto.CustomFaceExtPb import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ImgExtPbResvAttrCommon import net.mamoe.mirai.internal.network.protocol.data.proto.NotOnlineImageExtPb import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.ImageType import net.mamoe.mirai.utils.generateImageId import net.mamoe.mirai.utils.generateImageIdFromResourceId import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.structureToString internal sealed interface OnlineImage : Image, ConstOriginUrlAware { override val originUrl: String } /** * 接收消息时获取到的 [FriendImage]. 它可以直接获取下载链接 [originUrl] */ internal sealed class OnlineFriendImage : FriendImage(), OnlineImage @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(with = OnlineFriendImageImpl.Serializer::class) internal class OnlineFriendImageImpl( internal val delegate: ImMsgBody.NotOnlineImage, ) : @Suppress("DEPRECATION") OnlineFriendImage() { object Serializer : Image.FallbackSerializer("OnlineFriendImage") override val size: Long get() = delegate.fileLen override val width: Int get() = delegate.picWidth override val height: Int get() = delegate.picHeight override val imageType: ImageType get() = OnlineImageIds.speculateImageType(delegate.filePath, delegate.imgType) override val imageId: String = kotlin.run { val imageType = imageType.formatName generateImageIdFromResourceId(delegate.resId, imageType) ?: kotlin.run { if (delegate.picMd5.size == 16) generateImageId(delegate.picMd5, imageType) else { Image.logger.warning( contextualBugReportException( "Failed to compute friend imageId: resId=${delegate.resId}", delegate.structureToString(), additional = "并描述此时 Bot 是否正在从好友或群接受消息, 尽量附加该图片原文件" ) ) delegate.resId } } } override val originUrl: String get() = if (delegate.origUrl.isNotBlank()) { "http://c2cpicdw.qpic.cn" + this.delegate.origUrl } else if (delegate.resId.isNotEmpty() && delegate.resId[0] == '{') { // https://github.com/mamoe/mirai/issues/1600 gchatImageUrlByImageId(imageId) } else { "http://c2cpicdw.qpic.cn/offpic_new/0/" + delegate.resId + "/0?term=2" } override val isEmoji: Boolean by lazy { delegate.pbReserve.pbImageResv_checkIsEmoji(NotOnlineImageExtPb.ResvAttr.serializer()) } } private fun gchatImageUrlByImageId(imageId: String) = "http://gchat.qpic.cn/gchatpic_new/0/0-0-${ imageId.substring(1..36) .replace("-", "") }/0?term=2" private fun <T : ImgExtPbResvAttrCommon> ByteArray.pbImageResv_checkIsEmoji(serializer: KSerializer<T>): Boolean { val data = this return kotlin.runCatching { data.takeIf { it.isNotEmpty() }?.loadAs(serializer)?.let { ext -> ext.imageBizType == 1 || ext.textSummary.decodeToString() == "[动画表情]" } }.getOrNull() ?: false } /** * 接收消息时获取到的 [GroupImage]. 它可以直接获取下载链接 [originUrl] */ @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") internal sealed class OnlineGroupImage : GroupImage(), OnlineImage @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(with = OnlineGroupImageImpl.Serializer::class) internal class OnlineGroupImageImpl( internal val delegate: ImMsgBody.CustomFace, ) : OnlineGroupImage() { object Serializer : Image.FallbackSerializer("OnlineGroupImage") override val size: Long get() = delegate.size.toLong() override val width: Int get() = delegate.width override val height: Int get() = delegate.height override val imageType: ImageType get() = OnlineImageIds.speculateImageType(delegate.filePath, delegate.imageType) override val imageId: String = generateImageId( delegate.picMd5, OnlineImageIds.speculateImageTypeNameFromFilePath(delegate.filePath) ).takeIf { Image.IMAGE_ID_REGEX.matches(it) } ?: generateImageId(delegate.picMd5) override val originUrl: String get() = if (delegate.origUrl.isBlank()) { gchatImageUrlByImageId(imageId) } else "http://gchat.qpic.cn" + delegate.origUrl override val isEmoji: Boolean by lazy { delegate.pbReserve.pbImageResv_checkIsEmoji(CustomFaceExtPb.ResvAttr.serializer()) } } @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") internal sealed class OnlineNewTechImage : NewTechImage(), OnlineImage @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(with = OnlineNewTechImageImpl.Serializer::class) internal class OnlineNewTechImageImpl( internal val commonElem: ImMsgBody.CommonElem, ) : OnlineNewTechImage() { private val delegate = commonElem.pbElem.loadAs(ImMsgBody.NewTechImageInfo.serializer()) object Serializer : Image.FallbackSerializer("OnlineNewTechImage") override val md5 = delegate.info.msgInfo.imageInfo.md5.hexToBytes() private val senderMeta = delegate.meta.main.friendMeta ?: delegate.meta.main.groupMeta!! override val size: Long get() = delegate.info.msgInfo.imageInfo.size override val width: Int get() = delegate.info.msgInfo.imageInfo.imageWidth override val height: Int get() = delegate.info.msgInfo.imageInfo.imageHeight override val imageType: ImageType get() = OnlineImageIds.speculateImageType( delegate.info.msgInfo.imageInfo.filePath, delegate.info.msgInfo.imageInfo.imageType.type ) override val imageId: String = generateImageId( md5, OnlineImageIds.speculateImageTypeNameFromFilePath(delegate.info.msgInfo.imageInfo.filePath) ).takeIf { Image.IMAGE_ID_REGEX.matches(it) } ?: generateImageId(md5) override val originUrl: String get() = if (senderMeta.origUrl.isBlank()) { gchatImageUrlByImageId(imageId) } else "http://" + delegate.info.noKeyDownloadInfo.domain + "/download?appid=1407&fileid=" + delegate.info.msgInfo.fileId + senderMeta.origUrl override val isEmoji: Boolean by lazy { delegate.meta.main.isEmoji == 1 || delegate.meta.main.displayStr == "[动画表情]" } } private object OnlineImageIds { fun speculateImageType(filePath: String, imageTypeInt: Int): ImageType { return getImageTypeById(imageTypeInt) ?: speculateImageTypeFromImageId(filePath) ?: ImageType.UNKNOWN } fun speculateImageTypeFromImageId(filePathOrImageId: String): ImageType? { return speculateImageTypeNameFromFilePath(filePathOrImageId)?.let { ImageType.matchOrNull(it) } } /** * @param filePath should ends with `.type` */ fun speculateImageTypeNameFromFilePath(filePath: String): String? { return filePath.substringAfterLast('.').lowercase().let { ext -> if (ext == "null") { // official clients might send `null` null } else ext } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/image/jceData.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.image import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.utils.toUHexString internal val Image.friendImageId: String get() { // /1234567890-3666252994-EFF4427CE3D27DB6B1D9A8AB72E7A29C return "/000000000-000000000-${md5.toUHexString("")}" } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/imagesImpl.kt ================================================ ================================================ FILE: mirai-core/src/commonMain/kotlin/message/messageToElems.kt ================================================ /* * 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 net.mamoe.mirai.message.data.PlainText internal val UNSUPPORTED_VOICE_MESSAGE_PLAIN = PlainText("收到语音消息,你需要升级到最新版QQ才能接收,升级地址https://im.qq.com") ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePostprocessor import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreprocessor import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.systemProp import net.mamoe.mirai.utils.withSwitch import kotlin.reflect.KClass // Loaded by ServiceLoader internal abstract class MessageProtocol( val priority: UInt = PRIORITY_CONTENT // the higher, the prior it being called ) { val logger: MiraiLogger by lazy { MiraiLogger.Factory.create(this::class).withSwitch(systemProp("mirai.message.protocol.log.full", false)) } fun collectProcessors(processorCollector: ProcessorCollector) { processorCollector.collectProcessorsImpl() } protected abstract fun ProcessorCollector.collectProcessorsImpl() companion object { const val PRIORITY_METADATA: UInt = 10000u const val PRIORITY_CONTENT: UInt = 1000u const val PRIORITY_IGNORE: UInt = 500u const val PRIORITY_UNSUPPORTED: UInt = 100u const val PRIORITY_GENERAL_SENDER: UInt = 100u } object PriorityComparator : Comparator<MessageProtocol> { override fun compare(a: MessageProtocol, b: MessageProtocol): Int { // Do not use o1.compareTo // > Task :mirai-core:checkAndroidApiLevel // > /Users/runner/work/mirai/mirai/mirai-core/build/classes/kotlin/android/main/net/mamoe/mirai/internal/message/protocol/MessageProtocol$PriorityComparator.class // > Method compare(Lnet/mamoe/mirai/internal/message/protocol/MessageProtocol;Lnet/mamoe/mirai/internal/message/protocol/MessageProtocol;)I // > Invoke method java/lang/Integer.compareUnsigned(II)I // Couldn't access java/lang/Integer.compareUnsigned(II)I: java/lang/Integer.compareUnsigned(II)I since api level 26 return uintCompare(a.priority.toInt(), b.priority.toInt()) } private fun uintCompare(v1: Int, v2: Int): Int = (v1 xor Int.MIN_VALUE).compareTo(v2 xor Int.MIN_VALUE) } } internal abstract class ProcessorCollector { inline fun <reified T : SingleMessage> add(encoder: MessageEncoder<T>) = add(encoder, T::class) abstract fun <T : SingleMessage> add(encoder: MessageEncoder<T>, elementType: KClass<T>) abstract fun add(decoder: MessageDecoder) abstract fun add(preprocessor: OutgoingMessagePreprocessor) abstract fun add(transformer: OutgoingMessageTransformer) abstract fun add(sender: OutgoingMessageSender) abstract fun add(postprocessor: OutgoingMessagePostprocessor) abstract fun <T : Any> add(serializer: MessageSerializer<T>) } /* This stub is used for allocate new empty MessageProtocolFacade only */ internal object StubMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { } } /////////////////////////////////////////////////////////////////////////// // refiners /////////////////////////////////////////////////////////////////////////// //internal interface MessageRefiner : Processor<MessageRefinerContext> // //internal interface MessageRefinerContext : ProcessorPipelineContext<SingleMessage, Message?> { // /** // * Refine if possible (without suspension), returns self otherwise. // * @since 2.6 // */ // see #1157 // fun tryRefine( // bot: Bot, // context: MessageChain, // refineContext: RefineContext = EmptyRefineContext, // ): Message? = this // // /** // * This message [RefinableMessage] will be replaced by return value of [refineLight] // */ // suspend fun refine( // bot: Bot, // context: MessageChain, // refineContext: RefineContext = EmptyRefineContext, // ): Message? = tryRefine(bot, context, refineContext) //} // ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/MessageProtocolFacade.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.polymorphic import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.contact.impl import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.EmptyRefineContext import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.RefineContext import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.protocol.decode.* import net.mamoe.mirai.internal.message.protocol.encode.* import net.mamoe.mirai.internal.message.protocol.outgoing.* import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.COMPONENTS import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.MESSAGE_TO_RETRY import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.buildComponentStorage import net.mamoe.mirai.internal.network.component.withFallback 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.pipeline.ProcessResult import net.mamoe.mirai.internal.utils.runCoroutineInPlace import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor import net.mamoe.mirai.message.data.visitor.accept import net.mamoe.mirai.utils.* import kotlin.reflect.KClass internal interface MessageProtocolFacade { val remark: String get() = "MessageProtocolFacade" val encoderPipeline: MessageEncoderPipeline val decoderPipeline: MessageDecoderPipeline val preprocessorPipeline: OutgoingMessagePipeline val outgoingPipeline: OutgoingMessagePipeline val serializers: Collection<MessageSerializer<*>> val loaded: List<MessageProtocol> /** * Encode high-level [MessageChain] to give list of low-level and protocol-specific [ImMsgBody.Elem]s. */ fun encode( chain: MessageChain, messageTarget: ContactOrBot?, // for At.display, QuoteReply, Image, and more. withGeneralFlags: Boolean, // important for RichMessages, may also be helpful for others isForward: Boolean = false, // is inside forward, for At.display ): List<ImMsgBody.Elem> /** * Decode list of low-level and protocol-specific [ImMsgBody.Elem]s to give a high-level [MessageChain]. * * [SingleMessage]s are appended to the [builder]. */ fun decode( elements: List<ImMsgBody.Elem>, groupIdOrZero: Long, messageSourceKind: MessageSourceKind, bot: Bot, builder: MessageChainBuilder, containingMsg: MsgComm.Msg? = null, ) /** * Pre-process a message * @see OutgoingMessagePreprocessor */ suspend fun <C : AbstractContact> preprocess( target: C, message: Message, components: ComponentStorage, ): MessageChain /** * Send a message * @see OutgoingMessageProcessor */ suspend fun <C : AbstractContact> sendOutgoing( target: C, message: Message, components: ComponentStorage, ): MessageReceipt<C> /** * Preprocess and send a message * @see OutgoingMessagePreprocessor * @see OutgoingMessageProcessor */ suspend fun <C : AbstractContact> preprocessAndSendOutgoing( target: C, message: Message, components: ComponentStorage, ): MessageReceipt<C> /** * Preprocess and send a message * @see OutgoingMessagePreprocessor * @see OutgoingMessageProcessor */ @TestOnly suspend fun <C : AbstractContact> preprocessAndSendOutgoingImpl( target: C, message: Message, components: ComponentStorage, ): ProcessResult<OutgoingMessagePipelineContext, MessageReceipt<*>> /** * Decode list of low-level and protocol-specific [ImMsgBody.Elem]s to give a high-level [MessageChain]. */ fun decode( elements: List<ImMsgBody.Elem>, groupIdOrZero: Long, messageSourceKind: MessageSourceKind, bot: Bot, ): MessageChain = buildMessageChain { decode(elements, groupIdOrZero, messageSourceKind, bot, this, null) } fun createSerializersModule(): SerializersModule = SerializersModule { serializers.forEach { ms -> @Suppress("UNCHECKED_CAST") ms as MessageSerializer<SingleMessage> for (superclass in ms.superclasses) { polymorphic(superclass) { subclass(ms.forClass, ms.serializer) } } if (ms.registerAlsoContextual) { contextual(ms.forClass, ms.serializer) } // contextual(ms.forClass, ms.serializer) } } fun copy(): MessageProtocolFacade /** * The default global instance. */ companion object INSTANCE : MessageProtocolFacade by MessageProtocolFacadeImpl(), ComponentKey<MessageProtocolFacade> { init { MessageSerializers.registerSerializers(createSerializersModule()) } } } internal fun MessageProtocolFacade.decodeAndRefineLight( elements: List<ImMsgBody.Elem>, groupIdOrZero: Long, messageSourceKind: MessageSourceKind, bot: Bot, refineContext: RefineContext = EmptyRefineContext ): MessageChain = decode(elements, groupIdOrZero, messageSourceKind, bot).refineLight(bot, refineContext) internal suspend fun MessageProtocolFacade.decodeAndRefineDeep( elements: List<ImMsgBody.Elem>, groupIdOrZero: Long, messageSourceKind: MessageSourceKind, bot: Bot, refineContext: RefineContext = EmptyRefineContext ): MessageChain = decode(elements, groupIdOrZero, messageSourceKind, bot).refineDeep(bot, refineContext) private const val errorTips = "This should not happen if you are using mirai under default JVM classloader or using Mirai Console." + "If so, please file an issue. " + "If you are trying to load mirai manually from other classloader, " + "e.g. in another plugin system like Minecraft, it's your responsibility to ensure the Java SPI works." internal class MessageProtocolFacadeImpl( private val protocols: Iterable<MessageProtocol> = loadServices(MessageProtocol::class).asIterable(), override val remark: String = "MessageProtocolFacade" ) : MessageProtocolFacade { override val encoderPipeline: MessageEncoderPipeline = MessageEncoderPipelineImpl() override val decoderPipeline: MessageDecoderPipeline = MessageDecoderPipelineImpl() override val preprocessorPipeline: OutgoingMessagePipeline = OutgoingMessagePipelineImpl() override val outgoingPipeline: OutgoingMessagePipeline = OutgoingMessagePipelineImpl() override val serializers: MutableCollection<MessageSerializer<*>> = ArrayList(10) override val loaded: List<MessageProtocol> = kotlin.run { val instances = protocols .sortedWith(MessageProtocol.PriorityComparator.reversed()) if (instances.isEmpty()) { error( "Failed to load services for MessageProtocol from your classpath. " + "Check you ClassLoader environment and ensure services for '${MessageProtocol::class.qualifiedName}' can be loaded. $errorTips" ) } for (instance in instances) { instance.collectProcessors(object : ProcessorCollector() { override fun <T : SingleMessage> add(encoder: MessageEncoder<T>, elementType: KClass<T>) { this@MessageProtocolFacadeImpl.encoderPipeline.registerProcessor( MessageEncoderProcessor( encoder, elementType ) ) } override fun add(decoder: MessageDecoder) { this@MessageProtocolFacadeImpl.decoderPipeline.registerProcessor(MessageDecoderProcessor(decoder)) } override fun add(preprocessor: OutgoingMessagePreprocessor) { preprocessorPipeline.registerProcessor(OutgoingMessageProcessorAdapter(preprocessor)) } override fun add(transformer: OutgoingMessageTransformer) { outgoingPipeline.registerProcessor(OutgoingMessageProcessorAdapter(transformer)) } override fun add(sender: OutgoingMessageSender) { outgoingPipeline.registerProcessor(OutgoingMessageProcessorAdapter(sender)) } override fun add(postprocessor: OutgoingMessagePostprocessor) { outgoingPipeline.registerProcessor(OutgoingMessageProcessorAdapter(postprocessor)) } override fun <T : Any> add(serializer: MessageSerializer<T>) { serializers.add(serializer) } }) } instances.toList() } private fun checkOutgoingPipeline() { if (outgoingPipeline.processors.isEmpty()) { error( "`outgoingPipeline` is empty. It means you have corrupted classpath or bad service configuration. $errorTips" ) } } override fun encode( chain: MessageChain, messageTarget: ContactOrBot?, withGeneralFlags: Boolean, isForward: Boolean ): List<ImMsgBody.Elem> { val pipeline = encoderPipeline val attributes = buildTypeSafeMap { set(MessageEncoderContext.CONTACT, messageTarget) set(MessageEncoderContext.ORIGINAL_MESSAGE, chain) set(MessageEncoderContext.ADD_GENERAL_FLAGS, withGeneralFlags) set(MessageEncoderContext.IS_FORWARD, isForward) } val builder = ArrayList<ImMsgBody.Elem>(chain.size) chain.accept(object : RecursiveMessageVisitor<Unit>() { override fun visitSingleMessage(message: SingleMessage, data: Unit) { runCoroutineInPlace { builder.addAll(pipeline.process(message, attributes).collected) } } }) return builder } override fun decode( elements: List<ImMsgBody.Elem>, groupIdOrZero: Long, messageSourceKind: MessageSourceKind, bot: Bot, builder: MessageChainBuilder, containingMsg: MsgComm.Msg? ) { val pipeline = decoderPipeline val attributes = buildTypeSafeMap { set(MessageDecoderContext.BOT, bot) set(MessageDecoderContext.MESSAGE_SOURCE_KIND, messageSourceKind) set(MessageDecoderContext.GROUP_ID, groupIdOrZero) set(MessageDecoderContext.CONTAINING_MSG, containingMsg) } runCoroutineInPlace { elements.forEach { builder.addAll(pipeline.process(it, attributes).collected) } } } private val thisComponentStorage by lazy { buildComponentStorage { set( MessageProtocolFacade, this@MessageProtocolFacadeImpl ) } } override suspend fun <C : AbstractContact> preprocess( target: C, message: Message, components: ComponentStorage ): MessageChain { val attributes = createAttributesForOutgoingMessage(target, message, components) return preprocessorPipeline.process(message.toMessageChain(), attributes).context.currentMessageChain } override suspend fun <C : AbstractContact> sendOutgoing( target: C, message: Message, components: ComponentStorage ): MessageReceipt<C> { checkOutgoingPipeline() val attributes = createAttributesForOutgoingMessage(target, message, components) val (_, result) = outgoingPipeline.process(message.toMessageChain(), attributes) return getSingleReceipt(result, message) } override suspend fun <C : AbstractContact> preprocessAndSendOutgoing( target: C, message: Message, components: ComponentStorage ): MessageReceipt<C> { @OptIn(TestOnly::class) return getSingleReceipt(preprocessAndSendOutgoingImpl(target, message, components).collected, message) } @TestOnly override suspend fun <C : AbstractContact> preprocessAndSendOutgoingImpl( target: C, message: Message, components: ComponentStorage ): ProcessResult<OutgoingMessagePipelineContext, MessageReceipt<*>> { checkOutgoingPipeline() val attributes = createAttributesForOutgoingMessage(target, message, components) val data = message.toMessageChain() val (context, _) = preprocessorPipeline.process(data, attributes) val preprocessed = context.currentMessageChain return outgoingPipeline.process( data, outgoingPipeline.createContext(preprocessed, context.attributes.plus(MESSAGE_TO_RETRY to preprocessed)), attributes ) } override fun copy(): MessageProtocolFacade { return MessageProtocolFacadeImpl(protocols) } private fun <C : AbstractContact> getSingleReceipt( result: Collection<MessageReceipt<*>>, message: Message ): MessageReceipt<C> { when (result.size) { 0 -> throw contextualBugReportException( "Internal error: no MessageReceipt was returned from OutgoingMessagePipeline for message", forDebug = message.structureToString() ) 1 -> return result.single().castUp() else -> throw contextualBugReportException( "Internal error: multiple MessageReceipts were returned from OutgoingMessagePipeline: $result", forDebug = message.structureToString() ) } } private fun <C : AbstractContact> createAttributesForOutgoingMessage( target: C, message: Message, context: ComponentStorage ): MutableTypeSafeMap { val chain = message.toMessageChain() val attributes = buildTypeSafeMap { set(CONTACT, target.impl()) set(ORIGINAL_MESSAGE, message) set(ORIGINAL_MESSAGE_AS_CHAIN, chain) set(STEP, SendMessageStep.FIRST) set(COMPONENTS, thisComponentStorage.withFallback(context)) set(MESSAGE_TO_RETRY, chain) } return attributes } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/decode/MessageDecoder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.decode import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker import net.mamoe.mirai.internal.pipeline.Processor internal interface MessageDecoder : PipelineConsumptionMarker { suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) } /** * Adapter for [MessageDecoder] to be used as [Processor]. */ internal class MessageDecoderProcessor( private val decoder: MessageDecoder, ) : Processor<MessageDecoderContext, ImMsgBody.Elem> { override val origin: Any get() = this override suspend fun process(context: MessageDecoderContext, data: ImMsgBody.Elem) { @Suppress("ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL") decoder.run { context.process(data) } // TODO: 2022/4/27 handle exceptions } override fun toString(): String { return "MessageDecoderProcessor(decoder=$decoder)" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/decode/MessageDecoderPipeline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.decode import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext.Companion.CONTAINING_MSG 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.pipeline.* import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.utils.* import kotlin.coroutines.RestrictsSuspension internal interface MessageDecoderPipeline : ProcessorPipeline<MessageDecoderProcessor, MessageDecoderContext, ImMsgBody.Elem, Message> @RestrictsSuspension // Implementor can only call `MessageDecoderContext.process` and `processAlso` so there will be no suspension point internal interface MessageDecoderContext : ProcessorPipelineContext<ImMsgBody.Elem, Message> { companion object { val BOT = TypeKey<Bot>("bot") val MESSAGE_SOURCE_KIND = TypeKey<MessageSourceKind>("messageSourceKind") val GROUP_ID = TypeKey<Long>("groupId") // zero if not group val CONTAINING_MSG = TypeKey<MsgComm.Msg?>("containingMsg") val FROM_ID = TypeKey<Long>("fromId") // group/temp = sender, friend/stranger = this } } internal open class MessageDecoderPipelineImpl : AbstractProcessorPipeline<MessageDecoderProcessor, MessageDecoderContext, ImMsgBody.Elem, Message>( PipelineConfiguration(stopWhenConsumed = true), @OptIn(TestOnly::class) defaultTraceLogging ), MessageDecoderPipeline { inner class MessageDecoderContextImpl(attributes: TypeSafeMap) : MessageDecoderContext, BaseContextImpl(attributes) override fun createContext(data: ImMsgBody.Elem, attributes: TypeSafeMap): MessageDecoderContext = MessageDecoderContextImpl(attributes) override suspend fun process( data: ImMsgBody.Elem, context: MessageDecoderContext, attributes: TypeSafeMap ): ProcessResult<MessageDecoderContext, Message> { context.attributes[CONTAINING_MSG]?.let { msg -> traceLogging.info { "Processing MsgCommon.Msg: ${msg.structureToStringAndDesensitizeIfAvailable()}" } } return super.process(data, context, attributes) } companion object { @TestOnly val defaultTraceLogging: MiraiLoggerWithSwitch by lazy { MiraiLogger.Factory.create(MessageDecoderPipelineImpl::class, "MessageDecoderPipeline") .withSwitch(systemProp("mirai.message.decoder.pipeline.log.full", false)) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/encode/MessageEncoder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.encode import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker import net.mamoe.mirai.internal.pipeline.Processor import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.uncheckedCast import kotlin.reflect.KClass internal fun interface MessageEncoder<T : SingleMessage> : PipelineConsumptionMarker { suspend fun MessageEncoderContext.process(data: T) } /** * Adapter for [MessageEncoder] to be used as [Processor]. */ internal class MessageEncoderProcessor<T : SingleMessage>( private val encoder: MessageEncoder<T>, private val elementType: KClass<T>, ) : Processor<MessageEncoderContext, SingleMessage> { override val origin: Any get() = this override suspend fun process(context: MessageEncoderContext, data: SingleMessage) { if (elementType.isInstance(data)) { @Suppress("ILLEGAL_RESTRICTED_SUSPENDING_FUNCTION_CALL") encoder.run { context.process(data.uncheckedCast()) } // TODO: 2022/4/27 handle exceptions } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/encode/MessageEncoderPipeline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.encode import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.pipeline.* import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.* import kotlin.coroutines.RestrictsSuspension internal interface MessageEncoderPipeline : ProcessorPipeline<MessageEncoderProcessor<*>, MessageEncoderContext, SingleMessage, ImMsgBody.Elem> { } /** * The context for a [MessageEncoder]. [RestrictsSuspension] ensures no real suspension may happen during [Processor.process]. */ @RestrictsSuspension internal interface MessageEncoderContext : ProcessorPipelineContext<SingleMessage, ImMsgBody.Elem> { /** * General flags that should be appended to the end of the result. * * Do not update this property directly, but call [collectGeneralFlags]. */ var generalFlags: ImMsgBody.Elem companion object { val ADD_GENERAL_FLAGS = TypeKey<Boolean>("addGeneralFlags") val MessageEncoderContext.addGeneralFlags get() = attributes[ADD_GENERAL_FLAGS] /** * Override default generalFlags if needed */ inline fun MessageEncoderContext.collectGeneralFlags(block: () -> ImMsgBody.Elem) { if (addGeneralFlags) { generalFlags = block() } } val CONTACT = TypeKey<ContactOrBot?>("contactOrBot") val MessageEncoderContext.contact get() = attributes[CONTACT] val ORIGINAL_MESSAGE = TypeKey<MessageChain>("originalMessage") val MessageEncoderContext.originalMessage get() = attributes[ORIGINAL_MESSAGE] val IS_FORWARD = TypeKey<Boolean>("isForward") val MessageEncoderContext.isForward get() = attributes[IS_FORWARD] } } /** * This pipeline encodes [SingleMessage] into [ImMsgBody.Elem]s, in the context of [MessageEncoderContext] */ internal open class MessageEncoderPipelineImpl : AbstractProcessorPipeline<MessageEncoderProcessor<*>, MessageEncoderContext, SingleMessage, ImMsgBody.Elem>( PipelineConfiguration(stopWhenConsumed = true), @OptIn(TestOnly::class) defaultTraceLogging ), MessageEncoderPipeline { private inner class MessageEncoderContextImpl(attributes: TypeSafeMap) : MessageEncoderContext, BaseContextImpl(attributes) { override var generalFlags: ImMsgBody.Elem by lateinitMutableProperty { ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_ELSE)) } } override fun createContext(data: SingleMessage, attributes: TypeSafeMap): MessageEncoderContext = MessageEncoderContextImpl(attributes) companion object { private val PB_RESERVE_FOR_ELSE = "78 00 F8 01 00 C8 02 00".hexToBytes() @TestOnly val defaultTraceLogging: MiraiLoggerWithSwitch by lazy { MiraiLogger.Factory.create(MessageEncoderPipelineImpl::class, "MessageEncoderPipeline") .withSwitch(systemProp("mirai.message.encoder.pipeline.log.full", false)) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/AudioProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.data.OfflineAudioImpl import net.mamoe.mirai.internal.message.data.OnlineAudioImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.message.data.* internal class AudioProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { MessageSerializer.superclassesScope( OnlineAudio::class, Audio::class, MessageContent::class, SingleMessage::class ) { add(MessageSerializer(OnlineAudioImpl::class, OnlineAudioImpl.serializer())) } MessageSerializer.superclassesScope( OfflineAudio::class, Audio::class, MessageContent::class, SingleMessage::class ) { add(MessageSerializer(OfflineAudioImpl::class, OfflineAudioImpl.serializer())) } MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { @Suppress("DEPRECATION_ERROR") add( MessageSerializer( net.mamoe.mirai.message.data.Voice::class, net.mamoe.mirai.message.data.Voice.serializer() ) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/CustomMessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.CustomMessage import net.mamoe.mirai.utils.read import net.mamoe.mirai.utils.toUHexString internal class CustomMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Decoder()) } private companion object { private const val MIRAI_CUSTOM_ELEM_TYPE = 103904510 // "mirai.hashCode()" } private class Encoder : MessageEncoder<CustomMessage> { override suspend fun MessageEncoderContext.process(data: CustomMessage) { markAsConsumed() @Suppress("UNCHECKED_CAST") collect( ImMsgBody.Elem( customElem = ImMsgBody.CustomElem( enumType = MIRAI_CUSTOM_ELEM_TYPE, data = CustomMessage.dump( data.getFactory() as CustomMessage.Factory<CustomMessage>, data ) ) ) ) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.customElem == null) return markAsConsumed() kotlin.runCatching { if (data.customElem.enumType != MIRAI_CUSTOM_ELEM_TYPE) return data.customElem.data.read { CustomMessage.load(this) } }.fold( onFailure = { if (it is CustomMessage.Companion.CustomMessageFullDataDeserializeInternalException) { throw IllegalStateException( "Internal error: " + "exception while deserializing CustomMessage head data," + " 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) { collect(it) } } ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/FaceProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer 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.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.Face import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.toByteArray internal class FaceProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Type1Decoder()) add(Type2Decoder()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add( MessageSerializer( Face::class, Face.serializer() ) ) } } private class Encoder : MessageEncoder<Face> { override suspend fun MessageEncoderContext.process(data: Face) { markAsConsumed() collect( if (data.id >= 260) { ImMsgBody.Elem(commonElem = data.toCommData()) } else { ImMsgBody.Elem(face = data.toJceData()) } ) } private companion object { private val FACE_BUF = "00 01 00 04 52 CC F5 D0".hexToBytes() fun Face.toJceData(): ImMsgBody.Face { return ImMsgBody.Face( index = this.id, old = (0x1445 - 4 + this.id).toShort().toByteArray(), buf = FACE_BUF ) } fun Face.toCommData(): ImMsgBody.CommonElem { return ImMsgBody.CommonElem( serviceType = 33, pbElem = HummerCommelem.MsgElemInfoServtype33( index = this.id, name = "/${this.name}".toByteArray(), compat = "/${this.name}".toByteArray() ).toByteArray(HummerCommelem.MsgElemInfoServtype33.serializer()), businessType = 1 ) } } } private class Type1Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val commonElem = data.commonElem ?: return if (commonElem.serviceType != 33) return markAsConsumed() val proto = commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype33.serializer()) collect(Face(proto.index)) } } private class Type2Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val face = data.face ?: return markAsConsumed() collect(Face(face.index)) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/FileMessageProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import io.ktor.utils.io.core.* import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.message.data.FileMessageImpl import net.mamoe.mirai.internal.message.data.checkIsImpl import net.mamoe.mirai.internal.message.flags.AllowSendFileMessage import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.message.visitor.MessageVisitorEx import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.data.proto.ObjMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor import net.mamoe.mirai.message.data.visitor.acceptChildren import net.mamoe.mirai.utils.read import net.mamoe.mirai.utils.systemProp internal class FileMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { // no encoder add(Decoder()) add(OutgoingMessageTransformer { if (attributes[OutgoingMessagePipelineContext.STEP] == SendMessageStep.FIRST ) { verifyFileMessage(currentMessageChain) } }) add(FileMessageSender()) MessageSerializer.superclassesScope(FileMessage::class, MessageContent::class, SingleMessage::class) { add( MessageSerializer( FileMessageImpl::class, FileMessageImpl.serializer() ) ) } } companion object { private val ALLOW_SENDING_FILE_MESSAGE = systemProp("mirai.message.allow.sending.file.message", false) fun verifyFileMessage(message: MessageChain) { var hasFileMessage = false var hasAllowSendFileMessage = false message.acceptChildren(object : RecursiveMessageVisitor<Unit>(), MessageVisitorEx<Unit, Unit> { override fun isFinished(): Boolean { return hasAllowSendFileMessage // finish early if allow send } override fun visitAllowSendFileMessage(message: AllowSendFileMessage, data: Unit) { hasAllowSendFileMessage = true } override fun visitFileMessage(message: FileMessage, data: Unit) { if (ALLOW_SENDING_FILE_MESSAGE) return // #1715 if (message !is FileMessageImpl) error("Customized FileMessage cannot be send") if (!message.allowSend) { hasFileMessage = true } } }) if (hasAllowSendFileMessage) { // allowing return } if (hasFileMessage) { throw IllegalStateException( "Sending FileMessage is not allowed, as it may cause unexpected results. " + "Add JVM argument `-Dmirai.message.allow.sending.file.message=true` to disable this check. " + "Do this only for compatibility!" ) } } } private class FileMessageSender : OutgoingMessageSender { override suspend fun OutgoingMessagePipelineContext.process() { val file = currentMessageChain[FileMessage] ?: return markAsConsumed() file.checkIsImpl() val contact = attributes[CONTACT] val bot = contact.bot val strategy = components[MessageProtocolStrategy] val source = coroutineScope { val source = async { strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 2021) } bot.network.sendAndExpect(FileManagement.Feed(bot.client, contact.id, file.busId, file.id)) source.await() } collect(source.createMessageReceipt(contact, true)) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.transElemInfo == null) return if (data.transElemInfo.elemType != 24) return markAsConsumed() data.transElemInfo.elemValue.read { // group file feed // 01 00 77 08 06 12 0A 61 61 61 61 61 61 2E 74 78 74 1A 06 31 35 42 79 74 65 3A 5F 12 5D 08 66 12 25 2F 64 37 34 62 62 66 33 61 2D 37 62 32 35 2D 31 31 65 62 2D 38 34 66 38 2D 35 34 35 32 30 30 37 62 35 64 39 66 18 0F 22 0A 61 61 61 61 61 61 2E 74 78 74 28 00 3A 00 42 20 61 33 32 35 66 36 33 34 33 30 65 37 61 30 31 31 66 37 64 30 38 37 66 63 33 32 34 37 35 34 39 63 // fun getFileRsrvAttr(file: ObjMsg.MsgContentInfo.MsgFile): HummerResv21.ResvAttr? { // if (file.ext.isEmpty()) return null // val element = kotlin.runCatching { // jsonForFileDecode.parseToJsonElement(file.ext) as? JsonObject // }.getOrNull() ?: return null // val extInfo = element["ExtInfo"]?.toString()?.decodeBase64() ?: return null // return extInfo.loadAs(HummerResv21.ResvAttr.serializer()) // } val var7 = readByte() if (var7 == 1.toByte()) { while (remaining > 2) { val proto = readProtoBuf(ObjMsg.ObjMsg.serializer(), readShort().toUShort().toInt()) // proto.msgType=6 val file = proto.msgContentInfo.firstOrNull()?.msgFile ?: continue // officially get(0) only. // val attr = getFileRsrvAttr(file) ?: continue // val info = attr.forwardExtFileInfo ?: continue collect( FileMessageImpl( id = file.filePath, busId = file.busId, // path i.e. /a99e95fa-7b2d-11eb-adae-5452007b698a name = file.fileName, size = file.fileSize ) ) } } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/FlashImageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.User import net.mamoe.mirai.internal.message.image.OnlineFriendImageImpl import net.mamoe.mirai.internal.message.image.OnlineGroupImageImpl import net.mamoe.mirai.internal.message.image.friendImageId import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.collectGeneralFlags import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.contact import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer 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.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.FlashImage import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.hexToBytes internal class FlashImageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Decoder()) add(Encoder()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(FlashImage::class, FlashImage.serializer())) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.commonElem == null) return if (data.commonElem.serviceType != 3) return markAsConsumed() val proto = data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype3.serializer()) if (proto.flashTroopPic != null) { collect(FlashImage(OnlineGroupImageImpl(proto.flashTroopPic))) } if (proto.flashC2cPic != null) { collect(FlashImage(OnlineFriendImageImpl(proto.flashC2cPic))) } } } private class Encoder : MessageEncoder<FlashImage> { override suspend fun MessageEncoderContext.process(data: FlashImage) { markAsConsumed() collect(data.toJceData(contact)) processAlso(UNSUPPORTED_FLASH_MESSAGE_PLAIN) collectGeneralFlags { ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_DOUTU)) } } private companion object { @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 UNSUPPORTED_FLASH_MESSAGE_PLAIN = PlainText("[闪照]请使用新版手机QQ查看闪照。") private fun FlashImage.toJceData(messageTarget: ContactOrBot?): ImMsgBody.Elem { return if (messageTarget is User) { ImMsgBody.Elem( commonElem = ImMsgBody.CommonElem( serviceType = 3, businessType = 0, pbElem = HummerCommelem.MsgElemInfoServtype3( flashC2cPic = ImMsgBody.NotOnlineImage( filePath = image.friendImageId, resId = image.friendImageId, picMd5 = image.md5, oldPicMd5 = false, pbReserve = byteArrayOf(0x78, 0x06) ) ).toByteArray(HummerCommelem.MsgElemInfoServtype3.serializer()) ) ) } else { ImMsgBody.Elem( commonElem = ImMsgBody.CommonElem( serviceType = 3, businessType = 0, pbElem = HummerCommelem.MsgElemInfoServtype3( flashTroopPic = ImMsgBody.CustomFace( filePath = image.imageId, picMd5 = image.md5, pbReserve = byteArrayOf(0x78, 0x06) ) ).toByteArray(HummerCommelem.MsgElemInfoServtype3.serializer()) ) ) } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/ForwardMessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MessageTooLargeException import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.data.forwardMessage import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreprocessor import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.message.data.* internal class ForwardMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(ForwardMessageUploader()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(ForwardMessage::class, ForwardMessage.serializer())) } } class ForwardMessageUploader : OutgoingMessagePreprocessor { override suspend fun OutgoingMessagePipelineContext.process() { val forward = currentMessageChain[ForwardMessage] ?: return val contact = attributes[CONTACT] if (!currentMessageChain.contains(IgnoreLengthCheck)) { checkLength(forward, contact) sequence { forward.nodeList.forEach { yieldAll(it.messageChain) } }.asIterable().verifyLength(forward, contact) } val resId = components[HighwayUploader].uploadMessages( contact, components, forward.nodeList, false ) currentMessageChain += RichMessage.forwardMessage( resId = resId, fileName = components[ClockHolder].local.currentTimeSeconds().toString(), forwardMessage = forward, ).toMessageChain() } private fun checkLength( forward: ForwardMessage, contact: AbstractContact ) { check(forward.nodeList.size <= 200) { throw MessageTooLargeException( contact, forward, forward, "ForwardMessage allows up to 200 nodes, but found ${forward.nodeList.size}" ) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/GeneralMessageSenderProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.coroutines.Deferred import net.mamoe.mirai.contact.* import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.message.flags.ForceAsFragmentedMessage import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.MESSAGE_TO_RETRY import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.message.source.OutgoingMessageSourceInternal import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg import net.mamoe.mirai.message.data.AtAll import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.content import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.buildTypeSafeMap import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.truncated internal class GeneralMessageSenderProtocol : MessageProtocol(PRIORITY_GENERAL_SENDER) { override fun ProcessorCollector.collectProcessorsImpl() { add(GeneralMessageSender(logger)) add(MessageSerializer(MessageChain::class, MessageChain.serializer(), emptyArray())) } class GeneralMessageSender( private val logger: MiraiLogger, ) : OutgoingMessageSender { override suspend fun OutgoingMessagePipelineContext.process() { markAsConsumed() @Suppress("UNCHECKED_CAST") val strategy = components[MessageProtocolStrategy] as MessageProtocolStrategy<AbstractContact> val step = attributes[STEP] val contact = attributes[CONTACT] val bot = contact.bot var source: Deferred<OnlineMessageSource.Outgoing>? = null val packets = strategy.createPacketsForGeneralMessage( client = bot.client, contact = contact, message = currentMessageChain, originalMessage = attributes[ORIGINAL_MESSAGE_AS_CHAIN], fragmented = step == SendMessageStep.FRAGMENTED || currentMessageChain.contains(ForceAsFragmentedMessage) ) { source = it } // Patch time to be actual server time var finalTime = bot.clock.server.currentTimeSeconds().toInt() val sendPacketOk = sendAllPackets(bot, step, contact, packets) { _, rsp -> if (rsp is MessageSvcPbSendMsg.Response.SUCCESS) { finalTime = rsp.sendTime } } if (sendPacketOk) { val sourceAwait = source?.await() ?: error("Internal error: source is not initialized") (sourceAwait as OutgoingMessageSourceInternal).time = finalTime sourceAwait.tryEnsureSequenceIdAvailable() collect(sourceAwait.createMessageReceipt(contact, true)) } } /** * @return `true`, if source needs to be added */ private suspend fun OutgoingMessagePipelineContext.sendAllPackets( bot: AbstractBot, step: SendMessageStep, contact: Contact, packets: List<OutgoingPacket>, packetResponseConsumer: (Int, MessageSvcPbSendMsg.Response) -> Unit = { _, _ -> }, ): Boolean { if (!step.allowMultiplePackets && packets.size != 1) { throw IllegalStateException("Internal error: step $step doesn't allow multiple packets while found ${packets.size} ones.") } packets.forEachIndexed { index, packet -> if (!sendSinglePacket( bot, packet, step, contact, ) { packetResponseConsumer(index, it) } ) return@sendAllPackets false } return true } private suspend fun OutgoingMessagePipelineContext.sendSinglePacket( bot: AbstractBot, packet: OutgoingPacket, step: SendMessageStep, contact: Contact, packetResponseConsumer: (MessageSvcPbSendMsg.Response) -> Unit, ): Boolean { val originalMessage = attributes[ORIGINAL_MESSAGE] val protocolStrategy = components[MessageProtocolStrategy] val finalMessage = currentMessageChain val resp = protocolStrategy.sendPacket(bot, packet) as MessageSvcPbSendMsg.Response packetResponseConsumer(resp) if (resp is MessageSvcPbSendMsg.Response.MessageTooLarge) { logger.info { "STEP $step: message too large." } val next = step.nextStepOrNull() ?: throw MessageTooLargeException( contact, originalMessage, finalMessage, "Message '${finalMessage.content.truncated(10)}' is too large." ) // retry with next step logger.info { "Retrying with STEP $next" } val (_, receipts) = processAlso( attributes[MESSAGE_TO_RETRY], extraAttributes = buildTypeSafeMap { set(STEP, next) }, ) check(receipts.size == 1) { "Internal error: expected exactly one receipt collected from sub-process, but found ${receipts.size}." } // We expect to get a Receipt from processAlso return false } if (resp is MessageSvcPbSendMsg.Response.ServiceUnavailable) { throw IllegalStateException("Send message to $contact failed, server service is unavailable.") } if (resp is MessageSvcPbSendMsg.Response.Failed) { when (resp.resultType) { 120 -> if (contact is Group) throw BotIsBeingMutedException(contact, originalMessage) 121 -> if (AtAll in currentMessageChain) throw SendMessageFailedException( contact, SendMessageFailedException.Reason.AT_ALL_LIMITED, originalMessage ) 299 -> if (contact is Group) throw SendMessageFailedException( contact, SendMessageFailedException.Reason.GROUP_CHAT_LIMITED, originalMessage ) // #2127 46 -> throw SendMessageFailedException( contact, SendMessageFailedException.Reason.LIMITED_MESSAGING, originalMessage, tips = "问题原因可能是账号被多次举报或被服务器认为不安全. 若账号在官方客户端也无法发出消息, 可尝试用手机 QQ 登录后访问 https://accounts.qq.com/safe/message/unlock?lock_info=5_5 解冻." ) } } check(resp is MessageSvcPbSendMsg.Response.SUCCESS) { "Send message failed: $resp" } return true } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/IgnoredMessagesProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.flags.InternalFlagOnlyMessage import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.ForwardMessage import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.ShowImageFlag import net.mamoe.mirai.message.data.SingleMessage internal class IgnoredMessagesProtocol : MessageProtocol(PRIORITY_IGNORE) { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Decoder()) // 所有未处理的 Elem 都会变成 UnsupportedMessage 所有不用在这里处理 } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { when { data.elemFlags2 != null || data.extraInfo != null || data.generalFlags != null || data.anonGroupMsg != null -> markAsConsumed() } } } private class Encoder : MessageEncoder<SingleMessage> { override suspend fun MessageEncoderContext.process(data: SingleMessage) { when (data) { is ForwardMessage, is MessageSource, // mirai metadata only -> { markAsConsumed() } is InternalFlagOnlyMessage, is ShowImageFlag -> { // ignored markAsConsumed() } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/ImageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.User import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.message.data.transform import net.mamoe.mirai.internal.message.image.* import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.contact import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreprocessor import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.protocol.data.proto.CustomFace import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.utils.ImagePatcher import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.generateImageId import net.mamoe.mirai.utils.toUHexString internal class ImageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(ImageEncoder()) add(ImageDecoder()) add(ImagePatcherForGroup()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { @Suppress("DEPRECATION", "DEPRECATION_ERROR") add(MessageSerializer(Image::class, Image.Serializer, registerAlsoContextual = true)) } MessageSerializer.superclassesScope(Image::class, MessageContent::class, SingleMessage::class) { add(MessageSerializer(OfflineGroupImage::class, OfflineGroupImage.serializer())) add(MessageSerializer(OfflineFriendImage::class, OfflineFriendImage.serializer())) add(MessageSerializer(OnlineFriendImageImpl::class, OnlineFriendImageImpl.serializer())) add(MessageSerializer(OnlineGroupImageImpl::class, OnlineGroupImageImpl.serializer())) add(MessageSerializer(OnlineNewTechImageImpl::class, OnlineNewTechImageImpl.serializer())) } } private class ImagePatcherForGroup : OutgoingMessagePreprocessor { override suspend fun OutgoingMessagePipelineContext.process() { val contact = attributes[CONTACT] if (contact !is GroupImpl) return val patcher = contact.bot.components[ImagePatcher] currentMessageChain = currentMessageChain.transform { element -> when (element) { is OfflineGroupImage -> { patcher.patchOfflineGroupImage(contact, element) element } is FriendImage -> { patcher.patchFriendImageToGroupImage(contact, element) } else -> element } } } } private class ImageDecoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { markAsConsumed() when { data.notOnlineImage != null -> { collect(OnlineFriendImageImpl(data.notOnlineImage)) } data.customFace != null -> { collect(OnlineGroupImageImpl(data.customFace)) data.customFace.pbReserve.let { if (it.isNotEmpty() && it.loadAs(CustomFace.ResvAttr.serializer()).msgImageShow != null) { collect(ShowImageFlag) } } } data.commonElem != null && data.commonElem.serviceType == 48 -> { collect(OnlineNewTechImageImpl(data.commonElem)) } else -> { markNotConsumed() } } } } private class ImageEncoder : MessageEncoder<AbstractImage> { override suspend fun MessageEncoderContext.process(data: AbstractImage) { markAsConsumed() when (data) { is OfflineGroupImage -> { if (contact is User) { collect(ImMsgBody.Elem(notOnlineImage = data.toJceData().toNotOnlineImage())) } else { collect(ImMsgBody.Elem(customFace = data.toJceData())) } } is OnlineGroupImageImpl -> { if (contact is User) { collect(ImMsgBody.Elem(notOnlineImage = data.delegate.toNotOnlineImage())) } else { collect(ImMsgBody.Elem(customFace = data.delegate)) } } is OnlineFriendImageImpl -> { if (contact is User) { collect(ImMsgBody.Elem(notOnlineImage = data.delegate)) } else { collect(ImMsgBody.Elem(customFace = data.delegate.toCustomFace())) } } is OfflineFriendImage -> { if (contact is User) { collect(ImMsgBody.Elem(notOnlineImage = data.toJceData())) } else { collect(ImMsgBody.Elem(customFace = data.toJceData().toCustomFace())) } } is OnlineNewTechImageImpl -> { collect(ImMsgBody.Elem(commonElem = data.commonElem)) } } } companion object { private fun OfflineGroupImage.toJceData(): ImMsgBody.CustomFace { return ImMsgBody.CustomFace( fileId = this.fileId ?: 0, filePath = this.imageId, picMd5 = this.md5, flag = ByteArray(4), size = size.toInt(), width = width.coerceAtLeast(1), height = height.coerceAtLeast(1), imageType = getIdByImageType(imageType), origin = if (imageType == ImageType.GIF) { 0 } else { 1 }, //_400Height = 235, //_400Url = "/gchatpic_new/000000000/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", //_400Width = 351, // pbReserve = "08 00 10 00 32 00 50 00 78 08".autoHexToBytes(), bizType = 5, fileType = 66, useful = 1, // pbReserve = CustomFaceExtPb.ResvAttr().toByteArray(CustomFaceExtPb.ResvAttr.serializer()) ) } private fun ImMsgBody.CustomFace.toNotOnlineImage(): ImMsgBody.NotOnlineImage { val resId = calculateResId() return ImMsgBody.NotOnlineImage( filePath = filePath, resId = resId, oldPicMd5 = false, picWidth = width, picHeight = height, imgType = imageType, picMd5 = picMd5, fileLen = size.toLong(), oldVerSendFile = oldData, downloadPath = resId, original = origin, bizType = bizType, pbReserve = byteArrayOf(0x78, 0x02), ) } private fun ImMsgBody.NotOnlineImage.toCustomFace(): ImMsgBody.CustomFace { return ImMsgBody.CustomFace( filePath = generateImageId(picMd5, getImageType(imgType)), picMd5 = picMd5, bizType = 5, fileType = 66, useful = 1, flag = ByteArray(4), bigUrl = bigUrl, origUrl = origUrl, width = picWidth.coerceAtLeast(1), height = picHeight.coerceAtLeast(1), imageType = imgType, //_400Height = 235, //_400Url = "/gchatpic_new/000000000/1041235568-2195821338-01E9451B70EDEAE3B37C101F1EEBF5B5/400?term=2", //_400Width = 351, origin = original, size = fileLen.toInt() ) } // aka friend image id private fun ImMsgBody.NotOnlineImageOrCustomFace.calculateResId(): String { val url = origUrl.takeIf { it.isNotBlank() } ?: thumbUrl.takeIf { it.isNotBlank() } ?: _400Url.takeIf { it.isNotBlank() } ?: "" // gchatpic_new // offpic_new val picSenderId = url.substringAfter("pic_new/").substringBefore("/") .takeIf { it.isNotBlank() } ?: "000000000" val unknownInt = url.substringAfter("-").substringBefore("-") .takeIf { it.isNotBlank() } ?: "000000000" return "/$picSenderId-$unknownInt-${picMd5.toUHexString("")}" } private fun OfflineFriendImage.toJceData(): ImMsgBody.NotOnlineImage { val friendImageId = this.friendImageId return ImMsgBody.NotOnlineImage( filePath = friendImageId, resId = friendImageId, oldPicMd5 = false, picMd5 = this.md5, fileLen = size, downloadPath = friendImageId, original = if (imageType == ImageType.GIF) { 0 } else { 1 }, picWidth = width, picHeight = height, imgType = getIdByImageType(imageType), pbReserve = byteArrayOf(0x78, 0x02) ) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/LongMessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.contact.takeContent import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.flags.DontAsLongMessage import net.mamoe.mirai.internal.message.flags.ForceAsLongMessage import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.STEP import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageTransformer import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.utils.truncated internal class LongMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(OutgoingMessageTransformer { currentMessageChain = convertToLongMessageIfNeeded( currentMessageChain, attributes[STEP], attributes[CONTACT] ) }) } /** * Convert to [LongMessageInternal] iff [SendMessageStep.FIRST] has failed. */ private suspend fun OutgoingMessagePipelineContext.convertToLongMessageIfNeeded( chain: MessageChain, step: SendMessageStep, contact: AbstractContact, ): MessageChain { val uploader = components[HighwayUploader] suspend fun sendLongImpl(): MessageChain { val time = components[ClockHolder].local.currentTimeSeconds() val resId = uploader.uploadLongMessage(contact, components, chain, time.toInt()) return chain + createLongMessage( brief = chain.takeContent(27), resId = resId, timeSeconds = time ) // LongMessageInternal replaces all contents and preserves metadata } return when (step) { SendMessageStep.FIRST -> { // 只需要在第一次发送的时候验证长度 // 后续重试直接跳过 if (chain.contains(ForceAsLongMessage)) { return sendLongImpl() } if (!chain.contains(IgnoreLengthCheck)) { chain.verifyLength(chain, contact) } chain } SendMessageStep.LONG_MESSAGE -> { if (chain.contains(DontAsLongMessage)) chain // fragmented else sendLongImpl() } SendMessageStep.FRAGMENTED -> chain } } private fun createLongMessage(brief: String, resId: String, timeSeconds: Long): LongMessageInternal { val limited: String = brief.truncated(30) val template = """ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="$limited" m_resid="$resId" m_fileName="$timeSeconds" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="1"> <item layout="1"> <title>$limited</title> <hr hidden="false" style="0"/> <summary>点击查看完整消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg> """.trimIndent().trim() return LongMessageInternal(template, resId) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/MarketFaceProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.data.MarketFaceImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.collectGeneralFlags import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.copy import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.map internal class MarketFaceProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(DiceEncoder()) add(RockPaperScissorsEncoder()) add(MarketFaceImplEncoder()) add(MarketFaceDecoder()) // Serialization overview: // Using MarketFace as serial type: // - convert data to MarketFaceImpl on serialization. Convert them back to subtypes on deserialization. // - serial name is always "MarketFace" // Using subtypes: // - serial name is name of subtype, i.e. "MarketFace" / "Dice". // - Note that we don't use MarketFaceImpl but MarketFace for compatibility concerns. add( MessageSerializer( MarketFace::class, MarketFaceImpl.serializer().map( resultantDescriptor = MarketFaceImpl.serializer().descriptor.copy(MarketFace.SERIAL_NAME), deserialize = { it.delegate.toDiceOrNull() ?: it.delegate.toRockPaperScissorsOrNull() ?: it }, serialize = { when (it) { is Dice -> MarketFaceImpl(it.toJceStruct()) is RockPaperScissors -> MarketFaceImpl(it.toJceStruct()) is MarketFaceImpl -> it else -> { error("Unsupported MarketFace type ${it::class.qualifiedName}") } } } ), emptyArray() ) ) MessageSerializer.superclassesScope(MarketFace::class, MessageContent::class, SingleMessage::class) { add(MessageSerializer(MarketFaceImpl::class, MarketFaceImpl.serializer())) add(MessageSerializer(Dice::class, Dice.serializer())) add(MessageSerializer(RockPaperScissors::class, RockPaperScissors.serializer())) } } private class MarketFaceImplEncoder : MessageEncoder<MarketFaceImpl> { override suspend fun MessageEncoderContext.process(data: MarketFaceImpl) { collect(ImMsgBody.Elem(marketFace = data.delegate)) processAlso(PlainText(data.name)) collect(ImMsgBody.Elem(extraInfo = ImMsgBody.ExtraInfo(flags = 8, groupMask = 1))) collectGeneralFlags { ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_MARKET_FACE)) } } private companion object { 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 class DiceEncoder : MessageEncoder<Dice> { override suspend fun MessageEncoderContext.process(data: Dice) { markAsConsumed() processAlso(MarketFaceImpl(data.toJceStruct())) } } private class RockPaperScissorsEncoder : MessageEncoder<RockPaperScissors> { override suspend fun MessageEncoderContext.process(data: RockPaperScissors) { markAsConsumed() processAlso(MarketFaceImpl(data.toJceStruct())) } } private class MarketFaceDecoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val proto = data.marketFace ?: return proto.toDiceOrNull()?.let { collect(it) return } proto.toRockPaperScissorsOrNull()?.let { collect(it) return } collect(MarketFaceImpl(proto)) } } private companion object { /** * PC 客户端没有 [ImMsgBody.MarketFace.mobileParam], 是按 [ImMsgBody.MarketFace.faceId] 发的... */ @Suppress("SpellCheckingInspection") private val DICE_PC_FACE_IDS = mapOf( 1 to "E6EEDE15CDFBEB4DF0242448535354F1".hexToBytes(), 2 to "C5A95816FB5AFE34A58AF0E837A3B5A0".hexToBytes(), 3 to "382131D722EEA4624F087C5B8035AF5F".hexToBytes(), 4 to "FA90E956DCAD76742F2DB87723D3B669".hexToBytes(), 5 to "D51FA892017647431BB243920EC9FB8E".hexToBytes(), 6 to "7A2303AD80755FCB6BBFAC38327E0C01".hexToBytes(), ) private val RPS_PC_FACE_IDS = mapOf( 48 to "E5D889F1DF79B2B45183F625584465D3".hexToBytes(), 49 to "628FA4AB7B6C2BCCFCDCD0C2DAF7A60C".hexToBytes(), 50 to "457CDE420F598EB424CED2E905D38D8B".hexToBytes(), ) private fun ImMsgBody.MarketFace.toDiceOrNull(): Dice? { if (this.tabId != 11464) return null val value = when { mobileParam.isNotEmpty() -> mobileParam.lastOrNull()?.toInt()?.and(0xff)?.minus(47) ?: return null else -> DICE_PC_FACE_IDS.entries.find { it.value.contentEquals(faceId) }?.key ?: return null } if (value in 1..6) { return Dice(value) } return null } private fun ImMsgBody.MarketFace.toRockPaperScissorsOrNull(): RockPaperScissors? { if (tabId != 11415) return null val value = when { mobileParam.isNotEmpty() -> { val theLast = mobileParam.lastOrNull() ?: return null theLast.toInt().and(0xff) } else -> RPS_PC_FACE_IDS.entries.find { it.value.contentEquals(faceId) }?.key ?: return null } return when (value) { 48 -> RockPaperScissors.ROCK 49 -> RockPaperScissors.SCISSORS 50 -> RockPaperScissors.PAPER else -> null } } // From https://github.com/mamoe/mirai/issues/1012 private 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 RockPaperScissors.toJceStruct(): ImMsgBody.MarketFace { return ImMsgBody.MarketFace( faceName = byteArrayOf(91, -25, -116, -100, -26, -117, -77, 93), itemType = 6, faceInfo = 1, faceId = byteArrayOf( -125, -56, -94, -109, -82, 101, -54, 20, 15, 52, -127, 32, -89, 116, 72, -18 ), tabId = 11415, subType = 3, key = byteArrayOf(55, 100, 101, 51, 57, 102, 101, 98, 99, 102, 52, 53, 101, 54, 100, 98), mediaType = 0, imageWidth = 200, imageHeight = 200, mobileParam = byteArrayOf( 114, 115, 99, 84, 121, 112, 101, 63, 49, 59, 118, 97, 108, 117, 101, 61, internalId ), pbReserve = byteArrayOf(10, 6, 8, -56, 1, 16, -56, 1, 64, 1) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/MusicShareProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.Group import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.ORIGINAL_MESSAGE_AS_CHAIN import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePipelineContext.Companion.components import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageSender import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.network.protocol.packet.chat.MusicSharePacket import net.mamoe.mirai.message.data.* internal class MusicShareProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) // no decoder. refined from LightApp // add(Decoder()) add(Sender()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(MusicShare::class, MusicShare.serializer(), registerAlsoContextual = true)) } } private class Encoder : MessageEncoder<MusicShare> { override suspend fun MessageEncoderContext.process(data: MusicShare) { markAsConsumed() // 只有在 QuoteReply 的 source 里才会进行 MusicShare 转换, 因此可以转 PT. // 发送消息时会被特殊处理 processAlso(PlainText(data.content)) } } open class Sender : OutgoingMessageSender { override suspend fun OutgoingMessagePipelineContext.process() { val musicShare = currentMessageChain[MusicShare] ?: return markAsConsumed() val contact = attributes[CONTACT] val strategy = components[MessageProtocolStrategy] val bot = contact.bot sendMusicSharePacket(bot, musicShare, contact, strategy) val source = strategy.constructSourceForSpecialMessage(attributes[ORIGINAL_MESSAGE_AS_CHAIN], 3116) source.tryEnsureSequenceIdAvailable() collect(source.createMessageReceipt(contact, true)) } protected open suspend fun sendMusicSharePacket( bot: QQAndroidBot, musicShare: MusicShare, contact: AbstractContact, strategy: MessageProtocolStrategy<*> ) { val packet = MusicSharePacket( bot.client, musicShare, contact.id, targetKind = if (contact is Group) MessageSourceKind.GROUP else MessageSourceKind.FRIEND // always FRIEND ) val result = strategy.sendPacket(bot, packet) result.pkg.checkSuccess("send music share") } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/PokeMessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer 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.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.PokeMessage import net.mamoe.mirai.message.data.SingleMessage internal class PokeMessageProtocol : MessageProtocol() { companion object { val UNSUPPORTED_POKE_MESSAGE_PLAIN = PlainText("[戳一戳]请使用最新版手机QQ体验新功能。") } override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Decoder()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(PokeMessage::class, PokeMessage.serializer())) } } private class Encoder : MessageEncoder<PokeMessage> { override suspend fun MessageEncoderContext.process(data: PokeMessage) { markAsConsumed() collect( ImMsgBody.Elem( commonElem = ImMsgBody.CommonElem( serviceType = 2, businessType = data.pokeType, pbElem = HummerCommelem.MsgElemInfoServtype2( pokeType = data.pokeType, vaspokeId = data.id, vaspokeMinver = "7.2.0", vaspokeName = data.name ).toByteArray(HummerCommelem.MsgElemInfoServtype2.serializer()) ) ) ) processAlso(UNSUPPORTED_POKE_MESSAGE_PLAIN) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.commonElem == null) return if (data.commonElem.serviceType != 2) return markAsConsumed() val proto = data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype2.serializer()) val name = proto.vaspokeName.takeIf { it.isNotEmpty() } ?: PokeMessage.values.firstOrNull { it.id == proto.vaspokeId && it.pokeType == proto.pokeType }?.name .orEmpty() collect( PokeMessage( name = name, pokeType = proto.pokeType, id = proto.vaspokeId ) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/PttMessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.collectGeneralFlags import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.hexToBytes internal class PttMessageProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(At::class, At.serializer())) add(MessageSerializer(AtAll::class, AtAll.serializer())) add(MessageSerializer(PlainText::class, PlainText.serializer())) } } private class Encoder : MessageEncoder<PttMessage> { override suspend fun MessageEncoderContext.process(data: PttMessage) { markAsConsumed() collect( ImMsgBody.Elem( extraInfo = ImMsgBody.ExtraInfo(flags = 16, groupMask = 1) ) ) collect( ImMsgBody.Elem( elemFlags2 = ImMsgBody.ElemFlags2( vipStatus = 1 ) ) ) collectGeneralFlags { ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_PTT)) } } private companion object { 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() } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/QuoteReplyProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.AnonymousMember import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext.Companion.BOT import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext.Companion.GROUP_ID import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext.Companion.MESSAGE_SOURCE_KIND import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessagePreprocessor import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.message.source.* import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.map internal class QuoteReplyProtocol : MessageProtocol(PRIORITY_METADATA) { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Decoder()) add(OutgoingMessagePreprocessor { currentMessageChain[QuoteReply]?.source?.ensureSequenceIdAvailable() }) MessageSerializer.superclassesScope(MessageSource::class, MessageMetadata::class, SingleMessage::class) { add( MessageSerializer( OnlineMessageSourceFromGroupImpl::class, OnlineMessageSourceFromGroupImpl.serializer() ) ) add( MessageSerializer( OnlineMessageSourceFromFriendImpl::class, OnlineMessageSourceFromFriendImpl.serializer() ) ) add( MessageSerializer( OnlineMessageSourceFromTempImpl::class, OnlineMessageSourceFromTempImpl.serializer() ) ) add( MessageSerializer( OnlineMessageSourceFromStrangerImpl::class, OnlineMessageSourceFromStrangerImpl.serializer() ) ) add( MessageSerializer( OnlineMessageSourceToGroupImpl::class, OnlineMessageSourceToGroupImpl.serializer() ) ) add( MessageSerializer( OnlineMessageSourceToFriendImpl::class, OnlineMessageSourceToFriendImpl.serializer() ) ) add( MessageSerializer( OnlineMessageSourceToTempImpl::class, OnlineMessageSourceToTempImpl.serializer() ) ) add( MessageSerializer( OnlineMessageSourceToStrangerImpl::class, OnlineMessageSourceToStrangerImpl.serializer() ) ) add( MessageSerializer( OfflineMessageSourceImplData::class, OfflineMessageSourceImplData.serializer() ) ) } MessageSerializer.superclassesScope(MessageMetadata::class, SingleMessage::class) { @Suppress("DEPRECATION") add( MessageSerializer( MessageSource::class, OfflineMessageSourceImplData.serializer().map( MessageSourceSerializerImpl.serialDataSerializer().descriptor, { it }, { OfflineMessageSourceImplData( kind, ids, botId, time, fromId, targetId, originalMessage, internalIds ) } ), registerAlsoContextual = true ) ) } // add( // MessageSerializer( // MessageSource::class, // PolymorphicSerializer(MessageSource::class), // emptyArray(), // registerAlsoContextual = true // ) // ) MessageSerializer.superclassesScope(MessageMetadata::class, SingleMessage::class) { add(MessageSerializer(QuoteReply::class, QuoteReply.serializer())) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") add(MessageSerializer(ShowImageFlag::class, ShowImageFlag.Serializer)) add(MessageSerializer(MessageOrigin::class, MessageOrigin.serializer())) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.srcMsg == null) return markAsConsumed() collect( QuoteReply( OfflineMessageSourceImplData( data.srcMsg, attributes[BOT], attributes[MESSAGE_SOURCE_KIND], attributes[GROUP_ID] ) ) ) } } private class Encoder : MessageEncoder<QuoteReply> { override suspend fun MessageEncoderContext.process(data: QuoteReply) { val source = data.source as? MessageSourceInternal ?: return val sourceCommon = source as MessageSource markAsConsumed() collect(ImMsgBody.Elem(srcMsg = source.toJceData())) if (sourceCommon.kind == MessageSourceKind.GROUP) { fun isPlusNeed(): Boolean { if (source is OnlineMessageSource.Incoming.FromGroup) { try { val sender0 = source.sender return sender0 !is AnonymousMember } catch (_: IllegalStateException) { // Member not available now } } return sourceCommon.fromId != 80000000L // #2501 } if (isPlusNeed()) { processAlso(At(sourceCommon.fromId)) // transformOneMessage(PlainText(" ")) // removed by https://github.com/mamoe/mirai/issues/524 // 发送 QuoteReply 消息时无可避免的产生多余空格 #524 } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/RichMessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.message.data.ForwardMessageInternal import net.mamoe.mirai.internal.message.data.LightAppInternal import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.collectGeneralFlags import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.message.runWithBugReport import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* /** * Handles: * - [RichMessage] * - [LongMessageInternal] * - [ServiceMessage] * - [ForwardMessage] */ internal class RichMessageProtocol : MessageProtocol() { companion object { val UNSUPPORTED_MERGED_MESSAGE_PLAIN = PlainText("你的QQ暂不支持查看[转发多条消息],请期待后续版本。") } override fun ProcessorCollector.collectProcessorsImpl() { add(RichMsgDecoder()) add(LightAppDecoder()) add(Encoder()) MessageSerializer.superclassesScope( ServiceMessage::class, RichMessage::class, MessageContent::class, SingleMessage::class ) { add(MessageSerializer(SimpleServiceMessage::class, SimpleServiceMessage.serializer())) } MessageSerializer.superclassesScope(RichMessage::class, MessageContent::class, SingleMessage::class) { add(MessageSerializer(LightApp::class, LightApp.serializer())) } add(MessageSerializer(MessageOriginKind::class, MessageOriginKind.serializer(), emptyArray())) } private class Encoder : MessageEncoder<RichMessage> { override suspend fun MessageEncoderContext.process(data: RichMessage) { markAsConsumed() val content = data.content.toByteArray().deflate() var longTextResId: String? = null when (data) { is ForwardMessageInternal -> { collect( ImMsgBody.Elem( richMsg = ImMsgBody.RichMsg( serviceId = data.serviceId, // ok template1 = byteArrayOf(1) + content ) ) ) // transformOneMessage(UNSUPPORTED_MERGED_MESSAGE_PLAIN) } is LongMessageInternal -> { collect( ImMsgBody.Elem( richMsg = ImMsgBody.RichMsg( serviceId = data.serviceId, // ok template1 = byteArrayOf(1) + content ) ) ) processAlso(UNSUPPORTED_MERGED_MESSAGE_PLAIN) longTextResId = data.resId } is LightApp -> collect( ImMsgBody.Elem( lightApp = ImMsgBody.LightAppElem( data = byteArrayOf(1) + content ) ) ) else -> collect( ImMsgBody.Elem( richMsg = ImMsgBody.RichMsg( serviceId = when (data) { is ServiceMessage -> data.serviceId else -> error("unsupported RichMessage: ${data::class.simpleName}") }, template1 = byteArrayOf(1) + content ) ) ) } collectGeneralFlags { if (longTextResId != null) { ImMsgBody.Elem( generalFlags = ImMsgBody.GeneralFlags( longTextFlag = 1, longTextResid = longTextResId, pbReserve = "78 00 F8 01 00 C8 02 00".hexToBytes() ) ) } else { // 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 ImMsgBody.Elem(generalFlags = ImMsgBody.GeneralFlags(pbReserve = PB_RESERVE_FOR_RICH_MESSAGE)) } } } private companion object { 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 class LightAppDecoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val lightApp = data.lightApp ?: return markAsConsumed() val content = runWithBugReport("解析 lightApp", { "resId=" + lightApp.msgResid + "data=" + lightApp.data.toUHexString() }) { when (lightApp.data[0].toInt()) { 0 -> lightApp.data.decodeToString(startIndex = 1) 1 -> lightApp.data.toReadPacket(offset = 1).inflateInput().readAllText() else -> error("unknown compression flag=${lightApp.data[0]}") } } collect(LightAppInternal(content)) } } private class RichMsgDecoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.richMsg == null) return val richMsg = data.richMsg val content = runWithBugReport("解析 richMsg", { richMsg.template1.toUHexString() }) { when (richMsg.template1[0].toInt()) { 0 -> richMsg.template1.decodeToString(startIndex = 1) 1 -> richMsg.template1.toReadPacket(offset = 1).inflateInput().readAllText() else -> error("unknown compression flag=${richMsg.template1[0]}") } } fun findStringProperty(name: String): String { return content.substringAfter("$name=\"", "").substringBefore("\"", "") } val serviceId = when (val sid = richMsg.serviceId) { 0 -> { val serviceIdStr = findStringProperty("serviceID") if (serviceIdStr.isEmpty() || serviceIdStr.isBlank()) { 0 } else { serviceIdStr.toIntOrNull() ?: 0 } } else -> sid } when (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") collect(SimpleServiceMessage(1, content)) /** * [LongMessageInternal], [ForwardMessage] */ 35 -> { val resId = findStringProperty("m_resid") val fileName = findStringProperty("m_fileName").takeIf { it.isNotEmpty() } val msg = if (resId.isEmpty()) { // Nested ForwardMessage if (fileName != null && findStringProperty("action") == "viewMultiMsg") { ForwardMessageInternal(content, null, fileName) } else { SimpleServiceMessage(35, content) } } else when (findStringProperty("multiMsgFlag").toIntOrNull()) { 1 -> LongMessageInternal(content, resId) 0 -> ForwardMessageInternal(content, resId, fileName) else -> { // from PC QQ if (findStringProperty("action") == "viewMultiMsg") { ForwardMessageInternal(content, resId, fileName) } else { SimpleServiceMessage(35, content) } } } collect(msg) } // 104 新群员入群的消息 else -> { collect(SimpleServiceMessage(serviceId, content)) } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/ShortVideoProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.data.AbstractShortVideoWithThumbnail import net.mamoe.mirai.internal.message.data.OfflineShortVideoImpl import net.mamoe.mirai.internal.message.data.OnlineShortVideoImpl import net.mamoe.mirai.internal.message.data.OnlineShortVideoMsgInternal import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.ShortVideo import net.mamoe.mirai.message.data.SingleMessage internal class ShortVideoProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Decoder()) add(Encoder()) MessageSerializer.superclassesScope(ShortVideo::class, MessageContent::class, SingleMessage::class) { add(MessageSerializer(OfflineShortVideoImpl::class, OfflineShortVideoImpl.serializer())) add(MessageSerializer(OnlineShortVideoImpl::class, OnlineShortVideoImpl.serializer())) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val videoFile = data.videoFile ?: return markAsConsumed() collect(OnlineShortVideoMsgInternal(videoFile)) } } private class Encoder : MessageEncoder<AbstractShortVideoWithThumbnail> { override suspend fun MessageEncoderContext.process(data: AbstractShortVideoWithThumbnail) { markAsConsumed() collect(ImMsgBody.Elem(text = ImMsgBody.Text("你的 QQ 暂不支持查看视频短片,请期待后续版本。"))) val thumbWidth = if (data.thumbnail.width == null || data.thumbnail.width == 0) 1280 else data.thumbnail.width!! val thumbHeight = if (data.thumbnail.height == null || data.thumbnail.height == 0) 720 else data.thumbnail.height!! collect( ImMsgBody.Elem( videoFile = ImMsgBody.VideoFile( fileUuid = data.videoId.encodeToByteArray(), fileMd5 = data.fileMd5, fileName = data.filename.encodeToByteArray(), fileFormat = FORMAT.firstOrNull { it.first == data.fileFormat }?.second ?: 3, fileTime = 10, fileSize = data.fileSize.toInt(), thumbWidth = thumbWidth, thumbHeight = thumbHeight, thumbFileMd5 = data.thumbnail.md5, thumbFileSize = data.thumbnail.size.toInt(), busiType = 0, fromChatType = -1, toChatType = -1, boolSupportProgressive = true, fileWidth = thumbWidth, fileHeight = thumbHeight ) ) ) } } internal companion object { internal val FORMAT: List<Pair<String, Int>> = listOf( "ts" to 1, "avi" to 2, "mp4" to 3, "wmv" to 4, "mkv" to 5, "rmvb" to 6, "rm" to 7, "afs" to 8, "mov" to 9, "mod" to 10, "mts" to 11 ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/SuperFaceProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.collectGeneralFlags import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer 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.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.* import kotlin.text.toByteArray internal class SuperFaceProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Decoder()) add(Encoder()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(SuperFace::class, SuperFace.serializer())) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.commonElem == null) return if (data.commonElem.serviceType != 37) return markAsConsumed() val proto = data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype37.serializer()) collect(SuperFace(face = proto.qsId, id = proto.stickerId.decodeToString(), type = proto.stickerType)) } } private class Encoder : MessageEncoder<SuperFace> { override suspend fun MessageEncoderContext.process(data: SuperFace) { markAsConsumed() collect(ImMsgBody.Elem(commonElem = data.toCommData())) processAlso(PlainText("/${data.name}")) collectGeneralFlags { ImMsgBody.Elem( generalFlags = ImMsgBody.GeneralFlags( pbReserve = ImMsgBody.Text(str = "[${data.name}]请使用最新版手机QQ体验新功能") .toByteArray(ImMsgBody.Text.serializer()) ) ) } } } companion object { fun SuperFace.toCommData(): ImMsgBody.CommonElem { return ImMsgBody.CommonElem( serviceType = 37, pbElem = HummerCommelem.MsgElemInfoServtype37( packId = "1".encodeToByteArray(), stickerId = id.encodeToByteArray(), qsId = face, sourceType = 1, stickerType = type, text = "/${name}".toByteArray(), randomType = 1 ).toByteArray(HummerCommelem.MsgElemInfoServtype37.serializer()), businessType = type ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/TextProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmName("TextProtocol_common") package net.mamoe.mirai.internal.message.protocol.impl import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.CONTACT import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.isForward import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext.Companion.originalMessage import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.read import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmName /** * For [PlainText] and [At] */ internal class TextProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(PlainTextEncoder()) add(AtEncoder()) add(AtAllEncoder()) add(Decoder()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(PlainText::class, PlainText.serializer(), registerAlsoContextual = true)) add(MessageSerializer(At::class, At.serializer(), registerAlsoContextual = true)) add(MessageSerializer(AtAll::class, AtAll.serializer(), registerAlsoContextual = true)) add(MessageSerializer(Face::class, Face.serializer(), registerAlsoContextual = true)) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { val text = data.text ?: return markAsConsumed() if (text.attr6Buf.isEmpty()) { collect(PlainText(text.str)) } else { val id = text.attr6Buf.read { discardExact(7) readInt().toUInt().toLong() } if (id == 0L) { collect(AtAll) } else { collect(At(id)) // element.text.str } } } } private class PlainTextEncoder : MessageEncoder<PlainText> { override suspend fun MessageEncoderContext.process(data: PlainText) { markAsConsumed() collect(ImMsgBody.Elem(text = ImMsgBody.Text(str = data.content))) } } private class AtEncoder : MessageEncoder<At> { override suspend fun MessageEncoderContext.process(data: At) { markAsConsumed() collected += ImMsgBody.Elem( text = data.toJceData( attributes[CONTACT].safeCast(), originalMessage.sourceOrNull, isForward, ) ) // elements.add(ImMsgBody.Elem(text = ImMsgBody.Text(str = " "))) // removed by https://github.com/mamoe/mirai/issues/524 // 发送 QuoteReply 消息时无可避免的产生多余空格 #524 } private fun At.toJceData( group: Group?, source: MessageSource?, isForward: Boolean, ): ImMsgBody.Text { fun findFromGroup(g: Group?): String? { return g?.members?.get(this.target)?.nameCardOrNick } fun findFromSource(): String? { return when (source) { is OnlineMessageSource -> { return findFromGroup(source.target.safeCast()) } is OfflineMessageSource -> { if (source.kind == MessageSourceKind.GROUP) { return findFromGroup(group?.bot?.getGroup(source.targetId)) } else null } else -> null } } val nick = if (isForward) { findFromSource() ?: findFromGroup(group) } else { findFromGroup(group) ?: findFromSource() } ?: target.toString() val text = "@$nick".dropEmoji() return ImMsgBody.Text( str = text, attr6Buf = buildPacket { // MessageForText$AtTroopMemberInfo writeShort(1) // const writeShort(0) // startPos writeShort(text.length.toShort()) // textLen writeByte(0) // flag, may=1 writeInt(target.toInt()) // uin writeShort(0) // const }.readBytes() ) } companion object { // region Emoji pattern. <Licenced under the MIT LICENSE> // // https://github.com/mathiasbynens/emoji-test-regex-pattern // https://github.com/mathiasbynens/emoji-test-regex-pattern/blob/main/dist/latest/java.txt // @Suppress("RegExpSingleCharAlternation", "RegExpRedundantEscape") private val EMOJI_PATTERN: Regex? = runCatching { val resource = getEmojiPatternResourceOrNull() ?: return@runCatching null Regex(resource) }.getOrNull() // May some java runtime unsupported fun String.dropEmoji(): String { EMOJI_PATTERN?.let { regex -> return replace(regex, "") } return this } // endregion } } private class AtAllEncoder : MessageEncoder<AtAll> { override suspend fun MessageEncoderContext.process(data: AtAll) { markAsConsumed() collect(jceData) } companion object { private val jceData by lazy { ImMsgBody.Elem( text = ImMsgBody.Text( str = AtAll.display, attr6Buf = buildPacket { // MessageForText$AtTroopMemberInfo writeShort(1) // const writeShort(0) // startPos writeShort(AtAll.display.length.toShort()) // textLen writeByte(1) // flag, may=1 writeInt(0) // uin writeShort(0) // const }.readBytes() ) ) } } } } internal expect fun getEmojiPatternResourceOrNull(): String? ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/UnsupportedMessageProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.data.UnsupportedMessageImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.UnsupportedMessage internal class UnsupportedMessageProtocol : MessageProtocol(priority = PRIORITY_UNSUPPORTED) { override fun ProcessorCollector.collectProcessorsImpl() { add(Decoder()) add(Encoder()) MessageSerializer.superclassesScope(UnsupportedMessage::class, MessageContent::class, SingleMessage::class) { add( MessageSerializer( UnsupportedMessageImpl::class, UnsupportedMessageImpl.serializer() ) ) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { markAsConsumed() val struct = UnsupportedMessageImpl(data) if (struct.struct.isEmpty()) return collect(struct) } } private class Encoder : MessageEncoder<UnsupportedMessageImpl> { override suspend fun MessageEncoderContext.process(data: UnsupportedMessageImpl) { markAsConsumed() collect(data.structElem) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/impl/VipFaceProtocol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer 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.loadAs import net.mamoe.mirai.message.data.MessageContent import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.VipFace internal class VipFaceProtocol : MessageProtocol() { override fun ProcessorCollector.collectProcessorsImpl() { add(Encoder()) add(Decoder()) MessageSerializer.superclassesScope(MessageContent::class, SingleMessage::class) { add(MessageSerializer(VipFace::class, VipFace.serializer())) } } private class Encoder : MessageEncoder<VipFace> { override suspend fun MessageEncoderContext.process(data: VipFace) { markAsConsumed() processAlso(PlainText(data.contentToString())) } } private class Decoder : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { if (data.commonElem == null) return if (data.commonElem.serviceType != 23) return markAsConsumed() val proto = data.commonElem.pbElem.loadAs(HummerCommelem.MsgElemInfoServtype23.serializer()) collect(VipFace(VipFace.Kind(proto.faceType, proto.faceSummary), proto.faceBubbleCount)) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/outgoing/HighwayUploader.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.outgoing import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.nickIn import net.mamoe.mirai.internal.message.data.MultiMsgUploader import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.message.data.ForwardMessage import net.mamoe.mirai.message.data.MessageChain import kotlin.random.Random internal interface HighwayUploader { suspend fun uploadMessages( contact: AbstractContact, components: ComponentStorage, nodes: Collection<ForwardMessage.INode>, isLong: Boolean, senderName: String = contact.bot.nickIn(contact), ): String { nodes.forEach { it.messageChain.ensureSequenceIdAvailable() } val uploader = MultiMsgUploader( client = contact.bot.client, isLong = isLong, contact = contact, random = Random(components[ClockHolder].local.currentTimeSeconds()), senderName = senderName, components = components ).also { it.emitMain(nodes) } return uploader.uploadAndReturnResId() } suspend fun uploadLongMessage( contact: AbstractContact, components: ComponentStorage, chain: MessageChain, timeSeconds: Int, senderName: String = contact.bot.nickIn(contact), ): String { val bot = contact.bot return uploadMessages( contact, components, listOf( ForwardMessage.Node( senderId = bot.id, time = timeSeconds, messageChain = chain, senderName = senderName ) ), true, senderName = senderName, ) } companion object : ComponentKey<HighwayUploader> object Default : HighwayUploader } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/outgoing/MessageProtocolStrategy.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.outgoing import kotlinx.coroutines.Deferred import kotlinx.coroutines.withTimeoutOrNull import net.mamoe.mirai.event.EventPriority import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.nextEvent import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToFriendImpl import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor 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.receive.* import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OnlineMessageSource internal interface MessageProtocolStrategy<in C : AbstractContact> { suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet? { return bot.network.sendAndExpect(packet) } suspend fun <R : Packet?> sendPacket(bot: AbstractBot, packet: OutgoingPacketWithRespType<R>): R { return bot.network.sendAndExpect(packet) } /** * If [fragmented] is false, returned list must contain at most one element. */ suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: C, message: MessageChain, // to send originalMessage: MessageChain, // to create Receipt fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit, ): List<OutgoingPacket> suspend fun constructSourceForSpecialMessage( originalMessage: MessageChain, fromAppId: Int, ): OnlineMessageSource.Outgoing companion object : ComponentKey<MessageProtocolStrategy<*>> } internal sealed class UserMessageProtocolStrategy<C : AbstractUser> : MessageProtocolStrategy<C> { override suspend fun constructSourceForSpecialMessage( originalMessage: MessageChain, fromAppId: Int ): OnlineMessageSource.Outgoing { throw UnsupportedOperationException("Sending MusicShare or FileMessage to User is not yet supported") } } internal class FriendMessageProtocolStrategy( private val contact: FriendImpl, ) : UserMessageProtocolStrategy<FriendImpl>() { override suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: FriendImpl, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit ): List<OutgoingPacket> { return MessageSvcPbSendMsg.createToFriend(client, contact, message, originalMessage, fragmented, sourceCallback) } override suspend fun constructSourceForSpecialMessage( originalMessage: MessageChain, fromAppId: Int ): OnlineMessageSource.Outgoing { val receipt: PrivateMessageProcessor.SendPrivateMessageReceipt = withTimeoutOrNull(3000) { GlobalEventChannel.parentScope(this).nextEvent(EventPriority.MONITOR) { it.bot === contact.bot && it.fromAppId == fromAppId } } ?: PrivateMessageProcessor.SendPrivateMessageReceipt.EMPTY return OnlineMessageSourceToFriendImpl( internalIds = intArrayOf(receipt.messageRandom), sequenceIds = intArrayOf(receipt.sequenceId), sender = contact.bot, target = contact, time = contact.bot.clock.server.currentTimeSeconds().toInt(), originalMessage = originalMessage ) } } internal object StrangerMessageProtocolStrategy : UserMessageProtocolStrategy<StrangerImpl>() { override suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: StrangerImpl, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit ): List<OutgoingPacket> { return MessageSvcPbSendMsg.createToStranger( client, contact, message, originalMessage, fragmented, sourceCallback ) } } internal object GroupTempMessageProtocolStrategy : UserMessageProtocolStrategy<NormalMemberImpl>() { override suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: NormalMemberImpl, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit ): List<OutgoingPacket> { return MessageSvcPbSendMsg.createToTemp(client, contact, message, originalMessage, fragmented, sourceCallback) } } internal open class GroupMessageProtocolStrategy( private val contact: GroupImpl, ) : MessageProtocolStrategy<GroupImpl> { override suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: GroupImpl, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit ): List<OutgoingPacket> { return MessageSvcPbSendMsg.createToGroup(client, contact, message, originalMessage, fragmented, sourceCallback) } override suspend fun constructSourceForSpecialMessage( originalMessage: MessageChain, fromAppId: Int ): OnlineMessageSource.Outgoing { val receipt: GroupMessageProcessor.SendGroupMessageReceipt = withTimeoutOrNull(3000) { GlobalEventChannel.parentScope(this).nextEvent(EventPriority.MONITOR) { it.bot === contact.bot && it.fromAppId == fromAppId } } ?: GroupMessageProcessor.SendGroupMessageReceipt.EMPTY return OnlineMessageSourceToGroupImpl( contact, internalIds = intArrayOf(receipt.messageRandom), providedSequenceIds = intArrayOf(receipt.sequenceId), sender = contact.bot, target = contact, time = contact.bot.clock.server.currentTimeSeconds().toInt(), originalMessage = originalMessage ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipeline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.outgoing import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.MessageTooLargeException import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.contact.SendMessageStep import net.mamoe.mirai.internal.message.source.ensureSequenceIdAvailable import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.pipeline.* import net.mamoe.mirai.internal.utils.estimateLength import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* /////////////////////////////////////////////////////////////////////////// // Infrastructure for a ProcessorPipeline /////////////////////////////////////////////////////////////////////////// // Just to change this easily — you'd know it's actually Unit — a placeholder. internal typealias OutgoingMessagePipelineInput = MessageChain internal interface OutgoingMessagePipeline : ProcessorPipeline<OutgoingMessagePipelineProcessor, OutgoingMessagePipelineContext, OutgoingMessagePipelineInput, MessageReceipt<*>> internal open class OutgoingMessagePipelineImpl : AbstractProcessorPipeline<OutgoingMessagePipelineProcessor, OutgoingMessagePipelineContext, OutgoingMessagePipelineInput, MessageReceipt<*>>( PipelineConfiguration(stopWhenConsumed = true), @OptIn(TestOnly::class) defaultTraceLogging ), OutgoingMessagePipeline { inner class OutgoingMessagePipelineContextImpl( attributes: TypeSafeMap, override var currentMessageChain: MessageChain ) : OutgoingMessagePipelineContext, BaseContextImpl(attributes) { /** * Calls super [AbstractProcessorPipeline.BaseContextImpl.processAlso], Also updates [currentMessageChain]. */ override suspend fun processAlso( data: OutgoingMessagePipelineInput, extraAttributes: TypeSafeMap ): ProcessResult<out ProcessorPipelineContext<OutgoingMessagePipelineInput, MessageReceipt<*>>, MessageReceipt<*>> { return super.processAlso(data, extraAttributes).also { (context, _) -> this.currentMessageChain = (context as OutgoingMessagePipelineContext).currentMessageChain } } } override fun createContext( data: OutgoingMessagePipelineInput, attributes: TypeSafeMap ): OutgoingMessagePipelineContext = OutgoingMessagePipelineContextImpl(attributes, data) companion object { @TestOnly val defaultTraceLogging: MiraiLoggerWithSwitch by lazy { MiraiLogger.Factory.create(OutgoingMessagePipelineImpl::class, "OutgoingMessagePipeline") .withSwitch(systemProp("mirai.message.outgoing.pipeline.log.full", false)) } } } internal interface OutgoingMessagePipelineContext : ProcessorPipelineContext<OutgoingMessagePipelineInput, MessageReceipt<*>> { /** * Current message chain updated throughout the process. Will be updated from the [sub-processes][processAlso]. */ var currentMessageChain: MessageChain suspend fun MessageSource.tryEnsureSequenceIdAvailable() { val contact = attributes[CONTACT] val bot = contact.bot try { ensureSequenceIdAvailable() } catch (e: Exception) { bot.network.logger.warning( "Timeout awaiting sequenceId for message(${currentMessageChain.content.take(10)}). Some features may not work properly.", e ) } } fun Iterable<SingleMessage>.countImages(): Int = this.count { it is Image } fun Iterable<SingleMessage>.verifyLength( originalMessage: Message, target: Contact, ): Int { val chain = this val length = estimateLength(target, 15001) if (length > 15000 || countImages() > 50) { throw MessageTooLargeException( target, originalMessage, this.toMessageChain(), "message(${ chain.joinToString("", limit = 10).let { rsp -> if (rsp.length > 100) { rsp.take(100) + "..." } else rsp } }) is too large. Allow up to 50 images or 5000 chars" ) } return length } companion object { /** * Original */ val ORIGINAL_MESSAGE = TypeKey<Message>("originalMessage") /** * You should only use [ORIGINAL_MESSAGE_AS_CHAIN] if you can't use [ORIGINAL_MESSAGE] */ val ORIGINAL_MESSAGE_AS_CHAIN = TypeKey<MessageChain>("originalMessageAsChain") /** * Message chain used when retrying with next [step][SendMessageStep]s. */ val MESSAGE_TO_RETRY = TypeKey<MessageChain>("messageToRetry") /** * Message target */ val CONTACT = TypeKey<AbstractContact>("contact") val STEP = TypeKey<SendMessageStep>("step") val COMPONENTS = TypeKey<ComponentStorage>("components") val OutgoingMessagePipelineContext.components: ComponentStorage get() = attributes[COMPONENTS] } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessagePipelineProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.outgoing import net.mamoe.mirai.internal.pipeline.Processor internal sealed interface OutgoingMessagePipelineProcessor : Processor<OutgoingMessagePipelineContext, OutgoingMessagePipelineInput> /** * Adapter for [OutgoingMessageProcessor] to be used as [Processor]. */ internal class OutgoingMessageProcessorAdapter( private val processor: OutgoingMessageProcessor, ) : OutgoingMessagePipelineProcessor { override val origin: OutgoingMessageProcessor get() = processor override suspend fun process(context: OutgoingMessagePipelineContext, data: OutgoingMessagePipelineInput) { processor.run { context.process() } } override fun toString(): String { return "OutgoingMessageProcessorAdapter(transformer=$processor)" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/outgoing/OutgoingMessageProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.outgoing import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.pipeline.PipelineConsumptionMarker import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain /** * A handler that is responsible for some logic in sending a [MessageChain] to a target [Contact]. * * Broadcasting events is not of responsibility of [OutgoingMessageProcessor]. */ internal sealed interface OutgoingMessageProcessor { suspend fun OutgoingMessagePipelineContext.process() } /** * A preprocessor will be called in the first place. * * It is designed to do type conversions, and pre-conditional checks. * * **Preprocessors are not called in nested processing.** */ internal fun interface OutgoingMessagePreprocessor : OutgoingMessageProcessor /** * A transformer will be called after [Preprocessors][OutgoingMessagePreprocessor] and before [Postprocessors][OutgoingMessagePostprocessor]. * * It is capable for handling some special messages, e.g. transform long messages as [LongMessageInternal]. */ internal fun interface OutgoingMessageTransformer : OutgoingMessageProcessor /** * A transformer will be called after [Preprocessors][OutgoingMessagePreprocessor] and before [Postprocessors][OutgoingMessagePostprocessor]. * * It is capable for sending packets, and create [MessageReceipt]. * Senders can finish the pipeline by [OutgoingMessagePipelineContext.markAsConsumed]. */ internal fun interface OutgoingMessageSender : OutgoingMessageProcessor, PipelineConsumptionMarker /** * A postprocessor will be called in the last place. * * It is designed to do cleanup. * * Note that if an exception was thrown by previous processors, postprocessors may not be called, * so do not reply on the postprocessor to close resources. */ internal fun interface OutgoingMessagePostprocessor : OutgoingMessageProcessor //internal interface MessageRefiner // //internal fun interface DeepMessageRefiner : MessageRefiner, OutgoingMessageProcessor // //@RestrictsSuspension // only allowed to call `processAlso` //internal fun interface LightMessageRefiner : MessageRefiner { // suspend fun OutgoingMessagePipelineContext.process() //} ================================================ FILE: mirai-core/src/commonMain/kotlin/message/protocol/serialization/MessageSerializer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.serialization import kotlinx.serialization.KSerializer import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.ProcessorCollector import net.mamoe.mirai.message.data.SingleMessage import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmInline import kotlin.reflect.KClass /** * Collectd in [MessageProtocol.collectProcessors]. * @see ProcessorCollector.add */ internal class MessageSerializer<T : Any>( /** * The exact class the [serializer] is for */ val forClass: KClass<T>, val serializer: KSerializer<T>, /** * Polymorphic base */ val superclasses: Array<out KClass<in T>>, /** * Register also this as contextual */ val registerAlsoContextual: Boolean = superclasses.isEmpty(), ) { // This can help native targets, which has no reflection support. companion object { inline fun <T : SingleMessage, R> superclassesScope( vararg superclasses: KClass<in T>, block: SuperclassesScope<T>.() -> R ): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block(SuperclassesScope(superclasses)) } } } @JvmInline internal value class SuperclassesScope<T : SingleMessage>( val superclasses: Array<out KClass<in T>>, ) @Suppress("FunctionName") internal fun <T : SingleMessage> SuperclassesScope<in T>.MessageSerializer( forClass: KClass<T>, serializer: KSerializer<T>, registerAlsoContextual: Boolean = true, ): MessageSerializer<T> = MessageSerializer(forClass, serializer, superclasses, registerAlsoContextual) ================================================ FILE: mirai-core/src/commonMain/kotlin/message/rich/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.rich ================================================ FILE: mirai-core/src/commonMain/kotlin/message/source/MessageSourceInternal.kt ================================================ /* * 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.source import kotlinx.serialization.Transient import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.visitor.ex import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.cast /** * All [MessageSource] should implement this interface. */ internal interface MessageSourceInternal : MessageMetadata { @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: Boolean fun setRecalled(): Boolean // CAS fun toJceData(): ImMsgBody.SourceMsg override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.ex()?.visitMessageSourceInternal(this.cast(), data) ?: super.accept(visitor, data) } } /** * All [OnlineMessageSource.Outgoing] should implement this interface. */ internal interface OutgoingMessageSourceInternal : MessageSourceInternal { // #1371: // 问题是 `build` 得到的 `ForwardMessage` 会在 `transformSpecialMessages` // 时上传并变成 `ForwardMessageInternal` 再传递给 factory 发送, 并以这个 internal 结果构造了 receipt. // 于是构造 receipt 后会进行 light refine 并更新这个属性. /** * This 'overrides' [MessageSource.originalMessage]. */ var originalMessage: MessageChain /** * This for patch outgoing message source to real time (from server) */ var time: Int } /** * All [OnlineMessageSource.Incoming] should implement this interface. * */ internal interface IncomingMessageSourceInternal : MessageSourceInternal { // #1532, #1289 // 问题描述: 解析 Incoming 时存在中间元素 (如 ForwardMessageInternal) 等, // MessageChain.source.originMessage 中可能因为各种原因而存在这些中间元素 // 于是在广播 MessageEvent 前将 originalMessage 改成 refined 后的 MessageChain var originalMessageLazy: Lazy<MessageChain> } @Suppress("DEPRECATION_ERROR") internal fun <C : Contact> OnlineMessageSource.Outgoing.createMessageReceipt( target: C, doLightRefine: Boolean, ): MessageReceipt<C> { if (doLightRefine) { check(this is OutgoingMessageSourceInternal) { "Internal error: source !is OutgoingMessageSourceInternal" } this.originalMessage = this.originalMessage .dropMiraiInternalFlags() .refineLight(bot) } return MessageReceipt(this, target) } @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() } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/source/incomingSourceImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package net.mamoe.mirai.internal.message.source import kotlinx.atomicfu.atomic import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.internal.asQQAndroidBot 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.getGroupByUinOrCodeOrFail import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl import net.mamoe.mirai.internal.message.toMessageChainNoSource 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.network.protocol.data.proto.SourceMsg import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.encodeBase64 import net.mamoe.mirai.utils.mapToIntArray import net.mamoe.mirai.utils.structureToString @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceFromFriendImpl.Serializer::class) internal class OnlineMessageSourceFromFriendImpl( override val bot: Bot, private val msg: List<MsgComm.Msg>, ) : OnlineMessageSource.Incoming.FromFriend(), IncomingMessageSourceInternal { object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromFriend") override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq.and(0xFFFF) } private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) override val ids: IntArray get() = sequenceIds // msg.msgBody.richText.attr!!.random override val internalIds: IntArray = msg.mapToIntArray { it.decodeRandom() } override val time: Int = msg.first().msgHead.msgTime override var originalMessageLazy = lazy { msg.toMessageChainNoSource(bot, 0, MessageSourceKind.FRIEND) } override val originalMessage: MessageChain get() = originalMessageLazy.value override val isOriginalMessageInitialized: Boolean get() = originalMessageLazy.isInitialized() override val sender: Friend by lazy { if (fromId == bot.id) { bot.asFriend } else { bot.getFriendOrFail(fromId) } } override val subject: Friend by lazy { if (fromId == bot.id) { bot.getFriendOrFail(targetId) } else { bot.getFriendOrFail(fromId) } } override val fromId: Long get() = msg.first().msgHead.fromUin override val targetId: Long get() = msg.first().msgHead.toUin override val target: ContactOrBot by lazy { if (fromId == bot.id) { bot.getFriendOrFail(targetId) } else { bot } } private val jceData: ImMsgBody.SourceMsg by lazy { msg.toJceDataPrivate() } override fun toJceData(): ImMsgBody.SourceMsg = jceData override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<IncomingMessageSourceInternal>.accept(visitor, data) } } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceFromStrangerImpl.Serializer::class) internal class OnlineMessageSourceFromStrangerImpl( override val bot: Bot, private val msg: List<MsgComm.Msg>, ) : OnlineMessageSource.Incoming.FromStranger(), IncomingMessageSourceInternal { object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromStranger") override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq } private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) override val ids: IntArray get() = sequenceIds // msg.msgBody.richText.attr!!.random override val internalIds: IntArray = msg.mapToIntArray { it.decodeRandom() } override val time: Int = msg.first().msgHead.msgTime override var originalMessageLazy = lazy { msg.toMessageChainNoSource(bot, 0, MessageSourceKind.STRANGER) } override val originalMessage: MessageChain get() = originalMessageLazy.value override val isOriginalMessageInitialized: Boolean get() = originalMessageLazy.isInitialized() override val sender: Stranger by lazy { if (fromId == bot.id) { bot.asStranger } else { bot.getStrangerOrFail(fromId) } } override val subject: Stranger by lazy { if (fromId == bot.id) { bot.getStrangerOrFail(targetId) } else { bot.getStrangerOrFail(fromId) } } override val fromId: Long get() = msg.first().msgHead.fromUin override val targetId: Long get() = msg.first().msgHead.toUin override val target: ContactOrBot by lazy { if (fromId == bot.id) { bot.getStrangerOrFail(targetId) } else { bot } } private val jceData: ImMsgBody.SourceMsg by lazy { msg.toJceDataPrivate() } override fun toJceData(): ImMsgBody.SourceMsg = jceData override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<IncomingMessageSourceInternal>.accept(visitor, data) } } private fun List<MsgComm.Msg>.toJceDataPrivate(): ImMsgBody.SourceMsg { val elements = flatMap { it.msgBody.richText.elems }.toMutableList().also { if (it.lastOrNull()?.elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) } val firstMsgMsgHead = first().msgHead firstMsgMsgHead.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 = mutableListOf(firstMsgMsgHead.msgUid) ).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 = firstMsgMsgHead.msgUid, // ok // 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()) ) } } internal fun MsgComm.Msg.decodeRandom(): Int { msgBody.richText.attr?.random?.let { return it } // msg uin = 0x100000000000000 or rand.toLongUnsigned() return msgHead.msgUid.toInt() } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceFromTempImpl.Serializer::class) internal class OnlineMessageSourceFromTempImpl( override val bot: Bot, private val msg: List<MsgComm.Msg>, ) : OnlineMessageSource.Incoming.FromTemp(), IncomingMessageSourceInternal { object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromTemp") override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq } override val internalIds: IntArray = msg.mapToIntArray { it.decodeRandom() } private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) override val ids: IntArray get() = sequenceIds // override val time: Int = msg.first().msgHead.msgTime override var originalMessageLazy = lazy { msg.toMessageChainNoSource(bot, groupIdOrZero = 0, MessageSourceKind.TEMP) } override val originalMessage: MessageChain get() = originalMessageLazy.value override val isOriginalMessageInitialized: Boolean get() = originalMessageLazy.isInitialized() @Suppress("PropertyName") private val _group = with(msg.first().msgHead) { // it must be uin, see #1410 // corresponding test: net.mamoe.mirai.internal.notice.processors.MessageTest.group temp message test 2 // search for group code also is for tests. code may be passed as uin in tests. // clashing is unlikely possible in real time, so it would not be a problem. bot.asQQAndroidBot().getGroupByUinOrCodeOrFail(c2cTmpMsgHead!!.groupUin) } override val sender: Member by lazy { _group.getOrFail(fromId) } override val target: ContactOrBot by lazy { if (fromId == botId) { _group.getOrFail(targetId) } else bot } override val subject: Member by lazy { if (fromId == botId) { _group.getOrFail(targetId) } else { _group.getOrFail(fromId) } } override val fromId: Long get() = msg.first().msgHead.fromUin override val targetId: Long get() = msg.first().msgHead.toUin private val jceData: ImMsgBody.SourceMsg by lazy { msg.toJceDataPrivate() } override fun toJceData(): ImMsgBody.SourceMsg = jceData override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<IncomingMessageSourceInternal>.accept(visitor, data) } } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceFromGroupImpl.Serializer::class) internal class OnlineMessageSourceFromGroupImpl( override val bot: Bot, private val msg: List<MsgComm.Msg>, ) : OnlineMessageSource.Incoming.FromGroup(), IncomingMessageSourceInternal { object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceFromGroupImpl") private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) override val sequenceIds: IntArray = msg.mapToIntArray { it.msgHead.msgSeq } override val internalIds: IntArray = msg.mapToIntArray { it.decodeRandom() } override val ids: IntArray get() = sequenceIds override val time: Int = msg.first().msgHead.msgTime override var originalMessageLazy = lazy { msg.toMessageChainNoSource(bot, groupIdOrZero = group.id, MessageSourceKind.GROUP) } override val originalMessage: MessageChain get() = originalMessageLazy.value override val isOriginalMessageInitialized: Boolean get() = originalMessageLazy.isInitialized() override val subject: GroupImpl by lazy { val group = bot.getGroup(targetId)?.checkIsGroupImpl() ?: error("cannot find group for OnlineMessageSourceFromGroupImpl. Use `source.targetId` to get group id. msg=${msg.structureToString()}") group } override val sender: Member by lazy { val group = subject 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. Use `source.fromId` to get sender id. msg=${msg.structureToString()}") anonymousInfo.run { group.newAnonymous(anonGroupMsg!!.anonNick.decodeToString(), anonGroupMsg.anonId.encodeBase64()) } } override val fromId: Long get() = msg.first().msgHead.fromUin override val targetId: Long get() { return msg.first().msgHead.groupInfo?.groupCode ?: error("cannot find groupCode for OnlineMessageSourceFromGroupImpl. msg=${msg.structureToString()}") } private val jceData: ImMsgBody.SourceMsg by lazy { ImMsgBody.SourceMsg( origSeqs = intArrayOf(msg.first().msgHead.msgSeq), senderUin = msg.first().msgHead.fromUin, toUin = 0, flag = 1, elems = msg.flatMap { it.msgBody.richText.elems }, type = 0, time = msg.first().msgHead.msgTime, pbReserve = EMPTY_BYTE_ARRAY, srcMsg = EMPTY_BYTE_ARRAY ) } override fun toJceData(): ImMsgBody.SourceMsg { return jceData } override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<IncomingMessageSourceInternal>.accept(visitor, data) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/source/offlineSourceImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.source import kotlinx.atomicfu.atomic import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.message.toMessageChainNoSource 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.network.protocol.data.proto.SourceMsg import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.OfflineMessageSource import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.isSameType import net.mamoe.mirai.utils.mapToIntArray @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OfflineMessageSourceImplData.Serializer::class) internal class OfflineMessageSourceImplData( override val kind: MessageSourceKind, override val ids: IntArray, override val botId: Long, override val time: Int, override val fromId: Long, override val targetId: Long, private val originalMessageLazy: Lazy<MessageChain>, override val internalIds: IntArray, ) : OfflineMessageSource(), MessageSourceInternal { object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OfflineMessageSource") override val sequenceIds: IntArray get() = ids override val originalMessage: MessageChain by originalMessageLazy override val isOriginalMessageInitialized: Boolean get() = originalMessageLazy.isInitialized() // for override. // if provided, no need to serialize from message @Transient var originElems: List<ImMsgBody.Elem>? = null // may provided by OfflineMessageSourceImplBySourceMsg @Transient var jceData: ImMsgBody.SourceMsg? = null private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) override fun toJceData(): ImMsgBody.SourceMsg { jceData?.let { return it } return toJceDataImpl(null).also { jceData = it } } override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false val originElems = originElems if (originElems != null) { if (originElems == other.originElems) return true } if (kind != other.kind) return false if (!ids.contentEquals(other.ids)) return false if (botId != other.botId) return false if (time != other.time) return false if (fromId != other.fromId) return false if (targetId != other.targetId) return false if (originalMessage != other.originalMessage) return false if (!internalIds.contentEquals(other.internalIds)) return false return true } override fun hashCode(): Int { var result = kind.hashCode() result = 31 * result + ids.contentHashCode() result = 31 * result + botId.hashCode() result = 31 * result + time result = 31 * result + fromId.hashCode() result = 31 * result + targetId.hashCode() result = 31 * result + originalMessage.hashCode() result = 31 * result + internalIds.contentHashCode() return result } override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<MessageSourceInternal>.accept(visitor, data) } } private fun IntArray.fixIds(kind: MessageSourceKind): IntArray { if (kind == MessageSourceKind.FRIEND) { for (idx in this.indices) { this[idx] = this[idx].and(0xFFFF) } } return this } internal fun OfflineMessageSourceImplData( bot: Bot, delegate: List<MsgComm.Msg>, kind: MessageSourceKind, ): OfflineMessageSourceImplData { val head = delegate.first().msgHead return OfflineMessageSourceImplData( kind = kind, time = head.msgTime, fromId = head.fromUin, targetId = head.groupInfo?.groupCode ?: head.toUin, originalMessage = delegate.toMessageChainNoSource( bot, groupIdOrZero = head.groupInfo?.groupCode ?: 0, messageSourceKind = kind ), ids = delegate.mapToIntArray { it.msgHead.msgSeq }.fixIds(kind), internalIds = delegate.mapToIntArray { it.msgHead.msgUid.toInt() }, botId = bot.id ).apply { originElems = delegate.flatMap { it.msgBody.richText.elems } } } internal fun OfflineMessageSourceImplData( kind: MessageSourceKind, ids: IntArray, botId: Long, time: Int, fromId: Long, targetId: Long, originalMessage: MessageChain, internalIds: IntArray, ): OfflineMessageSourceImplData = OfflineMessageSourceImplData( kind = kind, ids = ids, botId = botId, time = time, fromId = fromId, targetId = targetId, originalMessageLazy = lazyOf(originalMessage), internalIds = internalIds ).also { it.originalMessage // initialize lazy, to make isOriginalMessageInitialized true. } @Suppress("LocalVariableName") internal fun OfflineMessageSourceImplData( delegate: ImMsgBody.SourceMsg, bot: Bot, _messageSourceKind: MessageSourceKind, _groupIdOrZero: Long, ): OfflineMessageSourceImplData { var messageSourceKind = _messageSourceKind var groupIdOrZero = _groupIdOrZero if (messageSourceKind != MessageSourceKind.GROUP && delegate.troopName.isNotEmpty()) { // FROM GROUP: 单独回复 messageSourceKind = MessageSourceKind.GROUP groupIdOrZero = 0 } return OfflineMessageSourceImplData( kind = messageSourceKind, ids = delegate.origSeqs.fixIds(messageSourceKind), internalIds = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer()) .origUids?.mapToIntArray { it.toInt() } ?: intArrayOf(), time = delegate.time, originalMessageLazy = lazy { val context = SimpleRefineContext(RefineContextKey.FromId to delegate.senderUin) delegate.toMessageChainNoSource(bot, messageSourceKind, groupIdOrZero, context) }, fromId = delegate.senderUin, targetId = when { groupIdOrZero != 0L -> groupIdOrZero delegate.toUin != 0L -> delegate.toUin delegate.srcMsg != null -> runCatching { delegate.srcMsg.loadAs(MsgComm.Msg.serializer()).msgHead.toUin }.getOrElse { 0L } else -> 0/*error("cannot find targetId. delegate=${delegate._miraiContentToString()}, delegate.srcMsg=${ kotlin.runCatching { delegate.srcMsg?.loadAs(MsgComm.Msg.serializer())?._miraiContentToString() } .fold( onFailure = { "<error: ${it.message}>" }, onSuccess = { it } ) }" )*/ }, botId = bot.id ).apply { jceData = delegate } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/source/outgoingSourceImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package net.mamoe.mirai.internal.message.source import kotlinx.atomicfu.atomic import kotlinx.coroutines.* import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.EventPriority import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.syncFromEvent import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor.SendGroupMessageReceipt 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.network.protocol.data.proto.SourceMsg import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.loadService import net.mamoe.mirai.utils.toLongUnsigned internal fun <T> T.toJceDataImpl(subject: ContactOrBot?): ImMsgBody.SourceMsg where T : MessageSourceInternal, T : MessageSource { val elements = MessageProtocolFacade.encode(originalMessage, subject, withGeneralFlags = false) val pdReserve = SourceMsg.ResvAttr( origUids = internalIds.map { 0x100000000000000 or it.toLongUnsigned() } // origUids = sequenceIds.zip(internalIds) // .map { (seq, internal) -> seq.toLong().shl(32) or internal.toLongUnsigned() } ) return ImMsgBody.SourceMsg( origSeqs = sequenceIds, senderUin = fromId, toUin = targetId, flag = 1, elems = elements, type = 0, time = time, pbReserve = pdReserve.toByteArray(SourceMsg.ResvAttr.serializer()), srcMsg = MsgComm.Msg( msgHead = MsgComm.MsgHead( fromUin = fromId, // qq toUin = targetId, // group msgType = 9, // 82? c2cCmd = 11, msgSeq = sequenceIds.first(), msgTime = time, msgUid = pdReserve.origUids!!.first(), // groupInfo = MsgComm.GroupInfo(groupCode = delegate.msgHead.groupInfo.groupCode), isSrcMsg = true ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = elements.toMutableList().also { if (it.lastOrNull()?.elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) } ) ) ).toByteArray(MsgComm.Msg.serializer()) ) } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceToFriendImpl.Serializer::class) internal class OnlineMessageSourceToFriendImpl( override val sequenceIds: IntArray, override val internalIds: IntArray, override var time: Int, override var originalMessage: MessageChain, override val sender: Bot, override val target: Friend, ) : OnlineMessageSource.Outgoing.ToFriend(), MessageSourceInternal, OutgoingMessageSourceInternal { object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToFriend") override val isOriginalMessageInitialized: Boolean get() = true override val bot: Bot get() = sender override val ids: IntArray get() = sequenceIds private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) private val jceData: ImMsgBody.SourceMsg by lazy { toJceDataImpl(subject) } override fun toJceData(): ImMsgBody.SourceMsg = jceData override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<OutgoingMessageSourceInternal>.accept(visitor, data) } } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceToStrangerImpl.Serializer::class) internal class OnlineMessageSourceToStrangerImpl( override val sequenceIds: IntArray, override val internalIds: IntArray, override var time: Int, override var originalMessage: MessageChain, override val sender: Bot, override val target: Stranger, ) : OnlineMessageSource.Outgoing.ToStranger(), MessageSourceInternal, OutgoingMessageSourceInternal { constructor( delegate: Outgoing, target: Stranger, ) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target) object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToStranger") override val isOriginalMessageInitialized: Boolean get() = true override val bot: Bot get() = sender override val ids: IntArray get() = sequenceIds private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) private val jceData: ImMsgBody.SourceMsg by lazy { toJceDataImpl(subject) } override fun toJceData(): ImMsgBody.SourceMsg = jceData override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<OutgoingMessageSourceInternal>.accept(visitor, data) } } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceToTempImpl.Serializer::class) internal class OnlineMessageSourceToTempImpl( override val sequenceIds: IntArray, override val internalIds: IntArray, override var time: Int, override var originalMessage: MessageChain, override val sender: Bot, override val target: Member, ) : OnlineMessageSource.Outgoing.ToTemp(), MessageSourceInternal, OutgoingMessageSourceInternal { constructor( delegate: Outgoing, target: Member, ) : this(delegate.ids, delegate.internalIds, delegate.time, delegate.originalMessage, delegate.sender, target) object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToTemp") override val isOriginalMessageInitialized: Boolean get() = true override val bot: Bot get() = sender override val ids: IntArray get() = sequenceIds private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) private val jceData: ImMsgBody.SourceMsg by lazy { toJceDataImpl(subject) } override fun toJceData(): ImMsgBody.SourceMsg = jceData override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<OutgoingMessageSourceInternal>.accept(visitor, data) } } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMessageSourceToGroupImpl.Serializer::class) internal class OnlineMessageSourceToGroupImpl( coroutineScope: CoroutineScope, override val internalIds: IntArray, // aka random override var time: Int, override var originalMessage: MessageChain, override val sender: Bot, override val target: Group, providedSequenceIds: IntArray? = null, ) : OnlineMessageSource.Outgoing.ToGroup(), MessageSourceInternal, OutgoingMessageSourceInternal { object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("OnlineMessageSourceToGroup") override val isOriginalMessageInitialized: Boolean get() = true override val ids: IntArray get() = sequenceIds override val bot: Bot get() = sender private val _isRecalledOrPlanned = atomic(false) @Transient override val isRecalledOrPlanned: Boolean get() = _isRecalledOrPlanned.value override fun setRecalled(): Boolean = _isRecalledOrPlanned.compareAndSet(expect = false, update = true) /** * Note that in tests result of this Deferred is always `null`. See TestMessageSourceSequenceIdAwaiter. */ private val sequenceIdDeferred: Deferred<IntArray?> = providedSequenceIds?.let { CompletableDeferred(it) } ?: run { MessageSourceSequenceIdAwaiter.instance.getSequenceIdAsync(this, coroutineScope) } @OptIn(ExperimentalCoroutinesApi::class) override val sequenceIds: IntArray get() = when { sequenceIdDeferred.isCompleted -> sequenceIdDeferred.getCompleted() ?: intArrayOf() !sequenceIdDeferred.isActive -> intArrayOf() else -> error("sequenceIds not yet available") } suspend fun ensureSequenceIdAvailable() = kotlin.run { sequenceIdDeferred.await() } private val jceData: ImMsgBody.SourceMsg by lazy { val elements = MessageProtocolFacade.encode(originalMessage, subject, withGeneralFlags = true) ImMsgBody.SourceMsg( origSeqs = sequenceIds, senderUin = fromId, toUin = target.uin, flag = 1, elems = elements, type = 0, time = time, pbReserve = SourceMsg.ResvAttr( origUids = internalIds.map { it.toLongUnsigned() } // ids is actually messageRandom ).toByteArray(SourceMsg.ResvAttr.serializer()), srcMsg = MsgComm.Msg( msgHead = MsgComm.MsgHead( fromUin = fromId, // qq toUin = target.uin, // group msgType = 82, // 82? c2cCmd = 1, msgSeq = sequenceIds.single(), msgTime = time, msgUid = internalIds.single().toLongUnsigned(), groupInfo = MsgComm.GroupInfo(groupCode = targetId), isSrcMsg = true ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = elements.toMutableList().also { if (it.lastOrNull()?.elemFlags2 == null) it.add(ImMsgBody.Elem(elemFlags2 = ImMsgBody.ElemFlags2())) } ) ) ).toByteArray(MsgComm.Msg.serializer()) ) } override fun toJceData(): ImMsgBody.SourceMsg = jceData override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return super<OutgoingMessageSourceInternal>.accept(visitor, data) } } internal open class MessageSourceSequenceIdAwaiter { open fun getSequenceIdAsync( sourceToGroupImpl: OnlineMessageSourceToGroupImpl, coroutineScope: CoroutineScope ): Deferred<IntArray?> { val multi = mutableMapOf<Int, Int>() return coroutineScope.async { withTimeoutOrNull( timeMillis = 3000L * sourceToGroupImpl.internalIds.size ) { GlobalEventChannel.parentScope(this) .syncFromEvent<SendGroupMessageReceipt, IntArray>(EventPriority.MONITOR) { receipt -> if (receipt.bot !== sourceToGroupImpl.bot) return@syncFromEvent null if (receipt.messageRandom in sourceToGroupImpl.internalIds) { multi[receipt.messageRandom] = receipt.sequenceId if (multi.size == sourceToGroupImpl.internalIds.size) { IntArray(multi.size) { index -> multi[sourceToGroupImpl.internalIds[index]]!! } } else null } else null } } } } companion object { @Suppress("ObjectPropertyName") private val _instance = atomic( loadService(MessageSourceSequenceIdAwaiter::class) { MessageSourceSequenceIdAwaiter() } ) val instance: MessageSourceSequenceIdAwaiter get() = _instance.value // Used for reset test env @TestOnly internal fun setInstance(instance: MessageSourceSequenceIdAwaiter) { _instance.value = instance } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/message/visitor/MessageVisitorEx.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.visitor import net.mamoe.mirai.internal.message.data.ForwardMessageInternal import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.MarketFaceImpl import net.mamoe.mirai.internal.message.flags.* import net.mamoe.mirai.internal.message.source.MessageSourceInternal import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.castOrNull internal fun <D, R> MessageVisitor<D, R>?.ex(): MessageVisitorEx<D, R>? = castOrNull() /** * For mirai-core-specific types */ internal interface MessageVisitorEx<in D, out R> : MessageVisitor<D, R> { fun <T> visitMessageSourceInternal(message: T, data: D): R where T : MessageSourceInternal, T : MessageSource { return visitMessageSource(message, data) } fun visitForwardMessageInternal(message: ForwardMessageInternal, data: D): R { return visitAbstractServiceMessage(message, data) } fun visitLongMessageInternal(message: LongMessageInternal, data: D): R { return visitAbstractServiceMessage(message, data) } fun visitMarketFaceImpl(message: MarketFaceImpl, data: D): R { return visitMarketFace(message, data) } fun visitInternalFlagOnlyMessage(message: InternalFlagOnlyMessage, data: D): R { return visitMessageMetadata(message, data) } fun visitForceAsLongMessage(message: ForceAsLongMessage, data: D): R { return visitInternalFlagOnlyMessage(message, data) } fun visitForceAsFragmentedMessage(message: ForceAsFragmentedMessage, data: D): R { return visitInternalFlagOnlyMessage(message, data) } fun visitDontAsLongMessage(message: DontAsLongMessage, data: D): R { return visitInternalFlagOnlyMessage(message, data) } fun visitIgnoreLengthCheck(message: IgnoreLengthCheck, data: D): R { return visitInternalFlagOnlyMessage(message, data) } fun visitSkipEventBroadcast(message: SkipEventBroadcast, data: D): R { return visitInternalFlagOnlyMessage(message, data) } fun visitAllowSendFileMessage(message: AllowSendFileMessage, data: D): R { return visitSkipEventBroadcast(message, data) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/ContactListCache.kt ================================================ /* * Copyright 2019-2022 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/dev/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.* internal val JsonForCache = Json { encodeDefaults = true ignoreUnknownKeys = true // isLenient = true // "null" will become null and cause errors 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, private val logger: MiraiLogger, ) { init { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") bot.eventChannel.parentScope(bot) .subscribeAlways<net.mamoe.mirai.event.events.BaseGroupMemberInfoChangeEvent> { groupListSaver.notice() } } private val changedGroups: MutableCollection<Long> = ConcurrentLinkedDeque() private val groupListSaver: ScheduledJob by lazy { ScheduledJob(bot.coroutineContext, bot.configuration.contactListCache.saveIntervalMillis) { runBIO { saveGroupCaches() } } } fun reportChanged(groupCode: Long) { changedGroups.add(groupCode) groupListSaver.notice() } private fun takeCurrentChangedGroups(): Map<Long, GroupMemberListCache> { val ret = HashMap<Long, GroupMemberListCache>() changedGroups.removeAll { ret[it] = get(it) true } return ret } private val cacheDir: MiraiFile by lazy { bot.configuration.groupCacheDir() } private fun resolveCacheFile(groupCode: Long): MiraiFile { 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.createNewFile() file.writeText(JsonForCache.encodeToString(GroupMemberListCache.serializer(), cache)) } 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) { try { val text = file.readText() if (text.isNotBlank()) { return JsonForCache.decodeFromString(GroupMemberListCache.serializer(), text) } } catch (e: Exception) { logger.warning( "Exception while loading GroupMemberListCache for group $id, possibly file corrupted. Deleting cache file.", e ) file.delete() } } GroupMemberListCache(0, emptyList()) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/DebuggingProperties.kt ================================================ /* * 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 internal object DebuggingProperties { const val SHOW_TLV_MAP_ON_LOGIN_SUCCESS = false } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/Packet.kt ================================================ /* * 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 net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.MiraiLogger /* // moved to `mirai-core` /** * 从服务器收到的包解析之后的结构化数据. * 它是一个数据包工厂的处理的返回值. */ interface Packet { /** * 实现这个接口的包将不会被记录到日志中 */ interface NoLog } */ /** * PacketFactory 可以一次解析多个包出来. 它们将会被分别广播. */ internal interface MultiPacket : Packet { /** * `true` if this packet has some useful meaning, otherwise it will be considered just as a wrapper of its children. */ val isMeaningful: Boolean /** * if item is [MultiPacket], its children will be ignored. */ fun children(): Iterator<Packet> } internal fun Collection<Packet>.toPacket(): Packet { return when (this.size) { 1 -> this.single() else -> MultiPacket(this) } } internal fun MultiPacket(delegate: Collection<Packet>): MultiPacket { return MultiPacketImpl(delegate) } internal fun MultiPacket(delegate: Packet): MultiPacket = if (delegate is MultiPacket) delegate else MultiPacket(listOf(delegate)) private class MultiPacketImpl( val delegate: Collection<Packet>, ) : MultiPacket { override val isMeaningful: Boolean get() = false override fun children(): Iterator<Packet> { return sequence { for (packet in delegate) { yield(packet) if (packet is MultiPacket) { yieldAll(packet.children()) } } }.iterator() } override fun toString(): String = delegate.joinToString( separator = "\n", prefix = "MultiPacket [\n", postfix = "]", ) } internal class ParseErrorPacket( val data: ProtocolStruct, val error: Throwable, val direction: Direction = Direction.TO_BOT_LOGGER, ) : Packet, Packet.NoLog { enum class Direction { TO_BOT_LOGGER { override fun getLogger(bot: AbstractBot): MiraiLogger = bot.logger }, TO_NETWORK_LOGGER { override fun getLogger(bot: AbstractBot): MiraiLogger = bot.network.logger }; abstract fun getLogger(bot: AbstractBot): MiraiLogger } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/QQAndroidClient.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "EXPERIMENTAL_API_USAGE", "DEPRECATION_ERROR", "unused") package net.mamoe.mirai.internal.network import io.ktor.utils.io.core.* import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.atomic import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.components.AccountSecrets import net.mamoe.mirai.internal.network.components.SsoProcessorContext import net.mamoe.mirai.internal.network.components.SsoSession import net.mamoe.mirai.internal.network.components.encryptServiceOrNull import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList import net.mamoe.mirai.internal.network.protocol.packet.Tlv import net.mamoe.mirai.internal.utils.AtomicIntSeq import net.mamoe.mirai.internal.utils.MiraiProtocolInternal import net.mamoe.mirai.internal.utils.NetworkType import net.mamoe.mirai.utils.* import kotlin.random.Random internal val DEFAULT_GUID = "%4;7t>;28<fc.5*6".toByteArray() /** * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] */ internal fun getRandomByteArray(length: Int): ByteArray = ByteArray(length) { Random.nextInt(0, 255).toByte() } // [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>> = "msfwifi.3g.qq.com:8080" .split(", ") .map { val host = it.substringBefore(':') val port = it.substringAfter(':').toInt() host to port }.shuffled().toMutableSet() /* APP ID: GetStViaSMSVerifyLogin = 16 GetStWithoutPasswd = 16 TICKET ID Pskey = 0x10_0000, from oicq/wlogin_sdk/request/WtloginHelper.java:2980 Skey = 0x1000 from oicq/wlogin_sdk/request/WtloginHelper.java:2986 DOMAINS Pskey: "openmobile.qq.com" */ /** * holds all the states related to network. */ internal open class QQAndroidClient( val account: BotAccount, val device: DeviceInfo, accountSecrets: AccountSecrets ) : AccountSecrets by accountSecrets, SsoSession { lateinit var _bot: QQAndroidBot val bot: QQAndroidBot get() = _bot /** * 真实 QQ 号. 使用邮箱等登录时则需获取这个 uin 进行后续一些操作. * * **注意**: 总是使用这个属性, 而不要使用 [BotAccount.id]. 将来它可能会变为 [String] */ val uin: Long get() = _uin override var outgoingPacketSessionId: ByteArray = 0x02B05B8B.toByteArray() override var loginState = 0 val supportedEncrypt by lazy { bot.encryptServiceOrNull?.supports(bot.configuration.protocol) ?: false } var onlineStatus: OnlineStatus = OnlineStatus.ONLINE var fileStoragePushFSSvcList: FileStoragePushFSSvcList? = null @Volatile private var _ssoSequenceId: Int = Random.nextInt(100000) @Synchronized @MiraiInternalApi("Do not use directly. Get from the lambda param of buildSsoPacket") internal fun nextSsoSequenceId(): Int { _ssoSequenceId += 2 val new = _ssoSequenceId if (new > 100000) { _ssoSequenceId = Random.nextInt(100000) + 60000 } return new } val apkVersionName: ByteArray get() = protocol.ver.toByteArray() //"8.4.18".toByteArray() val buildVer: String get() = protocol.buildVer // 8.2.0.1296 // 8.4.8.4810 // 8.2.7.4410 private val sequenceId: AtomicInt = atomic(getRandomUnsignedInt()) internal fun atomicNextMessageSequenceId(): Int = sequenceId.incrementAndGet() internal fun nextRequestPacketRequestId(): Int = sequenceId.incrementAndGet() @Volatile private var highwayDataTransSequenceId: Int = Random.nextInt(100000) @Synchronized internal fun nextHighwayDataTransSequenceId(): Int { highwayDataTransSequenceId += 1 val new = highwayDataTransSequenceId if (new > 1000000) { highwayDataTransSequenceId = Random.nextInt(1060000) } return new } internal var strangerSeq: Int = 0 /** * for send */ val sendFriendMessageSeq = AtomicIntSeq.forPrivateSync() internal val groupConfig: GroupConfig = GroupConfig() internal class GroupConfig { var robotConfigVersion: Int = 0 var aioKeyWordVersion: Int = 0 var robotUinRangeList: List<LongRange> = emptyList() fun isOfficialRobot(uin: Long): Boolean { return robotUinRangeList.any { range -> range.contains(uin) } } } var t150: Tlv? = null var rollbackSig: ByteArray? = null var ipFromT149: ByteArray? = null /** * 客户端与服务器时间差 */ var timeDifference: Long = 0 @Suppress("PropertyName") internal var _uin: Long = account.id var t530: ByteArray? = null var t528: ByteArray? = null var t174: ByteArray? = null /** * t186 */ var pwdFlag: Boolean = false lateinit var wFastLoginInfo: WFastLoginInfo var reserveUinInfo: ReserveUinInfo? = null var t402: ByteArray? = null lateinit var t104: ByteArray internal val t104Initialized get() = ::t104.isInitialized var t543: ByteArray? = null var t547: ByteArray? = null /** * t545 */ val qimei16: String? get() = bot.components[SsoProcessorContext].qimei16 val qimei36: String? get() = bot.components[SsoProcessorContext].qimei36 } internal val QQAndroidClient.apkId: ByteArray get() = protocol.apkId.toByteArray() internal val QQAndroidClient.ssoVersion: Int get() = protocol.ssoVersion internal val QQAndroidClient.networkType: NetworkType get() = NetworkType.WIFI internal val QQAndroidClient.appClientVersion: Int get() = 0 internal val QQAndroidClient.mainSigMap: Int get() = protocol.mainSigMap internal val QQAndroidClient.miscBitMap: Int get() = protocol.miscBitMap // 184024956 // 也可能是 150470524 ? internal val QQAndroidClient.clientVersion: String get() = "android ${protocol.ver}" // android 8.5.0 internal val QQAndroidClient.protocol get() = MiraiProtocolInternal[bot.configuration.protocol] internal val QQAndroidClient.sdkVersion: String get() = protocol.sdkVer internal val QQAndroidClient.buildTime: Long get() = protocol.buildTime internal val QQAndroidClient.subAppId: Long get() = protocol.id internal val QQAndroidClient.apkSignatureMd5: ByteArray get() = protocol.sign.hexToBytes() // "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D".hexToBytes() internal val QQAndroidClient.subSigMap: Int get() = protocol.subSigMap // 0x10400 //=66,560 ================================================ FILE: mirai-core/src/commonMain/kotlin/network/Ticket.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network internal class Ticket( val id: Int, val data: ByteArray, val key: ByteArray?, val creationTime: Long, val expireTime: Long ) { companion object { const val USER_A5 = 0x2 const val AQ_SIG = 0x200000 const val USER_SIG_64 = 0x2000 const val SUPER_KEY = 0x100000 const val OPEN_KEY = 0x4000 const val ACCESS_TOKEN = 0x8000 const val USER_ST_SIG = 0x80 const val USER_A8 = 0x10 const val LS_KEY = 0x200 const val S_KEY = 0x1000 const val V_KEY = 0x20000 const val TGT = 0x40 const val D2 = 0x40000 const val SID = 0x80000 const val USER_ST_WEB_SIG = 0x20 const val PAY_TOKEN = 0x800000 const val PF = 0x1000000 const val DA2 = 0x2000000 } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/auth/AuthControl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.auth import net.mamoe.mirai.auth.BotAuthInfo import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.internal.network.components.SsoProcessorImpl import net.mamoe.mirai.internal.utils.asUtilsLogger import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.channels.OnDemandChannel import net.mamoe.mirai.utils.channels.OnDemandReceiveChannel import net.mamoe.mirai.utils.channels.ProducerFailureException import kotlin.coroutines.CoroutineContext /** * Event sequence: * * 1. Starts a user coroutine [BotAuthorization.authorize]. * 2. User coroutine */ internal class AuthControl( private val botAuthInfo: BotAuthInfo, private val authorization: BotAuthorization, private val logger: MiraiLogger, parentCoroutineContext: CoroutineContext, ) { internal val exceptionCollector = ExceptionCollector() private val userDecisions: OnDemandReceiveChannel<Throwable?, SsoProcessorImpl.AuthMethod> = OnDemandChannel( parentCoroutineContext, logger.subLogger("AuthControl/UserDecisions").withSwitch(DEBUG_LOGGING).asUtilsLogger() ) { _ -> val sessionImpl = SafeBotAuthSession(this) authorization.authorize(sessionImpl, botAuthInfo) // OnDemandChannel handles exceptions for us } fun start() { userDecisions.expectMore(null) } // Does not throw suspend fun acquireAuth(): SsoProcessorImpl.AuthMethod { logger.verbose { "[AuthControl/acquire] Acquiring auth method" } val rsp = try { userDecisions.receiveOrNull() ?: SsoProcessorImpl.AuthMethod.NotAvailable } catch (e: ProducerFailureException) { SsoProcessorImpl.AuthMethod.Error(e.unwrap()) } logger.debug { "[AuthControl/acquire] Authorization responded: $rsp" } return rsp } fun actMethodFailed(cause: Throwable) { logger.verbose { "[AuthControl/resume] Fire auth failed with cause: $cause" } userDecisions.expectMore(cause) } fun actComplete() { logger.verbose { "[AuthControl/resume] Fire auth completed" } userDecisions.close() } private companion object { private val DEBUG_LOGGING = systemProp("mirai.network.auth.logging", false) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/auth/BotAuthSessionInternal.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.auth import net.mamoe.mirai.auth.BotAuthInfo import net.mamoe.mirai.auth.BotAuthResult import net.mamoe.mirai.auth.BotAuthSession import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.utils.SecretsProtection import net.mamoe.mirai.utils.md5 // With SecretsProtection support internal abstract class BotAuthSessionInternal : BotAuthSession { final override suspend fun authByPassword(password: String): BotAuthResult { return authByPassword(password.md5()) } final override suspend fun authByPassword(passwordMd5: ByteArray): BotAuthResult { return authByPassword(SecretsProtection.EscapedByteBuffer(passwordMd5)) } abstract suspend fun authByPassword(passwordMd5: SecretsProtection.EscapedByteBuffer): BotAuthResult } // With SecretsProtection support internal abstract class BotAuthorizationWithSecretsProtection : BotAuthorization { final override fun calculateSecretsKey(bot: BotAuthInfo): ByteArray { return calculateSecretsKeyImpl(bot).asByteArray } abstract fun calculateSecretsKeyImpl( bot: BotAuthInfo, ): SecretsProtection.EscapedByteBuffer abstract suspend fun authorize(session: BotAuthSessionInternal, bot: BotAuthInfo): BotAuthResult final override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult { return authorize(session as BotAuthSessionInternal, info) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/auth/DefaultBotAuthorizationFactoryImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.auth import net.mamoe.mirai.auth.BotAuthInfo import net.mamoe.mirai.auth.BotAuthResult import net.mamoe.mirai.auth.BotAuthSession import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.utils.SecretsProtection.EscapedByteBuffer /** * Provides default [BotAuthorization.byPassword] implementation. * @see net.mamoe.mirai.auth.DefaultBotAuthorizationFactory */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") internal class DefaultBotAuthorizationFactoryImpl : net.mamoe.mirai.auth.DefaultBotAuthorizationFactory { override fun byPassword(passwordMd5: ByteArray): BotAuthorization { val buffer = EscapedByteBuffer(passwordMd5) return byPassword(buffer) // Avoid referring passwordMd5(ByteArray) } private fun byPassword(buffer: EscapedByteBuffer): BotAuthorization { return object : BotAuthorizationWithSecretsProtection() { override fun calculateSecretsKeyImpl(bot: BotAuthInfo): EscapedByteBuffer = buffer override suspend fun authorize( session: BotAuthSessionInternal, bot: BotAuthInfo ): BotAuthResult = session.authByPassword(buffer) override fun toString(): String = "BotAuthorization.byPassword(<ERASED>)" } } override fun byQRCode(): BotAuthorization { return object : BotAuthorization { override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult = session.authByQRCode() override fun toString(): String = "BotAuthorization.byQRCode()" } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/auth/SafeBotAuthSession.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.auth import net.mamoe.mirai.auth.BotAuthResult import net.mamoe.mirai.internal.network.components.SsoProcessorImpl import net.mamoe.mirai.utils.SecretsProtection import net.mamoe.mirai.utils.channels.IllegalChannelStateException import net.mamoe.mirai.utils.channels.OnDemandSendChannel /** * Implements [BotAuthSessionInternal] from API, to be called by the user, to receive user's decisions. */ internal class SafeBotAuthSession( private val producer: OnDemandSendChannel<Throwable?, SsoProcessorImpl.AuthMethod> ) : BotAuthSessionInternal() { private val authResultImpl = object : BotAuthResult {} override suspend fun authByPassword(passwordMd5: SecretsProtection.EscapedByteBuffer): BotAuthResult { runWrapInternalException { producer.emit(SsoProcessorImpl.AuthMethod.Pwd(passwordMd5)) }?.let { throw it } return authResultImpl } override suspend fun authByQRCode(): BotAuthResult { runWrapInternalException { producer.emit(SsoProcessorImpl.AuthMethod.QRCode) }?.let { throw it } return authResultImpl } private inline fun <R> runWrapInternalException(block: () -> R): R { try { return block() } catch (e: IllegalChannelStateException) { if (e.lastStateWasSucceed) { throw IllegalStateException( "This login session has already completed. Please return the BotAuthResult you get from 'authBy*()' immediately", e ) } else { throw e // internal bug } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/component/ComponentKey.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("ComponentKeyKt_common") package net.mamoe.mirai.internal.network.component import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import kotlin.jvm.JvmName import kotlin.reflect.* /** * A key for specific component [T]. Components are not polymorphic. * * Most components locate in `net.mamoe.mirai.internal.network.components` while some like [MessageProtocolFacade] don't. * * @param T is a type hint. */ internal interface ComponentKey<T : Any> { companion object { /** * Get name of `T`. * * - If [qualified] is `false`, example: `PacketCodec`. * - If [qualified] is `true`, example: `net.mamoe.mirai.internal.network.components.PacketCodec`. */ fun <T : Any> ComponentKey<T>.componentName(qualified: Boolean = false): String { val argument = getComponentTypeArgument() argument?.render(qualified)?.let { return it } return argument?.type?.classifier.renderClassifier(qualified) } fun <T : Any> ComponentKey<T>.smartToString(qualified: Boolean = false): String { return "ComponentKey<${componentName(qualified)}>" } // reflection is slow, but it is initialized once only (if memory sufficient). private fun KTypeProjection.render( fullName: Boolean ): String? { val projection = this projection.type?.classifier?.let { classifier -> if (classifier is KClass<*>) { return classifier.run { if (fullName) qualifiedName else simpleName } ?: "?" } } projection.type?.arguments?.firstOrNull()?.let { argument -> return argument.render(fullName) } return null } private fun KClassifier?.renderClassifier( fullName: Boolean ): String { return when (val classifier = this) { null -> "?" is KClass<*> -> classifier.run { if (fullName) qualifiedName else simpleName } ?: "?" is KTypeParameter -> classifier.renderTypeParameter(fullName) else -> "?" } } private fun KType.renderType(fullName: Boolean) = classifier.renderClassifier(fullName) private fun KTypeParameter.renderTypeParameter(fullName: Boolean): String { val upperBounds = upperBounds return when (upperBounds.size) { 0 -> toString() 1 -> upperBounds[0].renderType(fullName) else -> upperBounds.joinToString(" & ") { it.renderType(fullName) } } } } } internal expect fun ComponentKey<*>.getComponentTypeArgument(): KTypeProjection? ================================================ FILE: mirai-core/src/commonMain/kotlin/network/component/ComponentStorage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import net.mamoe.mirai.utils.TestOnly import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** * Mediator for [component][ComponentKey]s accessing each other. * * Implementation must be thread-safe. * * @see MutableComponentStorage * @see ConcurrentComponentStorage * @see withFallback */ internal interface ComponentStorage { @TestOnly val size: Int @Throws(NoSuchComponentException::class) operator fun <T : Any> get(key: ComponentKey<T>): T fun <T : Any> getOrNull(key: ComponentKey<T>): T? val keys: Set<ComponentKey<*>> override fun toString(): String companion object { val EMPTY: ComponentStorage by lazy { ConcurrentComponentStorage() } } } internal fun buildComponentStorage(builderAction: MutableComponentStorage.() -> Unit): ComponentStorage { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } return ConcurrentComponentStorage(builderAction) } internal fun ComponentStorage?.withFallback(fallback: ComponentStorage?): ComponentStorage { if (this == null) return fallback ?: return ComponentStorage.EMPTY if (fallback == null) return this return CombinedComponentStorage(this, fallback) } internal fun ComponentStorage?.withPrimary(primary: ComponentStorage?): ComponentStorage = primary.withFallback(this) private class CombinedComponentStorage( val main: ComponentStorage, val fallback: ComponentStorage, ) : ComponentStorage { override val keys: Set<ComponentKey<*>> get() = main.keys + fallback.keys @TestOnly override val size: Int get() = main.size + fallback.size override fun <T : Any> get(key: ComponentKey<T>): T { return main.getOrNull(key) ?: fallback.getOrNull(key) ?: throw NoSuchComponentException(key, this) .apply { // kotlin.runCatching { main[key] }.exceptionOrNull()?.let(::addSuppressed) kotlin.runCatching { fallback[key] }.exceptionOrNull()?.let(::addSuppressed) } } override fun <T : Any> getOrNull(key: ComponentKey<T>): T? { return main.getOrNull(key) ?: fallback.getOrNull(key) } override fun toString(): String = buildString { appendLine("CombinedComponentStorage {") appendLine("* main:") main.toString().lines().forEach { append(" ").appendLine(it) } appendLine("*** fallback:") fallback.toString().lines().forEach { append(" ").appendLine(it) } appendLine("}") } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/component/ComponentStorageDelegate.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import net.mamoe.mirai.utils.TestOnly internal class ComponentStorageDelegate( private val instance: () -> ComponentStorage ) : ComponentStorage { @TestOnly override val size: Int get() = instance().size override fun <T : Any> get(key: ComponentKey<T>): T = instance()[key] override fun <T : Any> getOrNull(key: ComponentKey<T>): T? = instance().getOrNull(key) override val keys: Set<ComponentKey<*>> get() = instance().keys override fun toString(): String = instance().toString() } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/component/ConcurrentComponentStorage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import net.mamoe.mirai.internal.network.component.ComponentKey.Companion.componentName import net.mamoe.mirai.utils.ConcurrentHashMap import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.systemProp import kotlin.LazyThreadSafetyMode.NONE /** * A thread-safe implementation of [MutableComponentStorage] */ internal class ConcurrentComponentStorage( private val showAllComponents: Boolean = SHOW_ALL_COMPONENTS, private val creationStacktrace: Exception? = if (SHOW_COMPONENTS_CREATION_STACKTRACE) Exception("ConcurrentComponentStorage Creation stacktrace") else null, ) : ComponentStorage, MutableComponentStorage { private val map = ConcurrentHashMap<ComponentKey<*>, Any?>() override val keys: Set<ComponentKey<*>> get() = map.keys @TestOnly override val size: Int get() = map.size override operator fun <T : Any> get(key: ComponentKey<T>): T { return getOrNull(key) ?: throw NoSuchComponentException(key, this, creationStacktrace) } override fun <T : Any> getOrNull(key: ComponentKey<T>): T? { @Suppress("UNCHECKED_CAST") return map[key] as T? } override operator fun <T : Any> set(key: ComponentKey<T>, value: T) { map[key] = value } override fun <T : Any> remove(key: ComponentKey<T>): T? { @Suppress("UNCHECKED_CAST") return map.remove(key) as T? } override fun toString(): String { if (showAllComponents) { return buildString { append("ConcurrentComponentStorage(size=").append(map.size).append(") {").appendLine() for ((key, value) in map) { append(" ").append(key.componentName(qualified = false)).append(": ").append(value).appendLine() } append('}') } } return "ConcurrentComponentStorage(size=${map.size})" } } internal inline fun ConcurrentComponentStorage(builderAction: ConcurrentComponentStorage.() -> Unit): ConcurrentComponentStorage { return ConcurrentComponentStorage().apply(builderAction) } private val SHOW_ALL_COMPONENTS: Boolean by lazy(NONE) { systemProp("mirai.network.show.all.components", false) } private val SHOW_COMPONENTS_CREATION_STACKTRACE: Boolean by lazy(NONE) { systemProp( "mirai.network.show.components.creation.stacktrace", false ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/component/MutableComponentStorage.kt ================================================ /* * 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.component import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.cast internal interface MutableComponentStorage : ComponentStorage { override operator fun <T : Any> get(key: ComponentKey<T>): T operator fun <T : Any> set(key: ComponentKey<T>, value: T) fun <T : Any> remove(key: ComponentKey<T>): T? } @TestOnly internal fun MutableComponentStorage.setAll(other: ComponentStorage) { for (key in other.keys) { set(key.cast(), other[key]) } } internal operator fun <T : Any> MutableComponentStorage.set(key: ComponentKey<T>, value: T?) { if (value == null) { remove(key) } else { set(key, value) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/component/NoSuchComponentException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component /** * Should not be thrown normally. */ internal data class NoSuchComponentException( val key: ComponentKey<*>, val storage: ComponentStorage, override val cause: Throwable? = null ) : NoSuchElementException() { override val message: String by lazy { "No such component '$key' in storage: \n$storage" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/AccountSecretsManager.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import io.ktor.utils.io.core.* import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.network.LoginExtraData import net.mamoe.mirai.internal.network.WLoginSigInfo import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.getRandomByteArray import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.get_mpasswd import net.mamoe.mirai.internal.utils.accountSecretsFile import net.mamoe.mirai.internal.utils.crypto.QQEcdhInitialPublicKey import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.utils.* import kotlin.jvm.Synchronized import kotlin.jvm.Volatile import kotlin.random.Random /** * For a [Bot]. * * @see MemoryAccountSecretsManager * @see FileCacheAccountSecretsManager * @see CombinedAccountSecretsManager */ internal interface AccountSecretsManager : Cacheable { fun saveSecrets(account: BotAccount, secrets: AccountSecrets) fun getSecrets(account: BotAccount): AccountSecrets? override fun invalidate() companion object : ComponentKey<AccountSecretsManager> } /** * Secrets for authentication with server. (login) */ internal interface AccountSecrets { var wLoginSigInfoField: WLoginSigInfo? val wLoginSigInfoInitialized get() = wLoginSigInfoField != null var wLoginSigInfo: WLoginSigInfo get() = wLoginSigInfoField ?: error("wLoginSigInfoField is not yet initialized") set(value) { wLoginSigInfoField = value } /** * t537 */ var loginExtraData: MutableSet<LoginExtraData> var G: ByteArray // sigInfo[2] var dpwd: ByteArray var randSeed: ByteArray // t403 /** * t108 时更新 */ var ksid: ByteArray var tgtgtKey: ByteArray val randomKey: ByteArray var ecdhInitialPublicKey: QQEcdhInitialPublicKey } @Serializable internal data class AccountSecretsImpl( override var loginExtraData: MutableSet<LoginExtraData>, override var wLoginSigInfoField: WLoginSigInfo?, override var G: ByteArray, override var dpwd: ByteArray = get_mpasswd().toByteArray(), override var randSeed: ByteArray, override var ksid: ByteArray, override var tgtgtKey: ByteArray, override val randomKey: ByteArray, override var ecdhInitialPublicKey: QQEcdhInitialPublicKey, ) : AccountSecrets, ProtoBuf { override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (loginExtraData != other.loginExtraData) return false if (wLoginSigInfoField != other.wLoginSigInfoField) return false if (!G.contentEquals(other.G)) return false if (!dpwd.contentEquals(other.dpwd)) return false if (!randSeed.contentEquals(other.randSeed)) return false if (!ksid.contentEquals(other.ksid)) return false if (!tgtgtKey.contentEquals(other.tgtgtKey)) return false if (!randomKey.contentEquals(other.randomKey)) return false if (ecdhInitialPublicKey != other.ecdhInitialPublicKey) return false return true } override fun hashCode(): Int { var result = loginExtraData.hashCode() result = 31 * result + (wLoginSigInfoField?.hashCode() ?: 0) result = 31 * result + G.contentHashCode() result = 31 * result + dpwd.contentHashCode() result = 31 * result + randSeed.contentHashCode() result = 31 * result + ksid.contentHashCode() result = 31 * result + tgtgtKey.contentHashCode() result = 31 * result + randomKey.contentHashCode() result = 31 * result + ecdhInitialPublicKey.hashCode() return result } } internal fun AccountSecretsImpl( other: AccountSecrets, ): AccountSecretsImpl = other.run { AccountSecretsImpl( loginExtraData, wLoginSigInfoField, G, dpwd, randSeed, ksid, tgtgtKey, randomKey, ecdhInitialPublicKey ) } internal fun AccountSecretsImpl( device: DeviceInfo, ): AccountSecretsImpl { return AccountSecretsImpl( loginExtraData = ConcurrentSet(), wLoginSigInfoField = null, G = device.guid, dpwd = get_mpasswd().toByteArray(), randSeed = EMPTY_BYTE_ARRAY, ksid = EMPTY_BYTE_ARRAY, tgtgtKey = (Random.nextBytes(16) + device.guid).md5(), randomKey = getRandomByteArray(16), ecdhInitialPublicKey = QQEcdhInitialPublicKey.default ) } internal fun AccountSecretsManager.getSecretsOrCreate(account: BotAccount, device: DeviceInfo): AccountSecrets { var secrets = getSecrets(account) if (secrets == null) { secrets = AccountSecretsImpl(device) saveSecrets(account, secrets) } return secrets } internal class MemoryAccountSecretsManager : AccountSecretsManager { @Volatile private var instance: AccountSecrets? = null @Synchronized override fun saveSecrets(account: BotAccount, secrets: AccountSecrets) { instance = secrets } @Synchronized override fun getSecrets(account: BotAccount): AccountSecrets? = this.instance @Synchronized override fun invalidate() { instance = null } } internal class FileCacheAccountSecretsManager( val file: MiraiFile, val logger: MiraiLogger, ) : AccountSecretsManager { @Synchronized override fun saveSecrets(account: BotAccount, secrets: AccountSecrets) { if (secrets.wLoginSigInfoField == null) return saveSecretsToFile(file, account, secrets) logger.info { "Saved account secrets to local cache for fast login." } } @Synchronized override fun getSecrets(account: BotAccount): AccountSecrets? { return getSecretsImpl(account) } private fun getSecretsImpl(account: BotAccount): AccountSecrets? { if (!file.exists()) return null val loaded = kotlin.runCatching { TEA.decrypt(file.readBytes(), account.accountSecretsKey).loadAs(AccountSecretsImpl.serializer()) }.getOrElse { e -> if (e.message == "Field 'ecdhInitialPublicKey' is required for type with serial name 'net.mamoe.mirai.internal.network.components.AccountSecretsImpl', but it was missing") { logger.info { "Detected old account secrets, invalidating..." } } else { logger.error("Failed to load account secrets from local cache. Invalidating cache...", e) } file.delete() return null } logger.info { "Loaded account secrets from local cache." } return loaded } @Synchronized override fun invalidate() { file.delete() } companion object { fun saveSecretsToFile(file: MiraiFile, account: BotAccount, secrets: AccountSecrets) { file.writeBytes( TEA.encrypt( AccountSecretsImpl(secrets).toByteArray(AccountSecretsImpl.serializer()), account.accountSecretsKey ) ) } } } internal class CombinedAccountSecretsManager( private val primary: AccountSecretsManager, private val alternative: AccountSecretsManager, ) : AccountSecretsManager { override fun saveSecrets(account: BotAccount, secrets: AccountSecrets) { primary.saveSecrets(account, secrets) alternative.saveSecrets(account, secrets) } override fun getSecrets(account: BotAccount): AccountSecrets? { return primary.getSecrets(account) ?: alternative.getSecrets(account) } override fun invalidate() { primary.invalidate() alternative.invalidate() } } /** * Create a [CombinedAccountSecretsManager] with [MemoryAccountSecretsManager] as primary and [FileCacheAccountSecretsManager] as an alternative. */ internal fun BotConfiguration.createAccountsSecretsManager(logger: MiraiLogger): AccountSecretsManager { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return if (accountSecrets) { CombinedAccountSecretsManager( MemoryAccountSecretsManager(), FileCacheAccountSecretsManager(accountSecretsFile(), logger) ) } else { MemoryAccountSecretsManager() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/BdhSessionSyncer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.SetSerializer import net.mamoe.mirai.internal.network.JsonForCache import net.mamoe.mirai.internal.network.ProtoBufForCache import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.utils.actualCacheDir import net.mamoe.mirai.utils.* import kotlin.jvm.Volatile internal interface BdhSessionSyncer : Cacheable { val bdhSession: CompletableDeferred<BdhSession> val hasSession: Boolean fun overrideSession( session: BdhSession, doSave: Boolean = true ) fun loadServerListFromCache() fun loadFromCache() fun saveServerListToCache() fun saveToCache() companion object : ComponentKey<BdhSessionSyncer> } @Serializable internal class BdhSession( val sigSession: ByteArray, val sessionKey: ByteArray, var ssoAddresses: MutableSet<Pair<Int, Int>> = ConcurrentSet(), var otherAddresses: MutableSet<Pair<Int, Int>> = ConcurrentSet(), ) private val ServerListSerializer: KSerializer<Set<ServerAddress>> = SetSerializer(ServerAddress.serializer()) @OptIn(ExperimentalCoroutinesApi::class) internal class BdhSessionSyncerImpl( private val configuration: BotConfiguration, private val context: ComponentStorage, private val logger: MiraiLogger, ) : BdhSessionSyncer { @Volatile override var bdhSession: CompletableDeferred<BdhSession> = CompletableDeferred() override val hasSession: Boolean get() = kotlin.runCatching { bdhSession.getCompleted() }.isSuccess override fun overrideSession( session: BdhSession, doSave: Boolean ) { bdhSession.complete(session) bdhSession = CompletableDeferred(session) if (doSave) { saveToCache() } } private val sessionCacheFile: MiraiFile get() = configuration.actualCacheDir().resolve("session.bin") private val serverListCacheFile: MiraiFile get() = configuration.actualCacheDir().resolve("servers.json") override fun invalidate() { sessionCacheFile.delete() serverListCacheFile.delete() } override fun loadServerListFromCache() { val serverListCacheFile = this.serverListCacheFile if (serverListCacheFile.isFile) { logger.verbose("Loading server list from cache.") kotlin.runCatching { val list = JsonForCache.decodeFromString(ServerListSerializer, serverListCacheFile.readText()) context[ServerList].setPreferred(list.map { ServerAddress(it.host, it.port) }) }.onFailure { logger.warning("Error in loading server list from cache", it) } } else { logger.verbose("No server list cached.") } } override fun loadFromCache() { val sessionCacheFile = this.sessionCacheFile if (sessionCacheFile.isFile) { logger.verbose("Loading BdhSession from cache file") kotlin.runCatching { overrideSession( ProtoBufForCache.decodeFromByteArray(BdhSession.serializer(), sessionCacheFile.readBytes()), doSave = false ) }.onFailure { kotlin.runCatching { sessionCacheFile.delete() } logger.warning("Error in loading BdhSession from cache", it) } } else { logger.verbose("No BdhSession cache") } } override fun saveServerListToCache() { val serverListCacheFile = this.serverListCacheFile serverListCacheFile.parent?.mkdirs() logger.verbose("Saving server list to cache") kotlin.runCatching { serverListCacheFile.writeText( JsonForCache.encodeToString( ServerListSerializer, context[ServerList].getPreferred() ) ) }.onFailure { logger.warning("Error in saving ServerList to cache.", it) } } override fun saveToCache() { val sessionCacheFile = this.sessionCacheFile sessionCacheFile.parent?.mkdirs() if (bdhSession.isCompleted) { logger.verbose("Saving bdh session to cache") kotlin.runCatching { sessionCacheFile.writeBytes( ProtoBufForCache.encodeToByteArray( BdhSession.serializer(), bdhSession.getCompleted() ) ) }.onFailure { logger.warning("Error in saving BdhSession to cache.", it) } } else { sessionCacheFile.delete() logger.verbose("No BdhSession to save to cache") } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/BotClientHolder.kt ================================================ /* * 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.components import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.lateinitMutableProperty internal interface BotClientHolder { var client: QQAndroidClient fun refreshClient() companion object : ComponentKey<BotClientHolder> } internal class BotClientHolderImpl( private val bot: QQAndroidBot, private val logger: MiraiLogger, ) : BotClientHolder { override var client: QQAndroidClient by lateinitMutableProperty { createClient(bot) } override fun refreshClient() { client = createClient(bot) } private fun createClient(bot: QQAndroidBot): QQAndroidClient { val ssoContext = bot.components[SsoProcessorContext] val device = ssoContext.device return QQAndroidClient( ssoContext.account, device = device, accountSecrets = bot.components[AccountSecretsManager].getSecretsOrCreate(ssoContext.account, device) ).apply { _bot = bot } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/BotInitProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.atomicfu.atomic import kotlinx.coroutines.CancellationException import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.handler.state.JobAttachStateObserver import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPushForceOffline import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.Symbol /** * Handles initialization jobs after successful logon. * * The initialization includes: * - Downloading contact list, which might read from local cache * - Synchronizing message sequence id * - Synchronizing BDH session for resource uploading * * Calls [ContactUpdater], [OtherClientUpdater], [ConfigPushSyncer], ... (see [BotInitProcessorImpl]) * * Attached to handler state [NetworkHandler.State.LOADING] [as state observer][asObserver] in [QQAndroidBot.stateObserverChain]. */ internal interface BotInitProcessor { /** * Do initialization. Implementor must ensure initialization runs exactly single time. */ suspend fun init() /** * Called when login was potentially halted, meaning the data might not have been loaded, * so we need to set the flag that helps keep single-initialization to UNINITIALIZED. * * This is called in [MessageSvcPushForceOffline], which is in case connection is closed by server during the [NetworkHandler.State.LOADING] state. * * This function only marks current initialization work has failed. It has nothing to do with result of login. * To update that result, update `bot.components[SsoProcessor].firstLoginResult`. * * See [BotInitProcessorImpl.state]. */ fun setLoginHalted() companion object : ComponentKey<BotInitProcessor> } internal fun BotInitProcessor.asObserver(targetState: State = State.LOADING): StateObserver { return JobAttachStateObserver("BotInitProcessor.init", targetState) { init() } } internal class BotInitProcessorImpl( private val bot: QQAndroidBot, private val context: ComponentStorage, private val logger: MiraiLogger, ) : BotInitProcessor { companion object { private val UNINITIALIZED = Symbol("UNINITIALIZED") private val INITIALIZING = Symbol("INITIALIZING") private val INITIALIZED = Symbol("INITIALIZED") } private val state = atomic(UNINITIALIZED) override fun setLoginHalted() { state.compareAndSet(expect = INITIALIZING, update = UNINITIALIZED) } override suspend fun init() { if (!state.compareAndSet(expect = UNINITIALIZED, update = INITIALIZING)) return try { check(bot.isActive) { "bot is dead therefore network can't init." } context[ContactUpdater].closeAllContacts(CancellationException("re-init")) val registerResp = context[SsoProcessor].registerResp ?: error("Internal error: registerResp is not yet available.") context[MessageSvcSyncer].startSync() context[BdhSessionSyncer].loadFromCache() // do them parallel. coroutineScope { launch { runWithCoverage("loading OtherClients") { context[OtherClientUpdater].update() } } launch { runWithCoverage("loading friends") { context[ContactUpdater].reloadFriendList(registerResp.origin) } } launch { runWithCoverage("loading groups") { context[ContactUpdater].reloadGroupList() } } launch { runWithCoverage("loading otherClients") { context[ContactUpdater].reloadStrangerList() } } launch { runWithCoverage("loading friendGroups") { context[ContactUpdater].reloadFriendGroupList() } } } state.value = INITIALIZED bot.components[SsoProcessor].casFirstLoginResult(null, FirstLoginResult.PASSED) } catch (e: Throwable) { setLoginHalted() bot.components[SsoProcessor].casFirstLoginResult(null, FirstLoginResult.OTHER_FAILURE) throw e } } private inline fun runWithCoverage(hint: String, block: () -> Unit) { try { block() } catch (e: NetworkException) { logger.warning( "An NetworkException was thrown during '$hint' of Bot ${bot.id}. " + "This means your network is unstable at this moment, " + "or the server has closed the connection due to some reason (you will see the cause if further trials are all failed). " + "Halting the log-in process to wait for a while to reconnect..." ) throw e } catch (e: Throwable) { logger.warning( "An exception was thrown during '$hint' of Bot ${bot.id}. " + "Trying to ignore the error and continue logging in...", e ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/BotOfflineEventMonitor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.event.ConcurrencyKind import net.mamoe.mirai.event.EventPriority import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.subscribeAlways import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.utils.* /** * Handles [BotOfflineEvent]. It launches recovery jobs when receiving offline events from server. */ internal interface BotOfflineEventMonitor { companion object : ComponentKey<BotOfflineEventMonitor> /** * Attach a listener to the [scope]. [scope] is usually the scope of [NetworkHandler.State.OK]. */ fun attachJob(bot: AbstractBot, scope: CoroutineScope) } internal data class BotClosedByEvent( val event: BotOfflineEvent, override val message: String? = "Bot is closed by event '$event'.", ) : NetworkException(false) internal class BotOfflineEventMonitorImpl : BotOfflineEventMonitor { override fun attachJob(bot: AbstractBot, scope: CoroutineScope) { bot.eventChannel.parentScope(scope).subscribeAlways( ::onEvent, priority = EventPriority.MONITOR, concurrency = ConcurrencyKind.LOCKED, ) } private fun onEvent(event: BotOfflineEvent) { val bot = event.bot.asQQAndroidBot() val network = bot.network fun closeNetwork() { if (network.state == State.CLOSED) return // avoid recursive calls. network.close(if (event is BotOfflineEvent.CauseAware) event.cause else BotClosedByEvent(event)) } when (event) { is BotOfflineEvent.Active -> { // This event might also be broadcast by the network handler by a state observer. // In that case, `network.state` will be `CLOSED` then `closeNetwork` returns immediately. // So there won't be recursive calls. val cause = event.cause val msg = if (cause == null) "" else " with exception: $cause" bot.logger.info("Bot is closed manually$msg", cause) closeNetwork() } is BotOfflineEvent.Force -> { bot.logger.warning { "Connection occupied by another android device. Will try to resume connection. (${event.message})" } closeNetwork() } is BotOfflineEvent.MsfOffline -> { // This normally means bot is blocked and requires manual action. bot.logger.warning { "Server notifies offline. (${event.cause?.message ?: event.toString()})" } closeNetwork() // `closeNetwork` will close NetworkHandler, // after which NetworkHandlerSelector will create a new instance to try to fix the problem. // A new login attempt will fail because the bot is blocked, with LoginFailedException which then wrapped into NetworkException. (LoginFailedExceptionAsNetworkException) // Selector will handle this exception, and close the block while logging this down. // See SelectorNetworkHandler.instance for more information on how the Selector handles the exception. } is BotOfflineEvent.Dropped, is BotOfflineEvent.RequireReconnect, -> { val causeMessage = event.castOrNull<BotOfflineEvent.CauseAware>()?.cause?.toString() ?: event.toString() bot.logger.warning { "Connection lost, reconnecting... ($causeMessage)" } closeNetwork() } } if (event.reconnect) { launchRecovery(bot) } } private fun launchRecovery(bot: AbstractBot) { // Run this coroutine in EventDispatcher, so joinBroadcast will work. // EventDispatcher is in Bot's components level so won't be closed by network. bot.components[EventDispatcher].broadcastAsync { var success = false val time = measureTimeMillis { success = kotlin.runCatching { bot.network.resumeConnection() }.isSuccess } if (success) { bot.logger.info { "Reconnected successfully in ${time.millisToHumanReadableString()}." } } null } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/CacheValidator.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.utils.MiraiProtocolInternal import net.mamoe.mirai.internal.utils.io.writeShortLVString import net.mamoe.mirai.utils.* /** * Validator for checking caches is usable for current bot or not. */ internal interface CacheValidator { fun register(cache: Cacheable) fun validate() companion object : ComponentKey<CacheValidator> } internal interface Cacheable { fun invalidate() } internal class CacheValidatorImpl( private val ssoProcessorContext: SsoProcessorContext, private val hashFile: MiraiFile, private val logger: MiraiLogger, ) : CacheValidator { private val caches: MutableList<Cacheable> = mutableListOf() override fun register(cache: Cacheable) { caches.add(cache) } override fun validate() { val hash: ByteArray = buildPacket { val botConf = ssoProcessorContext.configuration writeInt(botConf.protocol.ordinal) val internalProtocol = MiraiProtocolInternal[botConf.protocol] writeShortLVString(internalProtocol.apkId) writeLong(internalProtocol.id) writeShortLVString(internalProtocol.sdkVer) writeInt(internalProtocol.miscBitMap) writeInt(internalProtocol.subSigMap) writeInt(internalProtocol.mainSigMap) writeShortLVString(internalProtocol.sign) writeLong(internalProtocol.buildTime) writeInt(internalProtocol.ssoVersion) val device = ssoProcessorContext.device @Suppress("INVISIBLE_MEMBER") writeFully(device.serializeToString().encodeToByteArray()) }.let { pkg -> try { pkg.readBytes() } finally { pkg.release() } }.sha1() if (!hashFile.exists()) { logger.verbose { "Invalidate caches because hash file not available." } invalidate() kotlin.runCatching { hashFile.writeBytes(hash) }.onFailure { logger.warning("Exception in writing hash to validation file", it) } return } if (!hashFile.isFile) { logger.verbose { "hash file isn't a file." } invalidate() kotlin.runCatching { hashFile.deleteRecursively() hashFile.writeBytes(hash) }.onFailure { logger.warning("Exception in writing hash to validation file", it) } return } try { val hashInFile = hashFile.readBytes() if (hashInFile.contentEquals(hash)) { logger.verbose { "Validated caches." } return } logger.verbose { "Hash not match. Invaliding caches....." } invalidate() hashFile.writeBytes(hash) } catch (e: Throwable) { logger.warning("Exception in validation. Invalidating.....", e) invalidate() } } private fun invalidate() { caches.forEach { it.invalidate() } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/ClockComponent.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.utils.Clock import net.mamoe.mirai.utils.lateinitMutableProperty internal open class ClockHolder { open val local: Clock get() = Clock.SystemDefault open var server: Clock by lateinitMutableProperty { local } companion object : ComponentKey<ClockHolder> { val QQAndroidBot.clock get() = components[ClockHolder] } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/ConfigPushProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.withTimeoutOrNull import net.mamoe.mirai.event.EventPriority import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.globalEventChannel import net.mamoe.mirai.event.nextEvent import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.protocol.packet.login.ConfigPushSvc import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.warning /** * Job: Switch server if ConfigPush not received. */ internal interface ConfigPushProcessor { suspend fun syncConfigPush(network: NetworkHandler) companion object : ComponentKey<ConfigPushProcessor> } internal class ConfigPushProcessorImpl( private val logger: MiraiLogger, ) : ConfigPushProcessor { override suspend fun syncConfigPush(network: NetworkHandler) { val resp = withTimeoutOrNull(60_000) { globalEventChannel().nextEvent<ConfigPushSvc.PushReq.PushReqResponse>( EventPriority.MONITOR ) { it.bot == network.context.bot } } if (resp == null) { val bdhSyncer = network.context[BdhSessionSyncer] if (!bdhSyncer.hasSession) { val e = NetworkException("Timeout waiting for ConfigPush.",true) bdhSyncer.bdhSession.completeExceptionally(e) logger.warning { "Missing ConfigPush. Switching server..." } network.context[SsoProcessor].casFirstLoginResult(null, FirstLoginResult.CHANGE_SERVER) network.context.bot.components[EventDispatcher].broadcastAsync( BotOfflineEvent.RequireReconnect( network.context.bot, e ) ) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/ConfigPushSyncer.kt ================================================ /* * 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.components import net.mamoe.mirai.internal.network.component.ComponentKey internal interface ConfigPushSyncer { suspend fun awaitSync() companion object : ComponentKey<ConfigPushSyncer> } internal class ConfigPushSyncerImpl : ConfigPushSyncer { override suspend fun awaitSync() { } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/ContactCacheService.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.serialization.json.Json import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.FriendListCache import net.mamoe.mirai.internal.network.GroupMemberListCaches import net.mamoe.mirai.internal.network.JsonForCache import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.utils.ScheduledJob import net.mamoe.mirai.internal.utils.friendCacheFile import net.mamoe.mirai.utils.* /** * Strategy of caching contacts. Used by [ContactUpdater]. */ internal interface ContactCacheService { val friendListCache: FriendListCache? // from old code val groupMemberListCaches: GroupMemberListCaches? // from old code /** * Implementation does not need to be thread-safe. */ fun saveFriendCache() // from old code companion object : ComponentKey<ContactCacheService> } internal class ContactCacheServiceImpl( private val bot: QQAndroidBot, private val logger: MiraiLogger, ) : ContactCacheService { private val configuration get() = bot.configuration /////////////////////////////////////////////////////////////////////////// // contact cache /////////////////////////////////////////////////////////////////////////// companion object { internal val json: Json = kotlin.runCatching { Json { isLenient = true ignoreUnknownKeys = true prettyPrint = true } }.getOrElse { @Suppress("JSON_FORMAT_REDUNDANT_DEFAULT") // compatibility for older versions (Json {}) } } override val friendListCache: FriendListCache? by lazy { if (!configuration.contactListCache.friendListCacheEnabled) return@lazy null val file = configuration.friendCacheFile() val ret = file.loadNotBlankAs(FriendListCache.serializer(), JsonForCache) ?: FriendListCache() @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") bot.eventChannel.parentScope(bot) .subscribeAlways<net.mamoe.mirai.event.events.FriendInfoChangeEvent> { friendListSaver?.notice() } ret } override val groupMemberListCaches: GroupMemberListCaches? by lazy { if (!configuration.contactListCache.groupMemberListCacheEnabled) { return@lazy null } GroupMemberListCaches(bot, logger) } private val friendListSaver: ScheduledJob? by lazy { if (!configuration.contactListCache.friendListCacheEnabled) return@lazy null ScheduledJob(bot.coroutineContext, configuration.contactListCache.saveIntervalMillis) { runBIO { saveFriendCache() } } } override fun saveFriendCache() { val friendListCache = friendListCache ?: return configuration.friendCacheFile().run { createFileIfNotExists() writeText(JsonForCache.encodeToString(FriendListCache.serializer(), friendListCache)) logger.info { "Saved ${friendListCache.list.size} friends to local cache." } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/ContactUpdater.kt ================================================ /* * 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.components import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withPermit import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.StrangerImpl import net.mamoe.mirai.internal.contact.friendgroup.FriendGroupImpl import net.mamoe.mirai.internal.contact.info.* import net.mamoe.mirai.internal.contact.toMiraiFriendInfo import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.isValid import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.protocol.data.jce.StGroupRankInfo 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.MiraiLogger import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.retryCatching import net.mamoe.mirai.utils.verbose import kotlin.jvm.Volatile /** * Manager of caches for [Contact]s. * * Uses [ContactCacheService]. */ internal interface ContactUpdater { val otherClientsLock: Mutex val groupListModifyLock: Mutex val friendListLock: Mutex val friendGroupsLock: Mutex val strangerListLock: Mutex suspend fun reloadFriendList(registerResp: SvcRespRegister) suspend fun reloadFriendGroupList() suspend fun reloadGroupList() suspend fun reloadStrangerList() /** * Closes all contacts and save them to cache if needed. * * Implementation must be thread-safe. */ fun closeAllContacts(e: CancellationException) companion object : ComponentKey<ContactUpdater> } internal class ContactUpdaterImpl( val bot: QQAndroidBot, // not good val components: ComponentStorage, private val logger: MiraiLogger, ) : ContactUpdater, NewContactSupport { override val otherClientsLock: Mutex = Mutex() override val groupListModifyLock: Mutex = Mutex() override val friendListLock: Mutex = Mutex() override val friendGroupsLock: Mutex = Mutex() override val strangerListLock: Mutex = Mutex() private val cacheService get() = components[ContactCacheService] 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 } } if (!initFriendGroupOk) { bot.friendGroups.friendGroups.clear() } } @Volatile private var initFriendOk = false @Volatile private var initFriendGroupOk = false @Volatile private var initGroupOk = false @Volatile private var initStrangerOk = false /** * Don't use concurrently */ override suspend fun reloadFriendList(registerResp: SvcRespRegister) = friendListLock.withLock { if (initFriendOk) { return } val friendListCache = cacheService.friendListCache fun updateCacheSeq(list: List<FriendInfoImpl>) { cacheService.friendListCache?.apply { friendListSeq = registerResp.iLargeSeq timeStamp = registerResp.timeStamp this.list = list cacheService.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 = bot.network.sendAndExpect( FriendList.GetFriendGroupList(bot.client, count, 150, 0, 0), timeout = 5000, attempts = 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 logger.info { "Loaded ${list.size} friends from local cache." } // For sync bot nick bot.network.sendAndExpect( FriendList.GetFriendGroupList( bot.client, 0, 1, 0, 0 ) ) list } else { refreshFriendList().also { updateCacheSeq(it) } } for (friendInfoImpl in list) { bot.addNewFriendAndRemoveStranger(friendInfoImpl) } initFriendOk = true } override suspend fun reloadFriendGroupList() = friendGroupsLock.withLock { if (initFriendGroupOk) return suspend fun refreshFriendGroupList(): List<FriendGroupImpl> { logger.info { "Start loading friendGroup list..." } val friendGroupInfos = mutableListOf<FriendGroupImpl>() var count = 0 var total: Short while (true) { val data = bot.network.sendAndExpect( FriendList.GetFriendGroupList(bot.client, 0, 0, count, 150) ) total = data.totoalGroupCount for (jceInfo in data.groupList) { friendGroupInfos.add( FriendGroupImpl( bot, FriendGroupInfo( jceInfo.groupId.toInt(), jceInfo.groupname ) ) ) } count += data.groupList.size logger.verbose { "Loading friendGroup list: ${count}/${total}" } if (count >= total) break } logger.info { "Successfully loaded friendGroup list: $count in total" } return friendGroupInfos } bot.friendGroups.friendGroups.clear() bot.friendGroups.friendGroups.addAll(refreshFriendGroupList()) initFriendGroupOk = true } private suspend fun addGroupToBot(stTroopNum: StTroopNum, stGroupRankInfo: StGroupRankInfo?) = stTroopNum.run { suspend fun refreshGroupMemberList(): Sequence<MemberInfo> { return Mirai.getRawGroupMemberList( bot, groupUin, groupCode, dwGroupOwnerUin ) } val cache = cacheService.groupMemberListCaches?.get(groupCode) val members = if (cache != null) { if (cache.isValid(stTroopNum)) { cache.list.asSequence().also { 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 } cacheService.groupMemberListCaches!!.map[groupCode] = cache cacheService.groupMemberListCaches!!.reportChanged(groupCode) } } else { refreshGroupMemberList() } bot.groups.delegate.add( GroupImpl( bot = bot, parentCoroutineContext = bot.coroutineContext, id = groupCode, groupInfo = GroupInfoImpl(stTroopNum, stGroupRankInfo), members = members, ), ) } override suspend fun reloadStrangerList() = strangerListLock.withLock { if (initStrangerOk) { return } var currentCount = 0 logger.info { "Start loading stranger list..." } val response = bot.network.sendAndExpect( StrangerList.GetStrangerList(bot.client), timeout = 5000, attempts = 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 } override suspend fun reloadGroupList() = groupListModifyLock.withLock { if (initGroupOk) { return } bot.network.sendAndExpect(TroopManagement.GetTroopConfig(bot.client)) logger.info { "Start loading group list..." } val troopListData = bot.network.sendAndExpect(FriendList.GetTroopListSimplify(bot.client), attempts = 5) val semaphore = Semaphore(30) coroutineScope { troopListData.groups.forEach { group -> launch { semaphore.withPermit { retryCatching(5) { addGroupToBot(group, troopListData.ranks.find { it.dwGroupCode == group.groupCode }) }.getOrThrow() } } } } logger.info { "Successfully loaded group list: ${troopListData.groups.size} in total." } cacheService.groupMemberListCaches?.saveGroupCaches() initGroupOk = true } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/EcdhInitialPublicKeyUpdater.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import io.ktor.client.request.* import io.ktor.client.statement.* import kotlinx.coroutines.withTimeout import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.protocol.packet.createChannelProxy import net.mamoe.mirai.internal.spi.EncryptServiceContext import net.mamoe.mirai.internal.utils.actualCacheDir import net.mamoe.mirai.internal.utils.crypto.QQEcdh import net.mamoe.mirai.internal.utils.crypto.QQEcdhInitialPublicKey import net.mamoe.mirai.internal.utils.crypto.verify import net.mamoe.mirai.internal.utils.workingDirPath import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.buildTypeSafeMap import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.time.Duration.Companion.seconds /** * Updater for updating [QQEcdhInitialPublicKey]. */ internal interface EcdhInitialPublicKeyUpdater { /** * Refresh the [QQEcdhInitialPublicKey] */ suspend fun refreshInitialPublicKeyAndApplyEcdh() suspend fun initializeSsoSecureEcdh() fun getQQEcdh(): QQEcdh companion object : ComponentKey<EcdhInitialPublicKeyUpdater> } internal class EcdhInitialPublicKeyUpdaterImpl( private val bot: QQAndroidBot, private val logger: MiraiLogger ) : EcdhInitialPublicKeyUpdater { @Serializable data class ServerRespPOJO( @SerialName("PubKeyMeta") val pubKeyMeta: PubKeyMeta, @SerialName("QuerySpan") val querySpan: Int = 0 ) @Serializable data class PubKeyMeta( @SerialName("PubKey") val pubKey: String, @SerialName("PubKeySign") val pubKeySign: String, @SerialName("KeyVer") val keyVer: Int ) var qqEcdh: QQEcdh? = null override fun getQQEcdh(): QQEcdh { if (qqEcdh == null) { error("Calling getQQEcdh without calling refreshInitialPublicKeyAndApplyEcdh") } return qqEcdh!! } override suspend fun refreshInitialPublicKeyAndApplyEcdh() { val initialPublicKey = kotlin.runCatching { val currentPublicKey = bot.client.ecdhInitialPublicKey if (currentPublicKey.expireTime > currentTimeSeconds()) { logger.info("ECDH key is valid.") currentPublicKey } else { logger.info("ECDH key is invalid, start to fetch ecdh public key from server.") val respStr = withTimeout(10.seconds) { bot.components[HttpClientProvider].getHttpClient() .get("https://keyrotate.qq.com/rotate_key?cipher_suite_ver=305&uin=${bot.client.uin}") .bodyAsText() } val resp = Json.decodeFromString(ServerRespPOJO.serializer(), respStr) resp.pubKeyMeta.let { meta -> val key = QQEcdhInitialPublicKey(meta.keyVer, meta.pubKey, currentTimeSeconds() + resp.querySpan) check(key.verify(meta.pubKeySign)) { "Ecdh public key which from server is invalid" } logger.info("Successfully fetched ecdh public key from server.") key } } }.getOrElse { logger.error("Failed to fetch ECDH public key from server, using default key instead", it) QQEcdhInitialPublicKey.default } bot.client.ecdhInitialPublicKey = initialPublicKey qqEcdh = QQEcdh(initialPublicKey) } override suspend fun initializeSsoSecureEcdh() { val encryptWorker = bot.encryptServiceOrNull if (encryptWorker == null) { logger.info("EncryptService SPI is not provided, sso secure ecdh will not be initialized.") return } encryptWorker.initialize(EncryptServiceContext(bot.id, buildTypeSafeMap { set(EncryptServiceContext.KEY_CHANNEL_PROXY, createChannelProxy(bot)) set(EncryptServiceContext.KEY_DEVICE_INFO, bot.client.device) set(EncryptServiceContext.KEY_BOT_PROTOCOL, bot.configuration.protocol) set(EncryptServiceContext.KEY_QIMEI36, bot.client.qimei36 ?: "") set(EncryptServiceContext.KEY_BOT_WORKING_DIR, bot.configuration.workingDirPath) set(EncryptServiceContext.KEY_BOT_CACHING_DIR, bot.configuration.actualCacheDir().absolutePath) })) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/EncryptServiceHolder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.spi.EncryptService import net.mamoe.mirai.internal.spi.EncryptServiceContext import net.mamoe.mirai.internal.utils.actualCacheDir import net.mamoe.mirai.internal.utils.workingDirPath import net.mamoe.mirai.utils.buildTypeSafeMap import net.mamoe.mirai.utils.childScope internal interface EncryptServiceHolder { companion object : ComponentKey<EncryptServiceHolder> val isAvailable: Boolean val service: EncryptService val serviceOrNull: EncryptService? } internal class EncryptServiceHolderImpl( bot: AbstractBot, ssoProcessorContext: SsoProcessorContext, ) : EncryptServiceHolder { override var isAvailable: Boolean = false private set private var service0: EncryptService? = null override val serviceOrNull: EncryptService? get() = service0 override val service: EncryptService get() = service0 ?: error("Encrypt Service not available") init { EncryptService.factory?.let { globalService -> try { service0 = globalService.createForBot( EncryptServiceContext(bot.id, buildTypeSafeMap { set(EncryptServiceContext.KEY_BOT_PROTOCOL, bot.configuration.protocol) set(EncryptServiceContext.KEY_DEVICE_INFO, ssoProcessorContext.device) set(EncryptServiceContext.KEY_BOT_WORKING_DIR, bot.configuration.workingDirPath) set(EncryptServiceContext.KEY_BOT_CACHING_DIR, bot.configuration.actualCacheDir().absolutePath) }), bot.childScope(name = "Encrypt Service"), ) isAvailable = true } catch (error: UnsupportedOperationException) { bot.logger.warning("$globalService is not yet supported EncryptService with bot $bot", error) isAvailable = false service0 = null } catch (_: EncryptService.SignalServiceNotAvailable) { isAvailable = false service0 = null } /* catch (error: Throwable) { throw error } */ // crash bot initialize } } } internal val AbstractBot.encryptService: EncryptService get() = components[EncryptServiceHolder].service internal val AbstractBot.encryptServiceOrNull: EncryptService? get() = components[EncryptServiceHolder].serviceOrNull ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/EventDispatcher.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.* import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.internal.event.EventChannelToEventDispatcherAdapter import net.mamoe.mirai.internal.event.InternalEventMechanism import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.utils.* import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmInline /** * All events will be caught and forwarded to [EventDispatcher]. Invocation of [Event.broadcast] and [EventDispatcher.broadcast] are effectively equal. */ internal interface EventDispatcher { val isActive: Boolean /** * Broadcast an event using [EventChannel]. It's safe to use this function internally. */ suspend fun broadcast(event: Event) fun broadcastAsync(event: Event, additionalContext: CoroutineContext = EmptyCoroutineContext): EventBroadcastJob fun broadcastAsync( additionalContext: CoroutineContext = EmptyCoroutineContext, event: suspend () -> Event?, ): EventBroadcastJob /** * Join all jobs. Joins also jobs launched during this call. */ @TestOnly suspend fun joinBroadcast() { throw UnsupportedOperationException("joinBroadcast is only supported in TestEventDispatcherImpl") } companion object : ComponentKey<EventDispatcher> } @JvmInline internal value class EventBroadcastJob( val job: Job ) { inline fun onSuccess(crossinline action: () -> Unit) { job.invokeOnCompletion { if (it == null) action() } } inline fun thenBroadcast(eventDispatcher: EventDispatcher, crossinline event: suspend () -> Event?) { eventDispatcher.broadcastAsync { job.join() event() } } } /** * If `true`, all event listeners runs directly in the broadcaster's thread until first suspension. * * If there is no suspension point in the listener, the coroutine executing [Event.broadcast] will not suspend, * so the thread before and after execution will be the same and no other code is being executed if there is only one thread. * * This is useful for tests to not depend on `delay` */ internal var EVENT_LAUNCH_UNDISPATCHED: Boolean by lateinitMutableProperty { systemProp("mirai.event.launch.undispatched", false) } internal val SHOW_VERBOSE_EVENT: Boolean by lazy { systemProp("mirai.event.show.verbose.events", false) } internal open class EventDispatcherImpl( lifecycleContext: CoroutineContext, protected val logger: MiraiLogger, ) : EventDispatcher, CoroutineScope by lifecycleContext .addNameHierarchically("EventDispatcher") .childScope() { override val isActive: Boolean get() = this.coroutineContext.isActive @OptIn(InternalEventMechanism::class) override suspend fun broadcast(event: Event) { try { EventChannelToEventDispatcherAdapter.instance.broadcastEventImpl(event) } catch (e: Exception) { if (e is CancellationException) return if (logger.isEnabled) { val msg = optimizeEventToString(event) logger.error(IllegalStateException("Exception while broadcasting event '$msg'", e)) } } } override fun broadcastAsync(event: Event, additionalContext: CoroutineContext): EventBroadcastJob { val job = launch( additionalContext, start = CoroutineStart.UNDISPATCHED ) { broadcast(event) } // UNDISPATCHED: starts the coroutine NOW in the current thread until its first suspension point, // so that after `broadcastAsync` the job is always already started and `joinBroadcast` will work normally. return EventBroadcastJob(job) } override fun broadcastAsync(additionalContext: CoroutineContext, event: suspend () -> Event?): EventBroadcastJob { val job = launch( additionalContext, start = CoroutineStart.UNDISPATCHED ) { event()?.let { broadcast(it) } } // UNDISPATCHED: starts the coroutine NOW in the current thread until its first suspension point, // so that after `broadcastAsync` the job is always already started and `joinBroadcast` will work normally. return EventBroadcastJob(job) } protected fun optimizeEventToString(event: Event): String { val qualified = event::class.qualifiedName ?: return event.toString() return qualified.substringAfter("net.mamoe.mirai.event.events.", "").ifEmpty { event.toString() } } /////////////////////////////////////////////////////////////////////////// // broadcast /////////////////////////////////////////////////////////////////////////// } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/HeartbeatProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.CancellationException import kotlinx.coroutines.TimeoutCancellationException import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc internal interface HeartbeatProcessor { /** * @throws TimeoutCancellationException if timed out waiting for response. * @throws CancellationException if [networkHandler] closed. * @throws Exception any other exceptions considered critical internal error and will stop SSO (i.e. stop Bot). */ suspend fun doAliveHeartbeatNow(networkHandler: NetworkHandler) /** * @throws TimeoutCancellationException if timed out waiting for response. * @throws CancellationException if [networkHandler] closed. * @throws Exception any other exceptions considered critical internal error and will stop SSO (i.e. stop Bot). */ suspend fun doStatHeartbeatNow(networkHandler: NetworkHandler) /** * @throws TimeoutCancellationException if timed out waiting for response. * @throws CancellationException if [networkHandler] closed. * @throws Exception any other exceptions considered critical internal error and will stop SSO (i.e. stop Bot). */ suspend fun doRegisterNow(networkHandler: NetworkHandler): StatSvc.Register.Response companion object : ComponentKey<HeartbeatProcessor> } internal class HeartbeatProcessorImpl : HeartbeatProcessor { override suspend fun doStatHeartbeatNow(networkHandler: NetworkHandler) { networkHandler.sendAndExpect( StatSvc.SimpleGet(networkHandler.context.bot.client), timeout = networkHandler.context[SsoProcessorContext].configuration.heartbeatTimeoutMillis, attempts = 2 ) } override suspend fun doAliveHeartbeatNow(networkHandler: NetworkHandler) { networkHandler.sendAndExpect( Heartbeat.Alive(networkHandler.context.bot.client), timeout = networkHandler.context[SsoProcessorContext].configuration.heartbeatTimeoutMillis, attempts = 2 ) } override suspend fun doRegisterNow(networkHandler: NetworkHandler): StatSvc.Register.Response { return networkHandler.context[SsoProcessor].sendRegister(networkHandler) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/HeartbeatScheduler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.* import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.handler.selector.PacketTimeoutException import net.mamoe.mirai.utils.BotConfiguration.HeartbeatStrategy.* import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info /** * Accepts any kinds of exceptions. A [NetworkException] can control whether this error is recoverable, while any other ones are regarded as unexpected failure. */ internal typealias HeartbeatFailureHandler = (name: String, e: Throwable) -> Unit /** * Schedules [HeartbeatProcessor] */ internal interface HeartbeatScheduler { fun launchJobsIn( network: NetworkHandlerSupport, scope: CoroutineScope, onHeartFailure: HeartbeatFailureHandler ): List<Job> companion object : ComponentKey<HeartbeatScheduler> } internal class TimeBasedHeartbeatSchedulerImpl( private val logger: MiraiLogger, ) : HeartbeatScheduler { override fun launchJobsIn( network: NetworkHandlerSupport, scope: CoroutineScope, onHeartFailure: HeartbeatFailureHandler ): List<Job> { val context: ComponentStorage = network.context val heartbeatProcessor = context[HeartbeatProcessor] val configuration = context[SsoProcessorContext].configuration val timeout = configuration.heartbeatTimeoutMillis val list = mutableListOf<Job>() when (val hb = context[SsoProcessorContext].configuration.heartbeatStrategy) { STAT_HB -> { list += launchHeartbeatJobAsync( scope = scope, name = "${network.context.bot.id}.StatHeartbeat", delay = { configuration.statHeartbeatPeriodMillis }, timeout = { timeout }, action = { heartbeatProcessor.doStatHeartbeatNow(network) }, onHeartFailure = onHeartFailure ) } REGISTER -> { list += launchHeartbeatJobAsync( scope = scope, name = "${network.context.bot.id}.RegisterHeartbeat", delay = { configuration.statHeartbeatPeriodMillis }, timeout = { timeout }, action = { heartbeatProcessor.doRegisterNow(network) }, onHeartFailure = onHeartFailure ) } NONE -> { } else -> throw IllegalStateException("Unexpected HeartbeatStrategy: $hb") } list += launchHeartbeatJobAsync( scope = scope, name = "${network.context.bot.id}.AliveHeartbeat", delay = { configuration.heartbeatPeriodMillis }, timeout = { timeout }, action = { heartbeatProcessor.doAliveHeartbeatNow(network) }, onHeartFailure = onHeartFailure ) return list } /** * If any of the functions throw an exception, HB will fail unexpectedly can [onHeartFailure] will be called. */ private fun launchHeartbeatJobAsync( scope: CoroutineScope, name: String, delay: () -> Long, timeout: () -> Long, action: suspend () -> Unit, onHeartFailure: HeartbeatFailureHandler, ): Deferred<Unit> { val coroutineName = "$name Scheduler" return scope.async(CoroutineName(coroutineName)) { while (isActive) { try { delay(delay()) } catch (e: CancellationException) { return@async // considered normally cancel } catch (e: Throwable) { onHeartFailure( name, IllegalStateException( "$coroutineName: Internal error: exception in heartbeat delay function", e ) // throwing a ISE will stop the handler. ) return@async } try { var cause: Throwable? = null val result = try { withTimeoutOrNull(timeout()) { action() } } catch (e: TimeoutCancellationException) { // from `action` cause = e null } if (result == null) { onHeartFailure( name, PacketTimeoutException( "$coroutineName: Timeout receiving action response", cause // cause is TimeoutCancellationException from `action` ) // This is a NetworkException that is recoverable ) return@async } } catch (e: Throwable) { // catch other errors in `action`, should not happen onHeartFailure( name, IllegalStateException("$coroutineName: Internal error: caught unexpected exception", e) ) // Terminal ISE return@async } } }.apply { invokeOnCompletion { e -> if (e is CancellationException) return@invokeOnCompletion // normally closed if (e != null) logger.info { "$name failed: $e." } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/HttpClientProvider.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import io.ktor.client.* import net.mamoe.mirai.internal.createDefaultHttpClient import net.mamoe.mirai.internal.network.component.ComponentKey internal interface HttpClientProvider { fun getHttpClient(): HttpClient companion object : ComponentKey<HttpClientProvider> } internal class HttpClientProviderImpl : HttpClientProvider { private val instance by lazy { createDefaultHttpClient() } override fun getHttpClient(): HttpClient = instance } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/KeyRefreshProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.* import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin15 import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.millisToHumanReadableString import net.mamoe.mirai.utils.minutesToMillis import kotlin.coroutines.cancellation.CancellationException internal interface KeyRefreshProcessor { suspend fun keyRefreshLoop(handler: NetworkHandler) @Throws(LoginFailedException::class, CancellationException::class) suspend fun refreshKeysNow(handler: NetworkHandler) companion object : ComponentKey<KeyRefreshProcessor> } internal class KeyRefreshProcessorImpl( private val logger: MiraiLogger, ) : KeyRefreshProcessor { override suspend fun keyRefreshLoop(handler: NetworkHandler): Unit = coroutineScope { val client = handler.context[SsoProcessor].client launch(CoroutineName("Login Session Refresh Scheduler")) { while (isActive) { client.wLoginSigInfo.vKey.run { //由过期时间最短的且不会被skey更换更新的vkey计算重新登录的时间 val delay = (expireTime - creationTime).times(1000) - 5.minutesToMillis logger.info { "Scheduled refresh login session in ${delay.millisToHumanReadableString()}." } delay(delay) } runCatching { handler.context[SsoProcessor].login(handler) }.onFailure { logger.warning("Failed to refresh login session.", it) } } } launch(CoroutineName("Key Refresh Scheduler")) { while (isActive) { client.wLoginSigInfo.sKey.run { val delay = (expireTime - creationTime).times(1000) - 5.minutesToMillis logger.info { "Scheduled key refresh in ${delay.millisToHumanReadableString()}." } delay(delay) } runCatching { refreshKeysNow(handler) }.onFailure { logger.error("Failed to refresh key.", it) } } } } override suspend fun refreshKeysNow(handler: NetworkHandler) { handler.sendAndExpect(WtLogin15(handler.context[SsoProcessor].client)) handler.context[SsoProcessor].client.bot.apply { components[AccountSecretsManager].saveSecrets(account, AccountSecretsImpl(client)) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/MessageSvcSyncer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.coroutines.* import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.event.globalEventChannel import net.mamoe.mirai.event.nextEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.addNameHierarchically import net.mamoe.mirai.utils.childScope import net.mamoe.mirai.utils.info import kotlin.coroutines.CoroutineContext import kotlin.jvm.Volatile internal interface MessageSvcSyncer { suspend fun startSync() suspend fun joinSync() companion object : ComponentKey<MessageSvcSyncer> } internal class MessageSvcSyncerImpl( private val bot: QQAndroidBot, private val parentContext: CoroutineContext, private val logger: MiraiLogger, ) : MessageSvcSyncer { @Volatile private var scope: CoroutineScope? = null @Volatile private var job: Job? = null private val lock = Mutex() private fun initScope() { scope = parentContext.addNameHierarchically("MessageSvcSyncerImpl").childScope() } override suspend fun startSync(): Unit = lock.withLock { scope?.cancel() initScope() job = scope!!.launch { syncMessageSvc() } } private suspend fun syncMessageSvc() { logger.info { "Syncing friend message history..." } withTimeoutOrNull(30000) { launch(CoroutineName("Syncing friend message history")) { globalEventChannel().nextEvent<MessageSvcPbGetMsg.GetMsgSuccess> { it.bot == this@MessageSvcSyncerImpl.bot } } bot.network.sendAndExpect(MessageSvcPbGetMsg(bot.client, MsgSvc.SyncFlag.START, null)) } ?: error("timeout syncing friend message history.") logger.info { "Syncing friend message history: Success." } } override suspend fun joinSync(): Unit = lock.withLock { job?.join() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/NetworkHandlerReference.kt ================================================ /* * 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.components import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler internal class NetworkHandlerReference( private val getInstance: () -> NetworkHandler, ) { fun get(): NetworkHandler = getInstance() companion object : ComponentKey<NetworkHandlerReference> } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/NoticeProcessorPipeline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.ParseErrorPacket import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.notice.BotAware import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.notice.decoders.DecodedNotifyMsgBody import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetMsg import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.OnlinePushPbPushTransMsg import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.internal.pipeline.* import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.* import kotlin.jvm.JvmStatic import kotlin.reflect.KClass /** * Centralized processor pipeline for [MessageSvcPbGetMsg] and [OnlinePushPbPushTransMsg] */ internal interface NoticeProcessorPipeline : ProcessorPipeline<NoticeProcessor, NoticePipelineContext, ProtocolStruct, Packet> { companion object : ComponentKey<NoticeProcessorPipeline> { val ComponentStorage.noticeProcessorPipeline get() = get(NoticeProcessorPipeline) @JvmStatic suspend inline fun QQAndroidBot.processPacketThroughPipeline( data: ProtocolStruct, attributes: TypeSafeMap = TypeSafeMap.EMPTY, ): Packet { return components.noticeProcessorPipeline.process(data, attributes).collected.toPacket() } } } internal interface NoticePipelineContext : BotAware, NewContactSupport, ProcessorPipelineContext<ProtocolStruct, Packet> { override val bot: QQAndroidBot companion object { val KEY_FROM_SYNC = TypeKey<Boolean>("fromSync") val KEY_MSG_INFO = TypeKey<MsgInfo>("msgInfo") val NoticePipelineContext.fromSync get() = attributes[KEY_FROM_SYNC] val NoticePipelineContext.fromSyncSafely get() = attributes[KEY_FROM_SYNC, false] /** * 来自 [MsgInfo] 的数据, 即 [MsgType0x210], [MsgType0x2DC] 的处理过程之中可以使用 */ val NoticePipelineContext.msgInfo get() = attributes[KEY_MSG_INFO] } } internal inline val NoticePipelineContext.context get() = this private val defaultTraceLogging: MiraiLogger by lazy { MiraiLogger.Factory.create(NoticeProcessorPipelineImpl::class, "NoticeProcessorPipeline") .withSwitch(systemProp("mirai.network.notice.pipeline.log.full", false)) } internal open class NoticeProcessorPipelineImpl protected constructor( private val bot: QQAndroidBot, traceLogging: MiraiLogger = defaultTraceLogging, ) : NoticeProcessorPipeline, AbstractProcessorPipeline<NoticeProcessor, NoticePipelineContext, ProtocolStruct, Packet>( PipelineConfiguration(stopWhenConsumed = false), traceLogging ) { open inner class ContextImpl( attributes: TypeSafeMap, ) : BaseContextImpl(attributes), NoticePipelineContext { override val bot: QQAndroidBot get() = this@NoticeProcessorPipelineImpl.bot } override fun handleExceptionInProcess( data: ProtocolStruct, context: NoticePipelineContext, attributes: TypeSafeMap, processor: NoticeProcessor, e: Throwable ) { context.collect( ParseErrorPacket( data, IllegalStateException( "Exception in $processor while processing packet ${packetToString(data)}.", e, ), ) ) } override fun createContext(data: ProtocolStruct, attributes: TypeSafeMap): NoticePipelineContext = ContextImpl(attributes) protected open fun packetToString(data: Any?): String = data.toDebugString("mirai.network.notice.pipeline.log.full") companion object { fun create(bot: QQAndroidBot, vararg processors: NoticeProcessor): NoticeProcessorPipelineImpl = NoticeProcessorPipelineImpl(bot, defaultTraceLogging).apply { for (processor in processors) { registerProcessor(processor) } } } } /////////////////////////////////////////////////////////////////////////// // NoticeProcessor /////////////////////////////////////////////////////////////////////////// /** * A processor handling some specific type of message. */ internal interface NoticeProcessor : Processor<NoticePipelineContext, ProtocolStruct> internal abstract class AnyNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type()) internal abstract class SimpleNoticeProcessor<in T : ProtocolStruct>( private val type: KClass<T>, ) : NoticeProcessor { final override suspend fun process(context: NoticePipelineContext, data: ProtocolStruct) { if (type.isInstance(data)) { context.processImpl(data.uncheckedCast()) } } protected abstract suspend fun NoticePipelineContext.processImpl(data: T) companion object { @JvmStatic protected inline fun <reified T : Any> type(): KClass<T> = T::class } } internal abstract class MsgCommonMsgProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) { abstract override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) } internal abstract class MixedNoticeProcessor : AnyNoticeProcessor() { final override suspend fun NoticePipelineContext.processImpl(data: ProtocolStruct) { when (data) { is PbMsgInfo -> processImpl(data) is MsgOnlinePush.PbPushMsg -> processImpl(data) is MsgComm.Msg -> processImpl(data) is MsgType0x210 -> processImpl(data) is MsgType0x2DC -> processImpl(data) is Structmsg.StructMsg -> processImpl(data) is RequestPushStatus -> processImpl(data) is DecodedNotifyMsgBody -> processImpl(data) } } protected open suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) {} // 528 protected open suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) {} // 732 protected open suspend fun NoticePipelineContext.processImpl(data: PbMsgInfo) {} protected open suspend fun NoticePipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) {} protected open suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) {} protected open suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) {} protected open suspend fun NoticePipelineContext.processImpl(data: RequestPushStatus) {} protected open suspend fun NoticePipelineContext.processImpl(data: DecodedNotifyMsgBody) {} } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/OtherClientUpdater.kt ================================================ /* * 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.components import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.ContactList import net.mamoe.mirai.contact.OtherClient import net.mamoe.mirai.contact.deviceName import net.mamoe.mirai.contact.platform import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.createOtherClient import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info internal interface OtherClientUpdater { suspend fun update() companion object : ComponentKey<OtherClientUpdater> } internal class OtherClientUpdaterImpl( private val bot: QQAndroidBot, private val context: ComponentStorage, private val logger: MiraiLogger, ) : OtherClientUpdater { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") val otherClientList = ContactList<OtherClient>() private val lock = Mutex() override suspend fun update() = lock.withLock { val list = Mirai.getOnlineOtherClientsList(bot) bot.otherClients.delegate.clear() bot.otherClients.delegate.addAll(list.map { bot.createOtherClient(it) }) if (bot.otherClients.isEmpty()) { logger.info { "No OtherClient online." } } else { logger.info { "Online OtherClients: " + bot.otherClients.joinToString { "${it.deviceName}(${it.platform?.name ?: "unknown platform"})" } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/PacketCodec.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.components.PacketCodec.Companion.PacketLogger import net.mamoe.mirai.internal.network.components.PacketCodecException.Kind.* import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.protocol.data.proto.SSOReserveField import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.sso.TRpcRawPacket import net.mamoe.mirai.internal.utils.crypto.Ecdh import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.utils.* /** * Packet decoders. * * - Transforms [ByteReadPacket] to [RawIncomingPacket] */ internal interface PacketCodec { /** * It's caller's responsibility to close [input]. * * @throws PacketCodecException normal, known errors * @throws Exception unexpected errors * @param input received from sockets. * @return decoded */ @Throws(PacketCodecException::class) fun decodeRaw(client: SsoSession, input: ByteReadPacket): RawIncomingPacket /** * Process [RawIncomingPacket] using [IncomingPacketFactory.decode]. * * This function throws **no** exception and wrap them into [IncomingPacket]. */ suspend fun processBody(bot: QQAndroidBot, input: RawIncomingPacket): IncomingPacket? companion object : ComponentKey<PacketCodec> { val PACKET_DEBUG = systemProp("mirai.network.packet.logger", false) internal val PacketLogger: MiraiLoggerWithSwitch by lazy { MiraiLogger.Factory.create(PacketCodec::class, "Packet").withSwitch(PACKET_DEBUG) } } } /** * Wraps an exception thrown by [PacketCodec.decodeRaw], which is not a [PacketCodecException] (meaning unexpected). */ internal data class ExceptionInPacketCodecException( override val cause: Throwable, ) : IllegalStateException("Exception in PacketCodec.", cause) /** * Thrown by [PacketCodec.decodeRaw], representing an excepted error. */ internal class PacketCodecException( val targetException: Throwable, val kind: Kind, ) : NetworkException(recoverable = true, cause = targetException) { constructor(message: String, kind: Kind) : this(IllegalStateException(message), kind) enum class Kind { /** * 会触发重连 */ SESSION_EXPIRED, /** * 只记录日志 */ PROTOCOL_UPDATED, /** * 只记录日志 */ OTHER, } // not available in native // override fun getStackTrace(): Array<StackTraceElement> { // return targetException.stackTrace // } } internal enum class IncomingPacketType(val value: Int) { Login(0x0A), Simple(0x0B), Unknown(-1) ; companion object { internal fun of(value: Int) = enumValues<IncomingPacketType>().find { it.value == value } ?: Unknown } } internal class PacketCodecImpl : PacketCodec { override fun decodeRaw( client: SsoSession, input: ByteReadPacket ): RawIncomingPacket = input.run { // packet type val packetType = IncomingPacketType.of(readInt()) PacketLogger.verbose { "开始处理一个包" } val encryptMethod = PacketEncryptType.of(readByte().toInt()) val flag3 = readByte().toInt() val flag3Exception = if (flag3 != 0) { PacketCodecException( "Illegal flag3. Expected 0, whereas got $flag3. packet type=$packetType, encrypt method=$encryptMethod. ", kind = PROTOCOL_UPDATED ) } else null readString(readInt() - 4)// uinAccount ByteArrayPool.useInstance(this.remaining.toInt()) { buffer -> val size = this.readAvailable(buffer) val raw = try { when (encryptMethod) { PacketEncryptType.Empty -> TEA.decrypt(buffer, DECRYPTER_16_ZERO, size) PacketEncryptType.D2 -> { TEA.decrypt(buffer, kotlin.runCatching { client.wLoginSigInfo.d2Key }.getOrElse { throw PacketCodecException( "Received packet needed d2Key to decrypt but d2Key doesn't existed, ignoring. Please report to https://github.com/mamoe/mirai/issues/new/choose if you see anything abnormal", PROTOCOL_UPDATED ) }, size) } PacketEncryptType.NoEncrypt -> buffer else -> throw PacketCodecException("Unknown encrypt type=$encryptMethod", PROTOCOL_UPDATED) }.let { decryptedData -> PacketLogger.verbose { "Parsing: type=${packetType}: len=${decryptedData.size}, value=${decryptedData.toUHexString()}" } when (packetType) { IncomingPacketType.Login -> parseSsoFrame(client, decryptedData) IncomingPacketType.Simple -> parseSsoFrame( client, decryptedData ) // 这里可能是 uni?? 但测试时候发现结构跟 sso 一样. else -> throw PacketCodecException( "unknown packet type: ${packetType.value.toUHexString()}", PROTOCOL_UPDATED ) }.also { pkg -> PacketLogger.debug { "result: ${pkg.commandName}, seq=${pkg.sequenceId}, ${ pkg.body.copy() .useBytes { data: ByteArray, length: Int -> data.toUHexString(length = length) } }" } } } } catch (e: Exception) { throw e.also { if (flag3Exception != null) { it.addSuppressed(flag3Exception) } } } if (flag3 != 0 && flag3Exception != null) { if (raw.commandName == WtLogin.TransEmp.commandName) { PacketLogger.warning( "unknown flag3: $flag3 in packet ${WtLogin.TransEmp.commandName}, " + "which may means protocol is updated.", flag3Exception ) } else if (raw.commandName.startsWith(TRpcRawPacket.COMMAND_PREFIX)) { PacketLogger.verbose { "received a trpc native packet: ${raw.commandName}" } } else { throw flag3Exception } } when (encryptMethod) { PacketEncryptType.NoEncrypt, PacketEncryptType.D2 -> RawIncomingPacket( raw.commandName, raw.sequenceId, raw.body.readBytes() ) PacketEncryptType.Empty -> { RawIncomingPacket( raw.commandName, raw.sequenceId, raw.body.withUse { if (raw.commandName.startsWith(TRpcRawPacket.COMMAND_PREFIX)) { readBytes() } else { try { parseOicqResponse(client, raw.commandName) } catch (e: Throwable) { throw PacketCodecException(e, PacketCodecException.Kind.OTHER) } } } ) } else -> error("unreachable") } } } internal class DecodeResult constructor( val commandName: String, val sequenceId: Int, /** * Can be passed to [PacketFactory] */ val body: ByteReadPacket, ) private fun parseSsoFrame(client: SsoSession, bytes: ByteArray): DecodeResult = bytes.toReadPacket().let { input -> val commandName: String val ssoSequenceId: Int val dataCompressed: Int input.readPacketExact(input.readInt() - 4).withUse { ssoSequenceId = readInt() PacketLogger.verbose { "sequenceId = $ssoSequenceId" } val returnCode = readInt() if (returnCode != 0) { if (returnCode <= -10000) { // #470: -10008, 例如在手机QQ强制下线机器人 // #1957: -10106, 未知原因, 但会导致收不到消息 throw PacketCodecException( "Received packet returnCode = $returnCode, which may mean session expired.", SESSION_EXPIRED ) // 备注: 之后该异常将会导致 NetworkHandler close, 然后由 selector 触发重连. // 重连时会在 net.mamoe.mirai.internal.network.components.SsoProcessorImpl.login 进行 FastLogin. // 不确定在这种情况下执行 FastLogin 是否正确. 若有问题, 考虑强制执行 SlowLogin (by invalidating session). } else { throw PacketCodecException( "Received unknown packet returnCode = $returnCode, ignoring. Please report to https://github.com/mamoe/mirai/issues/new/choose if you see anything abnormal", OTHER ) // 备注: OTHER 不会触发重连, 只会记录日志. } } if (PacketLogger.isEnabled) { val extraData = readBytes(readInt() - 4) if (extraData.isNotEmpty()) { PacketLogger.verbose { "(sso/inner)extraData = ${extraData.toUHexString()}, result= ${ kotlin.runCatching { readProtoBuf( SSOReserveField.ReserveFields.serializer() ).structureToString() }.getOrElse { e -> "error: " + e.message } }" } } } else { discardExact(readInt() - 4) } commandName = readString(readInt() - 4) client.outgoingPacketSessionId = readBytes(readInt() - 4) dataCompressed = readInt() } val packet = when (dataCompressed) { 0 -> { val size = input.readInt().toLong() and 0xffffffff if (size == input.remaining || size == input.remaining + 4) { input } else { buildPacket { writeInt(size.toInt()) writePacket(input) } } } 1 -> { input.discardExact(4) input.inflateAllAvailable().let { bytes -> val size = bytes.toInt() if (size == bytes.size || size == bytes.size + 4) { bytes.toReadPacket(offset = 4) } else { bytes.toReadPacket() } } } 8 -> input else -> throw PacketCodecException("Unknown dataCompressed flag: $dataCompressed", PROTOCOL_UPDATED) } // body return DecodeResult(commandName, ssoSequenceId, packet) } private fun ByteReadPacket.parseOicqResponse( client: SsoSession, commandName: String ): ByteArray { val qqEcdh = (client as QQAndroidClient).bot.components[EcdhInitialPublicKeyUpdater].getQQEcdh() fun decrypt(encryptionMethod: Int): ByteArray { return when (encryptionMethod) { 4 -> { val size = (this.remaining - 1).toInt() val data = TEA.decrypt( this.readBytes(), qqEcdh.initialQQShareKey, length = size ) val peerShareKey = qqEcdh.calculateQQShareKey(Ecdh.Instance.importPublicKey(readUShortLVByteArray())) TEA.decrypt(data, peerShareKey) } 3 -> { val size = (this.remaining - 1).toInt() // session TEA.decrypt( this.readBytes(), client.wLoginSigInfo.wtSessionTicketKey, length = size ) } 0 -> { if (client.loginState == 0) { val size = (this.remaining - 1).toInt() val byteArrayBuffer = this.readBytes(size) runCatching { TEA.decrypt(byteArrayBuffer, qqEcdh.initialQQShareKey, length = size) }.getOrElse { TEA.decrypt(byteArrayBuffer, client.randomKey, length = size) } } else { val size = (this.remaining - 1).toInt() TEA.decrypt(this.readBytes(), client.randomKey, length = size) } } else -> error("Illegal encryption method. expected 0 or 4, got $encryptionMethod") } } val packetType = readByte().toInt() if (packetType != 2) { val fullPacketDump = copy().readBytes().toUHexString() var decryptedData: String? = null if (remaining > 15) { discardExact(12) val encryptionMethod = this.readShort().toUShort().toInt() discardExact(1) decryptedData = kotlin.runCatching { decrypt(encryptionMethod).toUHexString() }.getOrNull() } throw PacketCodecException( "Received unknown oicq packet type = $packetType, command name = $commandName, ignoring..." + "\nPlease report this message to https://github.com/mamoe/mirai/issues/new/choose, \n" + "Full packet dump: $fullPacketDump\n" + "Decrypted data (contains your encrypted password, please change your password after reporting issue): $decryptedData", PROTOCOL_UPDATED ) } this.discardExact(2) this.discardExact(2) this.readShort().toUShort() this.readShort() this.readInt().toUInt().toLong() val encryptionMethod = this.readShort().toUShort().toInt() this.discardExact(1) return decrypt(encryptionMethod) } /** * Process [RawIncomingPacket] using [IncomingPacketFactory.decode]. * * This function wraps exceptions into [IncomingPacket] */ override suspend fun processBody(bot: QQAndroidBot, input: RawIncomingPacket): IncomingPacket? { val factory = KnownPacketFactories.findPacketFactory(input.commandName) ?: TRpcRawPacket.takeIf { input.commandName.startsWith(TRpcRawPacket.COMMAND_PREFIX) } ?: return null return kotlin.runCatching { input.body.toReadPacket().use { body -> when (factory) { // specially is TRpcRawPacket -> TRpcRawPacket.decode(input, body) is OutgoingPacketFactory -> factory.decode(bot, body) is IncomingPacketFactory -> factory.decode(bot, body, input.sequenceId) } } }.fold( onSuccess = { packet -> IncomingPacket(input.commandName, input.sequenceId, packet) }, onFailure = { exception: Throwable -> IncomingPacket(input.commandName, input.sequenceId, exception) } ) } } /** * Represents a packet that has just been decrypted. Subsequent operation is normally passing it to a responsible [PacketFactory] according to [commandName] from [KnownPacketFactories]. */ internal class RawIncomingPacket constructor( val commandName: String, val sequenceId: Int, /** * Can be passed to [PacketFactory] */ val body: ByteArray, ) ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/PacketHandler.kt ================================================ /* * 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.components import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.event.Event import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.MultiPacket import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.utils.Either.Companion.ifRight import net.mamoe.mirai.utils.Either.Companion.onRight import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.cast import kotlin.coroutines.cancellation.CancellationException internal interface PacketHandler { suspend fun handlePacket(incomingPacket: IncomingPacket) companion object : ComponentKey<PacketHandler> } internal class PacketHandlerChain( private val instances: Collection<PacketHandler> ) : PacketHandler { constructor(vararg instances: PacketHandler?) : this(instances.filterNotNull()) constructor(instances: Iterable<PacketHandler?>) : this(instances.filterNotNull()) override suspend fun handlePacket(incomingPacket: IncomingPacket) { for (instance in instances) { try { instance.handlePacket(incomingPacket) } catch (e: Throwable) { if (e is CancellationException) return throw ExceptionInPacketHandlerException(instance, incomingPacket, e) } } } } internal data class ExceptionInPacketHandlerException( val packetHandler: PacketHandler, val incomingPacket: IncomingPacket, override val cause: Throwable, ) : IllegalStateException("Exception in PacketHandler '$packetHandler' for command '${incomingPacket.commandName}'.") internal class LoggingPacketHandlerAdapter( private val strategy: PacketLoggingStrategy, private val logger: MiraiLogger, ) : PacketHandler { override suspend fun handlePacket(incomingPacket: IncomingPacket) { strategy.logReceived(logger, incomingPacket) } override fun toString(): String = "LoggingPacketHandlerAdapter" } internal class EventBroadcasterPacketHandler( private val components: ComponentStorage, ) : PacketHandler { override suspend fun handlePacket(incomingPacket: IncomingPacket) { incomingPacket.result.ifRight(::impl) } private fun impl(packet: Packet?) { if (packet == null) return if (packet is MultiPacket) { for (p in packet.children()) { impl(p) } } when { packet is CancellableEvent && packet.isCancelled -> return packet is BroadcastControllable && !packet.shouldBroadcast -> return packet is Event -> { components[EventDispatcher].broadcastAsync(packet) } } } override fun toString(): String = "EventBroadcasterPacketHandler" } internal class CallPacketFactoryPacketHandler( private val bot: QQAndroidBot, ) : PacketHandler { override suspend fun handlePacket(incomingPacket: IncomingPacket) { incomingPacket.result.onRight { data -> val factory = KnownPacketFactories.findPacketFactory(incomingPacket.commandName) ?: return factory.cast<PacketFactory<Packet?>>().run { when (this) { is IncomingPacketFactory -> { val r = bot.handle(data, incomingPacket.sequenceId) if (r != null) { bot.network.sendWithoutExpect(r) } } is OutgoingPacketFactory -> bot.handle(data) } } } } override fun toString(): String = "CallPacketFactoryPacketHandler" } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/PacketLoggingStrategy.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.contact.logMessageReceived import net.mamoe.mirai.internal.contact.replaceMagicCodes import net.mamoe.mirai.internal.network.MultiPacket import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.ParseErrorPacket import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.utils.Either.Companion.fold import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.systemProp import net.mamoe.mirai.utils.verbose import kotlin.coroutines.cancellation.CancellationException /** * Implementation must be fast and non-blocking, throwing no exception. */ internal interface PacketLoggingStrategy { fun logSent(logger: MiraiLogger, outgoingPacket: OutgoingPacket) fun logReceived(logger: MiraiLogger, incomingPacket: IncomingPacket) companion object : ComponentKey<PacketLoggingStrategy> } internal class PacketLoggingStrategyImpl( private val bot: AbstractBot, private val blacklist: Set<String> = getDefaultBlacklist(), ) : PacketLoggingStrategy { override fun logSent(logger: MiraiLogger, outgoingPacket: OutgoingPacket) { if (outgoingPacket.commandName in blacklist) return logger.verbose { "Send: ${outgoingPacket.displayName}" } } override fun logReceived(logger: MiraiLogger, incomingPacket: IncomingPacket) { incomingPacket.result.fold( onLeft = { e -> if (e is CancellationException) return logger.error("Exception in decoding packet.", e) }, onRight = { packet -> packet ?: return if (!bot.logger.isEnabled && !logger.isEnabled) return if (packet is MultiPacket) { if (packet.isMeaningful) logReceivedImpl(packet, incomingPacket, logger) for (d in packet.children()) { logReceivedImpl(d, incomingPacket, logger) } } logReceivedImpl(packet, incomingPacket, logger) }, ) } private fun logReceivedImpl(packet: Packet, incomingPacket: IncomingPacket, logger: MiraiLogger) { when (packet) { is ParseErrorPacket -> { packet.direction.getLogger(bot).error("Exception on parsing packet.", packet.error) } is MessageEvent -> packet.logMessageReceived() is Packet.NoLog -> { // nothing to do } // packet is Event && packet !is Packet.NoEventLog -> bot.logger.verbose { // "Event: $packet".replaceMagicCodes() // } // processed in global `Event.broadcast` else -> { if (incomingPacket.commandName in blacklist) return if (SHOW_PACKET_DETAILS) { logger.verbose { "Recv: ${incomingPacket.commandName} ${incomingPacket.result}".replaceMagicCodes() } } else { logger.verbose { "Recv: ${incomingPacket.commandName}".replaceMagicCodes() } } } } } companion object { fun getDefaultBlacklist(): Set<String> { if (systemProp("mirai.network.show.verbose.packets", false)) return emptySet() return DEFAULT_BLACKLIST } private val DEFAULT_BLACKLIST: Set<String> by lazy { setOf( // C2C event sync, too verbose to show. "MessageSvc.PushNotify", "MessageSvc.PbGetMsg", "MessageSvc.PbDeleteMsg", // Group event sync, decoded as specific events, to optimize logs. "OnlinePush.ReqPush", "OnlinePush.RespPush", // Periodic heartbeat, showing them does not help anything. "Heartbeat.Alive", "StatSvc.SimpleGet", ) } } } private val SHOW_PACKET_DETAILS = systemProp("mirai.network.show.packet.details", false) ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/QRCodeLoginProcessor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.atomicfu.atomic import kotlinx.coroutines.delay import net.mamoe.mirai.auth.QRCodeLoginListener import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.QRCodeLoginData import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.utils.MiraiProtocolInternal.Companion.asInternal import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.debug internal interface QRCodeLoginProcessor { suspend fun process(handler: NetworkHandler, client: QQAndroidClient): QRCodeLoginData = error("Not implemented") /** * Allocate a special processor for once login request */ fun prepareProcess(handler: NetworkHandler, client: QQAndroidClient): QRCodeLoginProcessor = error("Not implemented") companion object : ComponentKey<QRCodeLoginProcessor> { internal val NOOP = object : QRCodeLoginProcessor {} fun parse(ssoContext: SsoProcessorContext, logger: MiraiLogger): QRCodeLoginProcessor { return QRCodeLoginProcessorPreLoaded(ssoContext, logger) } } } internal class QRCodeLoginProcessorPreLoaded( private val ssoContext: SsoProcessorContext, private val logger: MiraiLogger, ) : QRCodeLoginProcessor { override fun prepareProcess(handler: NetworkHandler, client: QQAndroidClient): QRCodeLoginProcessor { check(ssoContext.bot.configuration.protocol.asInternal.supportsQRLogin) { "The login protocol must be ANDROID_WATCH or MACOS while enabling qrcode login." + "Set it by `bot.configuration.protocol = BotConfiguration.MiraiProtocol.ANDROID_WATCH`." } val loginSolver = ssoContext.bot.configuration.loginSolver ?: throw IllegalStateException( "No LoginSolver found while enabling qrcode login. " + "Please provide by BotConfiguration.loginSolver. " + "For example use `BotFactory.newBot(...) { loginSolver = yourLoginSolver}` in Kotlin, " + "use `BotFactory.newBot(..., new BotConfiguration() {{ setLoginSolver(yourLoginSolver) }})` in Java." ) val qrCodeLoginListener = loginSolver.createQRCodeLoginListener(client.bot) return loginSolver.run { QRCodeLoginProcessorImpl(qrCodeLoginListener, logger) } } } internal class QRCodeLoginProcessorImpl( private val qrCodeLoginListener: QRCodeLoginListener, private val logger: MiraiLogger, ) : QRCodeLoginProcessor { private var state = atomic(QRCodeLoginListener.State.DEFAULT) private suspend fun requestQRCode( handler: NetworkHandler, client: QQAndroidClient ): WtLogin.TransEmp.Response.FetchQRCode { logger.debug { "requesting qrcode." } val resp = handler.sendAndExpect( WtLogin.TransEmp.FetchQRCode( client, size = qrCodeLoginListener.qrCodeSize, margin = qrCodeLoginListener.qrCodeMargin, ecLevel = qrCodeLoginListener.qrCodeEcLevel, ), ) check(resp is WtLogin.TransEmp.Response.FetchQRCode) { "Cannot fetch qrcode, resp=$resp" } qrCodeLoginListener.onFetchQRCode(handler.context.bot, resp.imageData) return resp } private suspend fun queryQRCodeStatus( handler: NetworkHandler, client: QQAndroidClient, sig: ByteArray ): WtLogin.TransEmp.Response { logger.debug { "querying qrcode state." } val resp = handler.sendAndExpect(WtLogin.TransEmp.QueryQRCodeStatus(client, sig)) check( resp is WtLogin.TransEmp.Response.QRCodeStatus || resp is WtLogin.TransEmp.Response.QRCodeConfirmed ) { "Cannot query qrcode status, resp=$resp" } val currentState = state.value val newState = resp.mapProtocolState() if (currentState != newState && state.compareAndSet(currentState, newState)) { logger.debug { "qrcode state changed: $state" } qrCodeLoginListener.onStateChanged(handler.context.bot, newState) } return resp } override suspend fun process(handler: NetworkHandler, client: QQAndroidClient): QRCodeLoginData { return try { process0(handler, client) } finally { qrCodeLoginListener.onCompleted() } } private suspend fun process0(handler: NetworkHandler, client: QQAndroidClient): QRCodeLoginData { main@ while (true) { val qrCodeData = requestQRCode(handler, client) state@ while (true) { qrCodeLoginListener.onIntervalLoop() when (val status = queryQRCodeStatus(handler, client, qrCodeData.sig)) { is WtLogin.TransEmp.Response.QRCodeConfirmed -> { return status.data } is WtLogin.TransEmp.Response.QRCodeStatus -> when (status.state) { WtLogin.TransEmp.Response.QRCodeStatus.State.TIMEOUT, WtLogin.TransEmp.Response.QRCodeStatus.State.CANCELLED -> { break@state } else -> {} // WAITING_FOR_SCAN or WAITING_FOR_CONFIRM } // status is FetchQRCode, which is unreachable. else -> { error("query qrcode status should not be FetchQRCode.") } } delay(qrCodeLoginListener.qrCodeStateUpdateInterval.coerceAtLeast(200L)) } } } private fun WtLogin.TransEmp.Response.mapProtocolState(): QRCodeLoginListener.State { return when (this) { is WtLogin.TransEmp.Response.QRCodeStatus -> when (this.state) { WtLogin.TransEmp.Response.QRCodeStatus.State.WAITING_FOR_SCAN -> QRCodeLoginListener.State.WAITING_FOR_SCAN WtLogin.TransEmp.Response.QRCodeStatus.State.WAITING_FOR_CONFIRM -> QRCodeLoginListener.State.WAITING_FOR_CONFIRM WtLogin.TransEmp.Response.QRCodeStatus.State.CANCELLED -> QRCodeLoginListener.State.CANCELLED WtLogin.TransEmp.Response.QRCodeStatus.State.TIMEOUT -> QRCodeLoginListener.State.TIMEOUT } is WtLogin.TransEmp.Response.QRCodeConfirmed -> QRCodeLoginListener.State.CONFIRMED is WtLogin.TransEmp.Response.FetchQRCode -> error("$this cannot be mapped to listener state.") } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/ServerList.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.components.ServerList.Companion.DEFAULT_SERVER_LIST import net.mamoe.mirai.internal.network.handler.SocketAddress import net.mamoe.mirai.internal.network.handler.createSocketAddress import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.verbose import kotlin.jvm.Synchronized import kotlin.jvm.Volatile @Serializable internal data class ServerAddress( val host: String, val port: Int ) { init { require(port >= 0) { "port must be positive: '$port'" } require(host.isNotBlank()) { "host is invalid: '$host'" } } override fun toString(): String { return "$host:$port" } fun toSocketAddress(): SocketAddress = createSocketAddress(host, port) } /** * Self-refillable (similarly circular) queue of servers. Pop each time when trying to connect. * * [Preferred][getPreferred] prevails if present than [DEFAULT_SERVER_LIST]. * * *+Implementation must be thread-safe**. */ internal interface ServerList { /** * Set preferred so not using [DEFAULT_SERVER_LIST]. */ fun setPreferred(list: Collection<ServerAddress>) /** * Might return [DEFAULT_SERVER_LIST] if not present. */ fun getPreferred(): Set<ServerAddress> /** * Refill the queue. Mostly do not call this function. */ fun refresh() /** * Last disconnected ip */ var lastDisconnectedIP: String /** * Last connected ip */ var lastConnectedIP: String /** * Get last poll ip */ fun getLastPolledIP(): String /** * [Poll][Queue.poll] from current address list. Returns `null` if current address list is empty. */ fun pollCurrent(): ServerAddress? /** * [Poll][Queue.poll] from current address list, before which the list is filled with preferred addresses or default list if empty. */ fun pollAny(): ServerAddress companion object : ComponentKey<ServerList> { val DEFAULT_SERVER_LIST: Set<ServerAddress> = """msfwifi.3g.qq.com:8080""".trimMargin() .splitToSequence(",").filterNot(String::isBlank) .map { it.trim() } .map { val host = it.substringBefore(':') val port = it.substringAfter(':').toInt() ServerAddress(host, port) }.shuffled().toMutableSet() } } internal class ServerListImpl( private val logger: MiraiLogger, initial: Collection<ServerAddress> = emptyList() ) : ServerList { @TestOnly constructor(initial: Collection<ServerAddress>) : this(MiraiLogger.Factory.create(ServerListImpl::class), initial) @TestOnly constructor() : this(MiraiLogger.Factory.create(ServerListImpl::class)) @Volatile private var preferred: Set<ServerAddress> = DEFAULT_SERVER_LIST @Volatile private var current: ArrayDeque<ServerAddress> = ArrayDeque(initial) @Volatile private var lastPolledAddress: ServerAddress? = null @Synchronized override fun setPreferred(list: Collection<ServerAddress>) { logger.verbose { "Server list: ${list.joinToString()}." } require(list.isNotEmpty()) { "list cannot be empty." } preferred = list.toSet() } override fun getPreferred() = preferred init { refresh() } @Synchronized override fun refresh() { current = preferred.toCollection(ArrayDeque(current.size)) check(current.isNotEmpty()) { "Internal error: failed to fill server list. No server available." } } override var lastDisconnectedIP: String = "" override var lastConnectedIP: String = "" override fun getLastPolledIP(): String = lastPolledAddress?.host ?: "" /** * [Poll][Queue.poll] from current address list. Returns `null` if current address list is empty. */ @Synchronized override fun pollCurrent(): ServerAddress? { return current.removeFirstOrNull()?.also { address -> lastPolledAddress = address } } /** * [Poll][Queue.poll] from current address list, before which the list is filled with preferred addresses or default list if empty. */ @Synchronized override fun pollAny(): ServerAddress { if (current.isEmpty()) refresh() return current.removeFirst().also { address -> lastPolledAddress = address } } override fun toString(): String { return "ServerListImpl(current.size=${current.size})" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/SsoProcessor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import net.mamoe.mirai.auth.AuthReason import net.mamoe.mirai.auth.BotAuthInfo import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.QRCodeLoginData import net.mamoe.mirai.internal.network.WLoginSigInfo import net.mamoe.mirai.internal.network.auth.AuthControl import net.mamoe.mirai.internal.network.auth.BotAuthorizationWithSecretsProtection import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.handler.selector.SelectorRequireReconnectException import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.login.DeviceVerificationResultImpl import net.mamoe.mirai.internal.network.protocol.packet.login.SmsDeviceVerificationResult import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.network.protocol.packet.login.UrlDeviceVerificationResult import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin.Login.LoginPacketResponse.Captcha import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.* import net.mamoe.mirai.internal.network.qimei.requestQimei import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.network.* import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol import kotlin.coroutines.cancellation.CancellationException /** * Handles login, and acts also as a mediator of [BotInitProcessor] */ internal interface SsoProcessor { val client: QQAndroidClient val ssoSession: SsoSession val firstLoginResult: FirstLoginResult? // null means just initialized fun casFirstLoginResult( expect: FirstLoginResult?, update: FirstLoginResult? ): Boolean // enable compiler optimization fun setFirstLoginResult(value: FirstLoginResult?) val firstLoginSucceed: Boolean get() = firstLoginResult?.success ?: false val registerResp: StatSvc.Register.Response? var isFirstLogin: Boolean var authReason: AuthReason /** * Do login. Throws [LoginFailedException] if failed */ @Throws(LoginFailedException::class, CancellationException::class) suspend fun login(handler: NetworkHandler) suspend fun logout(handler: NetworkHandler) suspend fun sendRegister(handler: NetworkHandler): StatSvc.Register.Response companion object : ComponentKey<SsoProcessor> } /** * Wraps [LoginFailedException] into [NetworkException] */ internal class LoginFailedExceptionAsNetworkException( private val underlying: LoginFailedException ) : NetworkException(underlying.message ?: "Login failed", underlying, !underlying.killBot) { override fun unwrapForPublicApi(): Throwable { return underlying } } internal enum class FirstLoginResult( val success: Boolean, val canRecoverOnFirstLogin: Boolean, ) { PASSED(true, true), CHANGE_SERVER(false, true), // by ConfigPush OTHER_FAILURE(false, false), } /** * Contains secrets for encryption and decryption during a session created by [SsoProcessor] and [PacketCodec]. * * @see AccountSecrets */ internal interface SsoSession { var outgoingPacketSessionId: ByteArray /** * always 0 for now. */ var loginState: Int // also present in AccountSecrets var wLoginSigInfo: WLoginSigInfo val randomKey: ByteArray } /** * Strategy that performs the process of single sing-on (SSO). (login) * * And allows to retire the [session][ssoSession] after success. * * Used by `NettyNetworkHandler.StateConnecting`. */ internal open class SsoProcessorImpl( val ssoContext: SsoProcessorContext, ) : SsoProcessor { /////////////////////////////////////////////////////////////////////////// // public /////////////////////////////////////////////////////////////////////////// private val _firstLoginResult: AtomicRef<FirstLoginResult?> = atomic(null) override val firstLoginResult get() = _firstLoginResult.value override fun casFirstLoginResult(expect: FirstLoginResult?, update: FirstLoginResult?): Boolean = _firstLoginResult.compareAndSet(expect, update) override fun setFirstLoginResult(value: FirstLoginResult?) { _firstLoginResult.value = value } @Volatile override var registerResp: StatSvc.Register.Response? = null override var client get() = ssoContext.bot.components[BotClientHolder].client set(value) { ssoContext.bot.components[BotClientHolder].client = value } private val qimeiLogger by lazy { ssoContext.bot.network.logger.subLogger("QimeiApi") } override val ssoSession: SsoSession get() = client private val components get() = ssoContext.bot.components override var isFirstLogin: Boolean = true override var authReason: AuthReason by lateinitMutableProperty { AuthReason.FreshLogin(ssoContext.bot, null) } private val botAuthInfo = object : BotAuthInfo { override val id: Long get() = ssoContext.bot.id override val deviceInfo: DeviceInfo get() = ssoContext.device override val configuration: BotConfiguration get() = ssoContext.bot.configuration override val isFirstLogin: Boolean get() = this@SsoProcessorImpl.isFirstLogin override val reason: AuthReason get() = this@SsoProcessorImpl.authReason } protected open suspend fun doSlowLogin( handler: NetworkHandler, loginType: LoginType ) { SlowLoginImpl(handler, loginType).doLogin() } protected open suspend fun doFastLogin(handler: NetworkHandler) { FastLoginImpl(handler).doLogin() } /** * Throws [LoginFailedException] if failed. Any other exceptions are considered as internal error. */ final override suspend fun login(handler: NetworkHandler) { fun initAndStartAuthControl() { authControl = AuthControl( botAuthInfo, ssoContext.bot.account.authorization, ssoContext.bot.network.logger, ssoContext.bot.coroutineContext, // do not use network context because network may restart whilst auth control should keep alive ).also { it.start() } } suspend fun loginSuccess() { components[AccountSecretsManager].saveSecrets(ssoContext.account, AccountSecretsImpl(client)) sendRegister(handler) ssoContext.bot.logger.info { "Login successful." } } if (authControl == null) { ssoContext.bot.account.let { account -> if (account.accountSecretsKeyBuffer == null) { account.accountSecretsKeyBuffer = when (val authorization = account.authorization) { is BotAuthorizationWithSecretsProtection -> authorization.calculateSecretsKeyImpl(botAuthInfo) else -> SecretsProtection.EscapedByteBuffer(authorization.calculateSecretsKey(botAuthInfo)) } } } components[CacheValidator].validate() components[BdhSessionSyncer].loadServerListFromCache() try { ssoContext.bot.requestQimei(qimeiLogger) } catch (exception: Throwable) { qimeiLogger.warning("Cannot get qimei from server.", exception) } // trpc ecdh service needs qimei to init components[EcdhInitialPublicKeyUpdater].initializeSsoSecureEcdh() // try fast login if (client.wLoginSigInfoInitialized) { ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh() kotlin.runCatching { doFastLogin(handler) }.onFailure { e -> // first fast-login exception should also be considered as re-auth cause. if (isFirstLogin) { authReason = AuthReason.FastLoginError(ssoContext.bot, e.message) } initAndStartAuthControl() authControl!!.exceptionCollector.collect(e) throw SelectorRequireReconnectException() } loginSuccess() return } else if (isFirstLogin) { authReason = AuthReason.FreshLogin(ssoContext.bot, null) } } if (authControl == null) initAndStartAuthControl() val authControl0 = authControl!! var nextAuthMethod: AuthMethod? = null try { ssoContext.bot.components[BotClientHolder].refreshClient() ssoContext.bot.components[EcdhInitialPublicKeyUpdater].refreshInitialPublicKeyAndApplyEcdh() when (val authw = authControl0.acquireAuth().also { nextAuthMethod = it }) { is AuthMethod.Error -> { authControl = null throw BotAuthorizationException(ssoContext.account.authorization, authw.exception) } AuthMethod.NotAvailable -> { authControl = null error("No more auth method available") } is AuthMethod.Pwd -> { val loginType = LoginType.Password(authw.passwordMd5) doSlowLogin(handler, loginType) } AuthMethod.QRCode -> { val rsp = ssoContext.bot.components[QRCodeLoginProcessor].prepareProcess( handler, client ).process(handler, client) val loginType = LoginType.QRCode(rsp) doSlowLogin(handler, loginType) } } authControl!!.actComplete() authControl = null } catch (exception: Throwable) { if (exception is SelectorRequireReconnectException) { throw exception } ssoContext.bot.network.logger.warning({ "Failed with auth method: $nextAuthMethod" }, exception) authControl0.exceptionCollector.collectException(exception) if (nextAuthMethod !is AuthMethod.Error && nextAuthMethod != null) { authControl0.actMethodFailed(exception) } if (exception is NetworkException) { if (exception.recoverable) throw exception } if (nextAuthMethod == null || nextAuthMethod is AuthMethod.NotAvailable || nextAuthMethod is AuthMethod.Error) { authControl = null authControl0.exceptionCollector.throwLast() } throw SelectorRequireReconnectException() } loginSuccess() } sealed class AuthMethod { object NotAvailable : AuthMethod() { override fun toString(): String = "NotAvailable" } object QRCode : AuthMethod() { override fun toString(): String = "QRCode" } class Pwd(val passwordMd5: SecretsProtection.EscapedByteBuffer) : AuthMethod() { override fun toString(): String = "Password@${hashCode()}" } /** * Exception in [BotAuthorization] */ class Error( val exception: Throwable // unwrapped ) : AuthMethod() { override fun toString(): String = "Error[$exception]@${hashCode()}" } } private var authControl: AuthControl? = null override suspend fun sendRegister(handler: NetworkHandler): StatSvc.Register.Response { return handler.sendAndExpect(StatSvc.Register.online(client)).also { registerResp = it } } override suspend fun logout(handler: NetworkHandler) { if (firstLoginSucceed) { handler.sendWithoutExpect(StatSvc.Register.offline(client)) } } /////////////////////////////////////////////////////////////////////////// // login /////////////////////////////////////////////////////////////////////////// // we have exactly two methods----slow and fast. protected abstract inner class LoginStrategy( val handler: NetworkHandler, ) { protected val context get() = handler.context protected val bot get() = context.bot protected val logger get() = bot.logger protected suspend fun <R : Packet?> OutgoingPacketWithRespType<R>.sendAndExpect(): R = handler.sendAndExpect(this) abstract suspend fun doLogin() } private inner class SlowLoginImpl( handler: NetworkHandler, private val loginType: LoginType ) : LoginStrategy(handler) { private fun loginSolverNotNull(): LoginSolver { fun LoginSolver?.notnull(): LoginSolver { checkNotNull(this) { "No LoginSolver found. Please provide by BotConfiguration.loginSolver. " + "For example use `BotFactory.newBot(...) { loginSolver = yourLoginSolver}` in Kotlin, " + "use `BotFactory.newBot(..., new BotConfiguration() {{ setLoginSolver(yourLoginSolver) }})` in Java." } return this } return bot.configuration.loginSolver.notnull() } private val sliderSupported get() = bot.configuration.loginSolver?.isSliderCaptchaSupported ?: false private fun createUnsupportedSliderCaptchaException(allowSlider: Boolean): UnsupportedSliderCaptchaException { return UnsupportedSliderCaptchaException( buildString { append("Mirai 无法完成滑块验证.") if (allowSlider) { append(" 使用协议 ") append(ssoContext.protocol) append(" 强制要求滑块验证, 请更换协议后重试.") } append(", extra={ login-solver=") bot.configuration.loginSolver.let { ls -> append(ls) append(" <") append(ls?.let { it::class }) append(">") } append(" 另请参阅: https://github.com/project-mirai/mirai-login-solver-selenium") } ) } override suspend fun doLogin() = withExceptionCollector { @Suppress("FunctionName") fun SSOWtLogin9(allowSlider: Boolean) = when (loginType) { is LoginType.Password -> WtLogin9.Password(client, loginType.passwordMd5.asByteArray, allowSlider) is LoginType.QRCode -> WtLogin9.QRCode(client, loginType.qrCodeLoginData) } var allowSlider = sliderSupported || bot.configuration.protocol == MiraiProtocol.ANDROID_PHONE var response: LoginPacketResponse = SSOWtLogin9(allowSlider).sendAndExpect() mainloop@ while (true) { when (response) { is LoginPacketResponse.Success -> { logger.info { "Login successful" } break@mainloop } is LoginPacketResponse.DeviceLockLogin -> { response = WtLogin20(client).sendAndExpect() } is LoginPacketResponse.VerificationNeeded -> { val result = loginSolverNotNull().onSolveDeviceVerification( bot, response.requests ) check(result is DeviceVerificationResultImpl) response = when (result) { is UrlDeviceVerificationResult -> { SSOWtLogin9(allowSlider).sendAndExpect() } is SmsDeviceVerificationResult -> { WtLogin7(client, result.token, result.code).sendAndExpect() } } } is Captcha.Picture -> { var result = loginSolverNotNull().onSolvePicCaptcha(bot, response.data) if (result == null || result.length != 4) { //refresh captcha result = "ABCD" } response = WtLogin2.SubmitPictureCaptcha(client, response.sign, result).sendAndExpect() } is Captcha.Slider -> { if (sliderSupported) { // use solver val ticket = try { loginSolverNotNull().onSolveSliderCaptcha(bot, response.url)?.takeIf { it.isNotEmpty() } } catch (error: Throwable) { collectThrow(error) } response = if (ticket == null) { SSOWtLogin9(allowSlider).sendAndExpect() } else { WtLogin2.SubmitSliderCaptcha(client, ticket).sendAndExpect() } } else { // retry once if (!allowSlider) collectThrow(createUnsupportedSliderCaptchaException(allowSlider)) // allowSlider = false // TODO Reconnect without slider request // Need to create new connection NOT send it in current connection // response = WtLogin9(client, allowSlider).sendAndExpect() collectThrow(createUnsupportedSliderCaptchaException(false)) } } is LoginPacketResponse.Error -> { if (response.message.contains("0x9a")) { //Error(title=登录失败, message=请你稍后重试。(0x9a), errorInfo=) collectThrow(RetryLaterException("Login failed: $response")) } val msg = response.toString() collectThrow(WrongPasswordException(buildString(capacity = msg.length) { append(msg) if (msg.contains("当前上网环境异常")) { // Error(title=禁止登录, message=当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。, errorInfo=) append(", mirai 提示: 若频繁出现, 请尝试开启设备锁") } if (msg.contains("当前登录存在安全风险")) { // Error(title=禁止登录, message=当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。, errorInfo=) append(", mirai 提示: 这可能是尝试登录次数过多导致的, 请等待一段时间后再试") } if (!sliderSupported) { append(", extra={ sliderSupported=false, login-solver=").append(bot.configuration.loginSolver) append(" <").append( bot.configuration.loginSolver?.let { it::class } ) append("> }") if (msg.contains("版本过低")) { append(", mirai 提示: 提供给 mirai 的验证码处理器不支持滑块验证, 请报告至此验证器的作者") } } })) } is LoginPacketResponse.SmsRequestSuccess -> { error("Unexpected response: $response") } } } } } protected sealed class LoginType { class Password(val passwordMd5: SecretsProtection.EscapedByteBuffer) : LoginType() class QRCode(val qrCodeLoginData: QRCodeLoginData) : LoginType() } protected inner class FastLoginImpl(handler: NetworkHandler) : LoginStrategy(handler) { override suspend fun doLogin() { val login10 = handler.sendAndExpect(WtLogin10(client)) check(login10 is LoginPacketResponse.Success) { "Fast login failed: $login10" } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/SsoProcessorContext.kt ================================================ /* * 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.components import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.DeviceInfo /** * Provides the information needed by the [SsoProcessor]. */ internal interface SsoProcessorContext { /** * Use other properties instead. Use [bot] only when you cannot find other properties. */ val bot: QQAndroidBot val account: BotAccount val device: DeviceInfo val protocol: BotConfiguration.MiraiProtocol val configuration: BotConfiguration get() = bot.configuration /** * t545 */ var qimei16: String? var qimei36: String? companion object : ComponentKey<SsoProcessorContext> } internal class SsoProcessorContextImpl( override val bot: QQAndroidBot, ) : SsoProcessorContext { override val account: BotAccount get() = bot.account override val device: DeviceInfo = configuration.createDeviceInfo(bot) override val protocol: BotConfiguration.MiraiProtocol get() = configuration.protocol override val configuration: BotConfiguration get() = bot.configuration override var qimei16: String? = null override var qimei36: String? = null } internal fun BotConfiguration.createDeviceInfo(bot: Bot): DeviceInfo = deviceInfo?.invoke(bot) ?: DeviceInfo.random() ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/SyncController.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.components import kotlinx.atomicfu.AtomicBoolean import kotlinx.atomicfu.atomic import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.protocol.SyncingCacheList import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.jvm.Volatile internal interface SyncController { val firstNotify: Boolean fun casFirstNotify(expect: Boolean, update: Boolean): Boolean var latestMsgNewGroupTime: Long var latestMsgNewFriendTime: Long var syncCookie: ByteArray? var pubAccountCookie: ByteArray var msgCtrlBuf: ByteArray fun syncOnlinePush(uid: Long, sequence: Short, time: Long): Boolean fun syncNewFriend(sequence: Long, time: Long): Boolean fun syncNewGroup(sequence: Long, time: Long): Boolean fun syncGetMessage(uid: Long, sequence: Int, time: Int): Boolean fun syncPushTrans(uid: Long, sequence: Int, time: Int): Boolean fun syncGroupMessageReceipt(messageRandom: Int): Boolean fun containsGroupMessageReceipt(messageRandom: Int): Boolean companion object : ComponentKey<SyncController> { val AbstractBot.syncController get() = this.components[SyncController] val QQAndroidClient.syncController get() = bot.syncController var QQAndroidClient.syncCookie get() = bot.syncController.syncCookie set(value) { bot.syncController.syncCookie = value } } } internal fun SyncController.syncPushTrans(content: OnlinePushTrans.PbMsgInfo): Boolean = syncPushTrans(content.msgUid, content.msgSeq, content.msgTime) internal fun SyncController.syncGetMessage( msgHead: MsgComm.MsgHead, ) = msgHead.run { syncGetMessage(msgUid, msgSeq, msgTime) } internal fun SyncController.syncOnlinePush( msgInfo: MsgInfo, ) = syncOnlinePush( uid = msgInfo.lMsgUid ?: 0, sequence = msgInfo.shMsgSeq, time = msgInfo.uMsgTime, ) internal class SyncControllerImpl : SyncController { private val _firstNotify: AtomicBoolean = atomic(true) override val firstNotify get() = _firstNotify.value override fun casFirstNotify(expect: Boolean, update: Boolean): Boolean = _firstNotify.compareAndSet(expect, update) @Volatile override var latestMsgNewGroupTime: Long = currentTimeSeconds() @Volatile override var latestMsgNewFriendTime: Long = currentTimeSeconds() @Volatile override var syncCookie: ByteArray? = null @Volatile override var pubAccountCookie = EMPTY_BYTE_ARRAY @Volatile override var msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY private val pbGetMessageCacheList = SyncingCacheList<PbGetMessageSyncId>() private val systemMsgNewGroupCacheList = SyncingCacheList<SystemMsgNewSyncId>(10) private val systemMsgNewFriendCacheList = SyncingCacheList<SystemMsgNewSyncId>(10) private val pbPushTransMsgCacheList = SyncingCacheList<PbPushTransMsgSyncId>(10) private val onlinePushReqPushCacheList = SyncingCacheList<OnlinePushReqPushSyncId>(50) private val pendingGroupMessageReceiptCacheList = SyncingCacheList<PendingGroupMessageReceiptSyncId>(50) override fun syncOnlinePush(uid: Long, sequence: Short, time: Long): Boolean = onlinePushReqPushCacheList.addCache(OnlinePushReqPushSyncId(uid, sequence, time)) override fun syncNewFriend(sequence: Long, time: Long): Boolean = systemMsgNewFriendCacheList.addCache(SystemMsgNewSyncId(sequence, time)) override fun syncNewGroup(sequence: Long, time: Long): Boolean = systemMsgNewGroupCacheList.addCache(SystemMsgNewSyncId(sequence, time)) override fun syncGetMessage(uid: Long, sequence: Int, time: Int): Boolean = pbGetMessageCacheList.addCache(PbGetMessageSyncId(uid, sequence, time)) override fun syncPushTrans(uid: Long, sequence: Int, time: Int): Boolean = pbPushTransMsgCacheList.addCache(PbPushTransMsgSyncId(uid, sequence, time)) override fun syncGroupMessageReceipt(messageRandom: Int): Boolean = pendingGroupMessageReceiptCacheList.addCache(PendingGroupMessageReceiptSyncId(messageRandom)) override fun containsGroupMessageReceipt(messageRandom: Int): Boolean = pendingGroupMessageReceiptCacheList.contains { it.messageRandom == messageRandom } data class PbGetMessageSyncId( val uid: Long, val sequence: Int, val time: Int, ) data class SystemMsgNewSyncId( val sequence: Long, val time: Long, ) data class PbPushTransMsgSyncId( val uid: Long, val sequence: Int, val time: Int, ) data class OnlinePushReqPushSyncId( val uid: Long, val sequence: Short, val time: Long, ) data class PendingGroupMessageReceiptSyncId( val messageRandom: Int, ) } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/components/package.kt ================================================ /* * 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.components ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/CommonNetworkHandler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.onFailure import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.handler.NetworkHandler.Companion.runUnwrapCancellationException import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.handler.selector.NetworkHandlerSelector import net.mamoe.mirai.internal.network.handler.selector.SelectorRequireReconnectException import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.internal.network.impl.HeartbeatFailedException import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.* import kotlin.coroutines.CoroutineContext /** * Implements protocol-specific logic based on [NetworkHandlerSupport]. This can be shared between tests and */ internal abstract class CommonNetworkHandler<Conn>( context: NetworkHandlerContext, protected val address: SocketAddress, ) : NetworkHandlerSupport(context) { final override tailrec suspend fun sendPacketImpl(packet: OutgoingPacket) { val state = _state as CommonNetworkHandler<*>.CommonState if (state.sendPacketImpl(packet)) return // now the state it not yet ready for sending packet ... stateChannel.receive() // [SUSPENSION POINT] so we wait for next state ... return sendPacketImpl(packet) // and try again. } override fun toString(): String { return "CommonNetworkHandler(context=$context, address=$address)" } /////////////////////////////////////////////////////////////////////////// // exception handling /////////////////////////////////////////////////////////////////////////// protected open fun handleExceptionInDecoding(error: Throwable) { fun passToExceptionHandler() { // Typically, just log the exception coroutineContext[CoroutineExceptionHandler]!!.handleException( coroutineContext, ExceptionInPacketCodecException(error.unwrap<PacketCodecException>()) ) } if (error is PacketCodecException) { if (error.targetException is EOFException) return when (error.kind) { PacketCodecException.Kind.SESSION_EXPIRED -> { setState { StateClosed(error) } return } PacketCodecException.Kind.PROTOCOL_UPDATED -> passToExceptionHandler() PacketCodecException.Kind.OTHER -> passToExceptionHandler() } } passToExceptionHandler() } /////////////////////////////////////////////////////////////////////////// // conn. /////////////////////////////////////////////////////////////////////////// /** * Creates a connection. */ protected abstract suspend fun createConnection(): Conn /** * Writes and flushes the packet asynchronously. */ protected abstract fun Conn.writeAndFlushOrCloseAsync(packet: OutgoingPacket) /** * Close this connection for general reason, immediately. Immediately means any live objects with lifecycle must be marked *cancelled*, *dead* or any terminate state. */ @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "KotlinRedundantDiagnosticSuppress") // can happen on some platforms protected abstract fun Conn.close() /** * *Single-thread* event-loop packet decoder. * * Call [PacketDecodePipeline.send] to submit a decoding job. */ internal inner class PacketDecodePipeline(parentContext: CoroutineContext) : CoroutineScope by parentContext.childScope() { private val packetCodec: PacketCodec by lazy { context[PacketCodec] } private val ssoProcessor: SsoProcessor by lazy { context[SsoProcessor] } private val queue: Channel<ByteReadPacket> = Channel<ByteReadPacket>(Channel.BUFFERED) { undelivered -> launch { sendQueue(undelivered) } }.also { channel -> coroutineContext[Job]!!.invokeOnCompletion { channel.close(it) } } private suspend inline fun sendQueue(packet: ByteReadPacket) { queue.send(packet) } init { launch { while (isActive) { val result = queue.receiveCatching() packetLogger.verbose { "Decoding packet: $result" } result.onFailure { if (it is CancellationException) return@launch } result.getOrNull()?.let { packet -> try { val decoded = decodePacket(packet) launch(start = CoroutineStart.UNDISPATCHED) { processBody(decoded) } } catch (e: Throwable) { if (e is CancellationException) return@launch handleExceptionInDecoding(e) logger.error("Error while decoding packet '${packet}'", e) } } } } } private fun decodePacket(packet: ByteReadPacket): RawIncomingPacket { return if (packetLogger.isDebugEnabled) { val bytes = packet.readBytes() logger.verbose { "Decoding: len=${bytes.size}, value=${bytes.toUHexString()}" } val raw = packetCodec.decodeRaw( ssoProcessor.ssoSession, bytes.toReadPacket() ) logger.verbose { "Decoded: ${raw.commandName}" } raw } else { packetCodec.decodeRaw( ssoProcessor.ssoSession, packet ) } } private suspend fun processBody(raw: RawIncomingPacket) { packetLogger.debug { "Packet Handling Processor: receive packet ${raw.commandName}" } val result = packetCodec.processBody(context.bot, raw) if (result == null) { collectUnknownPacket(raw) } else collectReceived(result) } fun send(packet: ByteReadPacket) { queue.trySend(packet).onFailure { throw it ?: throw IllegalStateException("Internal error: Failed to decode '$packet' without reason.") } } } /////////////////////////////////////////////////////////////////////////// // states /////////////////////////////////////////////////////////////////////////// override fun close(cause: Throwable?) { if (state == NetworkHandler.State.CLOSED) return // quick check if already closed if (setState { StateClosed(cause) } == null) return // atomic check super.close(cause) // cancel coroutine scope } init { coroutineContext.job.invokeOnCompletion { e -> close(e) } } /** * When state is initialized, it must be set to [_state]. (inside [setState]) * * For what jobs each state will do, it is not solely decided by the state itself. [StateObserver]s may also launch jobs into the scope. * * @see StateObserver */ protected abstract inner class CommonState( correspondingState: NetworkHandler.State, ) : NetworkHandlerSupport.BaseStateImpl(correspondingState) { /** * @return `true` if packet has been sent, `false` if state is not ready for send. * @throws IllegalStateException if is [StateClosed]. */ abstract suspend fun sendPacketImpl(packet: OutgoingPacket): Boolean } protected inner class StateInitialized : CommonState(NetworkHandler.State.INITIALIZED) { override suspend fun sendPacketImpl(packet: OutgoingPacket): Boolean { // error("Cannot send packet when connection is not set. (resumeConnection not called.)") return false } override suspend fun resumeConnection0() { this.setState { StateConnecting(ExceptionCollector()) } ?.resumeConnection() ?: this@CommonNetworkHandler.resumeConnection() // concurrently closed by other thread. } override fun toString(): String = "StateInitialized" } /** * 1. Connect to server. * 2. Perform SSO login with [SsoProcessor] * * If failure, set state to [StateClosed] * If success, set state to [StateOK] */ protected inner class StateConnecting( /** * Collected (suppressed) exceptions that have led this state. * * Dropped when state becomes [StateOK]. */ private val collectiveExceptions: ExceptionCollector, ) : CommonState(NetworkHandler.State.CONNECTING) { private lateinit var connection: Deferred<Conn> private lateinit var connectResult: Deferred<Unit> override fun startState() { connection = async { createConnection() } connectResult = async { connection.join() try { context[SsoProcessor].login(this@CommonNetworkHandler) } catch (e: LoginFailedException) { throw LoginFailedExceptionAsNetworkException(e) } } connectResult.invokeOnCompletion { error -> if (error == null) { this@CommonNetworkHandler.launch { resumeConnection() } // go to next state. } else { // failed in SSO stage context[SsoProcessor].casFirstLoginResult( null, when (error) { is SelectorRequireReconnectException -> null else -> FirstLoginResult.OTHER_FAILURE } ) if (error is CancellationException) { // CancellationException is either caused by parent cancellation or manual `connectResult.cancel`. // The later should not happen, so it's definitely due to the parent cancellation. // It means that the super scope, the NetworkHandler is closed. // If we don't `return` here, state will be set to StateClosed with CancellationException, which isn't the real cause. return@invokeOnCompletion } setState { // logon failure closes the network handler. StateClosed(collectiveExceptions.collectGet(error)) // The exception will be ignored unless all further attempts recovering connection have failed. // This is to reduce useless logs for the user----there is nothing to worry about if we can recover the connection. } } } } override fun getCause(): Throwable? = collectiveExceptions.getLast() override suspend fun sendPacketImpl(packet: OutgoingPacket): Boolean = runUnwrapCancellationException { connection.await() // split line number .writeAndFlushOrCloseAsync(packet) return true } override suspend fun resumeConnection0() = runUnwrapCancellationException { connectResult.await() // propagates exceptions val connection = connection.await() this.setState { StateLoading(connection) } ?.resumeConnection() ?: this@CommonNetworkHandler.resumeConnection() // concurrently closed by other thread. } override fun toString(): String = "StateConnecting" } /** * @see BotInitProcessor * @see StateObserver */ protected inner class StateLoading( private val connection: Conn, ) : CommonState(NetworkHandler.State.LOADING) { override fun startState() { coroutineContext.job.invokeOnCompletion { if (it != null) { connection.close() } } } override suspend fun sendPacketImpl(packet: OutgoingPacket): Boolean { connection.writeAndFlushOrCloseAsync(packet) return true } // Yes, nothing to do in this state. override suspend fun resumeConnection0(): Unit = runUnwrapCancellationException { (coroutineContext.job as CompletableJob).run { complete() join() } setState { StateOK(connection) } } // noop override fun toString(): String = "StateLoading" } protected inner class StateOK( private val connection: Conn, ) : CommonState(NetworkHandler.State.OK) { override fun startState() { coroutineContext.job.invokeOnCompletion { err -> if (err is StateSwitchingException) { if (err.new.correspondingState == NetworkHandler.State.CLOSED) { return@invokeOnCompletion } } connection.close() } } private val heartbeatJobs = context[HeartbeatScheduler].launchJobsIn(this@CommonNetworkHandler, this) { name, e -> setState { StateClosed(HeartbeatFailedException(name, e)) } } // we can also move them as observers if needed. private val keyRefresh = launch(CoroutineName("Key refresh")) { context[KeyRefreshProcessor].keyRefreshLoop(this@CommonNetworkHandler) } private val configPush = this@CommonNetworkHandler.launch(CoroutineName("ConfigPush sync")) { context[ConfigPushProcessor].syncConfigPush(this@CommonNetworkHandler) } override suspend fun sendPacketImpl(packet: OutgoingPacket): Boolean { connection.writeAndFlushOrCloseAsync(packet) return true } override suspend fun resumeConnection0(): Unit = runUnwrapCancellationException { joinCompleted(coroutineContext.job) for (job in heartbeatJobs) joinCompleted(job) joinCompleted(configPush) joinCompleted(keyRefresh) } // noop override fun toString(): String = "StateOK" } /** * 这会永久关闭这个 [NetworkHandler], 但通常 bot 会使用 [NetworkHandlerSelector], selector 会创建新的 [NetworkHandler] 来恢复连接. * * 备注: selector 会恢复连接, 当且仅当 [exception] 类型是 [NetworkException] 且 [NetworkException.recoverable] 为 `true`. */ protected inner class StateClosed( val exception: Throwable?, ) : CommonState(NetworkHandler.State.CLOSED) { override fun afterUpdated() { close(exception) } override fun getCause(): Throwable? = exception override suspend fun sendPacketImpl(packet: OutgoingPacket) = error("NetworkHandler is already closed.") override suspend fun resumeConnection0() { exception?.let { throw it } } // noop override fun toString(): String = "StateClosed" } override fun initialState(): NetworkHandlerSupport.BaseStateImpl = StateInitialized() } internal suspend inline fun joinCompleted(job: Job) { if (job.isCompleted) job.join() } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/NetworkHandler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import kotlinx.atomicfu.atomic import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.takeWhile import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.BotInitProcessor import net.mamoe.mirai.internal.network.components.HeartbeatScheduler import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* import net.mamoe.mirai.internal.network.handler.selector.MaxAttemptsReachedException import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler import net.mamoe.mirai.internal.network.handler.state.StateObserver 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.PacketFactory import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.systemProp import net.mamoe.mirai.utils.unwrapCancellationException /** * Coroutine-based network framework. Usually wrapped with [SelectorNetworkHandler] to enable retrying. * * Implementation is typically subclass of [NetworkHandlerSupport]. * * Instances are often created by [NetworkHandlerFactory]. * * For more information, see [State]. * * @see NetworkHandlerSupport * @see NetworkHandlerFactory */ internal interface NetworkHandler : CoroutineScope { val context: NetworkHandlerContext /** * Current state of this handler. */ val state: State fun getLastFailure(): Throwable? /** * The channel that is sent with a [State] when changed. */ val stateChannel: ReceiveChannel<State> /** * State of this handler. * * ## States transition overview * * There are 5 [State]s, each of which encapsulates the state of the network connection. * * Initial state is [State.INITIALIZED], at which no packets can be sent before [resumeConnection], which transmits state into [State.CONNECTING]. * On [State.CONNECTING], [NetworkHandler] establishes a connection with the server while [SsoProcessor] takes responsibility in the single-sign-on process. * * Successful logon turns state to [State.LOADING], an **open state**, which does nothing by default. Jobs can be *attached* by [StateObserver]s. * For example, attaching a [BotInitProcessor] to handle account-relevant jobs to the [Bot]. * * Failure during [State.CONNECTING] and [State.LOADING] switches state to [State.CLOSED], on which [NetworkHandler] is considered **permanently dead**. * The state after finishing of [State.LOADING] is [State.OK]. This state lasts for the majority of time. * * When connection is lost (e.g. due to Internet unavailability), it does NOT return to [State.CONNECTING] but to [State.CLOSED]. No reconnection is allowed. * Retrial may only be performed in [SelectorNetworkHandler]. * * ## 深入了解状态维护机制 * * ### 登录时的状态转移 * * [NetworkHandler] 实例会在 [登录][Bot.login] 时创建 (定义在 [AbstractBot.network]), 此时初始状态为 [INITIALIZED]. * * [Bot.login] 会在创建该实例后就执行 [NetworkHandler.resumeConnection], 意图是将 [NetworkHandler] **调整**为可以收发功能数据包的正常工作状态, 即 [OK]. * 为了达成这个状态, [NetworkHandler] 将需要通过 [INITIALIZED] -> [CONNECTING] -> [LOADING] -> [OK], 即**初始化** -> **连接服务器** -> **载入** -> **完成**流程. * * 在 [CONNECTING] 状态中, [NetworkHandler] 将会与服务器建立网络连接, 并执行登录. * 若成功, 状态进入 [LOADING], 此时 [NetworkHandler] 将会从服务器拉取好友列表等必要信息. * 拉取顺利完成后状态进入 [OK], 用于维护登录会话的心跳任务等协程将会由启动 [HeartbeatScheduler] 启动, [NetworkHandler] 现在可以收发功能数据包. [NetworkHandler.resumeConnection] 返回, [Bot.login] 返回. * * ### 单向状态转移与"重置" * * [NetworkHandler] 的实现除了 [SelectorNetworkHandler] 外, 都只设计实现单向的状态转移, * 这意味着它们最终的状态都是 [CLOSED], 并且一旦到达 [CLOSED] 就无法回到之前的状态. * * [Selector][SelectorNetworkHandler] 是对某一个 [NetworkHandler] 的包装, 它为 [NetworkHandler] 增加异常处理和"可重置"的状态转移能力. * 若在单向状态转移过程 (比如登录) 中出现异常, 异常会被传递到 [NetworkHandler.resumeConnection] 抛出. * * ### 异常的区分 * * 网络系统中的异常是有语义的 — 它如果是 [NetworkException], [Selector][SelectorNetworkHandler] 会使用异常的 [NetworkException.recoverable] 信息判断是否应该"重置"状态. * [NetworkException] 是已知的异常. 而任何其他的异常, 例如 [IllegalStateException], [NoSuchElementException], 都是未知的异常. * 未知的异常会被认为是严重的内部错误, [Selector][SelectorNetworkHandler] 会[关闭 bot][Bot.close]. (提醒: 关闭 bot 是终止操作, 标志 bot 实例的结束) * * 你在 [NetworkHandler.resumeConnection] 和 [Selector][SelectorNetworkHandler] 中需要注意异常类型, 为函数清晰注释其可能抛出的异常类型. * * [Selector][SelectorNetworkHandler] 不会立即将异常向上传递, 而是会根据异常类型尝试"重置"状态, 以处理网络状态不佳或服务器要求重连到其他地址等情况. * * ### Selector 重置状态 * * [Selector][SelectorNetworkHandler] 通过实例化新的 [NetworkHandler] 来获得状态为 [INITIALIZED] 的实例, 对外来说就是"重置"了状态. * * @see state */ enum class State { /** * Just created and no connection has been made. * * At this state [resumeConnection] turns state into [CONNECTING] and * establishes a connection to the server and do authentication, for which [sendAndExpect] suspends. */ INITIALIZED, /** * Connection to server, including the process of authentication. * * At this state [resumeConnection] does nothing. [sendAndExpect] suspends for the result of connection started in [INITIALIZED]. */ CONNECTING, /** * Loading essential data from server and local cache. Data include contact list. * * At this state [resumeConnection] waits for the jobs. [sendAndExpect] works normally. */ LOADING, /** * Everything is working. * * At this state [resumeConnection] does nothing. [sendAndExpect] works normally. */ OK, /** * The terminal state. Both [resumeConnection] and [sendAndExpect] throw a [IllegalStateException]. * * **Important nodes**: iff [NetworkHandler] is [SelectorNetworkHandler], it might return to a normal state e.g. [INITIALIZED] if new instance of [NetworkHandler] is created. * However callers usually do not need to pay extra attention on this behavior. Everything will just work fine if you consider [CLOSED] as a final, non-recoverable state. * * At this state [resumeConnection] throws the exception caught from underlying socket implementation (i.e netty). * [sendAndExpect] throws [IllegalStateException]. */ CLOSED, } /** * Suspends the coroutine until [sendAndExpect] can be executed without suspension. * * If this functions returns normally, it indicates that [state] is [State.LOADING] or [State.OK] * * @throws NetworkException 已知的异常 * @throws Throwable 其他内部错误 * @throws MaxAttemptsReachedException 重试次数达到上限 * @throws CancellationException 协程被取消 * * @see SelectorNetworkHandler.instance */ @Throws(NetworkException::class, Throwable::class) suspend fun resumeConnection() /** * Sends [packet], suspends and expects to receive a response from the server. * * Coroutine suspension may happen if connection is not yet available however, * [IllegalStateException] is thrown if [NetworkHandler] is already in [State.CLOSED] since closure is final. * * @throws TimeoutCancellationException if timeout has been reached. * @throws CancellationException if the [NetworkHandler] is closed, with the last cause for closure. * @throws IllegalArgumentException if [timeout] or [attempts] are invalid. * * @param attempts ranges `1..INFINITY` */ suspend fun <P : Packet?> sendAndExpect( packet: OutgoingPacketWithRespType<P>, timeout: Long = 5000, attempts: Int = 2 ): P /** * Sends [packet], suspends and expects to receive a response from the server. * * Note that it's corresponding [PacketFactory]'s responsibility to decode the returned date from server to give resultant [Packet], * and that packet is cast to [P], hence unsafe (vulnerable for [ClassCastException]). * Always use [sendAndExpect] with [OutgoingPacketWithRespType], use this unsafe overload only for legacy code. */ suspend fun <P : Packet?> sendAndExpect(packet: OutgoingPacket, timeout: Long = 5000, attempts: Int = 2): P /** * Sends [packet] and does not expect any response. * * Response is still being processed but not passed as a return value of this function, so it does not suspend this function (due to awaiting for the response). * However, coroutine is still suspended if connection is not yet available. * [IllegalStateException] will be thrown if [NetworkHandler] is already in [State.CLOSED] since closure is final, since closure is final. * * @throws CancellationException if the [NetworkHandler] is closed, with the last cause for closure. */ suspend fun sendWithoutExpect(packet: OutgoingPacket) /** * Closes this handler gracefully (i.e. asynchronously). You should provide [cause] as possible as you can to give debugging information. * * After invocation of [close], [state] will always be [State.CLOSED]. */ fun close(cause: Throwable?) companion object { /** * [unwrapCancellationException] will give a rather long and complicated trace showing all internal traces. * The traces may help locate where the [Job.cancel] is called, but this is not usually important. * @since 2.13 */ var CANCELLATION_TRACE by atomic(systemProp("mirai.network.handler.cancellation.trace", false)) inline fun <R> runUnwrapCancellationException(block: () -> R): R { try { return block() } catch (e: CancellationException) { // e is like `Exception in thread "main" kotlinx.coroutines.JobCancellationException: Parent job is Cancelling; job=JobImpl{Cancelled}@f252f300` // and this is useless. throw e.unwrapCancellationException(CANCELLATION_TRACE) // if (e.suppressed.isNotEmpty()) throw e // preserve details. // throw e.findCause { it !is CancellationException } ?: e } } } } internal val NetworkHandler.logger: MiraiLogger get() = context.logger /** * Suspends coroutine to wait for the state [suspendUntil]. */ internal suspend fun NetworkHandler.awaitState(suspendUntil: NetworkHandler.State) { if (this.state == suspendUntil) return stateChannel.consumeAsFlow().takeWhile { it != suspendUntil }.collect() } /** * Suspends coroutine to wait for a state change. Returns immediately after [NetworkHandler.state] has changed. */ internal suspend fun NetworkHandler.awaitStateChange() { stateChannel.consumeAsFlow().first() } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerContext.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineName import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.SimpleLogger /** * Immutable context for [NetworkHandler] * @see NetworkHandlerContextImpl */ internal interface NetworkHandlerContext : ComponentStorage { val bot: QQAndroidBot // however migration requires a major change. val logger: MiraiLogger } internal inline fun NetworkHandlerContext.mapComponents(action: (ComponentStorage) -> ComponentStorage): NetworkHandlerContext { return NetworkHandlerContextImpl(bot, logger, this.let(action)) } internal class NetworkHandlerContextImpl( override val bot: QQAndroidBot, override val logger: MiraiLogger, private val storage: ComponentStorage, // should be the same as bot.components ) : NetworkHandlerContext, ComponentStorage by storage { override fun toString(): String { return "NetworkHandlerContextImpl(bot=${bot.id}, storage=$storage)" } } internal fun MiraiLogger.asCoroutineExceptionHandler( priority: SimpleLogger.LogPriority = SimpleLogger.LogPriority.ERROR, ): CoroutineExceptionHandler { return CoroutineExceptionHandler { context, e -> call( priority, context[CoroutineName]?.let { "Exception in coroutine '${it.name}'." } ?: "Exception in unnamed coroutine.", e ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerFactory.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State /** * Factory for a specific [NetworkHandler] implementation. */ internal expect fun interface NetworkHandlerFactory<out H : NetworkHandler> { open fun create(context: NetworkHandlerContext, host: String, port: Int): H /** * Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED] */ fun create(context: NetworkHandlerContext, address: SocketAddress): H companion object { fun getPlatformDefault(): NetworkHandlerFactory<*> } } internal expect abstract class SocketAddress @Suppress("EXTENSION_SHADOWED_BY_MEMBER") internal expect fun SocketAddress.getHost(): String @Suppress("EXTENSION_SHADOWED_BY_MEMBER") internal expect fun SocketAddress.getPort(): Int internal expect fun createSocketAddress(host: String, port: Int): SocketAddress ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/NetworkHandlerSupport.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.reentrantLock import kotlinx.atomicfu.locks.withLock import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.PacketCodec import net.mamoe.mirai.internal.network.components.PacketHandler import net.mamoe.mirai.internal.network.components.PacketLoggingStrategy import net.mamoe.mirai.internal.network.components.RawIncomingPacket import net.mamoe.mirai.internal.network.handler.selector.NetworkHandlerSelector import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.utils.fromMiraiLogger import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.Either.Companion.fold import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.Volatile import kotlin.reflect.KClass /** * Implements a state-based [NetworkHandler]. */ internal abstract class NetworkHandlerSupport( final override val context: NetworkHandlerContext, additionalCoroutineContext: CoroutineContext = EmptyCoroutineContext, ) : NetworkHandler, CoroutineScope { final override val coroutineContext: CoroutineContext = additionalCoroutineContext.childScopeContext(SupervisorJob(context.bot.coroutineContext.job)) .plus(CoroutineExceptionHandler.fromMiraiLogger(logger)) /** * Creates an instance of the initial state. This is guaranteed to be called at most once. */ protected abstract fun initialState(): BaseStateImpl /** * Performs network IO or launches a coroutine for that, to send the [packet]. * * **Node**: It's not guaranteed whether this function sends the packet in-place or launches a coroutine for it. * Caller should not rely on this characteristic. */ protected abstract suspend fun sendPacketImpl(packet: OutgoingPacket) /** * Handles *unknown* [RawIncomingPacket]s. Packets with unrecognized [RawIncomingPacket.commandName] are considered *unknown*. * * Can be called by implementation */ protected fun collectUnknownPacket(raw: RawIncomingPacket) { packetLogger.debug { "Unknown packet: commandName=${raw.commandName}, body=${raw.body.toUHexString()}" } // may add hooks here (to context) } override fun close(cause: Throwable?) { if (coroutineContext.job.isActive) { coroutineContext.job.cancel( if (cause is CancellationException) { cause } else { CancellationException("NetworkHandler closed", cause) } ) } } protected val packetLogger: MiraiLogger by lazy { context.logger.subLogger("NetworkDebug").withSwitch(PacketCodec.PACKET_DEBUG) } /////////////////////////////////////////////////////////////////////////// // packets synchronization impl /////////////////////////////////////////////////////////////////////////// private val packetHandler: PacketHandler by lazy { context[PacketHandler] } /** * Called when a packet is received. */ internal open fun collectReceived(packet: IncomingPacket) { for (listener in packetListeners) { if (!listener.isExpected(packet)) continue if (packetListeners.remove(listener)) { packet.result.fold( onLeft = { listener.result.completeExceptionally(it) }, onRight = { listener.result.complete(it) } ) } } launch(start = CoroutineStart.UNDISPATCHED) { try { packetHandler.handlePacket(packet) } catch (e: Throwable) { // do not pass it to CoroutineExceptionHandler for a more controllable behavior. logger.error(e) } } } final override suspend fun <P : Packet?> sendAndExpect(packet: OutgoingPacket, timeout: Long, attempts: Int): P { require(attempts >= 1) { "attempts must be at least 1." } val listener = PacketListener(packet.commandName, packet.sequenceId) packetListeners.add(listener) withExceptionCollector { try { repeat(attempts) { context[PacketLoggingStrategy].logSent(logger, packet) sendPacketImpl(packet) try { @Suppress("UNCHECKED_CAST") return withTimeout(timeout) { listener.result.await() } as P } catch (e: TimeoutCancellationException) { collectException(e) } } throwLast() } finally { packetListeners.remove(listener) if (listener.result.isActive) { listener.result.completeExceptionally( getLast() ?: IllegalStateException("Internal error: sendAndExpect failed without an exception.") ) } } } } final override suspend fun <P : Packet?> sendAndExpect( packet: OutgoingPacketWithRespType<P>, timeout: Long, attempts: Int ): P = sendAndExpect(packet as OutgoingPacket, timeout, attempts) final override suspend fun sendWithoutExpect(packet: OutgoingPacket) { context[PacketLoggingStrategy].logSent(logger, packet) sendPacketImpl(packet) } /** * Listens for the resultant packet from server. */ protected class PacketListener( val commandName: String, val sequenceId: Int, ) { /** * Response from server. May complete with [CompletableDeferred.completeExceptionally] for a meaningful stacktrace. */ val result = CompletableDeferred<Packet?>() fun isExpected(packet: IncomingPacket): Boolean = this.commandName == packet.commandName && this.sequenceId == packet.sequenceId } private val packetListeners = ConcurrentLinkedDeque<PacketListener>() /////////////////////////////////////////////////////////////////////////// // state impl /////////////////////////////////////////////////////////////////////////// /** * A **scoped** state corresponding to [NetworkHandler.State]. * * CoroutineScope is cancelled when switched to another state. * * State can only be changed inside [setState]. * * **IMPORTANT implementation notes:** * * You must create subclasses of [BaseStateImpl] for EVERY SINGLE [NetworkHandler.State]. * **DO NOT** use same type for more than one [NetworkHandler.State], * otherwise [setState] will refuse updating state in some concurrent situations and will be very difficult to debug. * * **IMPORTANT notes to lifecycle:** * * Normally if the state is set to [NetworkHandler.State.CLOSED] by [setState], [selector][NetworkHandlerSelector] may reinitialize an instance. * * Any exception caught by the scope (supervisor job) is considered as _fatal failure_ that will set state to CLOSE and **propagate the exception to user of [selector][NetworkHandlerSelector]**. * * * You must catch all the exceptions and change states by [setState] manually. */ abstract inner class BaseStateImpl( val correspondingState: NetworkHandler.State, ) : CoroutineScope { final override val coroutineContext: CoroutineContext = this@NetworkHandlerSupport.coroutineContext + Job(this@NetworkHandlerSupport.coroutineContext.job) // Important: read the above doc before implementing BaseStateImpl. // Do not use init blocks to launch anything. Do use [startState] /** * Starts things that should be done in this state. * * Called after this instance is initialized, and it is at suitable time for initialization. * * Note: must be fast. */ open fun startState() { } /** * Called after this instance is set to [_state]. (Visible publicly) */ open fun afterUpdated() { } open fun getCause(): Throwable? = null /** * May throw any exception that caused the state to fail. */ @Throws(Exception::class) suspend fun resumeConnection() { val observer = context.getOrNull(StateObserver) observer?.beforeStateResume(this@NetworkHandlerSupport, _state) val result = kotlin.runCatching { resumeConnection0() } observer?.afterStateResume(this@NetworkHandlerSupport, _state, result) result.getOrThrow() } protected abstract suspend fun resumeConnection0() } /** * State is *lazy*, initialized only if requested. * * You must not set this property directly, but use [setState]. */ @Suppress("PropertyName") protected var _state: BaseStateImpl by lateinitMutableProperty { initialState() } private set final override val state: NetworkHandler.State get() = _state.correspondingState override fun getLastFailure(): Throwable? = _state.getCause() private val _stateChannel = Channel<NetworkHandler.State>(0) final override val stateChannel: ReceiveChannel<NetworkHandler.State> get() = _stateChannel protected data class StateSwitchingException( val old: BaseStateImpl, val new: BaseStateImpl, override val cause: Throwable? = new.getCause(), // so it can be unwrapped ) : CancellationException("State is switched from $old to $new") /** * Calculate [new state][new] and set it as the current, returning the new state, * or `null` if state has concurrently been set to CLOSED, or has same [class][KClass] as current. * * You may need to call [BaseStateImpl.resumeConnection] to activate the new state, as states are lazy. */ protected inline fun <reified S : BaseStateImpl> setState(noinline new: () -> S): S? = _state.setState(new) /** * Attempts to change state if current state is [this]. * * Returns null if new state has same [class][KClass] as current or when current state is already set to another state concurrently by another thread. * * This is designed to be used inside [BaseStateImpl]. */ protected inline fun <reified S : BaseStateImpl> BaseStateImpl.setState( noinline new: () -> S, ): S? = lock.withLock { if (_state === this) { @OptIn(TestOnly::class) this@NetworkHandlerSupport.setStateImpl(S::class, new) } else { null } } private val lock = reentrantLock() internal val lockForSetStateWithOldInstance = SynchronizedObject() @Volatile private var changingState: KClass<out BaseStateImpl>? = null /** * This can only be called by [setState] or in tests. Note: */ // @TestOnly internal fun <S : BaseStateImpl> setStateImpl(newType: KClass<S>, new: () -> S): S? = lock.withLock { val old = _state if (old::class == newType) return@withLock null // already set to expected state by another thread. Avoid replications. if (old.correspondingState == NetworkHandler.State.CLOSED) return@withLock null // CLOSED is final. val changingState = changingState if (changingState != null) { if (changingState == newType) { // no duplicates return null } else { error("New state ${newType.simpleName} clashes with current switching process, changingState = ${changingState.simpleName}.") } } this.changingState = newType try { val stateObserver = context.getOrNull(StateObserver) val impl = try { new() } catch (e: Throwable) { stateObserver?.exceptionOnCreatingNewState(this, old, e) throw e } try { check(old !== impl) { "Old and new states cannot be the same." } stateObserver?.beforeStateChanged(this, old, impl) // We should startState before expose it publicly because State.resumeConnection may wait for some jobs that are launched in startState. // We cannot close old state before changing the 'public' _state to be the new one, otherwise every client will get some kind of exceptions (unspecified, maybe CancellationException). impl.startState() // launch jobs } catch (e: Throwable) { throw e } // No further change _state = impl // update current state // After _state is updated, we are safe. old.cancel(StateSwitchingException(old, impl)) // close old impl.afterUpdated() // now do post-update things. stateObserver?.stateChanged(this, old, impl) // notify observer _stateChannel.trySend(impl.correspondingState) // notify selector return@withLock impl } finally { this.changingState = null } } final override suspend fun resumeConnection() { _state.resumeConnection() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/AbstractKeepAliveNetworkHandlerSelector.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector import io.ktor.client.utils.* import kotlinx.atomicfu.atomic import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized import kotlinx.coroutines.* import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.utils.* import kotlin.jvm.Volatile /** * A lazy stateful implementation of [NetworkHandlerSelector]. * * - Calls [factory.create][NetworkHandlerFactory.create] to create [NetworkHandler]s. * - Re-initialize [NetworkHandler] instances if the old one is dead. * - Suspends requests when connection is not available. * * No connection is created until first invocation of [getCurrentInstanceOrNull], * and new connections are created only when calling [getCurrentInstanceOrNull] if the old connection was dead. */ // may be replaced with a better name. internal abstract class AbstractKeepAliveNetworkHandlerSelector<H : NetworkHandler>( private val maxAttempts: Int = DEFAULT_MAX_ATTEMPTS, private val logger: MiraiLogger, ) : NetworkHandlerSelector<H> { init { require(maxAttempts >= 1) { "maxAttempts must >= 1" } } private val current = atomic<H?>(null) @net.mamoe.mirai.utils.TestOnly internal fun setCurrent(h: H) { current.value = h } protected abstract fun createInstance(): H final override fun getCurrentInstanceOrNull(): H? = current.value final override tailrec fun getCurrentInstanceOrCreate(): H { getCurrentInstanceOrNull()?.let { return it } refreshInstance() return getCurrentInstanceOrCreate() } final override suspend fun awaitResumeInstance(): H = AwaitResumeInstance().run() private inner class AwaitResumeInstance : SynchronizedObject() { private inline fun logIfEnabled(block: () -> String) { if (SELECTOR_LOGGING) { logger.debug { "Attempt #$attempted: ${block.invoke()}" } } } private var attempted: Int = 0 private var lastNetwork: H? = null @Volatile private var _exceptionCollector: ExceptionCollector? = null // lazily initialize ExceptionCollector. private val exceptionCollector: ExceptionCollector get() { _exceptionCollector?.let { return it } return synchronized(this) { _exceptionCollector?.let { return it } object : ExceptionCollector() { override fun beforeCollect(throwable: Throwable) { lastNetwork?.logger?.warning("Exception in resumeConnection.", throwable) } }.also { _exceptionCollector = it } } } /** * 尝试重置状态, 捕获并处理发生的可被挽救的异常, 重新抛出其他异常. 每次此函数运行时都会创建新的 [H]. * 抛出异常: 表示 [NetworkHandler.resumeConnection] 抛出了某个异常, 且这个问题不可挽救 (需要停止 selector). * * 只有 [NetworkException] 是期望的异常 (根据 [NetworkException.recoverable] 决定是否可挽救). 任何其他异常都是未期望的, 将会被原封不动地抛出. */ @Throws( NetworkException::class, MaxAttemptsReachedException::class, CancellationException::class, Throwable::class ) suspend fun run(): H { return try { runImpl() } finally { exceptionCollector.dispose() lastNetwork = null // help gc } } private tailrec suspend fun runImpl(): H { if (attempted >= maxAttempts) { logIfEnabled { "Max attempt $maxAttempts reached." } throw MaxAttemptsReachedException(exceptionCollector.getLast()) } if (!currentCoroutineContext().isActive) { yield() // check cancellation } val current = getCurrentInstanceOrNull() lastNetwork = current if (current != null) { if (current.context[SsoProcessor].firstLoginResult?.canRecoverOnFirstLogin == false) { // == null 只表示 // == false 表示第一次登录失败, 且此失败没必要重试 logIfEnabled { "[FIRST LOGIN ERROR] current = $current" } logIfEnabled { "[FIRST LOGIN ERROR] current.state = ${current.state}" } throw current.getLastFailure() ?: exceptionCollector.getLast() ?: error("Failed to login with unknown reason.") } } /** * 执行 [NetworkHandler.resumeConnection]. * * 本函数有三个终止状态: * - 返回 `true`: 表示 [NetworkHandler.resumeConnection] 正常返回. * - 返回 `false`: 表示 [NetworkHandler.resumeConnection] 抛出了某个异常, 但这个问题可以挽救 (需要重置状态). * - 抛出异常: 表示 [NetworkHandler.resumeConnection] 抛出了某个异常, 且这个问题不可挽救 (需要停止 selector). * * 只有 [NetworkException] 是期望的异常 (根据 [NetworkException.recoverable] 决定是否可挽救). 任何其他异常都是未期望的, 将会被原封不动地抛出. */ @Throws(NetworkException::class, CancellationException::class, Throwable::class) suspend fun H.resumeInstanceCatchingException(): Boolean { logIfEnabled { "Try resumeConnection" } try { resumeConnection() return true // 一切正常 } catch (e: NetworkException) { logIfEnabled { "... failed with NetworkException (recoverable=${e.recoverable}): $e" } exceptionCollector.collect(e) close(e) // close H logIfEnabled { "CLOSED instance" } if (e.recoverable) { // 可挽救 return false } else { // 不可挽救 exceptionCollector.throwLast() } } catch (e: TimeoutCancellationException) { logIfEnabled { "... failed with TimeoutCancellationException: $e" } exceptionCollector.collect(e) close(e) // close H logIfEnabled { "CLOSED instance" } // 发包超时 可挽救 return false } catch (e: Throwable) { // 不可挽救 logIfEnabled { "... failed with unexpected: $e" } exceptionCollector.collect(e) close(e) // close H logIfEnabled { "CLOSED instance" } exceptionCollector.throwLast() } } logIfEnabled { "current.state = ${current?.state}" } return if (current != null) { when (current.state) { NetworkHandler.State.CLOSED -> { current.resumeInstanceCatchingException() // This may return false, meaning the error causing the state to be CLOSED is recoverable. // Otherwise, it throws, meaning it is unrecoverable. if (compareAndSetCurrent(current, null)) { logIfEnabled { "... Set current to null." } // invalidate the instance and try again. // NetworkHandler is CancellationException if closed by `NetworkHandler.close` // `unwrapCancellationException` will give a rather long and complicated trace showing all internal traces. // The traces may help locate exactly where the `NetworkHandler.close` is called, // but this is not usually desired. val lastFailure = current.getLastFailure()?.unwrapCancellationException(NetworkHandler.CANCELLATION_TRACE) logIfEnabled { "... Last failure was $lastFailure." } exceptionCollector.collectException(lastFailure) } if (attempted > 1) { logIfEnabled { "... Delaying ${RECONNECT_DELAY.millisToHumanReadableString()}." } delay(RECONNECT_DELAY) // make it slower to avoid massive reconnection on network failure. } attempted += 1 runImpl() // will create new instance (see the `else` branch). } NetworkHandler.State.INITIALIZED -> { if (!current.resumeInstanceCatchingException()) { attempted += 1 return runImpl() } logIfEnabled { "RETURN" } return current } NetworkHandler.State.CONNECTING -> { logIfEnabled { "RETURN" } // can send packet return current } NetworkHandler.State.LOADING -> { logIfEnabled { "RETURN" } return current } NetworkHandler.State.OK -> { if (current.resumeInstanceCatchingException()) { logIfEnabled { "RETURN" } return current } else { attempted += 1 return runImpl() } } } } else { logIfEnabled { "Creating new instance." } refreshInstance() logIfEnabled { "... Created." } runImpl() // directly retry, does not count for attempts. } } } private fun compareAndSetCurrent(expect: H?, update: H?) = current.compareAndSet(expect, update) // to enable compiler optimization private val lock = SynchronizedObject() protected open fun refreshInstance() { synchronized(lock) { // avoid concurrent `createInstance()` if (getCurrentInstanceOrNull() == null) this.current.compareAndSet(null, createInstance()) } } companion object { var DEFAULT_MAX_ATTEMPTS by atomic( systemProp( "mirai.network.handler.selector.max.attempts", Long.MAX_VALUE ).coerceIn(1..Int.MAX_VALUE.toLongUnsigned()).toInt() ) /** * millis */ var RECONNECT_DELAY by atomic(systemProp("mirai.network.reconnect.delay", 3000L).coerceIn(0..Long.MAX_VALUE)) var SELECTOR_LOGGING by atomic(systemProp("mirai.network.handler.selector.logging", false)) } } @Suppress("FunctionName") internal fun <H : NetworkHandler> KeepAliveNetworkHandlerSelector( maxAttempts: Int, logger: MiraiLogger, createInstance: () -> H, ): AbstractKeepAliveNetworkHandlerSelector<H> { return object : AbstractKeepAliveNetworkHandlerSelector<H>(maxAttempts, logger) { override fun createInstance(): H = createInstance() } } @Suppress("FunctionName") internal inline fun <H : NetworkHandler> KeepAliveNetworkHandlerSelector( logger: MiraiLogger, crossinline createInstance: () -> H, ): AbstractKeepAliveNetworkHandlerSelector<H> { return object : AbstractKeepAliveNetworkHandlerSelector<H>(logger = logger) { override fun createInstance(): H = createInstance() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/ExceptionInSelectorResumeException.kt ================================================ /* * 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.selector internal class ExceptionInSelectorResumeException( cause: Throwable, ) : RuntimeException(cause) ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/MaxAttemptsReachedException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector internal data class MaxAttemptsReachedException( override val cause: Throwable?, ) : IllegalStateException("Failed to resume instance. Maximum attempts reached.") { override fun toString(): String { return "MaxAttemptsReachedException" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/NetworkChannelException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector internal open class NetworkChannelException : NetworkException { constructor() : super(true) constructor(cause: Throwable?) : super(true, cause) constructor(message: String) : super(message, true) constructor(message: String, cause: Throwable?) : super(message, cause, true) } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/NetworkException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.utils.unwrap /** * 已知的网络异常. * * 在 [NetworkHandlerSupport.BaseStateImpl] 中抛出此异常, * 或用此异常 [close NetworkHandler][NetworkHandler.close], 可建议 [Selector][SelectorNetworkHandler] 是否执行重连. */ internal open class NetworkException : Exception { /** * If true, the selector may recover the network handler by some means. */ val recoverable: Boolean constructor(recoverable: Boolean) : super() { this.recoverable = recoverable } constructor(recoverable: Boolean, cause: Throwable?) : super(cause) { this.recoverable = recoverable } constructor(message: String, recoverable: Boolean) : super(message) { this.recoverable = recoverable } constructor(message: String, cause: Throwable?, recoverable: Boolean) : super(message, cause) { this.recoverable = recoverable } /** * Returns the exception to be thrown for public API. */ open fun unwrapForPublicApi(): Throwable { return this.unwrap<NetworkException>() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/NetworkHandlerSelector.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector import kotlinx.coroutines.CancellationException import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory /** * A **lazy** director([selector][SelectorNetworkHandler.selector]) of [NetworkHandler]. * * *lazy* means that no action is taken at any time until member functions are invoked. * * It can produce [H] instances (maybe by calling [NetworkHandlerFactory]), to be used by [SelectorNetworkHandler] * * @see SelectorNetworkHandler */ internal interface NetworkHandlerSelector<out H : NetworkHandler> { /** * Returns an instance immediately without suspension, or `null` if instance not ready. Returned [H] can be in any states. * * This function should not throw any exception. * @see awaitResumeInstance */ fun getCurrentInstanceOrNull(): H? /** * Returns the current [NetworkHandler] or creates a new one if it is `null`. Returned [H] can be in any states. */ fun getCurrentInstanceOrCreate(): H /** * Returns an alive [NetworkHandler], or suspends the coroutine until the connection has been made again. * Returned [H] can be in [NetworkHandler.State.LOADING] and [NetworkHandler.State.OK] only (but it may happen that the state changed just after returning from this function). * * @throws NetworkException [NetworkHandler.resumeConnection] 抛出了 [NetworkException] 并且 [NetworkException.recoverable] 为 `false`. * @throws Throwable [NetworkHandler.resumeConnection] 抛出了其他异常. 任何其他异常都属于内部错误并会原样抛出. * @throws MaxAttemptsReachedException 重试次数达到上限 (由 selector 构造) * @throws CancellationException 协程被取消 */ @Throws(NetworkException::class, MaxAttemptsReachedException::class, CancellationException::class, Throwable::class) suspend fun awaitResumeInstance(): H } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/NoServerAvailableException.kt ================================================ /* * 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.selector internal class NoServerAvailableException : NoSuchElementException("No server available. (Failed to connect to any of the servers)") ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/PacketTimeoutException.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector internal data class PacketTimeoutException( override val message: String, override val cause: Throwable? = null, ) : NetworkException(true) ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/SelectorNetworkHandler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.isActive import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.utils.addNameHierarchically import net.mamoe.mirai.utils.childScope import kotlin.coroutines.CoroutineContext import kotlin.coroutines.cancellation.CancellationException import kotlin.jvm.Volatile /** * A proxy to [NetworkHandler] that delegates calls to instance returned by [NetworkHandlerSelector.awaitResumeInstance]. * Selection logic is implemented in [NetworkHandlerSelector]. * * This is useful to implement a delegation of [NetworkHandler]. The functionality of *selection* is provided by the strategy [selector][NetworkHandlerSelector]. * * ### Important notes * * [NetworkHandlerSelector.awaitResumeInstance] is called everytime when an operation in [NetworkHandler] is called. * * Before every [sendAndExpect] call, [resumeConnection] is invoked. * * @see NetworkHandlerSelector */ internal open class SelectorNetworkHandler<out H : NetworkHandler>( val selector: NetworkHandlerSelector<H>, ) : NetworkHandler { @Volatile private var lastCancellationCause: Throwable? = null override val context: NetworkHandlerContext get() = selector.getCurrentInstanceOrCreate().context protected val scope: CoroutineScope by lazy { context.bot.coroutineContext .addNameHierarchically("SelectorNetworkHandler") .childScope() } private val lock = SynchronizedObject() /** * 挂起协程直到获取一个可用的 [instance]. 此函数有副作用: 获取 [instance] 应当是必须成功的, 不成功(在本函数重新抛出捕获的异常之前)则会关闭 bot. * * "不成功"包括多次重连后仍然不成功, bot 被 ban, 内部错误等已经被适当重试过了的情况. * * @see NetworkHandlerSelector.awaitResumeInstance */ @Throws(Exception::class) protected suspend inline fun instance(): H { if (!scope.isActive) { throw lastCancellationCause?.let(::CancellationException) ?: CancellationException("SelectorNetworkHandler is already closed") } return try { selector.awaitResumeInstance() } catch (e: Throwable) { // selector 抛出了无法处理的, 不可挽救的异常. close bot. logger.warning("Network selector received exception, closing bot. (${e})") context.bot.close(e) throw e } } override val state: State get() = selector.getCurrentInstanceOrCreate().state override fun getLastFailure(): Throwable? = selector.getCurrentInstanceOrCreate().getLastFailure() override val stateChannel: ReceiveChannel<State> get() = selector.getCurrentInstanceOrCreate().stateChannel override suspend fun resumeConnection() { instance() // the selector will resume connection for us. } override suspend fun <P : Packet?> sendAndExpect(packet: OutgoingPacket, timeout: Long, attempts: Int): P = instance().sendAndExpect(packet, timeout, attempts) override suspend fun <P : Packet?> sendAndExpect( packet: OutgoingPacketWithRespType<P>, timeout: Long, attempts: Int ): P = instance().sendAndExpect(packet, timeout, attempts) override suspend fun sendWithoutExpect(packet: OutgoingPacket) = instance().sendWithoutExpect(packet) override fun close(cause: Throwable?) { if (cause is NetworkException && cause.recoverable) { selector.getCurrentInstanceOrNull()?.close(cause) return } synchronized(lock) { if (scope.isActive) { lastCancellationCause = cause scope.cancel() } else { return } } selector.getCurrentInstanceOrNull()?.close(cause) } override val coroutineContext: CoroutineContext get() = selector.getCurrentInstanceOrNull()?.coroutineContext ?: scope.coroutineContext // merely use fallback override fun toString(): String = "SelectorNetworkHandler(currentInstance=${selector.getCurrentInstanceOrNull()})" } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/selector/SelectorRequireReconnectException.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector /** * A special exception that instructs selector to restart network connection */ internal expect class SelectorRequireReconnectException() : NetworkException ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/state/CombinedStateObserver.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.state import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport internal class CombinedStateObserver( private val list: List<StateObserver>, ) : StateObserver { override fun beforeStateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { list.forEach { it.beforeStateChanged(networkHandler, previous, new) } } override fun stateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { list.forEach { it.stateChanged(networkHandler, previous, new) } } override fun exceptionOnCreatingNewState( networkHandler: NetworkHandlerSupport, previousState: NetworkHandlerSupport.BaseStateImpl, exception: Throwable, ) { list.forEach { it.exceptionOnCreatingNewState(networkHandler, previousState, exception) } } override suspend fun beforeStateResume(networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl) { list.forEach { it.beforeStateResume(networkHandler, state) } } override suspend fun afterStateResume( networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl, result: Result<Unit>, ) { list.forEach { it.afterStateResume(networkHandler, state, result) } } override fun toString(): String { return list.joinToString( prefix = "CombinedStateObserver[", postfix = "]", separator = " -> " ) { it.toString() } } companion object { operator fun StateObserver?.plus(last: StateObserver?): StateObserver { return when { last == null -> this ?: StateObserver.NOP this == null -> last else -> { val result = mutableListOf<StateObserver>() if (this is CombinedStateObserver) result.addAll(this.list) else result.add(this) if (last is CombinedStateObserver) result.addAll(last.list) else result.add(last) CombinedStateObserver(result) } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/state/ExceptionInStateObserverException.kt ================================================ /* * 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.state internal class ExceptionInStateObserverException( override val cause: Throwable, ) : RuntimeException() ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/state/JobAttachStateObserver.kt ================================================ /* * 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.state import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.launch import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.utils.unwrapCancellationException import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * The [StateObserver] that attaches a job to the [CoroutineScope] of the state. */ internal class JobAttachStateObserver( private val name: String, targetState: NetworkHandler.State, private val coroutineContext: CoroutineContext = EmptyCoroutineContext, private val job: suspend CoroutineScope.(state: NetworkHandlerSupport.BaseStateImpl) -> Unit, ) : StateChangedObserver(targetState) { override fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { new.launch(CoroutineName(name) + coroutineContext, start = CoroutineStart.UNDISPATCHED) { try { job(new) } catch (e: Throwable) { throw IllegalStateException("Exception in attached Job '$name'", e.unwrapCancellationException()) } } } override fun toString(): String { return "JobAttachStateObserver(name=$name)" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/state/LoggingStateObserver.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.state import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.utils.* import kotlin.coroutines.coroutineContext internal class LoggingStateObserver( val logger: MiraiLogger, private val showStacktrace: Boolean = false, ) : StateObserver { override fun toString(): String = "LoggingStateObserver(logger=${logger.identity})" override fun beforeStateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { logger.debug( { "Before change: ${previous.correspondingState} -> ${new.correspondingState}" }, if (showStacktrace) Exception("Show stacktrace") else null ) } override fun stateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { logger.debug( { "State changed: ${previous.correspondingState} -> ${new.correspondingState}" }, if (showStacktrace) Exception("Show stacktrace") else null ) } override fun exceptionOnCreatingNewState( networkHandler: NetworkHandlerSupport, previousState: NetworkHandlerSupport.BaseStateImpl, exception: Throwable, ) { logger.debug { "State exception: ${previousState.correspondingState} -> $exception" } } override suspend fun beforeStateResume(networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl) { logger.debug { "State resuming: [${coroutineContext.coroutineName}] ${state.correspondingState}" } } override suspend fun afterStateResume( networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl, result: Result<Unit>, ) { result.fold( onSuccess = { logger.debug { "State resumed: [${coroutineContext.coroutineName}] ${state.correspondingState}." } }, onFailure = { logger.debug { "State resumed: [${coroutineContext.coroutineName}] ${state.correspondingState} " + "${result.exceptionOrNull()?.unwrapCancellationException(false)}" } } ) } companion object { /** * - `on`/`true` for simple logging * - `full` for logging with stacktrace */ var ENABLED = systemProp( "mirai.network.state.observer.logging", "off" ).lowercase() fun createLoggingIfEnabled(): StateObserver? { return when (ENABLED) { "full" -> { SafeStateObserver( LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States"), true), MiraiLogger.Factory.create(LoggingStateObserver::class, "LoggingStateObserver errors") ) } "on", "true" -> { SafeStateObserver( LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States"), false), MiraiLogger.Factory.create(LoggingStateObserver::class, "LoggingStateObserver errors") ) } else -> null } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/state/SafeStateObserver.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.state import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.error internal fun StateObserver.safe(logger: MiraiLogger): StateObserver { if (this is SafeStateObserver) return this return SafeStateObserver(this, logger) } /** * Catches exception then log by [logger] */ internal class SafeStateObserver( val delegate: StateObserver, val logger: MiraiLogger, ) : StateObserver { override fun toString(): String { return "SafeStateObserver(delegate=$delegate)" } override fun beforeStateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl ) { try { delegate.beforeStateChanged(networkHandler, previous, new) } catch (e: Throwable) { logger.error( { "Internal error: exception in StateObserver $delegate" }, ExceptionInStateObserverException(e) ) } } override fun stateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { try { delegate.stateChanged(networkHandler, previous, new) } catch (e: Throwable) { logger.error( { "Internal error: exception in StateObserver $delegate" }, ExceptionInStateObserverException(e) ) } } override fun exceptionOnCreatingNewState( networkHandler: NetworkHandlerSupport, previousState: NetworkHandlerSupport.BaseStateImpl, exception: Throwable, ) { try { delegate.exceptionOnCreatingNewState(networkHandler, previousState, exception) } catch (e: Throwable) { logger.error( { "Internal error: exception in StateObserver $delegate" }, ExceptionInStateObserverException(e) ) } } override suspend fun beforeStateResume(networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl) { try { delegate.beforeStateResume(networkHandler, state) } catch (e: Throwable) { logger.error( { "Internal error: exception in StateObserver $delegate" }, ExceptionInStateObserverException(e) ) } } override suspend fun afterStateResume( networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl, result: Result<Unit>, ) { try { delegate.afterStateResume(networkHandler, state, result) } catch (e: Throwable) { logger.error( { "Internal error: exception in StateObserver $delegate" }, ExceptionInStateObserverException(e) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/state/StateChangedObserver.kt ================================================ /* * 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.state import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport @Suppress("FunctionName") internal fun StateChangedObserver( name: String, to: State, action: (new: NetworkHandlerSupport.BaseStateImpl) -> Unit, ): StateObserver { return object : StateChangedObserver(to) { override fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { action(new) } override fun toString(): String = "StateChangedObserver($name)" } } @Suppress("FunctionName") internal fun StateChangedObserver( name: String, from: State, to: State, action: (new: NetworkHandlerSupport.BaseStateImpl) -> Unit, ): StateObserver { return object : StateObserver { override fun stateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { if (previous.correspondingState == from && new.correspondingState == to) { action(new) } } override fun toString(): String = "StateObserver($name)" } } @Suppress("FunctionName") internal fun BeforeStateChangedObserver( name: String, from: State, to: State, action: (new: NetworkHandlerSupport.BaseStateImpl) -> Unit, ): StateObserver { return object : StateObserver { override fun beforeStateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { if (previous.correspondingState == from && new.correspondingState == to) { action(new) } } override fun toString(): String = "BeforeStateChangedObserver($name)" } } internal abstract class StateChangedObserver( val state: State, ) : StateObserver { protected abstract fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) override fun stateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { if (previous.correspondingState != state && new.correspondingState == state) { stateChanged0(networkHandler, previous, new) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/handler/state/StateObserver.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.state import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport /** * Observer of state changes. * * @see SafeStateObserver * @see LoggingStateObserver */ internal interface StateObserver { /** * Called when _state is being changed_, where [NetworkHandlerSupport._state] is still [previous], and new state is not yet started. */ fun beforeStateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { } /** * Called when _state is fished changing_, where [NetworkHandlerSupport._state] had become [new]. */ fun stateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl, ) { } fun exceptionOnCreatingNewState( networkHandler: NetworkHandlerSupport, previousState: NetworkHandlerSupport.BaseStateImpl, exception: Throwable, ) { } suspend fun beforeStateResume( networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl, ) { } suspend fun afterStateResume( networkHandler: NetworkHandler, state: NetworkHandlerSupport.BaseStateImpl, result: Result<Unit>, ) { } companion object : ComponentKey<StateObserver> { internal val NOP = object : StateObserver { override fun toString(): String { return "StateObserver.NOP" } } fun chainOfNotNull( vararg observers: StateObserver?, ): StateObserver = CombinedStateObserver(observers.filterNotNull()) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/highway/ChunkedFlowSession.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.highway import io.ktor.utils.io.core.* import kotlinx.atomicfu.atomic import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import net.mamoe.mirai.utils.Closeable import net.mamoe.mirai.utils.runBIO import net.mamoe.mirai.utils.toLongUnsigned import net.mamoe.mirai.utils.withUse import kotlin.contracts.InvocationKind import kotlin.contracts.contract internal class ChunkedFlowSession<T>( private val input: Input, private val buffer: ByteArray, private val callback: Highway.ProgressionCallback? = null, private val mapper: (buffer: ByteArray, size: Int, offset: Long) -> T, ) : Closeable { override fun close() { input.close() } private val offset = atomic(0L) internal suspend inline fun useAll(crossinline block: suspend (T) -> Unit) { contract { callsInPlace(block, InvocationKind.UNKNOWN) } withUse { while (true) { val size = runBIO { input.readAvailable(buffer) } if (size <= 0) return block(mapper(buffer, size, offset.getAndAdd(size.toLongUnsigned()))) callback?.onProgression(offset.value) } } } internal fun asFlow(): Flow<T> = flow { useAll { emit(it) } } // 'single thread' producer } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/highway/Highway.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.highway import io.ktor.utils.io.core.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.produceIn import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.BdhSession import net.mamoe.mirai.internal.network.components.BdhSessionSyncer import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.data.proto.CSDataHighwayHead import net.mamoe.mirai.internal.network.subAppId import net.mamoe.mirai.internal.utils.PlatformSocket import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.retryWithServers import net.mamoe.mirai.internal.utils.sizeToString import net.mamoe.mirai.utils.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.Volatile import kotlin.math.roundToInt internal object Highway { @Suppress("ArrayInDataClass") data class BdhUploadResponse( var extendInfo: ByteArray? = null, ) fun interface ProgressionCallback { fun onProgression(size: Long) } suspend fun uploadResourceBdh( bot: QQAndroidBot, resource: ExternalResource, kind: ResourceKind, commandId: Int, // group image=2, friend image=1, groupPtt=29 extendInfo: ByteArray? = null, encrypt: Boolean = false, initialTicket: ByteArray? = null, // null then use sig session tryOnce: Boolean = false, noBdhAwait: Boolean = false, fallbackSession: (Throwable) -> BdhSession = { throw IllegalStateException("Failed to get bdh session", it) }, resultChecker: (CSDataHighwayHead.RspDataHighwayHead) -> Boolean = { it.errorCode == 0 }, createConnection: suspend (ip: String, port: Int) -> HighwayProtocolChannel = { ip, port -> PlatformSocket.connect(ip, port) }, callback: ProgressionCallback? = null, dataFlag: Int = 4096, localeId: Int = 2052, ): BdhUploadResponse { val bdhSession = kotlin.runCatching { val deferred = bot.asQQAndroidBot().components[BdhSessionSyncer].bdhSession // no need to care about timeout. proceed by bot init @OptIn(ExperimentalCoroutinesApi::class) if (noBdhAwait) deferred.getCompleted() else deferred.await() }.getOrElse(fallbackSession) return tryServersUpload( bot = bot, servers = if (tryOnce) listOf(bdhSession.ssoAddresses.random()) else bdhSession.ssoAddresses, resourceSize = resource.size, resourceKind = kind, channelKind = ChannelKind.HIGHWAY ) { ip, port -> val md5 = resource.md5 require(md5.size == 16) { "bad md5. Required size=16, got ${md5.size}" } val resp = BdhUploadResponse() highwayPacketSession( client = bot.client, appId = bot.client.subAppId.toInt(), command = "PicUp.DataUp", commandId = commandId, initialTicket = initialTicket ?: bdhSession.sigSession, data = resource, dataFlag = dataFlag, localeId = localeId, fileMd5 = md5, extendInfo = extendInfo?.let { if (encrypt) TEA.encrypt(extendInfo, bdhSession.sessionKey) else extendInfo }, callback = callback ).sendConcurrently( createConnection = { createConnection(ip, port) }, coroutines = bot.configuration.highwayUploadCoroutineCount, resultChecker = resultChecker, ) { head -> if (head.rspExtendinfo.isNotEmpty()) { resp.extendInfo = head.rspExtendinfo } } resp } } } internal enum class ResourceKind( private val display: String, ) { PRIVATE_IMAGE("private image"), GROUP_IMAGE("group image"), PRIVATE_AUDIO("private audio"), GROUP_AUDIO("group audio"), GROUP_FILE("group file"), LONG_MESSAGE("long message"), FORWARD_MESSAGE("forward message"), ANNOUNCEMENT_IMAGE("announcement image"), SHORT_VIDEO("short video") ; override fun toString(): String = display } internal enum class ChannelKind( private val display: String, ) { HIGHWAY("Highway"), HTTP("Http") ; override fun toString(): String = display } internal suspend inline fun <reified R, reified IP> tryServersUpload( bot: QQAndroidBot, servers: Collection<Pair<IP, Int>>, resourceSize: Long, resourceKind: ResourceKind, channelKind: ChannelKind, crossinline implOnEachServer: suspend (ip: String, port: Int) -> R, ) = servers.retryWithServers( (resourceSize * 1000 / 1024 / 10).coerceAtLeast(5000), onFail = { throw IllegalStateException("cannot upload $resourceKind, failed on all servers.", it) } ) { ip, port -> bot.network.logger.verbose { "[${channelKind}] Uploading $resourceKind to ${ip}:$port, size=${resourceSize.sizeToString()}" } var resp: R? = null val time = measureTimeMillis { runCatching { resp = implOnEachServer(ip, port) }.onFailure { bot.network.logger.verbose { "[${channelKind}] Uploading $resourceKind to ${ip}:$port, size=${resourceSize.sizeToString()} failed: $it" } throw it } } bot.network.logger.verbose { "[${channelKind}] Uploading $resourceKind: succeed at ${ (resourceSize.toDouble() / 1024 / time.toDouble().div(1000)).roundToInt() } KiB/s" } resp as R } internal suspend inline fun <reified R> tryServersDownload( bot: QQAndroidBot, servers: Collection<Pair<Int, Int>>, resourceKind: ResourceKind, channelKind: ChannelKind, crossinline implOnEachServer: suspend (ip: String, port: Int) -> R, ) = servers.retryWithServers( 5000, onFail = { throw IllegalStateException("cannot download $resourceKind, failed on all servers.", it) } ) { ip, port -> tryDownloadImplEach(bot, channelKind, resourceKind, ip, port, implOnEachServer) } internal suspend inline fun <reified R> tryDownload( bot: QQAndroidBot, host: String, port: Int, times: Int = 1, resourceKind: ResourceKind, channelKind: ChannelKind, crossinline implOnEachServer: suspend (ip: String, port: Int) -> R, ) = retryCatching(times) { tryDownloadImplEach(bot, channelKind, resourceKind, host, port, implOnEachServer) }.getOrElse { throw IllegalStateException("Cannot download $resourceKind", it) } private suspend inline fun <reified R> tryDownloadImplEach( bot: QQAndroidBot, channelKind: ChannelKind, resourceKind: ResourceKind, host: String, port: Int, crossinline implOnEachServer: suspend (ip: String, port: Int) -> R, ): R { bot.network.logger.verbose { "[${channelKind}] Downloading $resourceKind from ${host}:$port" } var resp: R? = null runCatching { resp = implOnEachServer(host, port) }.onFailure { bot.network.logger.verbose { "[${channelKind}] Downloading $resourceKind from ${host}:$port failed: $it" } throw it } bot.network.logger.verbose { "[${channelKind}] Downloading $resourceKind: succeed" } return resp as R } internal suspend fun ChunkedFlowSession<ByteReadPacket>.sendSequentially( socket: PlatformSocket, respCallback: (resp: CSDataHighwayHead.RspDataHighwayHead) -> Unit = {}, ) { contract { callsInPlace(respCallback, InvocationKind.UNKNOWN) } useAll { socket.send(it) //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 socket.read().withUse { discardExact(1) val headLength = readInt() discardExact(4) val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) check(proto.errorCode == 0) { "highway transfer failed, error ${proto.errorCode}" } respCallback(proto) } } } private fun <T> Flow<T>.produceIn0(coroutineScope: CoroutineScope): ReceiveChannel<T> { return kotlin.runCatching { @OptIn(FlowPreview::class) produceIn(coroutineScope) // this is experimental api }.getOrElse { // fallback strategy in case binary changes. val channel = Channel<T>() coroutineScope.launch(CoroutineName("Flow collector")) { collect { channel.send(it) } channel.close() } channel } } internal interface HighwayProtocolChannel { suspend fun send(packet: ByteReadPacket) suspend fun read(): ByteReadPacket } // backup // createConnection = { ip, port -> // SynchronousHighwayProtocolChannel { packet -> // val http = Mirai.Http // http.post("http://$ip:$port/cgi-bin/httpconn?htcmd=0x6FF0087&uin=${bot.id}") { // userAgent("QQClient") // val bytes = packet.readBytes() // body = object : OutgoingContent.WriteChannelContent() { // override val contentLength: Long get() = bytes.size.toLongUnsigned() // override val contentType: ContentType get() = ContentType.Any // override suspend fun writeTo(channel: ByteWriteChannel) { // channel.writeFully(bytes) // } // } // } // } // } internal class SynchronousHighwayProtocolChannel( val action: suspend (ByteReadPacket) -> ByteArray, ) : HighwayProtocolChannel { @Volatile var result: ByteArray? = null override suspend fun send(packet: ByteReadPacket) { result = action(packet) } override suspend fun read(): ByteReadPacket { return result?.toReadPacket() ?: error("result is null") } } internal suspend fun ChunkedFlowSession<ByteReadPacket>.sendConcurrently( createConnection: suspend () -> HighwayProtocolChannel, coroutines: Int = 5, resultChecker: (CSDataHighwayHead.RspDataHighwayHead) -> Boolean, respCallback: (resp: CSDataHighwayHead.RspDataHighwayHead) -> Unit = {}, ) = coroutineScope { val channel = asFlow().produceIn0(this) coroutineContext.job.invokeOnCompletion { if (channel is Channel<*>) { try { channel.close() // safely closes resource } catch (ignored: Exception) { } } } // 'single thread' producer emits chunks to channel repeat(coroutines) { launch(CoroutineName("Worker $it")) { val socket = createConnection() while (isActive) { val next = channel.receiveCatching().getOrNull() ?: return@launch // concurrent-safe receive val result = next.withUse { socket.sendReceiveHighway(next, resultChecker) } respCallback(result) } } } } private suspend fun HighwayProtocolChannel.sendReceiveHighway( it: ByteReadPacket, resultChecker: (CSDataHighwayHead.RspDataHighwayHead) -> Boolean, ): CSDataHighwayHead.RspDataHighwayHead { send(it) //0A 3C 08 01 12 0A 31 39 39 34 37 30 31 30 32 31 1A 0C 50 69 63 55 70 2E 44 61 74 61 55 70 20 E9 A7 05 28 00 30 BD DB 8B 80 02 38 80 20 40 02 4A 0A 38 2E 32 2E 30 2E 31 32 39 36 50 84 10 12 3D 08 00 10 FD 08 18 00 20 FD 08 28 C6 01 38 00 42 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 4A 10 D4 1D 8C D9 8F 00 B2 04 E9 80 09 98 EC F8 42 7E 50 89 92 A2 FB 06 58 00 60 00 18 53 20 01 28 00 30 04 3A 00 40 E6 B7 F7 D9 80 2E 48 00 50 00 read().withUse { discardExact(1) val headLength = readInt() discardExact(4) val proto = readProtoBuf(CSDataHighwayHead.RspDataHighwayHead.serializer(), length = headLength) check(resultChecker(proto)) { "highway transfer failed, error ${proto.errorCode}" } // error 70: 某属性有误 // error 79: 没有 body (可能) return proto } } internal fun highwayPacketSession( // RequestDataTrans client: QQAndroidClient, command: String, appId: Int, dataFlag: Int = 4096, commandId: Int, localeId: Int = 2052, initialTicket: ByteArray, data: ExternalResource, fileMd5: ByteArray, sizePerPacket: Int = ByteArrayPool.BUFFER_SIZE, extendInfo: ByteArray? = null, callback: Highway.ProgressionCallback? = null, ): ChunkedFlowSession<ByteReadPacket> { ByteArrayPool.checkBufferSize(sizePerPacket) // require(ticket.size == 128) { "bad uKey. Required size=128, got ${ticket.size}" } return ChunkedFlowSession(data.input(), ByteArray(sizePerPacket), callback) { buffer, size, offset -> val head = CSDataHighwayHead.ReqDataHighwayHead( msgBasehead = CSDataHighwayHead.DataHighwayHead( version = 1, uin = client.uin.toString(), command = command, seq = client.nextHighwayDataTransSequenceId(), retryTimes = 0, appid = appId, dataflag = dataFlag, commandId = commandId, localeId = localeId ), msgSeghead = CSDataHighwayHead.SegHead( // cacheAddr = 812157193, datalength = size, dataoffset = offset, filesize = data.size, serviceticket = initialTicket, md5 = buffer.md5(0, size), fileMd5 = fileMd5, flag = 0, rtcode = 0 ), reqExtendinfo = extendInfo, msgLoginSigHead = null ).toByteArray(CSDataHighwayHead.ReqDataHighwayHead.serializer()) buildPacket { writeByte(40) writeInt(head.size) writeInt(size) writeFully(head) writeFully(buffer, 0, size) writeByte(41) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/highway/Http.kt ================================================ /* * 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.highway import io.ktor.client.* import io.ktor.client.request.* import io.ktor.http.* import io.ktor.http.content.* import io.ktor.util.* import io.ktor.utils.io.* import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.copyTo import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.utils.withUse /** * 在发送完成后将会 [InputStream.close] */ internal fun ExternalResource.consumeAsWriteChannelContent(contentType: ContentType?): OutgoingContent.WriteChannelContent { return object : OutgoingContent.WriteChannelContent() { override val contentType: ContentType? = contentType override val contentLength: Long = size override suspend fun writeTo(channel: ByteWriteChannel) { input().withUse { copyTo(channel) } } } } internal val FALLBACK_HTTP_SERVER = "htdata2.qq.com" to 0 @OptIn(InternalAPI::class) // ktor bug @Suppress("SpellCheckingInspection") internal suspend fun HttpClient.postImage( serverIp: String, serverPort: Int = DEFAULT_PORT, htcmd: String, uin: Long, groupcode: Long?, imageInput: ExternalResource, uKeyHex: String, ): Boolean = post { url { protocol = URLProtocol.HTTP host = serverIp // "htdata2.qq.com" port = serverPort path("cgi-bin/httpconn") parameters["htcmd"] = htcmd parameters["uin"] = uin.toString() if (groupcode != null) parameters["groupcode"] = groupcode.toString() parameters["term"] = "pc" parameters["ver"] = "5603" parameters["filesize"] = imageInput.size.toString() parameters["range"] = 0.toString() parameters["ukey"] = uKeyHex userAgent("QQClient") } body = imageInput.consumeAsWriteChannelContent(ContentType.Image.Any) }.status == HttpStatusCode.OK internal suspend fun HttpClient.postPtt( serverIp: String, serverPort: Int, resource: ExternalResource, uKey: ByteArray, fileKey: ByteArray, ) { post { url("http://$serverIp:$serverPort") parameter("ver", 4679) parameter("ukey", uKey.toUHexString("")) parameter("filekey", fileKey.toUHexString("")) parameter("filesize", resource.size) parameter("bmd5", resource.md5.toUHexString("")) parameter("mType", "pttDu") parameter("voice_encodec", resource.voiceCodec) setBody(resource.consumeAsWriteChannelContent(null)) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/impl/HeartbeatFailedException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl import net.mamoe.mirai.internal.network.handler.selector.NetworkException internal class HeartbeatFailedException( private val name: String, // kind of HB override val cause: Throwable, recoverable: Boolean = cause is NetworkException && cause.recoverable, ) : NetworkException(recoverable) { override val message: String = "Exception in $name job" override fun toString(): String = "HeartbeatFailedException: $name, recoverable=$recoverable, cause=$cause" } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/impl/ServerClosedException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl import net.mamoe.mirai.internal.network.handler.selector.NetworkException internal sealed class ServerClosedException( override val message: String? = null, override val cause: Throwable? = null, ) : NetworkException(true) internal class ForceOfflineException( val title: String, override val message: String, cause: Throwable? = null, ) : ServerClosedException(message, cause) ================================================ FILE: mirai-core/src/commonMain/kotlin/network/impl/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl ================================================ FILE: mirai-core/src/commonMain/kotlin/network/keys.kt ================================================ /* * 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 io.ktor.utils.io.core.* import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.network.components.BotClientHolder import net.mamoe.mirai.utils.* internal class ReserveUinInfo( val imgType: ByteArray, val imgFormat: ByteArray, val imgUrl: ByteArray, ) { override fun toString(): String { return "ReserveUinInfo(imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()})" } } internal class WFastLoginInfo( val outA1: ByteReadPacket, var adUrl: String = "", var iconUrl: String = "", var profileUrl: String = "", var userJson: String = "", ) { override fun toString(): String { return "WFastLoginInfo(outA1=$outA1, adUrl='$adUrl', iconUrl='$iconUrl', profileUrl='$profileUrl', userJson='$userJson')" } } @Serializable internal class WLoginSimpleInfo( val uin: Long, // uin val imgType: ByteArray, val imgFormat: ByteArray, val imgUrl: ByteArray, val mainDisplayName: ByteArray, ) { override fun toString(): String { return "WLoginSimpleInfo(uin=$uin, imgType=${imgType.toUHexString()}, imgFormat=${imgFormat.toUHexString()}, imgUrl=${imgUrl.toUHexString()}, mainDisplayName=${mainDisplayName.toUHexString()})" } } @Serializable internal class LoginExtraData( val uin: Long, val ip: ByteArray, val time: Int, val version: Int, ) { override fun toString(): String { return "LoginExtraData(uin=$uin, ip=${ip.toUHexString()}, time=$time, version=$version)" } } internal fun BytePacketBuilder.writeLoginExtraData(loginExtraData: LoginExtraData) { loginExtraData.run { writeLong(uin) writeByte(ip.size.toByte()) writeFully(ip) writeInt(time) writeInt(version) } } @Serializable internal class QRCodeLoginData( val tmpPwd: ByteArray, // get from wtlogin.trans_emp, don't use client.wLoginSigInfo.encryptA1 val noPicSig: ByteArray, // get from wtlogin.trans_emp, don't use client.wLoginSigInfo.encryptA1 val tgtQR: ByteArray, ) { override fun toString(): String { return "QRCodeLoginData(tmpPwd=${tmpPwd.toUHexString()}, noPicSig=${noPicSig.toUHexString()}, tgtQR=${tgtQR.toUHexString()})" } } @Suppress("ArrayInDataClass") // for `copy` @Serializable internal data class WLoginSigInfo( val uin: Long, var encryptA1: ByteArray?, // sigInfo[0] /** * WARNING, please check [QQAndroidClient.tlv16a] */ var noPicSig: ByteArray?, // sigInfo[1] val simpleInfo: WLoginSimpleInfo, var appPri: Long, var a2ExpiryTime: Long, var loginBitmap: Long, var tgt: ByteArray, var a2CreationTime: Long, var tgtKey: ByteArray, var userStSig: KeyWithCreationTime, /** * TransEmpPacket 加密使用 */ var userStKey: ByteArray, var userStWebSig: KeyWithExpiry, var userA5: KeyWithCreationTime, var userA8: KeyWithExpiry, var lsKey: KeyWithExpiry, var sKey: KeyWithExpiry, var userSig64: KeyWithCreationTime, var openId: ByteArray, var openKey: KeyWithCreationTime, var vKey: KeyWithExpiry, var accessToken: KeyWithCreationTime, var d2: KeyWithExpiry, var d2Key: ByteArray, var sid: KeyWithExpiry, var aqSig: KeyWithCreationTime, var psKeyMap: PSKeyMap, var pt4TokenMap: MutableMap<String, KeyWithExpiry> = mutableMapOf(), // = Pt4TokenMap maybe compiler bug var superKey: ByteArray, var payToken: ByteArray, var pf: ByteArray, var pfKey: ByteArray, var da2: ByteArray, // val pt4Token: ByteArray, var wtSessionTicket: KeyWithCreationTime, var wtSessionTicketKey: ByteArray, var deviceToken: ByteArray, var encryptedDownloadSession: EncryptedDownloadSession? = null, ) { /** * 获取 获取群公告 所需的 bkn 参数 * */ val bkn: Int get() = sKey.data .fold(5381) { acc: Int, b: Byte -> acc + acc.shl(5) + b.toInt() } .and(Int.MAX_VALUE) //图片加密下载 //是否加密从bigdatachannel处得知 @Serializable internal class EncryptedDownloadSession( val appId: Long, //1600000226L val stKey: ByteArray, val stSig: ByteArray, ) override fun toString(): String { return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})" } fun getPsKey(name: String): String { return psKeyMap[name]?.data?.decodeToString() ?: error("Cannot find PsKey $name") } } internal typealias PSKeyMap = MutableMap<String, KeyWithExpiry> internal typealias Pt4TokenMap = MutableMap<String, KeyWithExpiry> internal fun parsePSKeyMapAndPt4TokenMap( data: ByteArray, creationTime: Long, expireTime: Long, outPSKeyMap: PSKeyMap, outPt4TokenMap: Pt4TokenMap, ) = data.read { repeat(readShort().toInt()) { val domain = readUShortLVString() val psKey = readUShortLVByteArray() val pt4token = readUShortLVByteArray() when { psKey.isNotEmpty() -> outPSKeyMap[domain] = KeyWithExpiry(psKey, creationTime, expireTime) pt4token.isNotEmpty() -> outPt4TokenMap[domain] = KeyWithExpiry(pt4token, creationTime, expireTime) } } } @Serializable internal open class KeyWithExpiry( @SerialName("data1") override val data: ByteArray, @SerialName("creationTime1") override val creationTime: Long, val expireTime: Long, ) : KeyWithCreationTime(data, creationTime) { override fun toString(): String { return "KeyWithExpiry(data=${data.toUHexString()}, creationTime=$creationTime)" } } internal val KeyWithExpiry.str get() = data.decodeToString() internal val AbstractBot.sKey get() = client.wLoginSigInfo.sKey.str internal fun AbstractBot.psKey(name: String) = client.wLoginSigInfo.getPsKey(name) internal val AbstractBot.client get() = components[BotClientHolder].client @Serializable internal open class KeyWithCreationTime( open val data: ByteArray, open val creationTime: Long, ) { override fun toString(): String { return "KeyWithCreationTime(data=${data.toUHexString()}, creationTime=$creationTime)" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/NewContactSupport.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice import kotlinx.coroutines.cancel import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.MemberPermission 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.StrangerImpl import net.mamoe.mirai.internal.contact.impl 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.getGroupByUin import net.mamoe.mirai.internal.network.protocol.data.jce.StGroupRankInfo import net.mamoe.mirai.internal.network.protocol.data.jce.StTroopNum import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList internal interface NewContactSupport { // can be a marker interface when context receivers are available. fun MsgComm.Msg.getNewMemberInfo(): MemberInfoImpl { return MemberInfoImpl( nameCard = msgHead.authNick.ifEmpty { msgHead.fromNick }, permission = MemberPermission.MEMBER, specialTitle = "", muteTimestamp = 0, uin = msgHead.authUin, nick = msgHead.authNick.ifEmpty { msgHead.fromNick }, remark = "", anonymousId = null, ) } suspend fun QQAndroidBot.addNewGroupByCode(code: Long): GroupImpl? { if (getGroup(code) != null) return null return getNewGroup(code)?.apply { groups.delegate.add(this) } } // final suspend fun QQAndroidBot.addNewGroupByUin(groupUin: Long): GroupImpl? { if (getGroupByUin(groupUin) != null) return null return addNewGroupByCode(Mirai.calculateGroupCodeByGroupUin(groupUin)) } suspend fun QQAndroidBot.addNewGroup(stTroopNum: StTroopNum, stGroupRankInfo: StGroupRankInfo?): GroupImpl? { if (getGroup(stTroopNum.groupCode) != null) return null return getNewGroup(stTroopNum, stGroupRankInfo).apply { groups.delegate.add(this) } } fun QQAndroidBot.removeStranger(id: Long): StrangerImpl? { val instance = strangers[id] ?: return null strangers.remove(instance.id) instance.cancel() return instance } fun QQAndroidBot.removeFriend(id: Long): FriendImpl? { val instance = friends[id] ?: return null friends.remove(instance.id) instance.cancel() return instance } fun QQAndroidBot.addNewFriendAndRemoveStranger(info: FriendInfoImpl): FriendImpl? { if (friends.contains(info.uin)) return null strangers[info.uin]?.let { removeStranger(it.id) } val friend = Mirai.newFriend(bot, info).impl() friends.delegate.add(friend) return friend } fun QQAndroidBot.addNewStranger(info: StrangerInfoImpl): StrangerImpl? { if (friends.contains(info.uin)) return null // cannot have both stranger and friend if (strangers.contains(info.uin)) return null val stranger = Mirai.newStranger(bot, info).impl() strangers.delegate.add(stranger) return stranger } private suspend fun QQAndroidBot.getNewGroup(groupCode: Long): GroupImpl? { val response = network.sendAndExpect( FriendList.GetTroopListSimplify(client), timeout = 10_000, attempts = 5 ) val troopNum = response.groups.firstOrNull { it.groupCode == groupCode } ?: return null val groupRankInfo = response.ranks.find { it.dwGroupCode == groupCode } return getNewGroup(troopNum, groupRankInfo) } private suspend fun QQAndroidBot.getNewGroup(troopNum: StTroopNum, stGroupRankInfo: StGroupRankInfo?): GroupImpl { return GroupImpl( bot = this, parentCoroutineContext = coroutineContext, id = troopNum.groupCode, groupInfo = GroupInfoImpl(troopNum, stGroupRankInfo), members = Mirai.getRawGroupMemberList( this, troopNum.groupUin, troopNum.groupCode, troopNum.dwGroupOwnerUin, ), ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/PrivateContactSupport.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl /////////////////////////////////////////////////////////////////////////// // Extension interfaces ---- should convert to context receivers in the future. /////////////////////////////////////////////////////////////////////////// internal interface BotAware : PrivateContactSupport { override val bot: QQAndroidBot } internal interface GroupAware : GroupMemberSupport, BotAware { override val group: GroupImpl override val bot: QQAndroidBot get() = group.bot } internal interface PrivateContactSupport { val bot: QQAndroidBot fun Long.findFriend() = bot.friends[this] fun Long.findStranger() = bot.strangers[this] fun Long.findFriendOrStranger() = findFriend() ?: findStranger() fun String.findFriend() = this.toLongOrNull()?.findFriend() fun String.findStranger() = this.toLongOrNull()?.findStranger() fun String.findFriendOrStranger() = this.toLongOrNull()?.findFriendOrStranger() } internal interface GroupMemberSupport { val group: GroupImpl fun Long.findMember() = group[this] fun String.findMember() = this.toLongOrNull()?.findMember() } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/TraceLoggingNoticeProcessor.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.systemProp import net.mamoe.mirai.utils.warning import net.mamoe.mirai.utils.withSwitch internal class TraceLoggingNoticeProcessor( logger: MiraiLogger ) : SimpleNoticeProcessor<ProtocolStruct>(type()) { private val logger: MiraiLogger = logger.withSwitch(systemProp("mirai.network.notice.trace.logging", false)) override suspend fun NoticePipelineContext.processImpl(data: ProtocolStruct) { logger.warning { "${data::class.simpleName}: isConsumed=$isConsumed" } } // override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) { // logger.warning { "MsgType0x210: isConsumed=$isConsumed" } // } // } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/UnconsumedNoticesAlerter.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact import net.mamoe.mirai.utils.* internal class UnconsumedNoticesAlerter( logger: MiraiLogger, ) : MixedNoticeProcessor() { private val logger: MiraiLogger = logger.withSwitch(systemProp("mirai.network.notice.unconsumed.logging", false)) override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) { if (isConsumed) return when (data.uSubMsgType) { 0x26L, // VIP 进群提示 0x111L, // 提示共同好友 0xD4L, // bot 在其他客户端被踢或主动退出而同步情况 -> { // Network(1994701021) 16:03:54 : unknown group 528 type 0x0000000000000026, data: 08 01 12 40 0A 06 08 F4 EF BB 8F 04 10 E7 C1 AD B8 02 18 01 22 2C 10 01 1A 1A 18 B4 DC F8 9B 0C 20 E7 C1 AD B8 02 28 06 30 02 A2 01 04 08 93 D6 03 A8 01 08 20 00 28 00 32 08 18 01 20 FE AF AF F5 05 28 00 } 0xE2L -> { // unknown // 0A 35 08 00 10 A2 FF 8C F0 03 1A 1B E5 90 8C E6 84 8F E4 BD A0 E7 9A 84 E5 8A A0 E5 A5 BD E5 8F 8B E8 AF B7 E6 B1 82 22 0C E6 BD 9C E6 B1 9F E7 BE A4 E5 8F 8B 28 01 // vProtobuf.loadAs(Msgtype0x210.serializer()) } else -> { logger.debug { "Unknown group 528 type 0x${data.uSubMsgType.toUHexString("")}, data: " + data.vProtobuf.toUHexString() } } } } override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) { if (isConsumed) return logger.debug { "Unknown group 732 type ${data.kind}, data: " + data.buf.toUHexString() } } override suspend fun NoticePipelineContext.processImpl(data: OnlinePushTrans.PbMsgInfo) { if (isConsumed) return when { data.msgType == 529 && data.msgSubtype == 9 -> { /* PbMsgInfo#1773430973 { fromUin=0x0000000026BA1173(649728371) generalFlag=0x00000001(1) msgData=0A 07 70 72 69 6E 74 65 72 10 02 1A CD 02 0A 1F 53 61 6D 73 75 6E 67 20 4D 4C 2D 31 38 36 30 20 53 65 72 69 65 73 20 28 55 53 42 30 30 31 29 0A 16 4F 6E 65 4E 6F 74 65 20 66 6F 72 20 57 69 6E 64 6F 77 73 20 31 30 0A 19 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 20 74 6F 20 45 76 65 72 6E 6F 74 65 0A 11 4F 6E 65 4E 6F 74 65 20 28 44 65 73 6B 74 6F 70 29 0A 1D 4D 69 63 72 6F 73 6F 66 74 20 58 50 53 20 44 6F 63 75 6D 65 6E 74 20 57 72 69 74 65 72 0A 16 4D 69 63 72 6F 73 6F 66 74 20 50 72 69 6E 74 20 74 6F 20 50 44 46 0A 15 46 6F 78 69 74 20 50 68 61 6E 74 6F 6D 20 50 72 69 6E 74 65 72 0A 03 46 61 78 32 09 0A 03 6A 70 67 10 01 18 00 32 0A 0A 04 6A 70 65 67 10 01 18 00 32 09 0A 03 70 6E 67 10 01 18 00 32 09 0A 03 67 69 66 10 01 18 00 32 09 0A 03 62 6D 70 10 01 18 00 32 09 0A 03 64 6F 63 10 01 18 01 32 0A 0A 04 64 6F 63 78 10 01 18 01 32 09 0A 03 74 78 74 10 00 18 00 32 09 0A 03 70 64 66 10 01 18 01 32 09 0A 03 70 70 74 10 01 18 01 32 0A 0A 04 70 70 74 78 10 01 18 01 32 09 0A 03 78 6C 73 10 01 18 01 32 0A 0A 04 78 6C 73 78 10 01 18 01 msgSeq=0x00001AFF(6911) msgSubtype=0x00000009(9) msgTime=0x5FDF21A3(1608458659) msgType=0x00000211(529) msgUid=0x010000005FDEE04C(72057595646369868) realMsgTime=0x5FDF21A3(1608458659) svrIp=0x3E689409(1047041033) toUin=0x0000000026BA1173(649728371) } */ return } } if (logger.isEnabled && logger.isDebugEnabled) { logger.debug( contextualBugReportException( "解析 OnlinePush.PbPushTransMsg, msgType=${data.msgType}", data.structureToString(), null, "并描述此时机器人是否被踢出, 或是否有成员列表变更等动作.", ) ) } } override suspend fun NoticePipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) { if (isConsumed) return } override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) { if (isConsumed) return when (data.msgHead.msgType) { 732 -> { // 732: 27 0B 60 E7 0C 01 3E 03 3F A2 5E 90 60 E2 00 01 44 71 47 90 00 00 02 58 // 732: 27 0B 60 E7 11 00 40 08 07 20 E7 C1 AD B8 02 5A 36 08 B4 E7 E0 F0 09 1A 1A 08 9C D4 16 10 F7 D2 D8 F5 05 18 D0 E2 85 F4 06 20 00 28 00 30 B4 E7 E0 F0 09 2A 0E 08 00 12 0A 08 9C D4 16 10 00 18 01 20 00 30 00 38 00 // 732: 27 0B 60 E7 11 00 33 08 07 20 E7 C1 AD B8 02 5A 29 08 EE 97 85 E9 01 1A 19 08 EE D6 16 10 FF F2 D8 F5 05 18 E9 E7 A3 05 20 00 28 00 30 EE 97 85 E9 01 2A 02 08 00 30 00 38 00 // unknown // 前 4 byte 是群号 } 84, 87 -> { // 请求入群验证 和 被邀请入群 bot.network.sendWithoutExpect(NewContact.SystemMsgNewGroup(bot.client)) } 187 -> { // 请求加好友验证 bot.network.sendWithoutExpect(NewContact.SystemMsgNewFriend(bot.client)) } else -> { logger.debug { "unknown PbGetMsg type ${data.msgHead.msgType}, data=${data.msgBody.msgContent.toUHexString()}" } } } } override suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) { if (isConsumed) return if (logger.isEnabled && logger.isDebugEnabled) { data.msg?.context { throw contextualBugReportException( "解析 NewContact.SystemMsgNewGroup, subType=$subType, groupMsgType=$groupMsgType", forDebug = this.structureToString(), additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群", ) } } } override suspend fun NoticePipelineContext.processImpl(data: RequestPushStatus) { if (isConsumed) return if (logger.isEnabled && logger.isDebugEnabled) { throw contextualBugReportException( "decode SvcRequestPushStatus (PC Client status change)", data.structureToString(), additional = "unknown status=${data.status}", ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/decoders/GroupNotificationDecoder.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.decoders import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.internal.utils.io.serialization.loadAs internal class GroupNotificationDecoder : MixedNoticeProcessor() { override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) { when (data.kind) { 0x10 -> { val proto = data.buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), offset = 1) processAlso(DecodedNotifyMsgBody(data.kind, data.group, proto)) } } } } internal data class DecodedNotifyMsgBody( override val kind: Int, override val group: GroupImpl, override val buf: TroopTips0x857.NotifyMsgBody, ) : BaseMsgType0x2DC<TroopTips0x857.NotifyMsgBody>, ProtocolStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/decoders/MsgInfoDecoder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.decoders import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.checkIsGroupImpl import net.mamoe.mirai.internal.getGroupByUin import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_MSG_INFO import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.components.syncOnlinePush import net.mamoe.mirai.internal.network.notice.GroupAware import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.jce.OnlinePushPack.SvcReqPushMsg import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.utils.* /** * Decodes [SvcReqPushMsg] to [MsgInfo] then re-fire [MsgType0x210] or [MsgType0x2DC] */ internal class MsgInfoDecoder( private val logger: MiraiLogger, ) : SimpleNoticeProcessor<SvcReqPushMsg>(type()) { override suspend fun NoticePipelineContext.processImpl(data: SvcReqPushMsg) { // SvcReqPushMsg is fully handled here, no need to set consumed. for (msgInfo in data.vMsgInfos) { decodeMsgInfo(msgInfo) } } private suspend fun NoticePipelineContext.decodeMsgInfo(data: MsgInfo) { if (!bot.syncController.syncOnlinePush(data)) return @Suppress("MoveVariableDeclarationIntoWhen") // for debug val id = data.shMsgType.toUShort().toInt() when (id) { // 528 0x210 -> processAlso(data.vMsg.loadAs(MsgType0x210.serializer()), KEY_MSG_INFO to data) // 732 0x2dc -> { data.vMsg.read { val groupCode = readInt().toUInt().toLong() val group = bot.getGroup(groupCode) ?: bot.getGroupByUin(groupCode) ?: return // group has not been initialized group.checkIsGroupImpl() val kind = readByte().toInt() discardExact(1) processAlso(MsgType0x2DC(kind, group, this.readBytes()), KEY_MSG_INFO to data) } } else -> { logger.debug { "Unknown MsgInfo kind ${data.shMsgType.toInt()}, data=${data.vMsg.toUHexString()}" } } } } } internal interface BaseMsgType0x2DC<V> : GroupAware { val kind: Int override val group: GroupImpl val buf: V override val bot get() = group.bot } internal data class MsgType0x2DC( override val kind: Int, // inner kind, read from vMsg override val group: GroupImpl, override val buf: ByteArray, ) : ProtocolStruct, BaseMsgType0x2DC<ByteArray> { override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (kind != other.kind) return false if (group != other.group) return false if (!buf.contentEquals(other.buf)) return false return true } override fun hashCode(): Int { var result = kind result = 31 * result + group.hashCode() result = 31 * result + buf.contentHashCode() return result } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/group/GroupMessageProcessor.kt ================================================ /* * 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.notice.group import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Member import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.GroupMessageEvent import net.mamoe.mirai.event.events.GroupMessageSyncEvent import net.mamoe.mirai.event.events.MemberCardChangeEvent import net.mamoe.mirai.event.events.MemberSpecialTitleChangeEvent import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.contact.info.MemberInfoImpl import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor.MemberNick.Companion.generateMemberNickFromMember import net.mamoe.mirai.internal.network.notice.priv.PrivateMessageProcessor 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.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x8fc import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.utils.* /** * Handles [GroupMessageEvent]. For private message events, see [PrivateMessageProcessor] */ internal class GroupMessageProcessor( private val logger: MiraiLogger, ) : SimpleNoticeProcessor<MsgOnlinePush.PbPushMsg>(type()) { internal data class SendGroupMessageReceipt( val bot: Bot?, val messageRandom: Int, val sequenceId: Int, val fromAppId: Int, ) : Packet, Event, Packet.NoLog, AbstractEvent() { override fun toString(): String { return "OnlinePush.PbPushGroupMsg.SendGroupMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)" } companion object { val EMPTY = SendGroupMessageReceipt(null, 0, 0, 0) } } private data class MemberNick(val nick: String, val isNameCard: Boolean = false) { companion object { fun Member.generateMemberNickFromMember(): MemberNick { return nameCard.takeIf { nameCard.isNotEmpty() }?.let { MemberNick(nameCard, true) } ?: MemberNick(nick, false) } } } override suspend fun NoticePipelineContext.processImpl(data: MsgOnlinePush.PbPushMsg) { val msgHead = data.msg.msgHead val isFromSelfAccount = msgHead.fromUin == bot.id if (isFromSelfAccount) { val messageRandom = data.msg.msgBody.richText.attr?.random ?: return if (bot.syncController.containsGroupMessageReceipt(messageRandom) || msgHead.fromAppid == 3116 || msgHead.fromAppid == 2021 ) { // 3116=group music share // 2021=group file // message sent by bot collect(SendGroupMessageReceipt(bot, messageRandom, msgHead.msgSeq, msgHead.fromAppid)) return } // else: sync form other device } if (msgHead.groupInfo == null) return val group = bot.getGroup(msgHead.groupInfo.groupCode) as GroupImpl? ?: return // 机器人还正在进群 // fragmented message val msgs = group.groupPkgMsgParsingCache.tryMerge(data).ifEmpty { return } var extraInfo: ImMsgBody.ExtraInfo? = null var anonymous: ImMsgBody.AnonymousGroupMsg? = null for (msg in msgs) { for (elem in msg.msg.msgBody.richText.elems) { when { elem.extraInfo != null -> extraInfo = elem.extraInfo elem.anonGroupMsg != null -> anonymous = elem.anonGroupMsg } } msg.msg.msgBody.richText.ptt?.let pttPatch@{ ptt -> if (ptt.downPara.isNotEmpty()) return@pttPatch kotlin.runCatching { val response = bot.network.sendAndExpect( PttStore.GroupPttDown( bot.client, group.groupCode, ptt, msg.msg ), 5000, 2 ) ptt.downPara = "http://${response.strDomain}${response.downPara.decodeToString()}".encodeToByteArray() } } } val sender: Member // null if sync from other client val nameCard: MemberNick if (anonymous != null) { // anonymous member sender = group.newAnonymous(anonymous.anonNick.decodeToString(), anonymous.anonId.encodeBase64()) nameCard = sender.generateMemberNickFromMember() } else { // normal member chat sender = group[msgHead.fromUin] ?: kotlin.run { logger.warning { "Failed to find member ${msgHead.fromUin} in group ${group.id}" } return } nameCard = findSenderName(extraInfo, msgHead.groupInfo) ?: sender.generateMemberNickFromMember() } sender.info?.castOrNull<MemberInfoImpl>()?.run { lastSpeakTimestamp = currentTimeSeconds().toInt() } sender.castOrNull<NormalMemberImpl>()?.run { val specialTitleNow = extraInfo?.senderTitle?.decodeToString().orEmpty() if (specialTitle != specialTitleNow) { //群特殊头衔 只有群主才能进行操作 collect(MemberSpecialTitleChangeEvent(specialTitle, specialTitleNow, this, group.owner)) _specialTitle = specialTitleNow } } if (isFromSelfAccount) { collect( GroupMessageSyncEvent( client = bot.otherClients.find { it.appId == msgHead.fromInstid } ?: return, // don't compare with dstAppId. diff. message = msgs.map { it.msg }.toMessageChainOnline( bot, group.id, MessageSourceKind.GROUP, SimpleRefineContext( mutableMapOf( RefineContextKey.MessageSourceKind to MessageSourceKind.GROUP, RefineContextKey.FromId to sender.uin, RefineContextKey.GroupIdOrZero to group.id, ) ) ), time = msgHead.msgTime, group = group, sender = sender, senderName = nameCard.nick, ), ) return } else { broadcastNameCardChangedEventIfNecessary(sender, nameCard) collect( GroupMessageEvent( senderName = nameCard.nick, sender = sender, message = msgs.map { it.msg }.toMessageChainOnline( bot, group.id, MessageSourceKind.GROUP, SimpleRefineContext( mutableMapOf( RefineContextKey.MessageSourceKind to MessageSourceKind.GROUP, RefineContextKey.FromId to sender.uin, RefineContextKey.GroupIdOrZero to group.id, ) ) ), permission = sender.permission, time = msgHead.msgTime, ), ) return } } private suspend inline fun broadcastNameCardChangedEventIfNecessary( sender: Member, new: MemberNick, ) { if (sender is NormalMemberImpl) { val currentNameCard = sender.nameCard if (new.isNameCard) { new.nick.let { name -> if (currentNameCard != name) { sender._nameCard = name MemberCardChangeEvent(currentNameCard, name, sender).broadcast() } } } else { // 说明删除了群名片 if (currentNameCard.isNotEmpty()) { sender._nameCard = "" MemberCardChangeEvent(currentNameCard, "", sender).broadcast() } } } } private fun findSenderName( extraInfo: ImMsgBody.ExtraInfo?, groupInfo: MsgComm.GroupInfo, ): MemberNick? = extraInfo?.groupCard?.takeIf { it.isNotEmpty() }?.decodeCommCardNameBuf()?.let { MemberNick(it, true) } ?: groupInfo.takeIf { it.groupCard.isNotEmpty() }?.let { MemberNick(it.groupCard, it.groupCardType != 2) } private fun ByteArray.decodeCommCardNameBuf() = kotlin.runCatching { if (this[0] == 0x0A.toByte()) { val nameBuf = loadAs(Oidb0x8fc.CommCardNameBuf.serializer()) if (nameBuf.richCardName.isNotEmpty()) { return@runCatching nameBuf.richCardName.joinToString("") { it.text.decodeToString() } } } return@runCatching null }.getOrNull() ?: decodeToString() } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/group/GroupNotificationProcessor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.group import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.UserOrBot import net.mamoe.mirai.contact.getMember import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.checkIsGroupImpl import net.mamoe.mirai.internal.contact.checkIsMemberImpl import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x122 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27 import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.parseToMessageDataList import net.mamoe.mirai.utils.* import kotlin.jvm.JvmName internal class GroupNotificationProcessor( private val logger: MiraiLogger, ) : MixedNoticeProcessor(), NewContactSupport { override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) = data.context { when (data.uSubMsgType) { 0x27L -> { val body = vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.SubMsgType0x27MsgBody.serializer()) for (msgModInfo in body.msgModInfos) { markAsConsumed(msgModInfo) when { msgModInfo.msgModGroupProfile != null -> handleGroupProfileChanged(msgModInfo.msgModGroupProfile) msgModInfo.msgModGroupMemberProfile != null -> handleGroupMemberProfileChanged(msgModInfo.msgModGroupMemberProfile) else -> markNotConsumed(msgModInfo) } } } } } /** * @see GroupNameChangeEvent */ private fun NoticePipelineContext.handleGroupProfileChanged( modGroupProfile: Submsgtype0x27.SubMsgType0x27.ModGroupProfile ) { for (info in modGroupProfile.msgGroupProfileInfos) { when (info.field) { 1 -> { // 群名 val new = info.value.decodeToString() val group = bot.getGroup(modGroupProfile.groupCode) ?: continue group.checkIsGroupImpl() val old = group.name if (new == old) continue if (modGroupProfile.cmdUin == bot.id) continue val operator = group[modGroupProfile.cmdUin] ?: continue group.settings.nameField = new collect(GroupNameChangeEvent(old, new, group, operator)) } 2 -> { // 头像 // top_package/akkz.java:3446 /* var4 = var82.byteAt(0); short var3 = (short) (var82.byteAt(1) | var4 << 8); var85 = var18.method_77927(var7 + ""); var85.troopface = var3; var85.hasSetNewTroopHead = true; */ // bot.logger.debug( // contextualBugReportException( // "解析 Transformers528 0x27L ModGroupProfile 群头像修改", // forDebug = "this=${this._miraiContentToString()}" // ) // ) } 3 -> { // troop.credit.data // top_package/akkz.java:3475 // top_package/akkz.java:3498 // bot.logger.debug( // contextualBugReportException( // "解析 Transformers528 0x27L ModGroupProfile 群 troop.credit.data", // forDebug = "this=${this._miraiContentToString()}" // ) // ) } else -> { } } } } /** * @see MemberCardChangeEvent */ private fun NoticePipelineContext.handleGroupMemberProfileChanged( modGroupMemberProfile: Submsgtype0x27.SubMsgType0x27.ModGroupMemberProfile ) { for (info in modGroupMemberProfile.msgGroupMemberProfileInfos) { when (info.field) { 1 -> { // name card val new = info.value val group = bot.getGroup(modGroupMemberProfile.groupCode) ?: continue group.checkIsGroupImpl() val member = group[modGroupMemberProfile.uin] ?: continue member.checkIsMemberImpl() val old = member.nameCard if (new == old) continue member._nameCard = new collect(MemberCardChangeEvent(old, new, member)) } 2 -> { if (info.value.singleOrNull()?.code != 0) { logger.debug { "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}" } } continue } else -> { logger.debug { "Unknown Transformers528 0x27L ModGroupMemberProfile, field=${info.field}, value=${info.value}" } continue } } } } /////////////////////////////////////////////////////////////////////////// // MsgType0x2DC /////////////////////////////////////////////////////////////////////////// override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) { when (data.kind) { 0x0C -> processMute(data) 0x0E -> processAllowAnonymousChat(data) 0x10 -> processNormalGrayTip(data) 0x14 -> processGeneralGrayTip(data) } } /** * @see MemberMuteEvent * @see MemberUnmuteEvent * @see GroupMuteAllEvent * @see BotMuteEvent * @see BotUnmuteEvent */ private fun NoticePipelineContext.processMute( data: MsgType0x2DC, ) = data.context { fun handleMuteMemberPacket( bot: QQAndroidBot, group: GroupImpl, operator: NormalMember, target: Long, timeSeconds: Int, ): Packet? { if (target == 0L) { val new = timeSeconds != 0 if (group.settings.isMuteAllField == new) { return null } group.settings.isMuteAllField = new return GroupMuteAllEvent(!new, new, group, operator) } if (target == bot.id) { return when { group.botMuteRemaining == timeSeconds -> null timeSeconds == 0 || timeSeconds == 0xFFFF_FFFF.toInt() -> { group.botAsMember.checkIsMemberImpl()._muteTimestamp = 0 BotUnmuteEvent(operator) } else -> { group.botAsMember.checkIsMemberImpl()._muteTimestamp = currentTimeSeconds().toInt() + timeSeconds BotMuteEvent(timeSeconds, operator) } } } val member = group[target] ?: return null member.checkIsMemberImpl() if (member.muteTimeRemaining == timeSeconds) return null member._muteTimestamp = currentTimeSeconds().toInt() + timeSeconds return if (timeSeconds == 0) MemberUnmuteEvent(member, operator) else MemberMuteEvent(member, timeSeconds, operator) } markAsConsumed() buf.read { val operatorUin = readInt().toUInt().toLong() if (operatorUin == bot.id) return val operator = group[operatorUin] ?: return readInt().toUInt().toLong() // time val length = readShort().toUShort().toInt() repeat(length) { val target = readInt().toUInt().toLong() val timeSeconds = readInt().toUInt() collected += handleMuteMemberPacket(bot, group, operator, target, timeSeconds.toInt()) } } } /** * @see GroupAllowAnonymousChatEvent */ private fun NoticePipelineContext.processAllowAnonymousChat( data: MsgType0x2DC, ) = data.context { markAsConsumed() buf.read { val operator = group[readInt().toUInt().toLong()] ?: return val new = readInt() == 0 if (group.settings.isAnonymousChatEnabledField == new) return group.settings.isAnonymousChatEnabledField = new collect(GroupAllowAnonymousChatEvent(!new, new, group, operator)) } } /** * @see GroupAllowConfessTalkEvent * @see MemberSpecialTitleChangeEvent */ //gray tip: 聊天中的灰色小框系统提示信息(无通用模板,为混合xml代码的文本) private fun NoticePipelineContext.processNormalGrayTip( data: MsgType0x2DC, ) = data.context { val proto = data.buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), offset = 1) markAsConsumed() when (proto.optEnumType) { 1 -> { val tipsInfo = proto.optMsgGraytips ?: return val message = tipsInfo.optBytesContent.decodeToString() when (tipsInfo.robotGroupOpt) { // 非机器人信息 0 -> { //坦白说开关 if (message.endsWith("群聊坦白说")) { val new = when (message) { "管理员已关闭群聊坦白说" -> false "管理员已开启群聊坦白说" -> true else -> { logger.debug { "Unknown server confess talk messages $message" } return } } collect( GroupAllowConfessTalkEvent( origin = !new, new = new, group = group, isByBot = false ) ) //群特殊头衔授予 } else if (message.endsWith(">头衔")) { message.parseToMessageDataList().let { seq -> if (seq.count() == 2) { val uin = seq.first().data.toLong() val newTitle = seq.last().text val member = group.getMember(uin) ?: return@let member.checkIsMemberImpl() collect( MemberSpecialTitleChangeEvent( member.specialTitle, newTitle, member, group.owner ) ) member._specialTitle = newTitle } else { logger.debug { "Unknown server special title messages $message" } return } } } } } } else -> markNotConsumed() } } /** * @see NudgeEvent * @see MemberHonorChangeEvent * @see GroupTalkativeChangeEvent */ // general gray tip: 聊天中的灰色小框系统提示信息(有通用模板) private fun NoticePipelineContext.processGeneralGrayTip( data: MsgType0x2DC, ) = data.context { val grayTip = buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), 1).optGeneralGrayTip markAsConsumed() when (grayTip?.templId) { // 群戳一戳 10043L, 1133L, 1132L, 1134L, 1135L, 1136L -> { fun String.findUser(): UserOrBot? { return if (this == bot.id.toString()) { group.botAsMember } else { this.findMember() ?: this.findFriendOrStranger() } } // group nudge // 预置数据,服务器将不会提供己方已知消息 val action = grayTip.msgTemplParam["action_str"].orEmpty() val from = grayTip.msgTemplParam["uin_str1"] val target = grayTip.msgTemplParam["uin_str2"] val suffix = grayTip.msgTemplParam["suffix_str"].orEmpty() val fromUser = from?.findUser() val targetUser = target?.findUser() if (fromUser == null || targetUser == null) { markNotConsumed() logger.debug { "Cannot find from or target in Transformers528 0x14 template\ntemplId=${grayTip.templId}\nPermList=${grayTip.msgTemplParam.structureToString()}" } } else { collected += NudgeEvent( from = fromUser, target = targetUser, action = action, suffix = suffix, subject = group, ) } } // 群签到/打卡 10036L, 10038L -> { val user = grayTip.msgTemplParam["mqq_uin"]?.findMember() ?: group.botAsMember val sign = grayTip.msgTemplParam["user_sign"].orEmpty() val img = grayTip.msgTemplParam["rank_img"] val rank = """今日第(\d+)个打卡""".toRegex().matchEntire(sign)?.groupValues?.get(1)?.toInt() collected += SignEvent( user = user, sign = sign, hasRank = img != null, rank = rank ) } // 龙王 10093L, 10094L, 1053L, 1054L, 1103L -> { val now = grayTip.msgTemplParam["uin"]?.findMember() ?: group.botAsMember val previous = grayTip.msgTemplParam["uin_last"]?.findMember() val lastTalkative = group.lastTalkative if (lastTalkative == now) return // duplicate if (!group.casLastTalkative(lastTalkative, now)) return if (previous == null) { collect(MemberHonorChangeEvent.Achieve(now, GroupHonorType.TALKATIVE)) } else { collect(GroupTalkativeChangeEvent(group, now, previous)) collect(MemberHonorChangeEvent.Lose(previous, GroupHonorType.TALKATIVE)) collect(MemberHonorChangeEvent.Achieve(now, GroupHonorType.TALKATIVE)) } } // 群聊之火 1052L, 1129L -> { val now = grayTip.msgTemplParam["uin"]?.findMember() ?: group.botAsMember now.info.honors += GroupHonorType.PERFORMER collect(MemberHonorChangeEvent.Achieve(now, GroupHonorType.PERFORMER)) } // 群聊炽焰 1055L -> { val now = grayTip.msgTemplParam["uin"]?.findMember() ?: group.botAsMember now.info.honors -= GroupHonorType.PERFORMER now.info.honors += GroupHonorType.LEGEND collect(MemberHonorChangeEvent.Lose(now, GroupHonorType.PERFORMER)) collect(MemberHonorChangeEvent.Achieve(now, GroupHonorType.LEGEND)) } // 快乐源泉 1067L -> { val now = grayTip.msgTemplParam["uin"]?.findMember() ?: group.botAsMember now.info.honors += GroupHonorType.EMOTION collect(MemberHonorChangeEvent.Achieve(now, GroupHonorType.EMOTION)) } // 善财福禄寿 10111L -> { val now = grayTip.msgTemplParam["uin"]?.findMember() ?: group.botAsMember val previous = grayTip.msgTemplParam["uin_last"]?.findMember() if (previous == null) { collect(MemberHonorChangeEvent.Achieve(now, GroupHonorType.RED_PACKET)) } else { // 善财福禄寿 也是唯一的, 也许要加 新事件 collect(MemberHonorChangeEvent.Lose(previous, GroupHonorType.RED_PACKET)) collect(MemberHonorChangeEvent.Achieve(now, GroupHonorType.RED_PACKET)) } } // else -> { markNotConsumed() logger.debug { "Unknown Transformers528 0x14 template\ntemplId=${grayTip?.templId}\nPermList=${grayTip?.msgTemplParam?.structureToString()}" } } } } } internal operator fun List<TroopTips0x857.TemplParam>.get(name: String) = this.findLast { it.name == name }?.value @JvmName("get2") internal operator fun List<Submsgtype0x122.Submsgtype0x122.TemplParam>.get(name: String) = this.findLast { it.name == name }?.value ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/group/GroupOrMemberListNoticeProcessor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.group import io.ktor.utils.io.core.* import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.MemberPermission.* import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.addNewNormalMember import net.mamoe.mirai.internal.contact.info.MemberInfoImpl import net.mamoe.mirai.internal.getGroupByCodeOrUin import net.mamoe.mirai.internal.getGroupByUin import net.mamoe.mirai.internal.getGroupByUinOrCode import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.components.ContactUpdater import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.notice.decoders.DecodedNotifyMsgBody import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44 import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.parseToMessageDataList import net.mamoe.mirai.internal.utils.toMemberInfo import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.context import net.mamoe.mirai.utils.read import net.mamoe.mirai.utils.structureToString /** * Member/Bot invited/active join // force/active leave * Member/Bot permission change * * @see BotJoinGroupEvent * @see MemberJoinEvent * * @see BotLeaveEvent * @see MemberLeaveEvent * * @see MemberPermissionChangeEvent * @see BotGroupPermissionChangeEvent * * @see BotInvitedJoinGroupRequestEvent * @see MemberJoinRequestEvent */ internal class GroupOrMemberListNoticeProcessor( private val logger: MiraiLogger, ) : MixedNoticeProcessor() { override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) { if (data.uSubMsgType != 0x44L) return markAsConsumed() val msg = data.vProtobuf.loadAs(Submsgtype0x44.Submsgtype0x44.MsgBody.serializer()) if (msg.msgGroupMsgSync == null) return when (msg.msgGroupMsgSync.msgType) { 1, 2 -> { bot.components[ContactUpdater].groupListModifyLock.withLock { bot.addNewGroupByCode(msg.msgGroupMsgSync.grpCode)?.let { collect(BotJoinGroupEvent.Active(it)) } } } } } /** * @see MemberJoinEvent.Invite * @see MemberLeaveEvent.Quit */ override suspend fun NoticePipelineContext.processImpl(data: DecodedNotifyMsgBody) = data.context { val proto = data.buf if (proto.optEnumType != 1) return val tipsInfo = proto.optMsgGraytips ?: return val message = tipsInfo.optBytesContent.decodeToString() // 机器人信息 markAsConsumed() when (tipsInfo.robotGroupOpt) { // 添加 1 -> { val dataList = message.parseToMessageDataList() val invitor = dataList.first().let { messageData -> group[messageData.data.toLong()] ?: return } val member = dataList.last().let { messageData -> group.addNewNormalMember(messageData.toMemberInfo()) ?: return } collect(MemberJoinEvent.Invite(member, invitor)) } // 移除 2 -> { message.parseToMessageDataList().first().let { val member = group.getOrFail(it.data.toLong()) group.members.delegate.remove(member) collect(MemberLeaveEvent.Quit(member)) } } else -> markNotConsumed() } } /** * @see MemberJoinEvent.Invite * @see BotJoinGroupEvent.Invite * @see MemberJoinEvent.Active * @see BotJoinGroupEvent.Active */ override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context { bot.components[ContactUpdater].groupListModifyLock.withLock { when (data.msgHead.msgType) { 33 -> processGroupJoin33(data) 34 -> Unit // 34 与 33 重复, 忽略 34 38 -> processGroupJoin38(data) 85 -> processGroupJoin85(data) else -> return } markAsConsumed() } } // 33 private suspend fun NoticePipelineContext.processGroupJoin33(data: MsgComm.Msg) = data.context { msgBody.msgContent.read { val groupCode = readInt().toUInt().toLong() if (remaining < 5) return discardExact(1) val joinedMemberUin = readInt().toUInt().toLong() if (joinedMemberUin == bot.id && bot.getGroupByCodeOrUin(groupCode) != null) return // duplicate val group = bot.getGroupByCodeOrUin(groupCode) ?: bot.addNewGroupByCode(groupCode) ?: return val joinType = readByte().toInt() val invitorUin = readInt().toUInt().toLong() when (joinType) { // 邀请加入 -125, 3 -> { val invitor = group[invitorUin] ?: return collected += if (joinedMemberUin == bot.id) { BotJoinGroupEvent.Invite(invitor) } else { MemberJoinEvent.Invite(group.addNewNormalMember(getNewMemberInfo()) ?: return, invitor) } } // 通过群员分享的二维码/直接加入 -126, 2 -> { collected += if (joinedMemberUin == bot.id) { BotJoinGroupEvent.Active(group) } else { MemberJoinEvent.Active(group.addNewNormalMember(getNewMemberInfo()) ?: return) } } // 忽略 else -> { } } } // 邀请入群 // package: 27 0B 60 E7 01 CA CC 69 8B 83 44 71 47 90 06 B9 DC C0 ED D4 B1 00 30 33 44 30 42 38 46 30 39 37 32 38 35 43 34 31 38 30 33 36 41 34 36 31 36 31 35 32 37 38 46 46 43 30 41 38 30 36 30 36 45 38 31 43 39 41 34 38 37 // package: groupUin + 01 CA CC 69 8B 83 + invitorUin + length(06) + string + magicKey // 主动入群, 直接加入: msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 42 39 41 30 33 45 38 34 30 39 34 42 46 30 45 32 45 38 42 31 43 43 41 34 32 42 38 42 44 42 35 34 44 42 31 44 32 32 30 46 30 38 39 46 46 35 41 38 // 主动直接加入 27 0B 60 E7 01 76 E4 B8 DD 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 33 30 45 38 42 31 33 46 41 41 31 33 46 38 31 35 34 41 38 33 32 37 31 43 34 34 38 35 33 35 46 45 31 38 32 43 39 42 43 46 46 32 44 39 39 46 41 37 // 有人被邀请(经过同意后)加入 27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 34 30 34 38 32 33 38 35 37 41 37 38 46 33 45 37 35 38 42 39 38 46 43 45 44 43 32 41 30 31 36 36 30 34 31 36 39 35 39 30 38 39 30 39 45 31 34 34 // 搜索到群, 直接加入 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35 } // 38 private suspend fun NoticePipelineContext.processGroupJoin38(data: MsgComm.Msg) = data.context { if (bot.getGroupByUin(msgHead.fromUin) != null) return bot.addNewGroupByUin(msgHead.fromUin)?.let { collect(BotJoinGroupEvent.Active(it)) } } // 85 private suspend fun NoticePipelineContext.processGroupJoin85(data: MsgComm.Msg) = data.context { // msgHead.authUin: 处理人 if (msgHead.toUin != bot.id) return processGroupJoin38(data) } /////////////////////////////////////////////////////////////////////////// // Structmsg.StructMsg /////////////////////////////////////////////////////////////////////////// override suspend fun NoticePipelineContext.processImpl(data: Structmsg.StructMsg) = data.msg.context { if (this == null) return markAsConsumed() when (subType) { 0 -> { if (groupMsgType == 8) { // #1388: 使用手机TIM邀请入群,我为管理员,成功邀请 bot 入群 // 能正常解析 BotInvitedJoinGroupRequestEvent 和 BotJoinGroupEvent.Active, 因此忽略该通知 return } else { throw contextualBugReportException( "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType", data.structureToString(), null, "并描述此时机器人是否被邀请加入群等其他", ) } } // 处理被邀请入群 或 处理成员入群申请 1 -> when (groupMsgType) { 1 -> { // 成员申请入群 collected += MemberJoinRequestEvent( bot, data.msgSeq, msgAdditional, data.reqUin, groupCode, groupName, reqUinNick ) } 2 -> { // Bot 被邀请入群 collected += BotInvitedJoinGroupRequestEvent( bot, data.msgSeq, actionUin, groupCode, groupName, actionUinNick ) } 22 -> { // 成员邀请入群 collected += MemberJoinRequestEvent( bot, data.msgSeq, msgAdditional, data.reqUin, groupCode, groupName, reqUinNick, actionUin ) } else -> throw contextualBugReportException( "parse SystemMsgNewGroup, subType=1", this.structureToString(), additional = "并尽量描述此时机器人是否正被邀请加入群, 或者是有有新群员加入此群" ) } 2 -> { // 被邀请入群, 自动同意, 不需处理 // val group = bot.getNewGroup(groupCode) ?: return null // val invitor = group[actionUin] // // BotJoinGroupEvent.Invite(invitor) } 3 -> { // 已被请他管理员处理 } 5 -> { val group = bot.getGroup(groupCode) ?: return when (groupMsgType) { 3 -> { // https://github.com/mamoe/mirai/issues/651 // msgDescribe=将你设置为管理员 // msgTitle=管理员设置 } 13 -> { // 成员主动退出, 机器人是管理员, 接到通知 // 但无法获取是哪个成员. } 7 -> { // 机器人被踢 val operator = group[actionUin] ?: return collected += BotLeaveEvent.Kick(operator) } 6 -> { // 其他管理员踢出了一个群成员, 测试时能正常解析但没有收到 groupMsgType=6 的消息, 但有 issue 收到这些消息 // #1429, #1171, #1263 // > 这是历史的群系统消息,实际上可以直接进行忽略,其实只是因为缺失了忽略的处理而已 // https://github.com/mamoe/mirai/issues/1171#issuecomment-907075637 } 16 -> { // #1467 // 历史消息同步 } else -> { throw contextualBugReportException( "解析 NewContact.SystemMsgNewGroup, subType=5, groupMsgType=$groupMsgType", this.structureToString(), null, "并描述此时机器人是否被踢出群等", ) } } } else -> markNotConsumed() } } /////////////////////////////////////////////////////////////////////////// // OnlinePushTrans.PbMsgInfo /////////////////////////////////////////////////////////////////////////// override suspend fun NoticePipelineContext.processImpl(data: OnlinePushTrans.PbMsgInfo) { markAsConsumed() when (data.msgType) { 44 -> data.msgData.read { // 3D C4 33 DD 01 FF CD 76 F4 03 C3 7E 2E 34 // 群转让 // start with 3D C4 33 DD 01 FF // 3D C4 33 DD 01 FF C3 7E 2E 34 CD 76 F4 03 // 权限变更 // 3D C4 33 DD 01 00/01 ..... // 3D C4 33 DD 01 01 C3 7E 2E 34 01 discardExact(5) val kind = readByte().toUByte().toInt() if (kind == 0xFF) { val from = readInt().toUInt().toLong() val to = readInt().toUInt().toLong() handleGroupOwnershipTransfer(data, from, to) } else { val var5 = if (kind == 0 || kind == 1) 0 else readInt().toUInt().toInt() val target = readInt().toUInt().toLong() if (var5 == 0) { val newPermission = if (remaining == 1L) readByte() else return handlePermissionChange(data, target, newPermission.toInt()) } } } 34 -> { /* quit 27 0B 60 E7 01 2F 55 7C B8 82 00 30 42 33 32 46 30 38 33 32 39 32 35 30 31 39 33 45 46 32 45 30 36 35 41 35 41 33 42 37 35 43 41 34 46 37 42 38 42 38 42 44 43 35 35 34 35 44 38 30 */ /* kick 27 0B 60 E7 01 A8 32 51 A1 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 39 32 46 45 30 36 31 41 33 37 36 43 44 35 37 35 37 39 45 37 32 34 44 37 37 30 36 46 39 39 43 35 35 33 33 31 34 44 32 44 46 35 45 42 43 31 31 36 */ data.msgData.read { readInt().toUInt().toLong() // groupCode readByte().toInt() // follow type val target = readInt().toUInt().toLong() val kind = readByte().toUByte().toInt() val operator = readInt().toUInt().toLong() val groupUin = data.fromUin handleLeave(target, kind, operator, groupUin) } } else -> markNotConsumed() } } private fun NoticePipelineContext.handleLeave( target: Long, kind: Int, operator: Long, groupUin: Long, ) { when (kind) { // 01 为bot为群主时解散群 1, 0x81 -> bot.getGroupByUinOrCode(groupUin)?.let { group -> collect(BotLeaveEvent.Disband(group)) bot.groups.delegate.remove(group) group.cancel(CancellationException("Being Disband")) } 2, 0x82 -> bot.getGroupByUinOrCode(groupUin)?.let { group -> if (target == bot.id) { collect(BotLeaveEvent.Active(group)) bot.groups.delegate.remove(group) group.cancel(CancellationException("Left actively")) } else { val member = group[target] ?: return collect(MemberLeaveEvent.Quit(member)) group.members.delegate.remove(member) member.cancel(CancellationException("Left actively")) } } // 03 包括 bot 是群主, 管理员踢出群成员 3, 0x83 -> bot.getGroupByUinOrCode(groupUin)?.let { group -> if (target == bot.id) { val member = group.members[operator] ?: return collect(BotLeaveEvent.Kick(member)) bot.groups.delegate.remove(group) group.cancel(CancellationException("Being kicked")) } else { val member = group[target] ?: return collect(MemberLeaveEvent.Kick(member, group.members[operator])) group.members.delegate.remove(member) member.cancel(CancellationException("Being kicked")) } } } } /** * Group owner changes permission of a member, when bot is a member. * * @see BotGroupPermissionChangeEvent * @see MemberPermissionChangeEvent */ private fun NoticePipelineContext.handlePermissionChange( data: OnlinePushTrans.PbMsgInfo, target: Long, newPermissionByte: Int, ) { val group = bot.getGroupByUinOrCode(data.fromUin) ?: return val newPermission = if (newPermissionByte == 1) ADMINISTRATOR else MEMBER if (target == bot.id) { if (group.botPermission == newPermission) return collect(BotGroupPermissionChangeEvent(group, group.botPermission, newPermission)) group.botAsMember.permission = newPermission } else { val member = group[target] ?: return if (member.permission == newPermission) return collect(MemberPermissionChangeEvent(member, member.permission, newPermission)) member.permission = newPermission } } /** * Owner of the group [from] transfers ownership to another member [to], or retrieve ownership. */ private suspend fun NoticePipelineContext.handleGroupOwnershipTransfer( data: OnlinePushTrans.PbMsgInfo, from: Long, to: Long, ) { val group = bot.getGroupByUinOrCode(data.fromUin) if (from == bot.id) { // bot -> member group ?: return markAsConsumed() // Bot permission changed to MEMBER if (group.botPermission != MEMBER) { collect(BotGroupPermissionChangeEvent(group, group.botPermission, MEMBER)) group.botAsMember.permission = MEMBER } // member Retrieve or permission changed to OWNER var newOwner = group[to] if (newOwner == null) { val nick = Mirai.queryProfile(bot, to).nickname newOwner = group.addNewNormalMember(MemberInfoImpl(uin = to, nick = nick, permission = OWNER)) ?: return collect(MemberJoinEvent.Retrieve(newOwner)) } else if (newOwner.permission != OWNER) { collect(MemberPermissionChangeEvent(newOwner, newOwner.permission, OWNER)) newOwner.permission = OWNER } } else { // member -> member/bot // bot Retrieve or permission changed to OWNER if (group == null) { // TODO: 2021/8/25 test this collect(BotJoinGroupEvent.Retrieve(bot.addNewGroupByUin(data.fromUin) ?: return)) return } // member permission changed to MEMBER val member = group[from] if (member != null && member.permission != MEMBER) { collect(MemberPermissionChangeEvent(member, member.permission, MEMBER)) member.permission = MEMBER } else { // if member is null, he has already quit the group in another event. } if (to == bot.id) { // member -> bot if (group.botPermission != OWNER) { collect(BotGroupPermissionChangeEvent(group, group.botPermission, OWNER)) group.botAsMember.permission = OWNER } } else { // member -> member group[to]?.let { newOwner -> if (newOwner.permission != OWNER) { collect(MemberPermissionChangeEvent(newOwner, newOwner.permission, OWNER)) } newOwner.permission = OWNER } } } } // backup, copied from old code /* 34 -> { // 主动入群 // 回答了问题, 还需要管理员审核 // msgContent=27 0B 60 E7 01 76 E4 B8 DD 82 00 30 45 41 31 30 35 35 42 44 39 39 42 35 37 46 44 31 41 31 46 36 42 43 42 43 33 43 42 39 34 34 38 31 33 34 42 36 31 46 38 45 43 39 38 38 43 39 37 33 // msgContent=27 0B 60 E7 01 76 E4 B8 DD 02 00 30 44 44 41 43 44 33 35 43 31 39 34 30 46 42 39 39 34 46 43 32 34 43 39 32 33 39 31 45 42 35 32 33 46 36 30 37 35 42 41 38 42 30 30 37 42 36 42 41 // 回答正确问题, 直接加入 // 27 0B 60 E7 01 76 E4 B8 DD 82 00 30 43 37 37 39 41 38 32 44 38 33 30 35 37 38 31 33 37 45 42 39 35 43 42 45 36 45 43 38 36 34 38 44 34 35 44 42 33 44 45 37 34 41 36 30 33 37 46 45 // 提交验证消息加入, 需要审核 // 被踢了?? // msgContent=27 0B 60 E7 01 76 E4 B8 DD 83 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 46 46 32 33 36 39 35 33 31 37 42 44 46 37 43 36 39 34 37 41 45 38 39 43 45 43 42 46 33 41 37 35 39 34 39 45 36 37 33 37 31 41 39 44 33 33 45 33 /* // 搜索后直接加入群 soutv 17:43:32 : 33类型的content = 27 0B 60 E7 01 07 6E 47 BA 82 3E 03 3F A2 06 B4 B4 BD A8 D5 DF 00 30 32 30 39 39 42 39 41 46 32 39 41 35 42 33 46 34 32 30 44 36 44 36 39 35 44 38 45 34 35 30 46 30 45 30 38 45 31 41 39 42 46 46 45 32 30 32 34 35 soutv 17:43:32 : 主动入群content = 2A 3D F5 69 01 35 D7 10 EA 83 4C EF 4F DD 06 B9 DC C0 ED D4 B1 00 30 37 41 39 31 39 34 31 41 30 37 46 38 32 31 39 39 43 34 35 46 39 30 36 31 43 37 39 37 33 39 35 43 34 44 36 31 33 43 31 35 42 37 32 45 46 43 43 36 */ val group = bot.getGroupByUinOrNull(msgHead.fromUin) group ?: return msgBody.msgContent.soutv("主动入群content") if (msgBody.msgContent.read { discardExact(4) // group code discardExact(1) // 1 discardExact(4) // requester uin readByte().toInt().and(0xff) // 0x02: 回答正确问题直接加入 // 0x82: 回答了问题, 或者有验证消息, 需要管理员审核 // 0x83: 回答正确问题直接加入 } != 0x82) { if (group.members.contains(msgHead.authUin)) { return } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return MemberJoinEvent.Active(group.newMember(getNewMemberInfo()) .also { group.members.delegate.addLast(it) }) } else return } */ } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/group/GroupRecallProcessor.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.group import net.mamoe.mirai.event.events.MessageRecallEvent import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.notice.decoders.MsgType0x2DC import net.mamoe.mirai.internal.network.protocol.data.proto.TroopTips0x857 import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.utils.mapToIntArray internal class GroupRecallProcessor : MixedNoticeProcessor() { override suspend fun NoticePipelineContext.processImpl(data: MsgType0x2DC) { val (kind, group, buf) = data if (kind != 0x11) return val proto = buf.loadAs(TroopTips0x857.NotifyMsgBody.serializer(), 1) val recallReminder = proto.optMsgRecall ?: return val operator = group[recallReminder.uin] ?: return markAsConsumed() for (firstPkg in recallReminder.recalledMsgList) { if (firstPkg.authorUin == bot.id && operator.id == bot.id) continue // already broadcast val author = group[firstPkg.authorUin] ?: continue collected += MessageRecallEvent.GroupRecall( bot = bot, authorId = firstPkg.authorUin, messageIds = recallReminder.recalledMsgList.mapToIntArray { it.seq }, messageInternalIds = recallReminder.recalledMsgList.mapToIntArray { it.msgRandom }, messageTime = firstPkg.time, operator = operator, group = group, author = author, ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/priv/FriendGroupNoticeProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.priv import net.mamoe.mirai.internal.contact.friendgroup.FriendGroupImpl import net.mamoe.mirai.internal.contact.friendgroup.impl import net.mamoe.mirai.internal.contact.impl import net.mamoe.mirai.internal.contact.info.FriendGroupInfo import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27 import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.context import net.mamoe.mirai.utils.error import net.mamoe.mirai.utils.warning internal class FriendGroupNoticeProcessor( private val logger: MiraiLogger, ) : MixedNoticeProcessor(), NewContactSupport { override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) = data.context { when (data.uSubMsgType) { 0x27L -> { val body = vProtobuf.loadAs(Submsgtype0x27.SubMsgType0x27.SubMsgType0x27MsgBody.serializer()) for (msgModInfo in body.msgModInfos) { markAsConsumed(msgModInfo) when { msgModInfo.msgModFriendGroup != null -> handleFriendGroupChanged( msgModInfo.msgModFriendGroup, logger ) msgModInfo.msgModGroupName != null -> handleFriendGroupNameChanged( msgModInfo.msgModGroupName, logger ) msgModInfo.msgDelGroup != null -> handleDelGroup(msgModInfo.msgDelGroup, logger) msgModInfo.msgAddGroup != null -> handleAddGroup(msgModInfo.msgAddGroup) else -> markNotConsumed(msgModInfo) } } } } } private fun NoticePipelineContext.handleAddGroup( addGroup: Submsgtype0x27.SubMsgType0x27.AddGroup ) { bot.friendGroups.friendGroups.add( FriendGroupImpl( bot, FriendGroupInfo(addGroup.groupid, addGroup.groupname.decodeToString()) ) ) } private fun NoticePipelineContext.handleDelGroup( delGroup: Submsgtype0x27.SubMsgType0x27.DelGroup, logger: MiraiLogger ) { bot.friendGroups[delGroup.groupid]?.let { friendGroup -> friendGroup.friends.forEach { it.impl().info.friendGroupId = 0 } bot.friendGroups.friendGroups.remove(friendGroup) } ?: let { logger.warning { "Detected friendGroup(id=${delGroup.groupid}) was removed but it isn't available in bot's friendGroups list" } return } } private fun NoticePipelineContext.handleFriendGroupNameChanged( modFriendGroup: Submsgtype0x27.SubMsgType0x27.ModGroupName, logger: MiraiLogger ) { bot.friendGroups[modFriendGroup.groupid]?.let { it.impl().name = modFriendGroup.groupname.decodeToString() } ?: let { logger.warning { "Detected friendGroup(id=${modFriendGroup.groupid}) was renamed but it cannot be found in bot's friendGroups list" } return } } private fun NoticePipelineContext.handleFriendGroupChanged( modFriendGroup: Submsgtype0x27.SubMsgType0x27.ModFriendGroup, logger: MiraiLogger ) { modFriendGroup.msgFrdGroup.forEach { body -> val friend = bot.getFriend(body.fuin) ?: let { logger.error { "Detected friend(id=${body.fuin}) was moved to friendGroup(id=${body.uint32NewGroupId}) but friend not found in bot's friends list" } return } if (friend.impl().info.friendGroupId == body.uint32NewGroupId.first()) return@forEach friend.info.friendGroupId = body.uint32NewGroupId.first() } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/priv/FriendNoticeProcessor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.priv import io.ktor.utils.io.core.* import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.impl import net.mamoe.mirai.internal.contact.info.FriendInfoImpl import net.mamoe.mirai.internal.contact.info.StrangerInfoImpl import net.mamoe.mirai.internal.contact.toMiraiFriendInfo import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.msgInfo import net.mamoe.mirai.internal.network.notice.NewContactSupport import net.mamoe.mirai.internal.network.notice.group.get import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.proto.FrdSysMsg import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x115.SubMsgType0x115 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x122 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27.* import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x44.Submsgtype0x44 import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0xb3.SubMsgType0xb3 import net.mamoe.mirai.internal.network.protocol.packet.chat.NewContact import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList.GetFriendGroupList import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.utils.* /** * All [FriendEvent] except [FriendMessageEvent] * * @see FriendInputStatusChangedEvent * @see FriendAddEvent * @see StrangerRelationChangeEvent.Friended */ internal class FriendNoticeProcessor( private val logger: MiraiLogger, ) : MixedNoticeProcessor(), NewContactSupport { override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context { if (msgHead.msgType != 191) return var fromGroup = 0L var pbNick = "" msgBody.msgContent.read { readByte().toUByte() // version discardExact(readByte().toUByte().toInt()) //skip readShort().toUShort() //source id readShort().toUShort() //SourceSubID discardExact(readShort().toUShort().toLong()) //skip size if (readShort().toUShort().toInt() != 0) { //hasExtraInfo discardExact(readShort().toUShort().toInt()) //mail address info, skip } discardExact(4 + readShort().toUShort().toInt()) //skip for (i in 1..readByte().toUByte().toInt()) { //pb size val type = readShort().toUShort().toInt() val pbArray = ByteArray(readShort().toUShort().toInt() and 0xFF) readAvailable(pbArray) when (type) { 1000 -> pbArray.loadAs(FrdSysMsg.GroupInfo.serializer()).let { fromGroup = it.groupUin } 1002 -> pbArray.loadAs(FrdSysMsg.FriendMiscInfo.serializer()) .let { pbNick = it.fromuinNick } else -> { } //ignore } } } msgHead.context { // 对方 qq val id = longArrayOf(fromUin, authUin).firstOrNull { it != 0L && it != bot.id } if (id == null) { logger.error { "Could not determine uin for new stranger" } return } if (bot.getStranger(id) != null) return val nick = fromNick.ifEmpty { authNick }.ifEmpty { pbNick } collect(StrangerAddEvent(bot.addNewStranger(StrangerInfoImpl(id, nick, fromGroup)) ?: return)) //同时需要请求好友验证消息(有新请求需要同意) bot.network.sendWithoutExpect(NewContact.SystemMsgNewFriend(bot.client)) } } override suspend fun NoticePipelineContext.processImpl(data: MsgType0x210) = data.context { markAsConsumed() when (data.uSubMsgType) { 0xB3L -> { // 08 01 12 52 08 A2 FF 8C F0 03 10 00 1D 15 3D 90 5E 22 2E E6 88 91 E4 BB AC E5 B7 B2 E7 BB 8F E6 98 AF E5 A5 BD E5 8F 8B E5 95 A6 EF BC 8C E4 B8 80 E8 B5 B7 E6 9D A5 E8 81 8A E5 A4 A9 E5 90 A7 21 2A 09 48 69 6D 31 38 38 6D 6F 65 30 07 38 03 48 DD F1 92 B7 07 val body: SubMsgType0xb3.MsgBody = vProtobuf.loadAs(SubMsgType0xb3.MsgBody.serializer()) handleFriendAddedB(data, body) } 0x44L -> { val body = vProtobuf.loadAs(Submsgtype0x44.MsgBody.serializer()) handleFriendAddedA(body) } 0x27L -> { val body = vProtobuf.loadAs(SubMsgType0x27MsgBody.serializer()) for (msgModInfo in body.msgModInfos) { when { msgModInfo.msgModFriendRemark != null -> handleRemarkChanged(msgModInfo.msgModFriendRemark) msgModInfo.msgDelFriend != null -> handleFriendDeleted(msgModInfo.msgDelFriend) msgModInfo.msgModCustomFace != null -> handleAvatarChanged(msgModInfo.msgModCustomFace) msgModInfo.msgModProfile != null -> handleProfileChanged(msgModInfo.msgModProfile) } } } 0x115L -> { val body = vProtobuf.loadAs(SubMsgType0x115.MsgBody.serializer()) handleInputStatusChanged(body) } 0x122L -> { val body = vProtobuf.loadAs(Submsgtype0x122.Submsgtype0x122.MsgBody.serializer()) when (body.templId) { //戳一戳 1132L, 1133L, 1134L, 1135L, 1136L, 10043L -> handlePrivateNudge(body) } } 0x8AL -> { val body = vProtobuf.loadAs(Sub8A.serializer()) processFriendRecall(body) } else -> markNotConsumed() } } @Serializable private class Wording( @ProtoNumber(1) val itemID: Int = 0, @ProtoNumber(2) val itemName: String = "", ) : ProtoBuf @Serializable private class Sub8AMsgInfo( @ProtoNumber(1) val fromUin: Long, @ProtoNumber(2) val botUin: Long, @ProtoNumber(3) val srcId: Int, @ProtoNumber(4) val srcInternalId: Long, @ProtoNumber(5) val time: Long, // see #2784 // @ProtoNumber(6) val random: Int, // @ProtoNumber(7) val pkgNum: Int, // 1 // @ProtoNumber(8) val pkgIndex: Int, // 0 // @ProtoNumber(9) val devSeq: Int, // 0 // @ProtoNumber(12) val flag: Int, // 1 // @ProtoNumber(13) val wording: Wording, ) : ProtoBuf @Serializable private class Sub8A( @ProtoNumber(1) val msgInfo: List<Sub8AMsgInfo>, @ProtoNumber(2) val appId: Int, // 1 @ProtoNumber(3) val instId: Int, // 1 @ProtoNumber(4) val longMessageFlag: Int, // 0 @ProtoNumber(5) val reserved: ByteArray? = null, // struct{ boolean(1), boolean(2) } ) : ProtoBuf private fun NoticePipelineContext.processFriendRecall(body: Sub8A) { for (info in body.msgInfo) { if (info.botUin != bot.id) continue collected += MessageRecallEvent.FriendRecall( bot = bot, messageIds = intArrayOf(info.srcId), messageInternalIds = intArrayOf(info.srcInternalId.toInt()), messageTime = info.time.toInt(), operatorId = info.fromUin, operator = bot.getFriend(info.fromUin) ?: continue, ) } } private fun NoticePipelineContext.handleInputStatusChanged(body: SubMsgType0x115.MsgBody) { val friend = bot.getFriend(body.fromUin) ?: return val item = body.msgNotifyItem ?: return collect(FriendInputStatusChangedEvent(friend, item.eventType == 1)) } private fun NoticePipelineContext.handleProfileChanged(body: ModProfile) { var containsUnknown = false for (profileInfo in body.msgProfileInfos) { when (profileInfo.field) { 20002 -> { // 昵称修改 val to = profileInfo.value if (body.uin == bot.id) { val from = bot.nick if (from == to) continue collect(BotNickChangedEvent(bot, from, to)) bot.nick = to } else { val friend = bot.getFriend(body.uin)?.impl() ?: continue val from = friend.nick if (from == to) continue collect(FriendNickChangedEvent(friend, from, to)) friend.info.nick = to } } else -> containsUnknown = true } } if (body.msgProfileInfos.isEmpty() || containsUnknown) { logger.debug { "Transformers528 0x27L: ProfileChanged new data: ${body.structureToString()}" } } } private fun NoticePipelineContext.handleRemarkChanged(body: ModFriendRemark) { for (new in body.msgFrdRmk) { val friend = bot.getFriend(new.fuin)?.impl() ?: continue collect(FriendRemarkChangeEvent(friend, friend.remark, new.rmkName)) friend.info.remark = new.rmkName } } private fun NoticePipelineContext.handleAvatarChanged(body: ModCustomFace) { if (body.uin == bot.id) { collect(BotAvatarChangedEvent(bot)) } else { collect(FriendAvatarChangedEvent(bot.getFriend(body.uin) ?: return)) } } private fun NoticePipelineContext.handleFriendDeleted(body: DelFriend) { for (id in body.uint64Uins) { collect(FriendDeleteEvent(bot.removeFriend(id) ?: continue)) } } private suspend fun NoticePipelineContext.handleFriendAddedA( body: Submsgtype0x44.MsgBody, ) = body.msgFriendMsgSync.context { if (this == null) return when (processtype) { 3, 9, 10 -> { if (bot.getFriend(fuin) != null) return val response = bot.network.sendAndExpect(GetFriendGroupList.forSingleFriend(bot.client, fuin)) val info = response.friendList.firstOrNull() ?: return collect( FriendAddEvent(bot.addNewFriendAndRemoveStranger(info.toMiraiFriendInfo()) ?: return), ) } } } private fun NoticePipelineContext.handleFriendAddedB(data: MsgType0x210, body: SubMsgType0xb3.MsgBody) = data.context { val info = FriendInfoImpl( uin = body.msgAddFrdNotify.fuin, nick = body.msgAddFrdNotify.fuinNick, remark = "", friendGroupId = 0, ) val removed = bot.removeStranger(info.uin) val added = bot.addNewFriendAndRemoveStranger(info) ?: return collect(FriendAddEvent(added)) if (removed != null) collect(StrangerRelationChangeEvent.Friended(removed, added)) } private fun NoticePipelineContext.handlePrivateNudge(body: Submsgtype0x122.Submsgtype0x122.MsgBody) { val action = body.msgTemplParam["action_str"].orEmpty() val from = body.msgTemplParam["uin_str1"]?.findFriendOrStranger() ?: bot.asFriend val target = body.msgTemplParam["uin_str2"]?.findFriendOrStranger() ?: bot.asFriend val suffix = body.msgTemplParam["suffix_str"].orEmpty() val subject: User = bot.getFriend(msgInfo.lFromUin) ?: bot.getStranger(msgInfo.lFromUin) ?: return collected += NudgeEvent( from = if (from.id == bot.id) bot else from, target = if (target.id == bot.id) bot else target, action = action, suffix = suffix, subject = subject, ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/priv/OtherClientNoticeProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.priv import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.ClientKind import net.mamoe.mirai.contact.OtherClientInfo import net.mamoe.mirai.contact.Platform import net.mamoe.mirai.event.events.OtherClientMessageEvent import net.mamoe.mirai.event.events.OtherClientOfflineEvent import net.mamoe.mirai.event.events.OtherClientOnlineEvent import net.mamoe.mirai.internal.contact.appId import net.mamoe.mirai.internal.contact.createOtherClient import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromFriendImpl import net.mamoe.mirai.internal.network.components.ContactUpdater import net.mamoe.mirai.internal.network.components.MixedNoticeProcessor import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.SubMsgType0x7 import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.buildMessageChain import net.mamoe.mirai.utils.context import net.mamoe.mirai.utils.structureToString /** * @see OtherClientOnlineEvent * @see OtherClientOfflineEvent * * @see OtherClientMessageEvent */ internal class OtherClientNoticeProcessor : MixedNoticeProcessor() { /** * @see OtherClientOnlineEvent * @see OtherClientOfflineEvent */ override suspend fun NoticePipelineContext.processImpl(data: RequestPushStatus) { markAsConsumed() bot.components[ContactUpdater].otherClientsLock.withLock { val instanceInfo = data.vecInstanceList?.firstOrNull() val appId = instanceInfo?.iAppId ?: 1 when (data.status.toInt()) { 1 -> { // online if (bot.otherClients.any { appId == it.appId }) return suspend fun tryFindInQuery(): OtherClientInfo? { return Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } ?: kotlin.run { delay(2000) // sometimes server sync slow Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } } } val info = tryFindInQuery() ?: kotlin.run { bot.network.logger.warning( contextualBugReportException( "SvcRequestPushStatus (OtherClient online)", "packet: \n" + data.structureToString() + "\n\nquery: \n" + Mirai.getOnlineOtherClientsList(bot).structureToString(), additional = "Failed to find corresponding instanceInfo.", ), ) OtherClientInfo(appId, Platform.WINDOWS, "", "电脑") } val client = bot.createOtherClient(info) bot.otherClients.delegate.add(client) collected += OtherClientOnlineEvent( client, ClientKind[data.nClientType?.toInt() ?: 0], ) } 2 -> { // off val client = bot.otherClients.find { it.appId == appId } ?: return client.cancel(CancellationException("Offline")) bot.otherClients.delegate.remove(client) collected += OtherClientOfflineEvent(client) } else -> markNotConsumed() } } } /** * @see OtherClientMessageEvent */ override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context { if (msgHead.msgType != 529) return // top_package/awbk.java:3765 markAsConsumed() // todo check if (msgHead.c2cCmd != 7) { // 各种垃圾 // 08 04 12 1E 08 E9 07 10 B7 F7 8B 80 02 18 E9 07 20 00 28 DD F1 92 B7 07 30 DD F1 92 B7 07 48 02 50 03 32 1E 08 88 80 F8 92 CD 84 80 80 10 10 01 18 00 20 01 2A 0C 0A 0A 08 01 12 06 E5 95 8A E5 95 8A return } val body = msgBody.msgContent.loadAs(SubMsgType0x7.MsgBody.serializer()) val textMsg = body.msgSubcmd0x4Generic?.buf?.loadAs(SubMsgType0x7.MsgBody.QQDataTextMsg.serializer()) ?: return with(body.msgHeader ?: return) { if (dstUin != bot.id) return val client = bot.otherClients.find { it.appId == srcInstId } ?: return // don't compare with dstAppId. diff. val chain = buildMessageChain { +OnlineMessageSourceFromFriendImpl(bot, listOf(data)) for (msgItem in textMsg.msgItems) { when (msgItem.type) { 1 -> +PlainText(msgItem.text) else -> { } } } } collect(OtherClientMessageEvent(client, chain, msgHead.msgTime)) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/notice/priv/PrivateMessageProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.notice.priv import net.mamoe.mirai.Bot import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.* import net.mamoe.mirai.internal.getGroupByUinOrCode import net.mamoe.mirai.internal.message.RefineContextKey import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.fromSync import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.utils.assertUnreachable import net.mamoe.mirai.utils.context /** * Handles [UserMessageEvent] and their sync events. Requires [KEY_FROM_SYNC]. * * For [GroupMessageEvent], see [GroupMessageProcessor]. * * @see StrangerMessageEvent * @see StrangerMessageSyncEvent * * @see FriendMessageEvent * @see FriendMessageSyncEvent * * @see GroupTempMessageEvent * @see GroupTempMessageSyncEvent */ internal class PrivateMessageProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type()) { internal data class SendPrivateMessageReceipt( val bot: Bot?, val messageRandom: Int, val sequenceId: Int, val fromAppId: Int, ) : Packet, Event, Packet.NoLog, AbstractEvent() { override fun toString(): String { return "OnlinePush.PbC2CMsgSync.SendPrivateMessageReceipt(messageRandom=$messageRandom, sequenceId=$sequenceId)" } companion object { val EMPTY = SendPrivateMessageReceipt(null, 0, 0, 0) } } override suspend fun NoticePipelineContext.processImpl(data: MsgComm.Msg) = data.context { markAsConsumed() val fromSync = attributes[KEY_FROM_SYNC, null] ?: return if (fromSync) { val msgFromAppid = msgHead.fromAppid // 3116 = music share // message sent by bot if (msgFromAppid == 3116) { handleSpecialMessageSendingResponse(data, msgFromAppid) return } } if (msgHead.fromUin == bot.id && fromSync) { // Bot send message to himself? or from other client? I am not the implementer. // This was `bot.client.sendFriendMessageSeq.updateIfSmallerThan(msgHead.msgSeq)`, // changed to `if (!bot.client.sendFriendMessageSeq.updateIfDifferentWith(msgHead.msgSeq)) return` // in 2021/12/20, 2.10.0-RC, 2.8.4, 2.9.0 // to fix 好友无法消息同步(FriendMessageSyncEvent) #1624 // Relevant tests: `MessageSyncTest` if (!bot.client.sendFriendMessageSeq.updateIfDifferentWith(msgHead.msgSeq)) return } if (!bot.components[SsoProcessor].firstLoginSucceed) return val senderUin = if (fromSync) msgHead.toUin else msgHead.fromUin when (msgHead.msgType) { 166, 167, // 单向好友 208, // friend ptt, maybe also support stranger -> { data.msgBody.richText.ptt?.let { ptt -> if (ptt.downPara.isEmpty()) { val rsp = bot.network.sendAndExpect( PttStore.C2CPttDown(bot.client, senderUin, ptt.fileUuid) ) if (rsp is PttStore.C2CPttDown.Response.Success) { ptt.downPara = rsp.downloadUrl.encodeToByteArray() } } } handlePrivateMessage( data, bot.getFriend(senderUin)?.impl() ?: bot.getStranger(senderUin)?.impl() ?: return ) } 141, // group temp -> { val tmpHead = msgHead.c2cTmpMsgHead ?: return val group = bot.getGroupByUinOrCode(tmpHead.groupUin) ?: return handlePrivateMessage(data, group[senderUin] ?: return) } else -> markNotConsumed() } } private suspend fun NoticePipelineContext.handlePrivateMessage( data: MsgComm.Msg, user: AbstractUser, ) = data.context { if (!user.messageSeq.updateIfDifferentWith(msgHead.msgSeq)) return if (contentHead?.autoReply == 1) return val msgs = user.fragmentedMessageMerger.tryMerge(this) if (msgs.isEmpty()) return val chain = msgs.toMessageChainOnline( bot, 0, user.correspondingMessageSourceKind, SimpleRefineContext( RefineContextKey.MessageSourceKind to MessageSourceKind.FRIEND, RefineContextKey.FromId to user.uin, RefineContextKey.GroupIdOrZero to 0L, ) ) val time = msgHead.msgTime collected += if (fromSync) { val client = bot.otherClients.find { it.appId == msgHead.fromInstid } ?: return // don't compare with dstAppId. diff. when (user) { is FriendImpl -> FriendMessageSyncEvent(client, user, chain, time) is StrangerImpl -> StrangerMessageSyncEvent(client, user, chain, time) is NormalMemberImpl -> GroupTempMessageSyncEvent(client, user, chain, time) is AnonymousMemberImpl -> assertUnreachable() } } else { when (user) { is FriendImpl -> FriendMessageEvent(user, chain, time) is StrangerImpl -> StrangerMessageEvent(user, chain, time) is NormalMemberImpl -> GroupTempMessageEvent(user, chain, time) is AnonymousMemberImpl -> assertUnreachable() } } } private fun NoticePipelineContext.handleSpecialMessageSendingResponse( data: MsgComm.Msg, fromAppId: Int, ) = data.context { val messageRandom = data.msgBody.richText.attr?.random ?: return collect( SendPrivateMessageReceipt( bot, messageRandom, data.msgHead.msgSeq, fromAppId ) ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/LoginType.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol import kotlin.jvm.JvmInline @JvmInline internal value class LoginType( val value: Int ) { companion object { /** * 短信验证登录 */ val SMS = LoginType(3) /** * 密码登录 */ val PASSWORD = LoginType(1) /** * 微信一键登录 */ val WE_CHAT = LoginType(4) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/SyncingCacheList.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol import net.mamoe.mirai.utils.LinkedList import kotlin.jvm.Synchronized internal class SyncingCacheList<E>(private val size: Int = 50) { private val packetIdList = LinkedList<E>() @Synchronized // faster than suspending Mutex fun addCache(element: E): Boolean { if (packetIdList.contains(element)) return false // duplicate packetIdList.add(element) if (packetIdList.size >= size) packetIdList.removeFirst() return true } @Synchronized fun removeFirst(condition: (E) -> Boolean): Boolean { val itr = packetIdList.listIterator() for (element in itr) { if (element.let(condition)) { itr.remove() return true } } return false } @Synchronized fun contains(condition: (E) -> Boolean): Boolean = packetIdList.any(condition) } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ChangeFriendNameReq.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class ChangeFriendNameReq( @JvmField @TarsId(0) val uFriendUin: Long = 0L, @JvmField @TarsId(1) val cstrName: String = "" ) : JceStruct @Serializable internal class ChangeFriendNameRes( @JvmField @TarsId(0) val result: Byte = 0 ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ConfigPush.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class BigDataChannel( @TarsId(0) @JvmField val vBigdataIplists: List<BigDataIpList>, @TarsId(1) @JvmField val sBigdataSigSession: ByteArray? = null, @TarsId(2) @JvmField val sBigdataKeySession: ByteArray? = null, @TarsId(3) @JvmField val uSigUin: Long? = null, @TarsId(4) @JvmField val iConnectFlag: Int? = 1, @TarsId(5) @JvmField val vBigdataPbBuf: ByteArray? = null, ) : JceStruct @Serializable internal class BigDataIpInfo( @TarsId(0) @JvmField val uType: Long, @TarsId(1) @JvmField val sIp: String = "", @TarsId(2) @JvmField val uPort: Long, ) : JceStruct @Serializable internal class BigDataIpList( @TarsId(0) @JvmField val uServiceType: Long, @TarsId(1) @JvmField val vIplist: List<BigDataIpInfo>, @TarsId(2) @JvmField val netSegConfs: List<NetSegConf>? = null, @TarsId(3) @JvmField val ufragmentSize: Long? = null, ) : JceStruct @Serializable internal class ClientLogConfig( @TarsId(1) @JvmField val type: Int, @TarsId(2) @JvmField val timeStart: TimeStamp? = null, @TarsId(3) @JvmField val timeFinish: TimeStamp? = null, @TarsId(4) @JvmField val loglevel: Byte? = null, @TarsId(5) @JvmField val cookie: Int? = null, @TarsId(6) @JvmField val lseq: Long? = null, ) : JceStruct @Serializable internal class DomainIpChannel( @TarsId(0) @JvmField val vDomainIplists: List<DomainIpList>, ) : JceStruct @Serializable internal class DomainIpInfo( @TarsId(1) @JvmField val uIp: Int, @TarsId(2) @JvmField val uPort: Int, ) : JceStruct @Serializable internal class DomainIpList( @TarsId(0) @JvmField val uDomainType: Int, @TarsId(1) @JvmField val vIplist: List<DomainIpInfo>, @TarsId(2) @JvmField val unknown: ByteArray? = null, @TarsId(4) @JvmField val int: Int? = null, // added ) : JceStruct @Serializable internal class _340( @TarsId(1) @JvmField val field1315: List<_339>, @TarsId(3) @JvmField val field1316: List<_339>, @TarsId(4) @JvmField val field1317: Int, @TarsId(5) @JvmField val field1318: Byte? = 0, @TarsId(6) @JvmField val field1319: Byte? = 0, @TarsId(7) @JvmField val field1320: Int? = 1, @TarsId(8) @JvmField val field1321: List<_339>? = null, @TarsId(9) @JvmField val field1322: List<_339>? = null, @TarsId(10) @JvmField val field1323: List<_339>? = null, @TarsId(11) @JvmField val field1324: List<_339>? = null, @TarsId(12) @JvmField val field1325: List<_339>? = null, @TarsId(13) @JvmField val field1326: List<_339>? = null, @TarsId(14) @JvmField val netType: Byte? = 0, @TarsId(15) @JvmField val heThreshold: Int? = 0, @TarsId(16) @JvmField val policyId: String? = "", ) : JceStruct @Serializable internal class _339( @TarsId(1) @JvmField val field1298: String = "", @TarsId(2) @JvmField val field1299: Int = 0, @TarsId(3) @JvmField val field1300: Byte = 0, @TarsId(4) @JvmField val field1301: Byte = 0, @TarsId(5) @JvmField val field1302: Byte? = 0, @TarsId(6) @JvmField val field1303: Int? = 8, @TarsId(7) @JvmField val field1304: Byte? = 0, @TarsId(8) @JvmField val field1305: String = "", @TarsId(9) @JvmField val field1306: String = "", ) : JceStruct /** * v8.5.5 */ @Serializable internal class FileStoragePushFSSvcList( @TarsId(0) @JvmField val vUpLoadList: List<FileStorageServerListInfo> = emptyList(), @TarsId(1) @JvmField val vPicDownLoadList: List<FileStorageServerListInfo> = emptyList(), @TarsId(2) @JvmField val vGPicDownLoadList: List<FileStorageServerListInfo> = emptyList(), @TarsId(3) @JvmField val vQzoneProxyServiceList: List<FileStorageServerListInfo> = emptyList(), @TarsId(4) @JvmField val vUrlEncodeServiceList: List<FileStorageServerListInfo> = emptyList(), @TarsId(5) @JvmField val bigDataChannel: BigDataChannel? = null, @TarsId(6) @JvmField val vVipEmotionList: List<FileStorageServerListInfo> = emptyList(), @TarsId(7) @JvmField val vC2CPicDownList: List<FileStorageServerListInfo> = emptyList(), @TarsId(8) @JvmField val fmtIPInfo: FmtIPInfo? = null, @TarsId(9) @JvmField val domainIpChannel: DomainIpChannel? = null, @TarsId(10) @JvmField val pttlist: ByteArray? = null, ) : JceStruct @Serializable internal class FileStorageServerListInfo( @TarsId(1) @JvmField val sIP: String = "", @TarsId(2) @JvmField val iPort: Int, ) : JceStruct @Serializable internal class FmtIPInfo( @TarsId(0) @JvmField val sGateIp: String = "", @TarsId(1) @JvmField val iGateIpOper: Long, ) : JceStruct @Serializable internal class NetSegConf( @TarsId(0) @JvmField val uint32NetType: Long? = null, @TarsId(1) @JvmField val uint32Segsize: Long? = null, @TarsId(2) @JvmField val uint32Segnum: Long? = null, @TarsId(3) @JvmField val uint32Curconnnum: Long? = null, ) : JceStruct @Suppress("ArrayInDataClass") @Serializable internal class PushReq( @TarsId(1) @JvmField val type: Int, @TarsId(2) @JvmField val jcebuf: ByteArray, @TarsId(3) @JvmField val seq: Long, ) : JceStruct, Packet @Serializable internal data class ServerListPush( @TarsId(1) val mobileSSOServerList: List<ServerInfo>, @TarsId(3) val wifiSSOServerList: List<ServerInfo>, @TarsId(4) val reconnectNeeded: Int = 0, //@JvmField @TarsId(5) val skipped:Byte? = 0, //@JvmField @TarsId(6) val skipped:Byte? = 0, //@JvmField @TarsId(7) val skipped:Int? = 1, @TarsId(8) val mobileHttpServerList: List<ServerInfo>, @TarsId(9) val wifiHttpServerList: List<ServerInfo>, @TarsId(10) val quicServerList: List<ServerInfo>, @TarsId(11) val ssoServerListIpv6: List<ServerInfo>, @TarsId(12) val httpServerListIpv6: List<ServerInfo>, @TarsId(13) val quicServerListIpv6: List<ServerInfo>, /** * wifi下&1==1则启用 * 移动数据(mobile)下&2==2则启用 */ @TarsId(14) val ipv6ConfigVal: Byte? = 0, //@JvmField @TarsId(15) val netTestDelay:Int? = 0, @TarsId(16) val configDesc: String? = "", ) : JceStruct { @Serializable data class ServerInfo( @TarsId(1) val host: String, @TarsId(2) val port: Int, //@JvmField @TarsId(3) val skipped: Byte = 0, //@JvmField @TarsId(4) val skipped: Byte = 0, /** * 2,3->http * 0,1->socket */ //@JvmField @TarsId(5) val protocolType: Byte? = 0, //@JvmField @TarsId(6) val skipped: Int? = 8, //@JvmField @TarsId(7) val skipped: Byte? = 0, @TarsId(8) val location: String = "", /** * cm->China mobile 中国移动 * uni->China unicom 中国联通 * others->其他 */ @TarsId(9) val ispName: String = "", ) : JceStruct { override fun toString(): String { return "$host:$port" } } } @Serializable internal class PushResp( @TarsId(1) @JvmField val type: Int, @TarsId(2) @JvmField val seq: Long, @TarsId(3) @JvmField val jcebuf: ByteArray? = null, ) : JceStruct @Serializable internal class SsoServerList( @TarsId(1) @JvmField val v2G3GList: List<SsoServerListInfo>, @TarsId(3) @JvmField val vWifiList: List<SsoServerListInfo>, @TarsId(4) @JvmField val iReconnect: Int, @TarsId(5) @JvmField val testSpeed: Byte? = null, @TarsId(6) @JvmField val useNewList: Byte? = null, @TarsId(7) @JvmField val iMultiConn: Int? = 1, @TarsId(8) @JvmField val vHttp2g3glist: List<SsoServerListInfo>? = null, @TarsId(9) @JvmField val vHttpWifilist: List<SsoServerListInfo>? = null, ) : JceStruct @Serializable internal class SsoServerListInfo( @TarsId(1) @JvmField val sIP: String = "", @TarsId(2) @JvmField val iPort: Int, @TarsId(3) @JvmField val linkType: Byte, @TarsId(4) @JvmField val proxy: Byte, @TarsId(5) @JvmField val protocolType: Byte? = null, @TarsId(6) @JvmField val iTimeOut: Int? = 10, ) : JceStruct @Serializable internal class TimeStamp( @TarsId(1) @JvmField val year: Int, @TarsId(2) @JvmField val month: Byte, @TarsId(3) @JvmField val day: Byte, @TarsId(4) @JvmField val hour: Byte, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/DeviceItemDes.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class DeviceItemDes( @JvmField @TarsId(0) val vecItemDes: ByteArray, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/FriendList.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class DelFriendReq( @JvmField @TarsId(0) val uin: Long, @JvmField @TarsId(1) val delUin: Long, @JvmField @TarsId(2) val delType: Byte, @JvmField @TarsId(3) val version: Int? = null, ) : JceStruct @Serializable internal class DelFriendResp( @JvmField @TarsId(0) val uin: Long, @JvmField @TarsId(1) val delUin: Long, @JvmField @TarsId(2) val result: Int, @JvmField @TarsId(3) val errorCode: Short? = null, ) : JceStruct @Serializable internal class ModifyGroupCardReq( @TarsId(0) @JvmField val dwZero: Long, @TarsId(1) @JvmField val dwGroupCode: Long, @TarsId(2) @JvmField val dwNewSeq: Long, @TarsId(3) @JvmField val vecUinInfo: List<stUinInfo>, ) : JceStruct @Serializable internal class stUinInfo( @TarsId(0) @JvmField val dwuin: Long, @TarsId(1) @JvmField val dwFlag: Long, @TarsId(2) @JvmField val sName: String = "", @TarsId(3) @JvmField val gender: Byte, @TarsId(4) @JvmField val sPhone: String = "", @TarsId(5) @JvmField val sEmail: String = "", @TarsId(6) @JvmField val sRemark: String = "", ) : JceStruct @Serializable internal class GetFriendListReq( @TarsId(0) @JvmField val reqtype: Int? = null, @TarsId(1) @JvmField val ifReflush: Byte? = null, @TarsId(2) @JvmField val uin: Long? = null, @TarsId(3) @JvmField val startIndex: Short? = null, @TarsId(4) @JvmField val getfriendCount: Short? = null, @TarsId(5) @JvmField val groupid: Byte? = null, @TarsId(6) @JvmField val ifGetGroupInfo: Byte? = null, @TarsId(7) @JvmField val groupstartIndex: Byte? = null, @TarsId(8) @JvmField val getgroupCount: Byte? = null, @TarsId(9) @JvmField val ifGetMSFGroup: Byte? = null, @TarsId(10) @JvmField val ifShowTermType: Byte? = null, @TarsId(11) @JvmField val version: Long? = null, @TarsId(12) @JvmField val uinList: List<Long>? = null, @TarsId(13) @JvmField val eAppType: Int = 0, @TarsId(14) @JvmField val ifGetDOVId: Byte? = null, @TarsId(15) @JvmField val ifGetBothFlag: Byte? = null, @TarsId(16) @JvmField val vec0xd50Req: ByteArray? = null, @TarsId(17) @JvmField val vec0xd6bReq: ByteArray? = null, @TarsId(18) @JvmField val vecSnsTypelist: List<Long>? = null, ) : JceStruct @Serializable internal class GetFriendListResp( @TarsId(0) @JvmField val reqtype: Int, @TarsId(1) @JvmField val ifReflush: Byte, @TarsId(2) @JvmField val uin: Long, @TarsId(3) @JvmField val startIndex: Short, @TarsId(4) @JvmField val getfriendCount: Short, @TarsId(5) @JvmField val totoalFriendCount: Short, @TarsId(6) @JvmField val friendCount: Short, @TarsId(7) @JvmField val vecFriendInfo: List<FriendInfo>? = null, @TarsId(8) @JvmField val groupid: Byte? = null, @TarsId(9) @JvmField val ifGetGroupInfo: Byte, @TarsId(10) @JvmField val groupstartIndex: Byte? = null, @TarsId(11) @JvmField val getgroupCount: Byte? = null, @TarsId(12) @JvmField val totoalGroupCount: Short? = null, @TarsId(13) @JvmField val groupCount: Byte? = null, @TarsId(14) @JvmField val vecGroupInfo: List<GroupInfo>? = null, @TarsId(15) @JvmField val result: Int, @TarsId(16) @JvmField val errorCode: Short? = null, @TarsId(17) @JvmField val onlineFriendCount: Short? = null, @TarsId(18) @JvmField val serverTime: Long? = null, @TarsId(19) @JvmField val sqqOnLineCount: Short? = null, @TarsId(20) @JvmField val vecMSFGroupInfo: List<GroupInfo>? = null, @TarsId(21) @JvmField val respType: Byte? = null, @TarsId(22) @JvmField val hasOtherRespFlag: Byte? = null, @TarsId(23) @JvmField val stSelfInfo: FriendInfo? = null, @TarsId(24) @JvmField val showPcIcon: Byte? = null, @TarsId(25) @JvmField val wGetExtSnsRspCode: Short? = null, @TarsId(26) @JvmField val stSubSrvRspCode: FriendListSubSrvRspCode? = null, ) : JceStruct @Serializable internal class FriendListSubSrvRspCode( @TarsId(0) @JvmField val wGetMutualMarkRspCode: Short? = null, @TarsId(1) @JvmField val wGetIntimateInfoRspCode: Short? = null, ) : JceStruct @Serializable internal class FriendInfo( @TarsId(0) @JvmField val friendUin: Long, @TarsId(1) @JvmField val groupId: Byte, @TarsId(2) @JvmField val faceId: Short, @TarsId(3) @JvmField val remark: String = "", @TarsId(4) @JvmField val sqqtype: Byte, @TarsId(5) @JvmField val status: Byte = 20, @TarsId(6) @JvmField val memberLevel: Byte? = null, @TarsId(7) @JvmField val isMqqOnLine: Byte? = null, @TarsId(8) @JvmField val sqqOnLineState: Byte? = null, @TarsId(9) @JvmField val isIphoneOnline: Byte? = null, @TarsId(10) @JvmField val detalStatusFlag: Byte? = null, @TarsId(11) @JvmField val sqqOnLineStateV2: Byte? = null, @TarsId(12) @JvmField val sShowName: String? = "", @TarsId(13) @JvmField val isRemark: Byte? = null, @TarsId(14) @JvmField val nick: String = "", @TarsId(15) @JvmField val specialFlag: Byte? = null, @TarsId(16) @JvmField val vecIMGroupID: ByteArray? = null, @TarsId(17) @JvmField val vecMSFGroupID: ByteArray? = null, @TarsId(18) @JvmField val iTermType: Int? = null, @TarsId(19) @JvmField val oVipInfo: VipBaseInfo? = null, //? bad @TarsId(20) @JvmField val network: Byte? = null, @TarsId(21) @JvmField val vecRing: ByteArray? = null, @TarsId(22) @JvmField val uAbiFlag: Long? = null, @TarsId(23) @JvmField val ulFaceAddonId: Long? = null, @TarsId(24) @JvmField val eNetworkType: Int? = 0, @TarsId(25) @JvmField val uVipFont: Long? = null, @TarsId(26) @JvmField val eIconType: Int? = 0, @TarsId(27) @JvmField val termDesc: String? = "", @TarsId(28) @JvmField val uColorRing: Long? = null, @TarsId(29) @JvmField val apolloFlag: Byte? = null, @TarsId(30) @JvmField val uApolloTimestamp: Long? = null, @TarsId(31) @JvmField val sex: Byte? = null, @TarsId(32) @JvmField val uFounderFont: Long? = null, @TarsId(33) @JvmField val eimId: String? = "", @TarsId(34) @JvmField val eimMobile: String? = "", @TarsId(35) @JvmField val olympicTorch: Byte? = null, @TarsId(36) @JvmField val uApolloSignTime: Long? = null, @TarsId(37) @JvmField val uLaviUin: Long? = null, @TarsId(38) @JvmField val uTagUpdateTime: Long? = null, @TarsId(39) @JvmField val uGameLastLoginTime: Long? = null, @TarsId(40) @JvmField val uGameAppid: Long? = null, @TarsId(41) @JvmField val vecCardID: ByteArray? = null, @TarsId(42) @JvmField val ulBitSet: Long? = null, @TarsId(43) @JvmField val kingOfGloryFlag: Byte? = null, @TarsId(44) @JvmField val ulKingOfGloryRank: Long? = null, @TarsId(45) @JvmField val masterUin: String? = "", @TarsId(46) @JvmField val uLastMedalUpdateTime: Long? = null, @TarsId(47) @JvmField val uFaceStoreId: Long? = null, @TarsId(48) @JvmField val uFontEffect: Long? = null, @TarsId(49) @JvmField val sDOVId: String? = "", @TarsId(50) @JvmField val uBothFlag: Long? = null, @TarsId(51) @JvmField val centiShow3DFlag: Byte? = null, @TarsId(52) @JvmField val vecIntimateInfo: ByteArray? = null, @TarsId(53) @JvmField val showNameplate: Byte? = null, @TarsId(54) @JvmField val newLoverDiamondFlag: Byte? = null, @TarsId(55) @JvmField val vecExtSnsFrdData: ByteArray? = null, @TarsId(56) @JvmField val vecMutualMarkData: ByteArray? = null, ) : JceStruct @Serializable internal class VipBaseInfo( @TarsId(0) @JvmField val mOpenInfo: Map<Int, VipOpenInfo>? = null, // 1, 2 are since 8.2.7 @TarsId(1) @JvmField val iNameplateVipType: Int? = 0, @TarsId(2) @JvmField val iGrayNameplateFlag: Int? = 0, ) : JceStruct @Serializable internal class VipOpenInfo( @TarsId(0) @JvmField val open: Boolean? = false, @TarsId(1) @JvmField val iVipType: Int = -1, @TarsId(2) @JvmField val iVipLevel: Int = -1, @TarsId(3) @JvmField val iVipFlag: Int? = null, @TarsId(4) @JvmField val nameplateId: Long? = null, ) : JceStruct @Serializable internal class GroupInfo( @TarsId(0) @JvmField val groupId: Byte, @TarsId(1) @JvmField val groupname: String = "", @TarsId(2) @JvmField val friendCount: Int, @TarsId(3) @JvmField val onlineFriendCount: Int, @TarsId(4) @JvmField val seqid: Byte? = null, @TarsId(5) @JvmField val sqqOnLineCount: Int? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/GroupMngReq.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class GroupMngReqJce( @TarsId(0) @JvmField val reqtype: Int, @TarsId(1) @JvmField val uin: Long, @TarsId(2) @JvmField val vecBody: ByteArray, @TarsId(3) @JvmField val checkInGroup: Byte? = null, @TarsId(4) @JvmField val sGroupLocation: String? = "", @TarsId(5) @JvmField val statOption: Byte? = null, @TarsId(6) @JvmField val wSourceID: Int? = null, @TarsId(7) @JvmField val wSourceSubID: Int? = null, @TarsId(8) @JvmField val isSupportAuthQuestionJoin: Byte? = null, @TarsId(9) @JvmField val ifGetAuthInfo: Byte? = null, @TarsId(10) @JvmField val dwDiscussUin: Long? = null, @TarsId(11) @JvmField val sJoinGroupKey: String? = "", @TarsId(12) @JvmField val sJoinGroupPicUrl: String? = "", @TarsId(13) @JvmField val vecJoinGroupRichMsg: ByteArray? = null, @TarsId(14) @JvmField val sJoinGroupAuth: String? = "", @TarsId(15) @JvmField val sJoinGroupVerifyToken: String? = "", @TarsId(16) @JvmField val dwJoinVerifyType: Long? = null, ) : JceStruct @Serializable internal class GroupMngRes( @TarsId(0) @JvmField val reqtype: Int, @TarsId(1) @JvmField val result: Byte, @TarsId(2) @JvmField val vecBody: ByteArray, @TarsId(3) @JvmField val errorString: String = "", @TarsId(4) @JvmField val errorCode: Short = 0, @TarsId(5) @JvmField val isInGroup: Byte? = null, @TarsId(6) @JvmField val sGroupLocation: String? = "", @TarsId(7) @JvmField val isMemInvite: Byte? = null, @TarsId(8) @JvmField val sAuthGrpInfo: String? = "", @TarsId(9) @JvmField val sJoinQuestion: String? = "", @TarsId(10) @JvmField val sJoinAnswer: String? = "", @TarsId(11) @JvmField val dwDis2GrpLimitType: Long? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/InstanceInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.ClientKind import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal data class InstanceInfo( @JvmField @TarsId(0) val iAppId: Int? = null, @JvmField @TarsId(1) val tablet: Byte? = null, @JvmField @TarsId(2) val iPlatform: Long? = null, /** * @see ClientKind */ @JvmField @TarsId(3) val iProductType: Long? = null, @JvmField @TarsId(4) val iClientType: Long? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/MoveGroupMemPack.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class MovGroupMemReq( @JvmField @TarsId(0) val uin: Long = 0L, @JvmField @TarsId(1) val reqtype: Byte = 0, @JvmField @TarsId(2) val vecBody: ByteArray? = null ) : JceStruct @Serializable internal class MovGroupMemResp( @JvmField @TarsId(0) val uin: Long = 0L, @JvmField @TarsId(1) val reqtype: Byte = 0, @JvmField @TarsId(2) val result: Byte = 0, @JvmField @TarsId(3) val errorString: String = "" ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/MsgType0x210.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27 import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class AddGroup( @TarsId(0) @JvmField val dwGroupID: Long? = null, @TarsId(1) @JvmField val dwSortID: Long? = null, @TarsId(2) @JvmField val groupName: String? = "", ) : JceStruct @Serializable internal class DelGroup( @TarsId(0) @JvmField val dwGroupID: Long? = null, ) : JceStruct @Serializable internal class FriendGroup( @TarsId(0) @JvmField val dwFuin: Long? = null, @TarsId(1) @JvmField val vOldGroupID: List<Long>? = null, @TarsId(2) @JvmField val vNewGroupID: List<Long>? = null, ) : JceStruct @Serializable internal class GroupSort( @TarsId(0) @JvmField val dwGroupID: Long? = null, @TarsId(1) @JvmField val dwSortID: Long? = null, ) : JceStruct @Serializable internal class MarketFaceInfo( @TarsId(0) @JvmField val insertIdx: Long, @TarsId(1) @JvmField val marketFaceBuff: ByteArray, ) : JceStruct @Serializable internal class ModFriendGroup( @TarsId(0) @JvmField val vMsgFrdGroup: List<FriendGroup>? = null, ) : JceStruct @Serializable internal class ModGroupName( @TarsId(0) @JvmField val dwGroupID: Long? = null, @TarsId(1) @JvmField val groupName: String? = "", ) : JceStruct @Serializable internal class ModGroupSort( @TarsId(0) @JvmField val vMsgGroupSort: List<GroupSort>? = null, ) : JceStruct @Serializable internal class MsgType0x210( @TarsId(0) @JvmField val uSubMsgType: Long, @TarsId(1) @JvmField val stMsgInfo0x2: MsgType0x210SubMsgType0x2? = null, @TarsId(3) @JvmField val stMsgInfo0xa: MsgType0x210SubMsgType0xa? = null, @TarsId(4) @JvmField val stMsgInfo0xe: MsgType0x210SubMsgType0xe? = null, @TarsId(5) @JvmField val stMsgInfo0x13: MsgType0x210SubMsgType0x13? = null, @TarsId(6) @JvmField val stMsgInfo0x17: MsgType0x210SubMsgType0x17? = null, @TarsId(7) @JvmField val stMsgInfo0x20: MsgType0x210SubMsgType0x20? = null, @TarsId(8) @JvmField val stMsgInfo0x1d: MsgType0x210SubMsgType0x1d? = null, @TarsId(9) @JvmField val stMsgInfo0x24: MsgType0x210SubMsgType0x24? = null, @NestedStructure(Deserializer::class) @TarsId(10) @JvmField val vProtobuf: ByteArray = EMPTY_BYTE_ARRAY, ) : JceStruct { object Deserializer : NestedStructureDesensitizer<MsgType0x210, ProtocolStruct> { override fun deserialize(context: MsgType0x210, byteArray: ByteArray): ProtocolStruct? { return when (context.uSubMsgType) { 0x27L -> byteArray.loadAs(Submsgtype0x27.SubMsgType0x27.SubMsgType0x27MsgBody.serializer()) else -> null } } } } @Serializable internal class MsgType0x210SubMsgType0x13( @TarsId(0) @JvmField val uint32SrcAppId: Long? = null, @TarsId(1) @JvmField val uint32SrcInstId: Long? = null, @TarsId(2) @JvmField val uint32DstAppId: Long? = null, @TarsId(3) @JvmField val uint32DstInstId: Long? = null, @TarsId(4) @JvmField val uint64DstUin: Long? = null, @TarsId(5) @JvmField val uint64Sessionid: Long? = null, @TarsId(6) @JvmField val uint32Size: Long? = null, @TarsId(7) @JvmField val uint32Index: Long? = null, @TarsId(8) @JvmField val uint32Type: Long? = null, @TarsId(9) @JvmField val buf: ByteArray? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x13_MsgItem( @TarsId(0) @JvmField val uint32Type: Long? = null, @TarsId(1) @JvmField val text: ByteArray? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x17( @TarsId(0) @JvmField val dwOpType: Long? = null, @TarsId(1) @JvmField val stAddGroup: AddGroup? = null, @TarsId(2) @JvmField val stDelGroup: DelGroup? = null, @TarsId(3) @JvmField val stModGroupName: ModGroupName? = null, @TarsId(4) @JvmField val stModGroupSort: ModGroupSort? = null, @TarsId(5) @JvmField val stModFriendGroup: ModFriendGroup? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x1d( @TarsId(0) @JvmField val dwOpType: Long? = null, @TarsId(1) @JvmField val dwUin: Long? = null, @TarsId(2) @JvmField val dwID: Long? = null, @TarsId(3) @JvmField val value: String? = "", ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x2( @TarsId(0) @JvmField val uSrcAppId: Long? = null, @TarsId(1) @JvmField val uSrcInstId: Long? = null, @TarsId(2) @JvmField val uDstAppId: Long? = null, @TarsId(3) @JvmField val uDstInstId: Long? = null, @TarsId(4) @JvmField val uDstUin: Long? = null, @TarsId(5) @JvmField val fileName: ByteArray? = null, @TarsId(6) @JvmField val fileIndex: ByteArray? = null, @TarsId(7) @JvmField val fileMd5: ByteArray? = null, @TarsId(8) @JvmField val fileKey: ByteArray? = null, @TarsId(9) @JvmField val uServerIp: Long? = null, @TarsId(10) @JvmField val uServerPort: Long? = null, @TarsId(11) @JvmField val fileLen: Long? = null, @TarsId(12) @JvmField val sessionId: Long? = null, @TarsId(13) @JvmField val originfileMd5: ByteArray? = null, @TarsId(14) @JvmField val uOriginfiletype: Long? = null, @TarsId(15) @JvmField val uSeq: Long? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x20( @TarsId(0) @JvmField val dwOpType: Long? = null, @TarsId(1) @JvmField val dwType: Long? = null, @TarsId(2) @JvmField val dwUin: Long? = null, @TarsId(3) @JvmField val remaek: String? = "", ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x24( @TarsId(0) @JvmField val vPluginNumList: List<PluginNum>? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0xa( @TarsId(0) @JvmField val uSrcAppId: Long? = null, @TarsId(1) @JvmField val uSrcInstId: Long? = null, @TarsId(2) @JvmField val uDstAppId: Long? = null, @TarsId(3) @JvmField val uDstInstId: Long? = null, @TarsId(4) @JvmField val uDstUin: Long? = null, @TarsId(5) @JvmField val uType: Long? = null, @TarsId(6) @JvmField val uServerIp: Long? = null, @TarsId(7) @JvmField val uServerPort: Long? = null, @TarsId(8) @JvmField val vUrlNotify: ByteArray? = null, @TarsId(9) @JvmField val vTokenKey: ByteArray? = null, @TarsId(10) @JvmField val uFileLen: Long? = null, @TarsId(11) @JvmField val fileName: ByteArray? = null, @TarsId(12) @JvmField val vMd5: ByteArray? = null, @TarsId(13) @JvmField val sessionId: Long? = null, @TarsId(14) @JvmField val originfileMd5: ByteArray? = null, @TarsId(15) @JvmField val uOriginfiletype: Long? = null, @TarsId(16) @JvmField val uSeq: Long? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0xe( @TarsId(0) @JvmField val uint32SrcAppId: Long? = null, @TarsId(1) @JvmField val uint32SrcInstId: Long? = null, @TarsId(2) @JvmField val uint32DstAppId: Long? = null, @TarsId(3) @JvmField val uint32DstInstId: Long? = null, @TarsId(4) @JvmField val uint64DstUin: Long? = null, @TarsId(5) @JvmField val uint64Sessionid: Long? = null, @TarsId(6) @JvmField val uint32Operate: Long? = null, @TarsId(7) @JvmField val uint32Seq: Long? = null, @TarsId(8) @JvmField val uint32Code: Long? = null, @TarsId(9) @JvmField val msg: String? = "", ) : JceStruct @Serializable internal class PersonInfoChange( @TarsId(0) @JvmField val type: Byte? = null, @TarsId(1) @JvmField val vChgField: List<PersonInfoField>? = null, ) : JceStruct @Serializable internal class PersonInfoField( @TarsId(0) @JvmField val uField: Long? = null, ) : JceStruct @Serializable internal class PluginNum( @TarsId(0) @JvmField val dwID: Long? = null, @TarsId(1) @JvmField val dwNUm: Long? = null, @TarsId(2) @JvmField val flag: Byte? = null, ) : JceStruct @Serializable internal class SlaveMasterMsg( @TarsId(0) @JvmField val uMsgType: Long? = null, @TarsId(1) @JvmField val uCmd: Long? = null, @TarsId(2) @JvmField val uSeq: Long? = null, @TarsId(3) @JvmField val fromUin: Long? = null, @TarsId(4) @JvmField val wFromApp: Short? = null, @TarsId(5) @JvmField val uFromInstId: Long? = null, @TarsId(6) @JvmField val toUin: Long? = null, @TarsId(7) @JvmField val wToApp: Short? = null, @TarsId(8) @JvmField val uToInstId: Long? = null, @TarsId(9) @JvmField val vOrigMsg: ByteArray? = null, @TarsId(10) @JvmField val uLastChangeTime: Long? = null, @TarsId(11) @JvmField val vReserved: ByteArray? = null, @TarsId(12) @JvmField val vMarketFace: List<MarketFaceInfo>? = null, @TarsId(13) @JvmField val uSuperQQBubbleId: Long? = null, ) : JceStruct @Serializable internal class Type_1_QQDataTextMsg( @TarsId(0) @JvmField val msgItem: List<MsgType0x210SubMsgType0x13_MsgItem>? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/OnlinePushPack.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField internal class OnlinePushPack { @Serializable internal class DelMsgInfo( @TarsId(0) @JvmField val fromUin: Long, @TarsId(1) @JvmField val uMsgTime: Long, @TarsId(2) @JvmField val shMsgSeq: Short, @TarsId(3) @JvmField val vMsgCookies: ByteArray? = null, @TarsId(4) @JvmField val wCmd: Short? = null, @TarsId(5) @JvmField val uMsgType: Long? = null, @TarsId(6) @JvmField val uAppId: Long? = null, @TarsId(7) @JvmField val sendTime: Long? = null, @TarsId(8) @JvmField val ssoSeq: Int? = null, @TarsId(9) @JvmField val ssoIp: Int? = null, @TarsId(10) @JvmField val clientIp: Int? = null, ) : JceStruct @Serializable internal class DeviceInfo( @TarsId(0) @JvmField val netType: Byte? = null, @TarsId(1) @JvmField val devType: String? = "", @TarsId(2) @JvmField val oSVer: String? = "", @TarsId(3) @JvmField val vendorName: String? = "", @TarsId(4) @JvmField val vendorOSName: String? = "", @TarsId(5) @JvmField val iOSIdfa: String? = "", ) : JceStruct @Serializable internal class Name( @TarsId(0) @JvmField val fromUin: Long, @TarsId(1) @JvmField val uMsgTime: Long, @TarsId(2) @JvmField val shMsgType: Short, @TarsId(3) @JvmField val shMsgSeq: Short, @TarsId(4) @JvmField val msg: String = "", @TarsId(5) @JvmField val uRealMsgTime: Int? = null, @TarsId(6) @JvmField val vMsg: ByteArray? = null, @TarsId(7) @JvmField val uAppShareID: Long? = null, @TarsId(8) @JvmField val vMsgCookies: ByteArray? = null, @TarsId(9) @JvmField val vAppShareCookie: ByteArray? = null, @TarsId(10) @JvmField val msgUid: Long? = null, @TarsId(11) @JvmField val lastChangeTime: Long? = 1L, @TarsId(12) @JvmField val vCPicInfo: List<CPicInfo>? = null, @TarsId(13) @JvmField val stShareData: ShareData? = null, @TarsId(14) @JvmField val fromInstId: Long? = null, @TarsId(15) @JvmField val vRemarkOfSender: ByteArray? = null, @TarsId(16) @JvmField val fromMobile: String? = "", @TarsId(17) @JvmField val fromName: String? = "", @TarsId(18) @JvmField val vNickName: List<String>? = null, @TarsId(19) @JvmField val stC2CTmpMsgHead: TempMsgHead? = null, ) : JceStruct @Serializable internal class SvcReqPushMsg( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val uMsgTime: Long, @TarsId(2) @JvmField val vMsgInfos: List<MsgInfo>, @TarsId(3) @JvmField val svrip: Int? = 0, @TarsId(4) @JvmField val vSyncCookie: ByteArray? = null, @TarsId(5) @JvmField val vUinPairMsg: List<UinPairMsg>? = null, @TarsId(6) @JvmField val mPreviews: Map<String, ByteArray>? = null, // @SerialId(7) @JvmField val wUserActive: Int? = null, //@SerialId(12) @JvmField val wGeneralFlag: Int? = null ) : JceStruct @Serializable internal class SvcRespPushMsg( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val vDelInfos: List<DelMsgInfo>, @TarsId(2) @JvmField val svrip: Int = 0, @TarsId(3) @JvmField val pushToken: ByteArray? = null, @TarsId(4) @JvmField val serviceType: Int? = null, @TarsId(5) @JvmField val deviceInfo: DeviceInfo? = null, ) : JceStruct @Serializable internal class UinPairMsg( @TarsId(1) @JvmField val uLastReadTime: Long? = null, @TarsId(2) @JvmField val peerUin: Long? = null, @TarsId(3) @JvmField val uMsgCompleted: Long? = null, @TarsId(4) @JvmField val vMsgInfos: List<MsgInfo>? = null, ) : JceStruct @Serializable internal class MsgType0x210( @TarsId(0) @JvmField val uSubMsgType: Long, @TarsId(1) @JvmField val stMsgInfo0x2: MsgType0x210SubMsgType0x2? = null, @TarsId(3) @JvmField val stMsgInfo0xa: MsgType0x210SubMsgType0xa? = null, @TarsId(4) @JvmField val stMsgInfo0xe: MsgType0x210SubMsgType0xe? = null, @TarsId(5) @JvmField val stMsgInfo0x13: MsgType0x210SubMsgType0x13? = null, @TarsId(6) @JvmField val stMsgInfo0x17: MsgType0x210SubMsgType0x17? = null, @TarsId(7) @JvmField val stMsgInfo0x20: MsgType0x210SubMsgType0x20? = null, @TarsId(8) @JvmField val stMsgInfo0x1d: MsgType0x210SubMsgType0x1d? = null, @TarsId(9) @JvmField val stMsgInfo0x24: MsgType0x210SubMsgType0x24? = null, @TarsId(10) @JvmField val vProtobuf: ByteArray? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x13( @TarsId(0) @JvmField val uint32SrcAppId: Long? = null, @TarsId(1) @JvmField val uint32SrcInstId: Long? = null, @TarsId(2) @JvmField val uint32DstAppId: Long? = null, @TarsId(3) @JvmField val uint32DstInstId: Long? = null, @TarsId(4) @JvmField val uint64DstUin: Long? = null, @TarsId(5) @JvmField val uint64Sessionid: Long? = null, @TarsId(6) @JvmField val uint32Size: Long? = null, @TarsId(7) @JvmField val uint32Index: Long? = null, @TarsId(8) @JvmField val uint32Type: Long? = null, @TarsId(9) @JvmField val buf: ByteArray? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x17( @TarsId(0) @JvmField val dwOpType: Long? = null, @TarsId(1) @JvmField val stAddGroup: AddGroup? = null, @TarsId(2) @JvmField val stDelGroup: DelGroup? = null, @TarsId(3) @JvmField val stModGroupName: ModGroupName? = null, @TarsId(4) @JvmField val stModGroupSort: ModGroupSort? = null, @TarsId(5) @JvmField val stModFriendGroup: ModFriendGroup? = null, ) : JceStruct @Serializable internal class AddGroup( @TarsId(0) @JvmField val dwGroupID: Long? = null, @TarsId(1) @JvmField val dwSortID: Long? = null, @TarsId(2) @JvmField val groupName: String? = "", ) : JceStruct @Serializable internal class DelGroup( @TarsId(0) @JvmField val dwGroupID: Long? = null, ) : JceStruct @Serializable internal class ModFriendGroup( @TarsId(0) @JvmField val vMsgFrdGroup: List<FriendGroup>? = null, ) : JceStruct @Serializable internal class FriendGroup( @TarsId(0) @JvmField val dwFuin: Long? = null, @TarsId(1) @JvmField val vOldGroupID: List<Long>? = null, @TarsId(2) @JvmField val vNewGroupID: List<Long>? = null, ) : JceStruct @Serializable internal class ModGroupName( @TarsId(0) @JvmField val dwGroupID: Long? = null, @TarsId(1) @JvmField val groupName: String? = "", ) : JceStruct @Serializable internal class ModGroupSort( @TarsId(0) @JvmField val vMsgGroupSort: List<GroupSort>? = null, ) : JceStruct @Serializable internal class GroupSort( @TarsId(0) @JvmField val dwGroupID: Long? = null, @TarsId(1) @JvmField val dwSortID: Long? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x1d( @TarsId(0) @JvmField val dwOpType: Long? = null, @TarsId(1) @JvmField val dwUin: Long? = null, @TarsId(2) @JvmField val dwID: Long? = null, @TarsId(3) @JvmField val value: String? = "", ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x2( @TarsId(0) @JvmField val uSrcAppId: Long? = null, @TarsId(1) @JvmField val uSrcInstId: Long? = null, @TarsId(2) @JvmField val uDstAppId: Long? = null, @TarsId(3) @JvmField val uDstInstId: Long? = null, @TarsId(4) @JvmField val uDstUin: Long? = null, @TarsId(5) @JvmField val fileName: ByteArray? = null, @TarsId(6) @JvmField val fileIndex: ByteArray? = null, @TarsId(7) @JvmField val fileMd5: ByteArray? = null, @TarsId(8) @JvmField val fileKey: ByteArray? = null, @TarsId(9) @JvmField val uServerIp: Long? = null, @TarsId(10) @JvmField val uServerPort: Long? = null, @TarsId(11) @JvmField val fileLen: Long? = null, @TarsId(12) @JvmField val sessionId: Long? = null, @TarsId(13) @JvmField val originfileMd5: ByteArray? = null, @TarsId(14) @JvmField val uOriginfiletype: Long? = null, @TarsId(15) @JvmField val uSeq: Long? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x20( @TarsId(0) @JvmField val dwOpType: Long? = null, @TarsId(1) @JvmField val dwType: Long? = null, @TarsId(2) @JvmField val dwUin: Long? = null, @TarsId(3) @JvmField val remaek: String? = "", ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0x24( @TarsId(0) @JvmField val vPluginNumList: List<PluginNum>? = null, ) : JceStruct @Serializable internal class PluginNum( @TarsId(0) @JvmField val dwID: Long? = null, @TarsId(1) @JvmField val dwNUm: Long? = null, @TarsId(2) @JvmField val flag: Byte? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0xa( @TarsId(0) @JvmField val uSrcAppId: Long? = null, @TarsId(1) @JvmField val uSrcInstId: Long? = null, @TarsId(2) @JvmField val uDstAppId: Long? = null, @TarsId(3) @JvmField val uDstInstId: Long? = null, @TarsId(4) @JvmField val uDstUin: Long? = null, @TarsId(5) @JvmField val uType: Long? = null, @TarsId(6) @JvmField val uServerIp: Long? = null, @TarsId(7) @JvmField val uServerPort: Long? = null, @TarsId(8) @JvmField val vUrlNotify: ByteArray? = null, @TarsId(9) @JvmField val vTokenKey: ByteArray? = null, @TarsId(10) @JvmField val uFileLen: Long? = null, @TarsId(11) @JvmField val fileName: ByteArray? = null, @TarsId(12) @JvmField val vMd5: ByteArray? = null, @TarsId(13) @JvmField val sessionId: Long? = null, @TarsId(14) @JvmField val originfileMd5: ByteArray? = null, @TarsId(15) @JvmField val uOriginfiletype: Long? = null, @TarsId(16) @JvmField val uSeq: Long? = null, ) : JceStruct @Serializable internal class MsgType0x210SubMsgType0xe( @TarsId(0) @JvmField val uint32SrcAppId: Long? = null, @TarsId(1) @JvmField val uint32SrcInstId: Long? = null, @TarsId(2) @JvmField val uint32DstAppId: Long? = null, @TarsId(3) @JvmField val uint32DstInstId: Long? = null, @TarsId(4) @JvmField val uint64DstUin: Long? = null, @TarsId(5) @JvmField val uint64Sessionid: Long? = null, @TarsId(6) @JvmField val uint32Operate: Long? = null, @TarsId(7) @JvmField val uint32Seq: Long? = null, @TarsId(8) @JvmField val uint32Code: Long? = null, @TarsId(9) @JvmField val msg: String? = "", ) : JceStruct } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/PushNotifyPack.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Suppress("ArrayInDataClass") @Serializable internal class RequestPushNotify( @TarsId(0) @JvmField val uin: Long? = 0L, @TarsId(1) @JvmField val ctype: Byte = 0, @TarsId(2) @JvmField val strService: String?, @TarsId(3) @JvmField val strCmd: String?, @TarsId(4) @JvmField val vNotifyCookie: ByteArray? = EMPTY_BYTE_ARRAY, @TarsId(5) @JvmField val usMsgType: Int?, @TarsId(6) @JvmField val wUserActive: Int?, @TarsId(7) @JvmField val wGeneralFlag: Int?, @TarsId(8) @JvmField val bindedUin: Long?, @TarsId(9) @JvmField val stMsgInfo: MsgInfo?, @TarsId(10) @JvmField val msgCtrlBuf: String?, @TarsId(11) @JvmField val serverBuf: ByteArray?, @TarsId(12) @JvmField val pingFlag: Long?, @TarsId(13) @JvmField val svrip: Int?, ) : JceStruct, Packet { override fun toString(): String { return "RequestPushNotify(usMsgType=$usMsgType)" } } @Serializable internal class MsgInfo( @TarsId(0) @JvmField val lFromUin: Long = 0L, @TarsId(1) @JvmField val uMsgTime: Long = 0L, @TarsId(2) @JvmField val shMsgType: Short, @TarsId(3) @JvmField val shMsgSeq: Short, @TarsId(4) @JvmField val strMsg: String?, @TarsId(5) @JvmField val uRealMsgTime: Int?, @param:NestedStructure(VMsgDesensitizationSerializer::class) @TarsId(6) @JvmField val vMsg: ByteArray, @TarsId(7) @JvmField val uAppShareID: Long?, @TarsId(8) @JvmField val vMsgCookies: ByteArray? = EMPTY_BYTE_ARRAY, @TarsId(9) @JvmField val vAppShareCookie: ByteArray? = EMPTY_BYTE_ARRAY, @TarsId(10) @JvmField val lMsgUid: Long?, @TarsId(11) @JvmField val lLastChangeTime: Long?, @TarsId(12) @JvmField val vCPicInfo: List<CPicInfo>?, @TarsId(13) @JvmField val stShareData: ShareData?, @TarsId(14) @JvmField val lFromInstId: Long?, @TarsId(15) @JvmField val vRemarkOfSender: ByteArray?, @TarsId(16) @JvmField val strFromMobile: String?, @TarsId(17) @JvmField val strFromName: String?, @TarsId(18) @JvmField val vNickName: List<String>?, //, //@SerialId(19) @JvmField val stC2CTmpMsgHead: TempMsgHead? ) : JceStruct internal object VMsgDesensitizationSerializer : NestedStructureDesensitizer<MsgInfo, ProtocolStruct> { override fun deserialize(context: MsgInfo, byteArray: ByteArray): ProtocolStruct? { return when (context.shMsgType.toUShort().toInt()) { 0x210 -> byteArray.loadAs(MsgType0x210.serializer()) else -> null } } } @Serializable internal class ShareData( @TarsId(0) @JvmField val pkgname: String = "", @TarsId(1) @JvmField val msgtail: String = "", @TarsId(2) @JvmField val picurl: String = "", @TarsId(3) @JvmField val url: String = "", ) : JceStruct @Serializable internal class TempMsgHead( @TarsId(0) @JvmField val c2c_type: Int? = 0, @TarsId(1) @JvmField val serviceType: Int? = 0, ) : JceStruct @Serializable internal class CPicInfo( @TarsId(0) @JvmField val vPath: ByteArray = EMPTY_BYTE_ARRAY, @TarsId(1) @JvmField val vHost: ByteArray? = EMPTY_BYTE_ARRAY, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ReqPushStatus.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class RequestPushStatus( @JvmField @TarsId(0) val uin: Long, @JvmField @TarsId(1) val status: Byte, @JvmField @TarsId(2) val dataLine: Byte? = null, @JvmField @TarsId(3) val printable: Byte? = null, @JvmField @TarsId(4) val viewFile: Byte? = null, @JvmField @TarsId(5) val nPCVer: Long? = null, @JvmField @TarsId(6) val nClientType: Long? = null, @JvmField @TarsId(7) val nInstanceId: Long? = null, @JvmField @TarsId(8) val vecInstanceList: List<InstanceInfo>? = null, ) : JceStruct, Packet ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/ReqSummaryCard.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class ReqHead( @JvmField @TarsId(0) val iVersion: Int = 1, ) : JceStruct @Serializable internal class ReqSummaryCard( @JvmField @TarsId(0) val uin: Long, @JvmField @TarsId(1) val eComeFrom: Int = 65535, @JvmField @TarsId(2) val uQzoneFeedTimestamp: Long? = null, @JvmField @TarsId(3) val isFriend: Byte? = null, @JvmField @TarsId(4) val groupCode: Long? = null, @JvmField @TarsId(5) val groupUin: Long? = null, //@JvmField @TarsId(6) val vSeed: ByteArray? = null, //@JvmField @TarsId(7) val searchName: String? = "", @JvmField @TarsId(8) val getControl: Long? = null, @JvmField @TarsId(9) val eAddFriendSource: Int? = null, @JvmField @TarsId(10) val vSecureSig: ByteArray? = null, //@JvmField @TarsId(11) val vReqLastGameInfo: ByteArray? = null, //@JvmField @TarsId(12) val vReqTemplateInfo: ByteArray? = null, //@JvmField @TarsId(13) val vReqStarInfo: ByteArray? = null, //@JvmField @TarsId(14) val vvReqServices: List<ByteArray>? = null, @JvmField @TarsId(15) val tinyId: Long? = null, @JvmField @TarsId(16) val uLikeSource: Long? = null, //@JvmField @TarsId(17) val stLocaleInfo: UserLocaleInfo? = null, @JvmField @TarsId(18) val reqMedalWallInfo: Byte? = null, @JvmField @TarsId(19) val vReq0x5ebFieldId: List<Int>? = null, @JvmField @TarsId(20) val reqNearbyGodInfo: Byte? = null, //@JvmField @TarsId(21) val reqCommLabel: Byte? = null, @JvmField @TarsId(22) val reqExtendCard: Byte? = null, //@JvmField @TarsId(23) val vReqKandianInfo: ByteArray? = null, //@JvmField @TarsId(24) val uRichCardNameVer: Long? = null ) : JceStruct @Serializable internal class RespHead( @JvmField @TarsId(0) val iVersion: Int, @JvmField @TarsId(1) val iResult: Int, @JvmField @TarsId(2) val errorMsg: String = "", @JvmField @TarsId(3) val vCookies: ByteArray? = null, ) : JceStruct @Serializable internal class RespSearch( @JvmField @TarsId(0) val vRecords: List<SearchInfo>, @JvmField @TarsId(1) val vSecureSig: ByteArray? = null, @JvmField @TarsId(2) val vvRespServices: List<ByteArray>? = null, ) : JceStruct @Serializable internal class RespSummaryCard( // @JvmField @TarsId(0) val iFace: Int? = null, @JvmField @TarsId(1) val sex: Byte? = null, @JvmField @TarsId(2) val age: Byte? = null, @JvmField @TarsId(3) val nick: String? = "", @JvmField @TarsId(4) val remark: String? = "", @JvmField @TarsId(5) val iLevel: Int? = null, @JvmField @TarsId(6) val province: String? = "", @JvmField @TarsId(7) val city: String? = "", @JvmField @TarsId(8) val sign: String? = "", @JvmField @TarsId(9) val groupName: String? = "", @JvmField @TarsId(10) val groupNick: String? = "", @JvmField @TarsId(11) val mobile: String? = "", @JvmField @TarsId(12) val contactName: String? = "", @JvmField @TarsId(13) val ulShowControl: Long? = null, @JvmField @TarsId(14) val qzoneFeedsDesc: String? = "", // @JvmField @TarsId(15) val oLatestPhotos:AlbumInfo? = null, @JvmField @TarsId(16) val iVoteCount: Int? = null, @JvmField @TarsId(17) val iLastestVoteCount: Int? = null, @JvmField @TarsId(18) val valid4Vote: Byte? = null, @JvmField @TarsId(19) val country: String? = "", @JvmField @TarsId(20) val status: String? = "", @JvmField @TarsId(21) val autoRemark: String? = "", @JvmField @TarsId(22) val cacheControl: Long? = null, @JvmField @TarsId(23) val uin: Long? = null, @JvmField @TarsId(24) val iPhotoCount: Int? = null, @JvmField @TarsId(25) val eAddOption: Int? = 101, @JvmField @TarsId(26) val vAddQuestion: List<String>? = null, @JvmField @TarsId(27) val vSeed: ByteArray? = null, @JvmField @TarsId(28) val discussName: String? = "", @JvmField @TarsId(29) val stVipInfo: VipBaseInfo? = null, @JvmField @TarsId(30) val showName: String? = "", @JvmField @TarsId(31) val stVoiceInfo: VoiceInfo? = null, @JvmField @TarsId(32) val vRichSign: ByteArray? = null, @JvmField @TarsId(33) val uSignModifyTime: Long? = null, @JvmField @TarsId(34) val vRespLastGameInfo: ByteArray? = null, @JvmField @TarsId(35) val userFlag: Long? = null, @JvmField @TarsId(36) val uLoginDays: Long? = null, @JvmField @TarsId(37) val loginDesc: String? = "", @JvmField @TarsId(38) val uTemplateId: Long? = null, @JvmField @TarsId(39) val uQQMasterLoginDays: Long? = 20L, @JvmField @TarsId(40) val ulFaceAddonId: Long? = null, @JvmField @TarsId(41) val vRespTemplateInfo: ByteArray? = null, @JvmField @TarsId(42) val respMusicInfo: String? = "", @JvmField @TarsId(43) val vRespStarInfo: ByteArray? = null, @JvmField @TarsId(44) val stDiamonds: VipBaseInfo? = null, @JvmField @TarsId(45) val uAccelerateMultiple: Long? = null, @JvmField @TarsId(46) val vvRespServices: List<ByteArray>? = null, @JvmField @TarsId(47) val spaceName: String? = "", // @JvmField @TarsId(48) val stDateCard:DateCard? = null, @JvmField @TarsId(49) val iBirthday: Int? = null, // @JvmField @TarsId(50) val stQCallInfo:QCallInfo? = null, // @JvmField @TarsId(51) val stGiftInfo:GiftInfo? = null, // @JvmField @TarsId(52) val stPanSocialInfo:PanSocialInfo? = null, // @JvmField @TarsId(53) val stVideoInfo:QQVideoInfo? = null, @JvmField @TarsId(54) val vTempChatSig: ByteArray? = null, // @JvmField @TarsId(55) val stInterestTag:InterestTagInfo? = null, // @JvmField @TarsId(56) val stUserFeed: UserFeed? = null, // @JvmField @TarsId(57) val stQiqiVideoInfo:QiqiVideoInfo? = null, // @JvmField @TarsId(58) val stPrivInfo:PrivilegeBaseInfo? = null, // @JvmField @TarsId(59) val stApollo:QQApolloInfo? = null, // @JvmField @TarsId(60) val stAddFrdSrcInfo:AddFrdSrcInfo? = null, // @JvmField @TarsId(61) val stBindPhoneInfo:BindPhoneInfo? = null, @JvmField @TarsId(62) val vVisitingCardInfo: ByteArray? = null, @JvmField @TarsId(63) val voteLimitedNotice: String? = "", @JvmField @TarsId(64) val haveVotedCnt: Short? = null, @JvmField @TarsId(65) val availVoteCnt: Short? = null, @JvmField @TarsId(66) val eIMBindPhoneNum: String? = "", @JvmField @TarsId(67) val eIMId: String? = "", @JvmField @TarsId(68) val email: String? = "", @JvmField @TarsId(69) val uCareer: Long? = null, @JvmField @TarsId(70) val personal: String? = "", @JvmField @TarsId(71) val vHotChatInfo: ByteArray? = null, // @JvmField @TarsId(72) val stOlympicInfo:OlympicInfo? = null, @JvmField @TarsId(73) val stCoverInfo: TCoverInfo? = null, @JvmField @TarsId(74) val stNowBroadcastInfo: TNowBroadcastInfo? = null, // @JvmField @TarsId(75) val stEimInfo:TEIMInfo? = null, @JvmField @TarsId(78) val stVideoHeadInfo: TVideoHeadInfo? = null, @JvmField @TarsId(79) val iContactNotBindQQ: Int? = null, // @JvmField @TarsId(80) val stMedalWallInfo:TMedalWallInfo? = null, @JvmField @TarsId(81) val vvRespServicesBigOrder: List<ByteArray>? = null, @JvmField @TarsId(82) val vResp0x5ebInfo: ByteArray? = null, @JvmField @TarsId(83) val stNearbyGodInfo: TNearbyGodInfo? = null, @JvmField @TarsId(84) val vRespQQStoryInfo: ByteArray? = null, @JvmField @TarsId(85) val vRespCustomLabelInfo: ByteArray? = null, @JvmField @TarsId(86) val vPraiseList: List<TPraiseInfo>? = null, @JvmField @TarsId(87) val stCampusCircleInfo: TCampusCircleInfo? = null, @JvmField @TarsId(88) val stTimInfo: TTimInfo? = null, @JvmField @TarsId(89) val stQimInfo: TQimInfo? = null, // @JvmField @TarsId(90) val stHeartInfo:HeartInfo? = null, @JvmField @TarsId(91) val vQzoneCoverInfo: ByteArray? = null, @JvmField @TarsId(92) val vNearbyTaskInfo: ByteArray? = null, @JvmField @TarsId(93) val vNowInfo: ByteArray? = null, @JvmField @TarsId(94) val uFriendGroupId: Long? = null, @JvmField @TarsId(95) val vCommLabel: ByteArray? = null, @JvmField @TarsId(96) val vExtendCard: ByteArray? = null, @JvmField @TarsId(97) val qzoneHeader: String? = "", @JvmField @TarsId(98) val mapQzoneEx: Map<String, String>? = null, @JvmField @TarsId(99) val vRespKandianInfo: ByteArray? = null, // @JvmField @TarsId(100) val stWeishiInfo:WeishiInfo? = null, @JvmField @TarsId(101) val uRichCardNameVer: Long? = null, @JvmField @TarsId(102) val uCurMulType: Long? = null, @JvmField @TarsId(103) val vLongNickTopicInfo: ByteArray? = null, ) : JceStruct @Serializable internal class RespVoiceManage( @JvmField @TarsId(0) val eOpType: Int, ) : JceStruct @Serializable internal class SearchInfo( @JvmField @TarsId(0) val uIN: Long, @JvmField @TarsId(1) val eSource: Int, @JvmField @TarsId(2) val nick: String? = "", @JvmField @TarsId(3) val mobile: String? = "", @JvmField @TarsId(4) val isFriend: Byte? = null, @JvmField @TarsId(5) val inContact: Byte? = null, @JvmField @TarsId(6) val isEnterpriseQQ: Byte? = null, ) : JceStruct @Serializable internal class TCampusCircleInfo( @JvmField @TarsId(0) val iIsSigned: Int? = null, @JvmField @TarsId(1) val name: String? = "", @JvmField @TarsId(2) val academy: String? = "", @JvmField @TarsId(3) val eStatus: Int? = null, @JvmField @TarsId(4) val stSchoolInfo: TCampusSchoolInfo? = null, ) : JceStruct @Serializable internal class TCampusSchoolInfo( @JvmField @TarsId(0) val uTimestamp: Long? = null, @JvmField @TarsId(1) val uSchoolId: Long? = null, @JvmField @TarsId(2) val iIsValidForCertified: Int? = null, ) : JceStruct @Serializable internal class TCoverInfo( @JvmField @TarsId(0) val vTagInfo: ByteArray? = null, ) : JceStruct @Serializable internal class TNearbyGodInfo( @JvmField @TarsId(0) val iIsGodFlag: Int? = null, @JvmField @TarsId(1) val jumpUrl: String? = "", ) : JceStruct @Serializable internal class TNowBroadcastInfo( @JvmField @TarsId(0) val iFlag: Int? = null, @JvmField @TarsId(1) val iconURL: String? = "", @JvmField @TarsId(2) val hrefURL: String? = "", @JvmField @TarsId(3) val vAnchorDataRsp: ByteArray? = null, ) : JceStruct @Serializable internal class TPraiseInfo( @JvmField @TarsId(0) val uCustomId: Long? = null, @JvmField @TarsId(1) val iIsPayed: Int? = null, ) : JceStruct @Serializable internal class TQimInfo( @JvmField @TarsId(0) val iIsOnline: Int? = null, ) : JceStruct @Serializable internal class TTimInfo( @JvmField @TarsId(0) val iIsOnline: Int? = null, ) : JceStruct @Serializable internal class TVideoHeadInfo( @JvmField @TarsId(0) val iNearbyFlag: Int? = null, @JvmField @TarsId(1) val iBasicFlag: Int? = null, @JvmField @TarsId(2) val vMsg: ByteArray? = null, ) : JceStruct @Serializable internal class UserFeed( @JvmField @TarsId(0) val uFlag: Long? = null, @JvmField @TarsId(1) val vFeedInfo: ByteArray? = null, ) : JceStruct @Serializable internal class UserLocaleInfo( @JvmField @TarsId(1) val longitude: Long? = null, @JvmField @TarsId(2) val latitude: Long? = null, ) : JceStruct @Serializable internal class VoiceInfo( @JvmField @TarsId(0) val vVoiceId: ByteArray? = null, @JvmField @TarsId(1) val shDuration: Short? = null, @JvmField @TarsId(2) val read: Byte? = 2, @JvmField @TarsId(3) val url: String? = "", ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/RequestMSFForceOffline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class RequestMSFForceOffline( @TarsId(0) @JvmField val uin: Long = 0L, @TarsId(1) @JvmField val iSeqno: Long = 0L, @TarsId(2) @JvmField val kickType: Byte = 0, @TarsId(3) @JvmField val info: String = "", @TarsId(4) @JvmField val title: String? = "", @TarsId(5) @JvmField val sigKick: Byte? = 0, @TarsId(6) @JvmField val vecSigKickData: ByteArray? = null, @TarsId(7) @JvmField val sameDevice: Byte? = 0, ) : JceStruct @Serializable internal class RspMSFForceOffline( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val seq: Long, @TarsId(2) @JvmField val const: Byte = 0, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/RequestPacket.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField private val EMPTY_MAP = mapOf<String, String>() @Serializable internal class RequestPacket( @TarsId(1) @JvmField val version: Short? = 3, @TarsId(2) @JvmField val cPacketType: Byte = 0, @TarsId(3) @JvmField val iMessageType: Int = 0, @TarsId(4) @JvmField val requestId: Int = 0, @TarsId(5) @JvmField val servantName: String = "", @TarsId(6) @JvmField val funcName: String = "", @TarsId(7) @JvmField val sBuffer: ByteArray = EMPTY_BYTE_ARRAY, @TarsId(8) @JvmField val iTimeout: Int? = 0, @TarsId(9) @JvmField val context: Map<String, String>? = EMPTY_MAP, @TarsId(10) @JvmField val status: Map<String, String>? = EMPTY_MAP, ) : JceStruct @Serializable internal class RequestDataVersion3( @TarsId(0) @JvmField val map: Map<String, ByteArray>, // 注意: ByteArray 不能直接放序列化的 JceStruct!! 要放类似 RequestDataStructSvcReqRegister 的 ) : JceStruct @Serializable internal class RequestDataVersion2( @TarsId(0) @JvmField val map: Map<String, Map<String, ByteArray>>, ) : JceStruct @Serializable internal class RequestDataStructSvcReqRegister( @TarsId(0) @JvmField val struct: SvcReqRegister, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/RequestPushForceOffline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class RequestPushForceOffline( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val title: String = "", @TarsId(2) @JvmField val tips: String = "", @TarsId(3) @JvmField val sameDevice: Byte? = null, ) : JceStruct, Packet ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SetGroupPack.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class SetGroupReq( @JvmField @TarsId(0) val reqtype: Int = 0, @JvmField @TarsId(1) val uin: Long = 0L, @JvmField @TarsId(2) val vecBody: ByteArray? = null ) : JceStruct @Serializable internal class SetGroupResp( @JvmField @TarsId(0) val reqtype: Byte = 0, @JvmField @TarsId(1) val result: Byte = 0, @JvmField @TarsId(2) val vecBody: ByteArray? = null, @JvmField @TarsId(3) val errorString: String = "" ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcDevLoginInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal data class SvcDevLoginInfo( @JvmField @TarsId(0) val iAppId: Long, // @JvmField @TarsId(1) val vecGuid: ByteArray? = null, @JvmField @TarsId(2) val iLoginTime: Long, @JvmField @TarsId(3) val iLoginPlatform: Long? = null, // 1: ios, 2: android, 3: windows, 4: symbian, 5: feature @JvmField @TarsId(4) val loginLocation: String? = "", @JvmField @TarsId(5) val deviceName: String? = "", @JvmField @TarsId(6) val deviceTypeInfo: String? = "", // @JvmField @TarsId(7) val stDeviceItemDes: DeviceItemDes? = null, @JvmField @TarsId(8) val iTerType: Long? = null, // 1:windows, 2: mobile, 3: ios @JvmField @TarsId(9) val iProductType: Long? = null, // always 0 @JvmField @TarsId(10) val iCanBeKicked: Long? = null, // isOnline ) : JceStruct /* vecCurrentLoginDevInfo=[SvcDevLoginInfo#1676411955 { deviceName=mirai deviceTypeInfo=mirai iAppId=0x000000002002E738(537061176) iCanBeKicked=0x0000000000000001(1) iLoginPlatform=0x0000000000000002(2) iLoginTime=0x000000005FE4A45C(1608819804) iProductType=0x0000000000000000(0) iTerType=0x0000000000000002(2) }, SvcDevLoginInfo#1676411955 { deviceName=xxx的iPad deviceTypeInfo=iPad iAppId=0x000000002002FB7C(537066364) iCanBeKicked=0x0000000000000001(1) iLoginPlatform=0x0000000000000001(1) iLoginTime=0x000000005FE4A418(1608819736) iProductType=0x0000000000000000(0) iTerType=0x0000000000000003(3) }, SvcDevLoginInfo#1676411955 { deviceName=Mi 10 Pro deviceTypeInfo=Mi 10 Pro iAppId=0x000000002002FBB7(537066423) iCanBeKicked=0x0000000000000001(1) iLoginPlatform=0x0000000000000002(2) iLoginTime=0x000000005FE4A628(1608820264) iProductType=0x0000000000000000(0) iTerType=0x0000000000000002(2) }, SvcDevLoginInfo#1676411955 { deviceName=DESKTOP-KMQEB7V deviceTypeInfo=电脑 iAppId=0x0000000000000001(1) iCanBeKicked=0x0000000000000001(1) iLoginPlatform=0x0000000000000003(3) iLoginTime=0x000000005FE4A5C1(1608820161) iProductType=0x0000000000000000(0) iTerType=0x0000000000000001(1) loginLocation=中国湖北省武汉市 }] */ @Serializable internal class SvcReqGetDevLoginInfo( @JvmField @TarsId(0) val vecGuid: ByteArray, @JvmField @TarsId(1) val appName: String = "", @JvmField @TarsId(2) val iLoginType: Long = 1L, @JvmField @TarsId(3) val iTimeStamp: Long, @JvmField @TarsId(4) val iNextItemIndex: Long, @JvmField @TarsId(5) val iRequireMax: Long, @JvmField @TarsId(6) val iGetDevListType: Long? = 7L, // 1: online list 2: recent list? 4: getAuthLoginDevList? ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcReqMSFLoginNotifyData.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField // ANDROID PHONE QQ // 2020-12-23 20:16:57 D/soutv: PK = //SvcReqMSFLoginNotifyData(iAppId=537066423, //status=2, //tablet=0, //iPlatform=109, //title=下线通知, //info=你的帐号在手机上退出了, //iProductType=0, //iClientType=65799, //vecInstanceList=[]) // ANDROID PHONE QQ // 2020-12-23 20:21:02 D/soutv: PK = SvcReqMSFLoginNotifyData( //iAppId=537066423, //status=1, //tablet=0, //iPlatform=109, title=上线通知, info=你的帐号在手机上登录了, iProductType=0, iClientType=65799, vecInstanceList=[InstanceInfo(iAppId=537066423, tablet=0, iPlatform=109, iProductType=0, iClientType=65799)]) @Serializable internal data class SvcReqMSFLoginNotifyData( @JvmField @TarsId(0) val iAppId: Long, @JvmField @TarsId(1) val status: Byte, // 上线=1, 下线=2 @JvmField @TarsId(2) val tablet: Byte? = null, @JvmField @TarsId(3) val iPlatform: Long? = null, @JvmField @TarsId(4) val title: String? = "", @JvmField @TarsId(5) val info: String? = "", @JvmField @TarsId(6) val iProductType: Long? = null, @JvmField @TarsId(7) val iClientType: Long? = null, @JvmField @TarsId(8) val vecInstanceList: List<InstanceInfo>? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcReqRegister.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class SvcReqRegister( @TarsId(0) @JvmField var lUin: Long = 0L, @TarsId(1) @JvmField var lBid: Long = 0L, @TarsId(2) @JvmField var cConnType: Byte = 0, @TarsId(3) @JvmField var sOther: String = "", @TarsId(4) @JvmField var iStatus: Int = 11, @TarsId(5) @JvmField var bOnlinePush: Byte = 0, @TarsId(6) @JvmField var bIsOnline: Byte = 0, @TarsId(7) @JvmField var bIsShowOnline: Byte = 0, @TarsId(8) @JvmField var bKikPC: Byte = 0, @TarsId(9) @JvmField var bKikWeak: Byte = 0, @TarsId(10) @JvmField var timeStamp: Long = 0L, @TarsId(11) @JvmField var iOSVersion: Long = 0L, @TarsId(12) @JvmField var cNetType: Byte = 0, @TarsId(13) @JvmField var sBuildVer: String? = "", @TarsId(14) @JvmField var bRegType: Byte = 0, @TarsId(15) @JvmField var vecDevParam: ByteArray? = null, @TarsId(16) @JvmField var vecGuid: ByteArray? = null, @TarsId(17) @JvmField var iLocaleID: Int = 2052, @TarsId(18) @JvmField var bSlientPush: Byte = 0, @TarsId(19) @JvmField var strDevName: String? = null, @TarsId(20) @JvmField var strDevType: String? = null, @TarsId(21) @JvmField var strOSVer: String? = null, @TarsId(22) @JvmField var bOpenPush: Byte, @TarsId(23) @JvmField var iLargeSeq: Long, @TarsId(24) @JvmField var iLastWatchStartTime: Long = 0L, @TarsId(26) @JvmField var uOldSSOIp: Long = 0L, @TarsId(27) @JvmField var uNewSSOIp: Long = 0L, @TarsId(28) @JvmField var sChannelNo: String? = null, @TarsId(29) @JvmField var lCpId: Long = 0L, @TarsId(30) @JvmField var strVendorName: String? = null, @TarsId(31) @JvmField var strVendorOSName: String? = null, @TarsId(32) @JvmField var strIOSIdfa: String? = null, @TarsId(33) @JvmField var bytes_0x769_reqbody: ByteArray? = null, @TarsId(34) @JvmField var bIsSetStatus: Byte = 0, @TarsId(35) @JvmField var vecServerBuf: ByteArray? = null, @TarsId(36) @JvmField var bSetMute: Byte = 0, // @SerialId(25) var vecBindUin: ArrayList<*>? = null // ?? 未知泛型 ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRequestPushReadedNotify.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class SvcRequestPushReadedNotify( @JvmField @TarsId(0) val notifyType: Byte, @JvmField @TarsId(1) val vC2CReadedNotify: List<C2CMsgReadedNotify>? = null, @JvmField @TarsId(2) val vGroupReadedNotify: List<GroupMsgReadedNotify>? = null, @JvmField @TarsId(3) val vDisReadedNotify: List<DisMsgReadedNotify>? = null, ) : JceStruct @Serializable internal class C2CMsgReadedNotify( @JvmField @TarsId(0) val peerUin: Long? = null, @JvmField @TarsId(1) val lastReadTime: Long? = null, @JvmField @TarsId(2) val flag: Long? = null, @JvmField @TarsId(3) val phoneNum: String? = "", @JvmField @TarsId(4) val bindedUin: Long? = null, ) : JceStruct @Serializable internal class DisMsgReadedNotify( @JvmField @TarsId(0) val disUin: Long? = null, @JvmField @TarsId(1) val opType: Long? = null, @JvmField @TarsId(2) val memberSeq: Long? = null, @JvmField @TarsId(3) val disMsgSeq: Long? = null, ) : JceStruct @Serializable internal class GPicInfo( @JvmField @TarsId(0) val vPath: ByteArray, @JvmField @TarsId(1) val vHost: ByteArray? = null, ) : JceStruct @Serializable internal class GroupMsgHead( @JvmField @TarsId(0) val usCmdType: Int, @JvmField @TarsId(1) val totalPkg: Byte, @JvmField @TarsId(2) val curPkg: Byte, @JvmField @TarsId(3) val usPkgSeq: Int, @JvmField @TarsId(4) val dwReserved: Long, ) : JceStruct @Serializable internal class GroupMsgReadedNotify( @JvmField @TarsId(0) val groupCode: Long? = null, @JvmField @TarsId(1) val opType: Long? = null, @JvmField @TarsId(2) val memberSeq: Long? = null, @JvmField @TarsId(3) val groupMsgSeq: Long? = null, ) : JceStruct @Serializable internal class RequestPushGroupMsg( @JvmField @TarsId(0) val uin: Long, @JvmField @TarsId(1) val type: Byte, @JvmField @TarsId(2) val service: String = "", @JvmField @TarsId(3) val cmd: String = "", @JvmField @TarsId(4) val groupCode: Long, @JvmField @TarsId(5) val groupType: Byte, @JvmField @TarsId(6) val sendUin: Long, @JvmField @TarsId(7) val lsMsgSeq: Long, @JvmField @TarsId(8) val uMsgTime: Int, @JvmField @TarsId(9) val infoSeq: Long, @JvmField @TarsId(10) val shMsgLen: Short, @JvmField @TarsId(11) val vMsg: ByteArray, @JvmField @TarsId(12) val groupCard: String? = "", @JvmField @TarsId(13) val uAppShareID: Long? = null, @JvmField @TarsId(14) val vGPicInfo: List<GPicInfo>? = null, @JvmField @TarsId(15) val vAppShareCookie: ByteArray? = null, @JvmField @TarsId(16) val stShareData: ShareData? = null, @JvmField @TarsId(17) val fromInstId: Long? = null, @JvmField @TarsId(18) val stGroupMsgHead: GroupMsgHead? = null, @JvmField @TarsId(19) val wUserActive: Int? = null, @JvmField @TarsId(20) val vMarketFace: List<MarketFaceInfo>? = null, @JvmField @TarsId(21) val uSuperQQBubbleId: Long? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRespRegister.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.network.FriendListCache import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class SvcRespRegister( @JvmField @TarsId(0) val uin: Long = 0L, @JvmField @TarsId(1) val bid: Long = 0L, @JvmField @TarsId(2) val replyCode: Byte = 0, @JvmField @TarsId(3) val result: String = "", @JvmField @TarsId(4) val serverTime: Long = 0L, @JvmField @TarsId(5) val logQQ: Byte = 0, @JvmField @TarsId(6) val needKik: Byte = 0, @JvmField @TarsId(7) val updateFlag: Byte = 0, @JvmField @TarsId(8) val timeStamp: Long = 0L, @JvmField @TarsId(9) val crashFlag: Byte? = 0, @JvmField @TarsId(10) val clientIP: String = "", @JvmField @TarsId(11) val iClientPort: Int = 0, @JvmField @TarsId(12) val iHelloInterval: Int = 300, @JvmField @TarsId(13) val iLargeSeq: Long = 0L, /** * =1 好友列表更新 */ @JvmField @TarsId(14) val largeSeqUpdate: Byte = 0, @JvmField @TarsId(15) val bytes_0x769_rspBody: ByteArray? = null, @JvmField @TarsId(16) val iStatus: Int? = 0, ) : JceStruct internal fun FriendListCache.isValid(svcRespRegister: SvcRespRegister): Boolean { return svcRespRegister.iLargeSeq == friendListSeq && svcRespRegister.timeStamp == timeStamp // return this.largeSeqUpdate != 0.toByte() } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/SvcRspGetDevLoginInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class SvcRspGetDevLoginInfo( @JvmField @TarsId(0) val iResult: Int, @JvmField @TarsId(1) val result: String? = "", @JvmField @TarsId(2) val iNextItemIndex: Long, @JvmField @TarsId(3) val iTotalItemCount: Long, @JvmField @TarsId(4) val vecCurrentLoginDevInfo: List<SvcDevLoginInfo>? = null, @JvmField @TarsId(5) val vecHistoryLoginDevInfo: List<SvcDevLoginInfo>? = null, @JvmField @TarsId(6) val vecAuthLoginDevInfo: List<SvcDevLoginInfo>? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/jce/TroopList.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.jce import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import kotlin.jvm.JvmField @Serializable internal class GetTroopListReqV2Simplify( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val getMSFMsgFlag: Byte? = null, @TarsId(2) @JvmField val vecCookies: ByteArray? = null, @TarsId(3) @JvmField val vecGroupInfo: List<StTroopNumSimplify>? = null, @TarsId(4) @JvmField val groupFlagExt: Byte? = null, @TarsId(5) @JvmField val shVersion: Int? = null, @TarsId(6) @JvmField val dwCompanyId: Long? = null, @TarsId(7) @JvmField val versionNum: Long? = null, @TarsId(8) @JvmField val getLongGroupName: Byte? = null, ) : JceStruct @Serializable internal class StTroopNumSimplify( @TarsId(0) @JvmField val groupCode: Long, @TarsId(1) @JvmField val dwGroupInfoSeq: Long? = null, @TarsId(2) @JvmField val dwGroupFlagExt: Long? = null, @TarsId(3) @JvmField val dwGroupRankSeq: Long? = null, @TarsId(4) @JvmField val dwGroupInfoExtSeq: Long? = 0L, ) : JceStruct @Serializable internal class GetTroopListRespV2( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val troopCount: Short, @TarsId(2) @JvmField val result: Int, @TarsId(3) @JvmField val errorCode: Short? = null, @TarsId(4) @JvmField val vecCookies: ByteArray? = null, @TarsId(5) @JvmField val vecTroopList: List<StTroopNum>? = null, @TarsId(6) @JvmField val vecTroopListDel: List<StTroopNum>? = null, @TarsId(7) @JvmField val vecTroopRank: List<StGroupRankInfo>? = null, @TarsId(8) @JvmField val vecFavGroup: List<StFavoriteGroup>? = null, @TarsId(9) @JvmField val vecTroopListExt: List<StTroopNum>? = null, @TarsId(10) @JvmField val vecGroupInfoExt: List<Long>? = null, ) : JceStruct @Serializable internal class StTroopNum( @TarsId(0) @JvmField val groupUin: Long, @TarsId(1) @JvmField val groupCode: Long, @TarsId(2) @JvmField val flag: Byte? = null, @TarsId(3) @JvmField val dwGroupInfoSeq: Long? = null, @TarsId(4) @JvmField val groupName: String = "", @TarsId(5) @JvmField val groupMemo: String = "", @TarsId(6) @JvmField val dwGroupFlagExt: Long? = null, @TarsId(7) @JvmField val dwGroupRankSeq: Long? = null, @TarsId(8) @JvmField val dwCertificationType: Long? = null, @TarsId(9) @JvmField val dwShutUpTimestamp: Long? = null, @TarsId(10) @JvmField val dwMyShutUpTimestamp: Long? = null, @TarsId(11) @JvmField val dwCmdUinUinFlag: Long? = null, @TarsId(12) @JvmField val dwAdditionalFlag: Long? = null, @TarsId(13) @JvmField val dwGroupTypeFlag: Long? = null, @TarsId(14) @JvmField val dwGroupSecType: Long? = null, @TarsId(15) @JvmField val dwGroupSecTypeInfo: Long? = null, @TarsId(16) @JvmField val dwGroupClassExt: Long? = null, @TarsId(17) @JvmField val dwAppPrivilegeFlag: Long? = null, @TarsId(18) @JvmField val dwSubscriptionUin: Long? = null, @TarsId(19) @JvmField val dwMemberNum: Long? = null, @TarsId(20) @JvmField val dwMemberNumSeq: Long? = null, @TarsId(21) @JvmField val dwMemberCardSeq: Long? = null, @TarsId(22) @JvmField val dwGroupFlagExt3: Long? = null, @TarsId(23) @JvmField val dwGroupOwnerUin: Long, @TarsId(24) @JvmField val isConfGroup: Byte? = null, @TarsId(25) @JvmField val isModifyConfGroupFace: Byte? = null, @TarsId(26) @JvmField val isModifyConfGroupName: Byte? = null, @TarsId(27) @JvmField val dwCmduinJoinTime: Long? = null, @TarsId(28) @JvmField val ulCompanyId: Long? = null, @TarsId(29) @JvmField val dwMaxGroupMemberNum: Long? = null, @TarsId(30) @JvmField val dwCmdUinGroupMask: Long? = null, @TarsId(31) @JvmField val udwHLGuildAppid: Long? = null, @TarsId(32) @JvmField val udwHLGuildSubType: Long? = null, @TarsId(33) @JvmField val udwCmdUinRingtoneID: Long? = null, @TarsId(34) @JvmField val udwCmdUinFlagEx2: Long? = null, @TarsId(35) @JvmField val dwGroupFlagExt4: Long? = 0L, @TarsId(36) @JvmField val dwAppealDeadline: Long? = 0L, @TarsId(37) @JvmField val dwGroupFlag: Long? = 0L, @TarsId(38) @JvmField val vecGroupRemark: ByteArray? = null, ) : JceStruct @Serializable internal class StGroupRankInfo( @TarsId(0) @JvmField val dwGroupCode: Long = 0L, @TarsId(1) @JvmField val groupRankSysFlag: Byte? = 0, @TarsId(2) @JvmField val groupRankUserFlag: Byte? = 0, @TarsId(3) @JvmField val vecRankMap: List<StLevelRankPair>? = null, @TarsId(4) @JvmField val dwGroupRankSeq: Long? = 0L, @TarsId(5) @JvmField val ownerName: String? = "", @TarsId(6) @JvmField val adminName: String? = "", @TarsId(7) @JvmField val dwOfficeMode: Long? = 0L, @TarsId(8) @JvmField val groupRankUserFlagNew: Byte? = 0, @TarsId(9) @JvmField val vecRankMapNew: List<StLevelRankPair>? = null, ) : JceStruct @Serializable internal class StFavoriteGroup( @TarsId(0) @JvmField val dwGroupCode: Long, @TarsId(1) @JvmField val dwTimestamp: Long? = null, @TarsId(2) @JvmField val dwSnsFlag: Long? = 1L, @TarsId(3) @JvmField val dwOpenTimestamp: Long? = null, ) : JceStruct @Serializable internal class StLevelRankPair( @TarsId(0) @JvmField val dwLevel: Long? = null, @TarsId(1) @JvmField val rank: String? = "", ) : JceStruct @Serializable internal class GetTroopMemberListReq( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val groupCode: Long, @TarsId(2) @JvmField val nextUin: Long, @TarsId(3) @JvmField val groupUin: Long, @TarsId(4) @JvmField val version: Long? = null, @TarsId(5) @JvmField val reqType: Long? = null, @TarsId(6) @JvmField val getListAppointTime: Long? = null, @TarsId(7) @JvmField val richCardNameVer: Byte? = null, ) : JceStruct @Serializable internal class GetTroopMemberListResp( @TarsId(0) @JvmField val uin: Long, @TarsId(1) @JvmField val groupCode: Long, @TarsId(2) @JvmField val groupUin: Long, @TarsId(3) @JvmField val vecTroopMember: List<StTroopMemberInfo>, @TarsId(4) @JvmField val nextUin: Long, @TarsId(5) @JvmField val result: Int, @TarsId(6) @JvmField val errorCode: Short? = null, @TarsId(7) @JvmField val officeMode: Long? = null, @TarsId(8) @JvmField val nextGetTime: Long? = null, ) : JceStruct @Serializable internal class StTroopMemberInfo( @TarsId(0) @JvmField val memberUin: Long, @TarsId(1) @JvmField val faceId: Short, @TarsId(2) @JvmField val age: Byte, @TarsId(3) @JvmField val gender: Byte, @TarsId(4) @JvmField val nick: String = "", @TarsId(5) @JvmField val status: Byte = 20, @TarsId(6) @JvmField val sShowName: String? = null, @TarsId(8) @JvmField val sName: String? = null, @TarsId(9) @JvmField val cGender: Byte? = null, @TarsId(10) @JvmField val sPhone: String? = "", @TarsId(11) @JvmField val sEmail: String? = "", @TarsId(12) @JvmField val sMemo: String? = "", @TarsId(13) @JvmField val autoRemark: String? = "", @TarsId(14) @JvmField val dwMemberLevel: Long? = null, @TarsId(15) @JvmField val dwJoinTime: Long? = null, @TarsId(16) @JvmField val dwLastSpeakTime: Long? = null, @TarsId(17) @JvmField val dwCreditLevel: Long? = null, @TarsId(18) @JvmField val dwFlag: Long? = null, @TarsId(19) @JvmField val dwFlagExt: Long? = null, @TarsId(20) @JvmField val dwPoint: Long? = null, @TarsId(21) @JvmField val concerned: Byte? = null, @TarsId(22) @JvmField val shielded: Byte? = null, @TarsId(23) @JvmField val sSpecialTitle: String? = "", @TarsId(24) @JvmField val dwSpecialTitleExpireTime: Long? = null, @TarsId(25) @JvmField val job: String? = "", @TarsId(26) @JvmField val apolloFlag: Byte? = null, @TarsId(27) @JvmField val dwApolloTimestamp: Long? = null, @TarsId(28) @JvmField val dwGlobalGroupLevel: Long? = null, @TarsId(29) @JvmField val dwTitleId: Long? = null, @TarsId(30) @JvmField val dwShutupTimestap: Long? = null, @TarsId(31) @JvmField val dwGlobalGroupPoint: Long? = null, @TarsId(32) @JvmField val qzusrinfo: QzoneUserInfo? = null, @TarsId(33) @JvmField val richCardNameVer: Byte? = null, @TarsId(34) @JvmField val dwVipType: Long? = null, @TarsId(35) @JvmField val dwVipLevel: Long? = null, @TarsId(36) @JvmField val dwBigClubLevel: Long? = null, @TarsId(37) @JvmField val dwBigClubFlag: Long? = null, @TarsId(38) @JvmField val dwNameplate: Long? = null, @TarsId(39) @JvmField val vecGroupHonor: ByteArray? = null, @TarsId(40) @JvmField val vecName: ByteArray? = null, @TarsId(41) @JvmField val richFlag: Byte? = 0, ) : JceStruct @Serializable internal class QzoneUserInfo( @TarsId(0) @JvmField val eStarState: Int? = null, @TarsId(1) @JvmField val extendInfo: Map<String, String>? = null, ) : JceStruct ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x346.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class Cmd0x346 : ProtoBuf { @Serializable internal class AddrList( @JvmField @ProtoNumber(2) val strIp: List<String> = emptyList(), @JvmField @ProtoNumber(3) val strDomain: String = "", @JvmField @ProtoNumber(4) val port: Int = 0, ) : ProtoBuf @Serializable internal class ApplyCleanTrafficReq : ProtoBuf @Serializable internal class ApplyCleanTrafficRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", ) : ProtoBuf @Serializable internal class ApplyCopyFromReq( @JvmField @ProtoNumber(10) val srcUin: Long = 0L, @JvmField @ProtoNumber(20) val srcGroup: Long = 0L, @JvmField @ProtoNumber(30) val srcSvcid: Int = 0, @JvmField @ProtoNumber(40) val srcParentfolder: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(50) val srcUuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(60) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val dstUin: Long = 0L, @JvmField @ProtoNumber(80) val fileSize: Long = 0L, @JvmField @ProtoNumber(90) val fileName: String = "", @JvmField @ProtoNumber(100) val dangerLevel: Int = 0, @JvmField @ProtoNumber(110) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyCopyFromRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(40) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyCopyToReq( @JvmField @ProtoNumber(10) val dstId: Long = 0L, @JvmField @ProtoNumber(20) val dstUin: Long = 0L, @JvmField @ProtoNumber(30) val dstSvcid: Int = 0, @JvmField @ProtoNumber(40) val srcUin: Long = 0L, @JvmField @ProtoNumber(50) val fileSize: Long = 0L, @JvmField @ProtoNumber(60) val fileName: String = "", @JvmField @ProtoNumber(70) val localFilepath: String = "", @JvmField @ProtoNumber(80) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ApplyCopyToRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val fileKey: String = "", ) : ProtoBuf @Serializable internal class ApplyDownloadAbsReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ApplyDownloadAbsRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val msgDownloadInfo: Cmd0x346.DownloadInfo? = null, ) : ProtoBuf @Serializable internal class ApplyDownloadReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(30) val ownerType: Int = 0, @JvmField @ProtoNumber(500) val extUintype: Int = 0, @JvmField @ProtoNumber(501) val needHttpsUrl: Int = 0, ) : ProtoBuf @Serializable internal class ApplyDownloadRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val msgDownloadInfo: Cmd0x346.DownloadInfo? = null, @JvmField @ProtoNumber(40) val msgFileInfo: Cmd0x346.FileInfo? = null, ) : ProtoBuf @Serializable internal class ApplyForwardFileReq( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(40) val dangerLevel: Int = 0, @JvmField @ProtoNumber(50) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyForwardFileRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val totalSpace: Long = 0L, @JvmField @ProtoNumber(40) val usedSpace: Long = 0L, @JvmField @ProtoNumber(50) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ApplyGetTrafficReq : ProtoBuf @Serializable internal class ApplyGetTrafficRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val useFileSize: Long = 0L, @JvmField @ProtoNumber(40) val useFileNum: Int = 0, @JvmField @ProtoNumber(50) val allFileSize: Long = 0L, @JvmField @ProtoNumber(60) val allFileNum: Int = 0, ) : ProtoBuf @Serializable internal class ApplyListDownloadReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val beginIndex: Int = 0, @JvmField @ProtoNumber(30) val reqCount: Int = 0, ) : ProtoBuf @Serializable internal class ApplyListDownloadRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val totalCount: Int = 0, @JvmField @ProtoNumber(40) val beginIndex: Int = 0, @JvmField @ProtoNumber(50) val rspCount: Int = 0, @JvmField @ProtoNumber(60) val isEnd: Int = 0, @JvmField @ProtoNumber(70) val msgFileList: List<Cmd0x346.FileInfo> = emptyList(), ) : ProtoBuf @Serializable internal class ApplyUploadHitReq( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val fileSize: Long = 0L, @JvmField @ProtoNumber(40) val fileName: String = "", @JvmField @ProtoNumber(50) val _10mMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(60) val localFilepath: String = "", @JvmField @ProtoNumber(70) val dangerLevel: Int = 0, @JvmField @ProtoNumber(80) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyUploadHitReqV2( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val fileSize: Long = 0L, @JvmField @ProtoNumber(40) val fileName: String = "", @JvmField @ProtoNumber(50) val _10mMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(60) val _3sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(80) val localFilepath: String = "", @JvmField @ProtoNumber(90) val dangerLevel: Int = 0, @JvmField @ProtoNumber(100) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyUploadHitReqV3( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val fileSize: Long = 0L, @JvmField @ProtoNumber(40) val fileName: String = "", @JvmField @ProtoNumber(50) val _10mMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(60) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val localFilepath: String = "", @JvmField @ProtoNumber(80) val dangerLevel: Int = 0, @JvmField @ProtoNumber(90) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyUploadHitRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val uploadIp: String = "", @JvmField @ProtoNumber(40) val uploadPort: Int = 0, @JvmField @ProtoNumber(50) val uploadDomain: String = "", @JvmField @ProtoNumber(60) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val uploadKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(80) val totalSpace: Long = 0L, @JvmField @ProtoNumber(90) val usedSpace: Long = 0L, @JvmField @ProtoNumber(100) val uploadDns: String = "", ) : ProtoBuf @Serializable internal class ApplyUploadHitRspV2( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val uploadIp: String = "", @JvmField @ProtoNumber(40) val uploadPort: Int = 0, @JvmField @ProtoNumber(50) val uploadDomain: String = "", @JvmField @ProtoNumber(60) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val uploadKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(80) val totalSpace: Long = 0L, @JvmField @ProtoNumber(90) val usedSpace: Long = 0L, @JvmField @ProtoNumber(100) val uploadHttpsPort: Int = 443, @JvmField @ProtoNumber(110) val uploadHttpsDomain: String = "", @JvmField @ProtoNumber(120) val uploadDns: String = "", ) : ProtoBuf @Serializable internal class ApplyUploadHitRspV3( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val uploadIp: String = "", @JvmField @ProtoNumber(40) val uploadPort: Int = 0, @JvmField @ProtoNumber(50) val uploadDomain: String = "", @JvmField @ProtoNumber(60) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val uploadKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(80) val totalSpace: Long = 0L, @JvmField @ProtoNumber(90) val usedSpace: Long = 0L, @JvmField @ProtoNumber(100) val uploadDns: String = "", ) : ProtoBuf @Serializable internal class ApplyUploadReq( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val fileType: Int = 0, @JvmField @ProtoNumber(40) val fileSize: Long = 0L, @JvmField @ProtoNumber(50) val fileName: ByteArray = EMPTY_BYTE_ARRAY, //String = "", @JvmField @ProtoNumber(60) val _10mMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val localFilepath: String = "", @JvmField @ProtoNumber(80) val dangerLevel: Int = 0, @JvmField @ProtoNumber(90) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyUploadReqV2( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val fileSize: Long = 0L, @JvmField @ProtoNumber(40) val fileName: String = "", @JvmField @ProtoNumber(50) val _10mMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(60) val _3sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val localFilepath: String = "", @JvmField @ProtoNumber(80) val dangerLevel: Int = 0, @JvmField @ProtoNumber(90) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyUploadReqV3( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val fileSize: Long = 0L, @JvmField @ProtoNumber(40) val fileName: String = "", @JvmField @ProtoNumber(50) val _10mMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(60) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(70) val localFilepath: String = "", @JvmField @ProtoNumber(80) val dangerLevel: Int = 0, @JvmField @ProtoNumber(90) val totalSpace: Long = 0L, ) : ProtoBuf @Serializable internal class ApplyUploadRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val totalSpace: Long = 0L, @JvmField @ProtoNumber(40) val usedSpace: Long = 0L, @JvmField @ProtoNumber(50) val uploadedSize: Long = 0L, @JvmField @ProtoNumber(60) val uploadIp: String = "", @JvmField @ProtoNumber(70) val uploadDomain: String = "", @JvmField @ProtoNumber(80) val uploadPort: Int = 0, @JvmField @ProtoNumber(90) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(100) val uploadKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(110) val boolFileExist: Boolean = false, @JvmField @ProtoNumber(120) val packSize: Int = 0, @JvmField @ProtoNumber(130) val strUploadipList: List<String> = emptyList(), @JvmField @ProtoNumber(140) val uploadDns: String = "", ) : ProtoBuf @Serializable internal class ApplyUploadRspV2( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val totalSpace: Long = 0L, @JvmField @ProtoNumber(40) val usedSpace: Long = 0L, @JvmField @ProtoNumber(50) val uploadedSize: Long = 0L, @JvmField @ProtoNumber(60) val uploadIp: String = "", @JvmField @ProtoNumber(70) val uploadDomain: String = "", @JvmField @ProtoNumber(80) val uploadPort: Int = 0, @JvmField @ProtoNumber(90) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(100) val uploadKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(110) val boolFileExist: Boolean = false, @JvmField @ProtoNumber(120) val packSize: Int = 0, @JvmField @ProtoNumber(130) val strUploadipList: List<String> = emptyList(), @JvmField @ProtoNumber(140) val httpsvrApiVer: Int = 0, @JvmField @ProtoNumber(141) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(142) val uploadHttpsPort: Int = 443, @JvmField @ProtoNumber(143) val uploadHttpsDomain: String = "", @JvmField @ProtoNumber(150) val uploadDns: String = "", @JvmField @ProtoNumber(160) val uploadLanip: String = "", ) : ProtoBuf @Serializable internal class ApplyUploadRspV3( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val totalSpace: Long = 0L, @JvmField @ProtoNumber(40) val usedSpace: Long = 0L, @JvmField @ProtoNumber(50) val uploadedSize: Long = 0L, @JvmField @ProtoNumber(60) val uploadIp: String = "", @JvmField @ProtoNumber(70) val uploadDomain: String = "", @JvmField @ProtoNumber(80) val uploadPort: Int = 0, @JvmField @ProtoNumber(90) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(100) val uploadKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(110) val boolFileExist: Boolean = false, @JvmField @ProtoNumber(120) val packSize: Int = 0, @JvmField @ProtoNumber(130) val strUploadipList: List<String> = emptyList(), @JvmField @ProtoNumber(140) val uploadHttpsPort: Int = 443, @JvmField @ProtoNumber(150) val uploadHttpsDomain: String = "", @JvmField @ProtoNumber(160) val uploadDns: String = "", @JvmField @ProtoNumber(170) val uploadLanip: String = "", ) : ProtoBuf @Serializable internal class DeleteFileReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val peerUin: Long = 0L, @JvmField @ProtoNumber(30) val deleteType: Int = 0, @JvmField @ProtoNumber(40) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class DeleteFileRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", ) : ProtoBuf @Serializable internal class DelMessageReq( @JvmField @ProtoNumber(1) val uinSender: Long = 0L, @JvmField @ProtoNumber(2) val uinReceiver: Long = 0L, @JvmField @ProtoNumber(10) val msgTime: Int = 0, @JvmField @ProtoNumber(20) val msgRandom: Int = 0, @JvmField @ProtoNumber(30) val msgSeqNo: Int = 0, ) : ProtoBuf @Serializable internal class DownloadInfo( @JvmField @ProtoNumber(10) val downloadKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(20) val downloadIp: String = "", @JvmField @ProtoNumber(30) val downloadDomain: String = "", @JvmField @ProtoNumber(40) val port: Int = 0, @JvmField @ProtoNumber(50) val downloadUrl: String = "", @JvmField @ProtoNumber(60) val strDownloadipList: List<String> = emptyList(), @JvmField @ProtoNumber(70) val cookie: String = "", @JvmField @ProtoNumber(80) val httpsPort: Int = 443, @JvmField @ProtoNumber(90) val httpsDownloadDomain: String = "", @JvmField @ProtoNumber(110) val downloadDns: String = "", ) : ProtoBuf @Serializable internal class DownloadSuccReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class DownloadSuccRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val int32DownStat: Int = 0, ) : ProtoBuf @Serializable internal class ExtensionReq( @JvmField @ProtoNumber(1) val id: Long = 0L, @JvmField @ProtoNumber(2) val type: Long = 0L, @JvmField @ProtoNumber(3) val dstPhonenum: String = "", @JvmField @ProtoNumber(4) val int32PhoneConvertType: Int = 0, @JvmField @ProtoNumber(20) val sig: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(100) val routeId: Long = 0L, @JvmField @ProtoNumber(90100) val msgDelMessageReq: Cmd0x346.DelMessageReq? = null, @JvmField @ProtoNumber(90200) val downloadUrlType: Int = 0, @JvmField @ProtoNumber(90300) val pttFormat: Int = 0, @JvmField @ProtoNumber(90400) val isNeedInnerIp: Int = 0, @JvmField @ProtoNumber(90500) val netType: Int = 255, @JvmField @ProtoNumber(90600) val voiceType: Int = 0, @JvmField @ProtoNumber(90700) val fileType: Int = 0, @JvmField @ProtoNumber(90800) val pttTime: Int = 0, @JvmField @ProtoNumber(90900) val bdhCmdid: Int = 0, @JvmField @ProtoNumber(91000) val reqTransferType: Int = 0, @JvmField @ProtoNumber(91100) val isAuto: Int = 0, ) : ProtoBuf @Serializable internal class ExtensionRsp( @JvmField @ProtoNumber(1) val transferType: Int = 0, @JvmField @ProtoNumber(2) val channelType: Int = 0, @JvmField @ProtoNumber(3) val allowRetry: Int = 0, @JvmField @ProtoNumber(4) val serverAddrIpv6List: Cmd0x346.AddrList? = null, ) : ProtoBuf @Serializable internal class FileInfo( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val dangerEvel: Int = 0, @JvmField @ProtoNumber(3) val fileSize: Long = 0L, @JvmField @ProtoNumber(4) val lifeTime: Int = 0, @JvmField @ProtoNumber(5) val uploadTime: Int = 0, @JvmField @ProtoNumber(6) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val fileName: String = "", @JvmField @ProtoNumber(90) val absFileType: Int = 0, @JvmField @ProtoNumber(100) val _10mMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(101) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(110) val clientType: Int = 0, @JvmField @ProtoNumber(120) val ownerUin: Long = 0L, @JvmField @ProtoNumber(121) val peerUin: Long = 0L, @JvmField @ProtoNumber(130) val expireTime: Int = 0, ) : ProtoBuf @Serializable internal class FileQueryReq( @JvmField @ProtoNumber(10) val uin: Long = 0L, @JvmField @ProtoNumber(20) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class FileQueryRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val msgFileInfo: Cmd0x346.FileInfo? = null, ) : ProtoBuf @Serializable internal class RecallFileReq( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RecallFileRsp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", ) : ProtoBuf @Serializable internal class RecvListQueryReq( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val beginIndex: Int = 0, @JvmField @ProtoNumber(3) val reqCount: Int = 0, ) : ProtoBuf @Serializable internal class RecvListQueryRsp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val fileTotCount: Int = 0, @JvmField @ProtoNumber(4) val beginIndex: Int = 0, @JvmField @ProtoNumber(5) val rspFileCount: Int = 0, @JvmField @ProtoNumber(6) val isEnd: Int = 0, @JvmField @ProtoNumber(7) val msgFileList: List<Cmd0x346.FileInfo> = emptyList(), ) : ProtoBuf @Serializable internal class RenewFileReq( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val addTtl: Int = 0, ) : ProtoBuf @Serializable internal class RenewFileRsp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val seq: Int = 0, @JvmField @ProtoNumber(3) val msgRecvListQueryReq: Cmd0x346.RecvListQueryReq? = null, @JvmField @ProtoNumber(4) val msgSendListQueryReq: Cmd0x346.SendListQueryReq? = null, @JvmField @ProtoNumber(5) val msgRenewFileReq: Cmd0x346.RenewFileReq? = null, @JvmField @ProtoNumber(6) val msgRecallFileReq: Cmd0x346.RecallFileReq? = null, @JvmField @ProtoNumber(7) val msgApplyUploadReq: Cmd0x346.ApplyUploadReq? = null, @JvmField @ProtoNumber(8) val msgApplyUploadHitReq: Cmd0x346.ApplyUploadHitReq? = null, @JvmField @ProtoNumber(9) val msgApplyForwardFileReq: Cmd0x346.ApplyForwardFileReq? = null, @JvmField @ProtoNumber(10) val msgUploadSuccReq: Cmd0x346.UploadSuccReq? = null, @JvmField @ProtoNumber(11) val msgDeleteFileReq: Cmd0x346.DeleteFileReq? = null, @JvmField @ProtoNumber(12) val msgDownloadSuccReq: Cmd0x346.DownloadSuccReq? = null, @JvmField @ProtoNumber(13) val msgApplyDownloadAbsReq: Cmd0x346.ApplyDownloadAbsReq? = null, @JvmField @ProtoNumber(14) val msgApplyDownloadReq: Cmd0x346.ApplyDownloadReq? = null, @JvmField @ProtoNumber(15) val msgApplyListDownloadReq: Cmd0x346.ApplyListDownloadReq? = null, @JvmField @ProtoNumber(16) val msgFileQueryReq: Cmd0x346.FileQueryReq? = null, @JvmField @ProtoNumber(17) val msgApplyCopyFromReq: Cmd0x346.ApplyCopyFromReq? = null, @JvmField @ProtoNumber(18) val msgApplyUploadReqV2: Cmd0x346.ApplyUploadReqV2? = null, @JvmField @ProtoNumber(19) val msgApplyUploadReqV3: Cmd0x346.ApplyUploadReqV3? = null, @JvmField @ProtoNumber(20) val msgApplyUploadHitReqV2: Cmd0x346.ApplyUploadHitReqV2? = null, @JvmField @ProtoNumber(21) val msgApplyUploadHitReqV3: Cmd0x346.ApplyUploadHitReqV3? = null, @JvmField @ProtoNumber(101) val businessId: Int = 0, @JvmField @ProtoNumber(102) val clientType: Int = 0, @JvmField @ProtoNumber(90000) val msgApplyCopyToReq: Cmd0x346.ApplyCopyToReq? = null, @JvmField @ProtoNumber(90001) val msgApplyCleanTrafficReq: Cmd0x346.ApplyCleanTrafficReq? = null, @JvmField @ProtoNumber(90002) val msgApplyGetTrafficReq: Cmd0x346.ApplyGetTrafficReq? = null, @JvmField @ProtoNumber(99999) val msgExtensionReq: Cmd0x346.ExtensionReq? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val seq: Int = 0, @JvmField @ProtoNumber(3) val msgRecvListQueryRsp: Cmd0x346.RecvListQueryRsp? = null, @JvmField @ProtoNumber(4) val msgSendListQueryRsp: Cmd0x346.SendListQueryRsp? = null, @JvmField @ProtoNumber(5) val msgRenewFileRsp: Cmd0x346.RenewFileRsp? = null, @JvmField @ProtoNumber(6) val msgRecallFileRsp: Cmd0x346.RecallFileRsp? = null, @JvmField @ProtoNumber(7) val msgApplyUploadRsp: Cmd0x346.ApplyUploadRsp? = null, @JvmField @ProtoNumber(8) val msgApplyUploadHitRsp: Cmd0x346.ApplyUploadHitRsp? = null, @JvmField @ProtoNumber(9) val msgApplyForwardFileRsp: Cmd0x346.ApplyForwardFileRsp? = null, @JvmField @ProtoNumber(10) val msgUploadSuccRsp: Cmd0x346.UploadSuccRsp? = null, @JvmField @ProtoNumber(11) val msgDeleteFileRsp: Cmd0x346.DeleteFileRsp? = null, @JvmField @ProtoNumber(12) val msgDownloadSuccRsp: Cmd0x346.DownloadSuccRsp? = null, @JvmField @ProtoNumber(13) val msgApplyDownloadAbsRsp: Cmd0x346.ApplyDownloadAbsRsp? = null, @JvmField @ProtoNumber(14) val msgApplyDownloadRsp: Cmd0x346.ApplyDownloadRsp? = null, @JvmField @ProtoNumber(15) val msgApplyListDownloadRsp: Cmd0x346.ApplyListDownloadRsp? = null, @JvmField @ProtoNumber(16) val msgFileQueryRsp: Cmd0x346.FileQueryRsp? = null, @JvmField @ProtoNumber(17) val msgApplyCopyFromRsp: Cmd0x346.ApplyCopyFromRsp? = null, @JvmField @ProtoNumber(18) val msgApplyUploadRspV2: Cmd0x346.ApplyUploadRspV2? = null, @JvmField @ProtoNumber(19) val msgApplyUploadRspV3: Cmd0x346.ApplyUploadRspV3? = null, @JvmField @ProtoNumber(20) val msgApplyUploadHitRspV2: Cmd0x346.ApplyUploadHitRspV2? = null, @JvmField @ProtoNumber(21) val msgApplyUploadHitRspV3: Cmd0x346.ApplyUploadHitRspV3? = null, @JvmField @ProtoNumber(90000) val msgApplyCopyToRsp: Cmd0x346.ApplyCopyToRsp? = null, @JvmField @ProtoNumber(90001) val msgApplyCleanTrafficRsp: Cmd0x346.ApplyCleanTrafficRsp? = null, @JvmField @ProtoNumber(90002) val msgApplyGetTrafficRsp: Cmd0x346.ApplyGetTrafficRsp? = null, @JvmField @ProtoNumber(99999) val msgExtensionRsp: Cmd0x346.ExtensionRsp? = null, ) : ProtoBuf @Serializable internal class SendListQueryReq( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val beginIndex: Int = 0, @JvmField @ProtoNumber(3) val reqCount: Int = 0, ) : ProtoBuf @Serializable internal class SendListQueryRsp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val fileTotCount: Int = 0, @JvmField @ProtoNumber(4) val beginIndex: Int = 0, @JvmField @ProtoNumber(5) val rspFileCount: Int = 0, @JvmField @ProtoNumber(6) val isEnd: Int = 0, @JvmField @ProtoNumber(7) val totLimit: Long = 0L, @JvmField @ProtoNumber(8) val usedLimit: Long = 0L, @JvmField @ProtoNumber(9) val msgFileList: List<Cmd0x346.FileInfo> = emptyList(), ) : ProtoBuf @Serializable internal class UploadSuccReq( @JvmField @ProtoNumber(10) val senderUin: Long = 0L, @JvmField @ProtoNumber(20) val recverUin: Long = 0L, @JvmField @ProtoNumber(30) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class UploadSuccRsp( @JvmField @ProtoNumber(10) val int32RetCode: Int = 0, @JvmField @ProtoNumber(20) val retMsg: String = "", @JvmField @ProtoNumber(30) val msgFileInfo: Cmd0x346.FileInfo? = null, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x352.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class Cmd0x352 : ProtoBuf { @Serializable internal class DelImgReq( @ProtoNumber(1) @JvmField val srcUin: Long = 0L, @ProtoNumber(2) @JvmField val dstUin: Long = 0L, @ProtoNumber(3) @JvmField val reqTerm: Int = 0, @ProtoNumber(4) @JvmField val reqPlatformType: Int = 0, @ProtoNumber(5) @JvmField val buType: Int = 0, @ProtoNumber(6) @JvmField val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val picWidth: Int = 0, @ProtoNumber(9) @JvmField val picHeight: Int = 0, ) : ProtoBuf @Serializable internal class DelImgRsp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GetImgUrlReq( @ProtoNumber(1) @JvmField val srcUin: Long = 0L, @ProtoNumber(2) @JvmField val dstUin: Long = 0L, @ProtoNumber(3) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val urlFlag: Int = 0, @ProtoNumber(6) @JvmField val urlType: Int = 0, @ProtoNumber(7) @JvmField val reqTerm: Int = 0, @ProtoNumber(8) @JvmField val reqPlatformType: Int = 0, @ProtoNumber(9) @JvmField val srcFileType: Int = 0, @ProtoNumber(10) @JvmField val innerIp: Int = 0, @ProtoNumber(11) @JvmField val boolAddressBook: Boolean = false, @ProtoNumber(12) @JvmField val buType: Int = 0, @ProtoNumber(13) @JvmField val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val picUpTimestamp: Int = 0, @ProtoNumber(15) @JvmField val reqTransferType: Int = 0, ) : ProtoBuf @Serializable internal class GetImgUrlRsp( @ProtoNumber(1) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val clientIp: Int = 0, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val bytesThumbDownUrl: List<ByteArray> = emptyList(), @ProtoNumber(6) @JvmField val bytesOriginalDownUrl: List<ByteArray> = emptyList(), @ProtoNumber(7) @JvmField val msgImgInfo: ImgInfo? = null, @ProtoNumber(8) @JvmField val uint32DownIp: List<Int> = emptyList(), @ProtoNumber(9) @JvmField val uint32DownPort: List<Int> = emptyList(), @ProtoNumber(10) @JvmField val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val bytesBigDownUrl: List<ByteArray> = emptyList(), @ProtoNumber(14) @JvmField val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(16) @JvmField val httpsUrlFlag: Int = 0, @ProtoNumber(26) @JvmField val msgDownIp6: List<IPv6Info> = emptyList(), @ProtoNumber(27) @JvmField val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Suppress("ArrayInDataClass") @Serializable internal class ImgInfo( @ProtoNumber(1) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fileType: Int = 0, @ProtoNumber(3) @JvmField val fileSize: Long = 0L, @ProtoNumber(4) @JvmField val fileWidth: Int = 0, @ProtoNumber(5) @JvmField val fileHeight: Int = 0, @ProtoNumber(6) @JvmField val fileFlag: Long = 0L, @ProtoNumber(7) @JvmField val fileCutPos: Int = 0, ) : ProtoBuf @Serializable internal class IPv6Info( @ProtoNumber(1) @JvmField val ip6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val port: Int = 0, ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val subcmd: Int = 0, //2是GetImgUrlReq 1是UploadImgReq @ProtoNumber(2) @JvmField val msgTryupImgReq: List<TryUpImgReq> = emptyList(), // optional @ProtoNumber(3) @JvmField val msgGetimgUrlReq: List<GetImgUrlReq> = emptyList(), // optional @ProtoNumber(4) @JvmField val msgDelImgReq: List<DelImgReq> = emptyList(), @ProtoNumber(10) @JvmField val netType: Int = 0, // 数据网络=5, wifi=3 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val subcmd: Int = 0, @ProtoNumber(2) @JvmField val msgTryupImgRsp: List<TryUpImgRsp> = emptyList(), @ProtoNumber(3) @JvmField val msgGetimgUrlRsp: List<GetImgUrlRsp> = emptyList(), @ProtoNumber(4) @JvmField val boolNewBigchan: Boolean = false, @ProtoNumber(5) @JvmField val msgDelImgRsp: List<DelImgRsp> = emptyList(), @ProtoNumber(10) @JvmField val failMsg: String? = "", ) : ProtoBuf @Serializable internal class TryUpImgReq( @ProtoNumber(1) @JvmField val srcUin: Long, @ProtoNumber(2) @JvmField val dstUin: Long, @ProtoNumber(3) @JvmField val fileId: Long = 0L, //从0开始的自增数?貌似有一个连接就要自增1, 但是又会重置回0 @ProtoNumber(4) @JvmField val fileMd5: ByteArray, @ProtoNumber(5) @JvmField val fileSize: Long, //默认为md5+".jpg" @ProtoNumber(6) @JvmField val fileName: String, @ProtoNumber(7) @JvmField val srcTerm: Int = 5, @ProtoNumber(8) @JvmField val platformType: Int = 9, @ProtoNumber(9) @JvmField val innerIP: Int = 0, @ProtoNumber(10) @JvmField val addressBook: Boolean = false, //chatType == 1006为true 我觉得发false没问题 @ProtoNumber(11) @JvmField val retry: Int = 0, //default @ProtoNumber(12) @JvmField val buType: Int = 1, //1或96 不确定 @ProtoNumber(13) @JvmField val imgOriginal: Boolean = false, //是否为原图 @ProtoNumber(14) @JvmField val imgWidth: Int = 0, @ProtoNumber(15) @JvmField val imgHeight: Int = 0, /** * ImgType: * JPG: 1000 * PNG: 1001 * WEBP: 1002 * BMP: 1005 * GIG: 2000 * APNG: 2001 * SHARPP: 1004 */ @ProtoNumber(16) @JvmField val imgType: Int = 1000, @ProtoNumber(17) @JvmField val buildVer: String = "8.2.7.4410", //版本号 @ProtoNumber(18) @JvmField val fileIndex: ByteArray = EMPTY_BYTE_ARRAY, //default @ProtoNumber(19) @JvmField val fileStoreDays: Int = 0, //default @ProtoNumber(20) @JvmField val stepFlag: Int = 0, //default @ProtoNumber(21) @JvmField val rejectTryFast: Boolean = false, //bool @ProtoNumber(22) @JvmField val srvUpload: Int = 1, //typeHotPic[1/2/3] @ProtoNumber(23) @JvmField val transferUrl: ByteArray = EMPTY_BYTE_ARRAY, //rawDownloadUrl, 如果没有就是EMPTY_BYTE_ARRAY ) : ImgReq @Serializable internal class TryUpImgRsp( @ProtoNumber(1) @JvmField val fileId: Long = 0L, @ProtoNumber(2) @JvmField val clientIp: Int = 0, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val failMsg: String = "", @ProtoNumber(5) @JvmField val boolFileExit: Boolean = false, @ProtoNumber(6) @JvmField val msgImgInfo: ImgInfo? = null, @ProtoNumber(7) @JvmField val uint32UpIp: List<Int> = emptyList(), @ProtoNumber(8) @JvmField val uint32UpPort: List<Int> = emptyList(), @ProtoNumber(9) @JvmField val upUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val upResid: String = "", @ProtoNumber(11) @JvmField val upUuid: String = "", @ProtoNumber(12) @JvmField val upOffset: Long = 0L, @ProtoNumber(13) @JvmField val blockSize: Long = 0L, @ProtoNumber(14) @JvmField val encryptDstip: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val roamdays: Int = 0, @ProtoNumber(26) @JvmField val msgUpIp6: List<IPv6Info> = emptyList(), @ProtoNumber(27) @JvmField val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(60) @JvmField val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(61) @JvmField val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(62) @JvmField val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(64) @JvmField val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(65) @JvmField val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(66) @JvmField val httpsUrlFlag: Int = 0, @ProtoNumber(1001) @JvmField val msgInfo4busi: TryUpInfo4Busi? = null, ) : ProtoBuf @Serializable internal class TryUpInfo4Busi( @ProtoNumber(1) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x388.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class Cmd0x388 : ProtoBuf { @Serializable internal class DelImgReq( @ProtoNumber(1) @JvmField val srcUin: Long = 0L, @ProtoNumber(2) @JvmField val dstUin: Long = 0L, @ProtoNumber(3) @JvmField val reqTerm: Int = 0, @ProtoNumber(4) @JvmField val reqPlatformType: Int = 0, @ProtoNumber(5) @JvmField val buType: Int = 0, @ProtoNumber(6) @JvmField val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val picWidth: Int = 0, @ProtoNumber(9) @JvmField val picHeight: Int = 0, ) : ProtoBuf @Serializable internal class DelImgRsp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ExpRoamExtendInfo( @ProtoNumber(1) @JvmField val resid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ExpRoamPicInfo( @ProtoNumber(1) @JvmField val shopFlag: Int = 0, @ProtoNumber(2) @JvmField val pkgId: Int = 0, @ProtoNumber(3) @JvmField val picId: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ExtensionCommPicTryUp( @ProtoNumber(1) @JvmField val bytesExtinfo: List<ByteArray> = emptyList(), ) : ProtoBuf @Serializable internal class ExtensionExpRoamTryUp( @ProtoNumber(1) @JvmField val msgExproamPicInfo: List<ExpRoamPicInfo> = emptyList(), ) : ProtoBuf @Serializable internal class GetImgUrlReq( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val dstUin: Long = 0L, @ProtoNumber(3) @JvmField val fileid: Long = 0L, @ProtoNumber(4) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val urlFlag: Int = 0, @ProtoNumber(6) @JvmField val urlType: Int = 0, @ProtoNumber(7) @JvmField val reqTerm: Int = 0, @ProtoNumber(8) @JvmField val reqPlatformType: Int = 0, @ProtoNumber(9) @JvmField val innerIp: Int = 0, @ProtoNumber(10) @JvmField val buType: Int = 0, @ProtoNumber(11) @JvmField val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val fileId: Long = 0L, @ProtoNumber(13) @JvmField val fileSize: Long = 0L, @ProtoNumber(14) @JvmField val originalPic: Int = 0, @ProtoNumber(15) @JvmField val retryReq: Int = 0, @ProtoNumber(16) @JvmField val fileHeight: Int = 0, @ProtoNumber(17) @JvmField val fileWidth: Int = 0, @ProtoNumber(18) @JvmField val picType: Int = 0, @ProtoNumber(19) @JvmField val picUpTimestamp: Int = 0, @ProtoNumber(20) @JvmField val reqTransferType: Int = 0, ) : ProtoBuf @Serializable internal class GetImgUrlRsp( @ProtoNumber(1) @JvmField val fileid: Long = 0L, @ProtoNumber(2) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val msgImgInfo: ImgInfo? = null, @ProtoNumber(6) @JvmField val bytesThumbDownUrl: List<ByteArray> = emptyList(), @ProtoNumber(7) @JvmField val bytesOriginalDownUrl: List<ByteArray> = emptyList(), @ProtoNumber(8) @JvmField val bytesBigDownUrl: List<ByteArray> = emptyList(), @ProtoNumber(9) @JvmField val uint32DownIp: List<Int> = emptyList(), @ProtoNumber(10) @JvmField val uint32DownPort: List<Int> = emptyList(), @ProtoNumber(11) @JvmField val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val thumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val originalDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val bigDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val fileId: Long = 0L, @ProtoNumber(16) @JvmField val autoDownType: Int = 0, @ProtoNumber(17) @JvmField val uint32OrderDownType: List<Int> = emptyList(), @ProtoNumber(19) @JvmField val bigThumbDownPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20) @JvmField val httpsUrlFlag: Int = 0, @ProtoNumber(26) @JvmField val msgDownIp6: List<IPv6Info> = emptyList(), @ProtoNumber(27) @JvmField val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GetPttUrlReq( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val dstUin: Long = 0L, @ProtoNumber(3) @JvmField val fileid: Long = 0L, @ProtoNumber(4) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val reqTerm: Int = 0, @ProtoNumber(6) @JvmField val reqPlatformType: Int = 0, @ProtoNumber(7) @JvmField val innerIp: Int = 0, @ProtoNumber(8) @JvmField val buType: Int = 0, @ProtoNumber(9) @JvmField val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val fileId: Long = 0L, @ProtoNumber(11) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val codec: Int = 0, @ProtoNumber(13) @JvmField val buId: Int = 0, @ProtoNumber(14) @JvmField val reqTransferType: Int = 0, @ProtoNumber(15) @JvmField val isAuto: Int = 0, ) : ProtoBuf @Serializable internal class GetPttUrlRsp( @ProtoNumber(1) @JvmField val fileid: Long = 0L, @ProtoNumber(2) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val failMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val bytesDownUrl: List<ByteArray> = emptyList(), @ProtoNumber(6) @JvmField val uint32DownIp: List<Int> = emptyList(), @ProtoNumber(7) @JvmField val uint32DownPort: List<Int> = emptyList(), @ProtoNumber(8) @JvmField val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val downPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val fileId: Long = 0L, @ProtoNumber(11) @JvmField val transferType: Int = 0, @ProtoNumber(12) @JvmField val allowRetry: Int = 0, @ProtoNumber(26) @JvmField val msgDownIp6: List<IPv6Info> = emptyList(), @ProtoNumber(27) @JvmField val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(28) @JvmField val strDomain: String = "", ) : ProtoBuf @Suppress("ArrayInDataClass") @Serializable internal class ImgInfo( @ProtoNumber(1) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fileType: Int = 0, @ProtoNumber(3) @JvmField val fileSize: Long = 0L, @ProtoNumber(4) @JvmField val fileWidth: Int = 0, @ProtoNumber(5) @JvmField val fileHeight: Int = 0, ) : ProtoBuf { override fun toString(): String { return "ImgInfo(fileMd5=${fileMd5.contentToString()}, fileType=$fileType, fileSize=$fileSize, fileWidth=$fileWidth, fileHeight=$fileHeight)" } } @Serializable internal class IPv6Info( @ProtoNumber(1) @JvmField val ip6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val port: Int = 0, ) : ProtoBuf @Serializable internal class PicSize( @ProtoNumber(1) @JvmField val original: Int = 0, @ProtoNumber(2) @JvmField val thumb: Int = 0, @ProtoNumber(3) @JvmField val high: Int = 0, ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val netType: Int = 0, @ProtoNumber(2) @JvmField val subcmd: Int = 0, @ProtoNumber(3) @JvmField val msgTryupImgReq: List<TryUpImgReq> = emptyList(), @ProtoNumber(4) @JvmField val msgGetimgUrlReq: List<GetImgUrlReq> = emptyList(), @ProtoNumber(5) @JvmField val msgTryupPttReq: List<TryUpPttReq> = emptyList(), @ProtoNumber(6) @JvmField val msgGetpttUrlReq: List<GetPttUrlReq> = emptyList(), @ProtoNumber(7) @JvmField val commandId: Int = 0, @ProtoNumber(8) @JvmField val msgDelImgReq: List<DelImgReq> = emptyList(), @ProtoNumber(1001) @JvmField val extension: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val clientIp: Int = 0, @ProtoNumber(2) @JvmField val subcmd: Int = 0, @ProtoNumber(3) @JvmField val msgTryupImgRsp: List<TryUpImgRsp> = emptyList(), @ProtoNumber(4) @JvmField val msgGetimgUrlRsp: List<GetImgUrlRsp> = emptyList(), @ProtoNumber(5) @JvmField val msgTryupPttRsp: List<TryUpPttRsp> = emptyList(), @ProtoNumber(6) @JvmField val msgGetpttUrlRsp: List<GetPttUrlRsp> = emptyList(), @ProtoNumber(7) @JvmField val msgDelImgRsp: List<DelImgRsp> = emptyList(), ) : ProtoBuf @Serializable internal class TryUpImgReq( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val srcUin: Long = 0L, @ProtoNumber(3) @JvmField val fileId: Long = 0L, @ProtoNumber(4) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fileSize: Long = 0L, @ProtoNumber(6) @JvmField val fileName: String = "", @ProtoNumber(7) @JvmField val srcTerm: Int = 0, @ProtoNumber(8) @JvmField val platformType: Int = 0, /** * if uinType = 1 then 1 else2 * UIN_QQ = new UINTYPE(0, 0, "UIN_QQ"); UIN_EMAIL = new UINTYPE(1, 1, "UIN_EMAIL"); UIN_MOBILE = new UINTYPE(2, 2, "UIN_MOBILE"); */ @ProtoNumber(9) @JvmField val buType: Int = 0, @ProtoNumber(10) @JvmField val picWidth: Int = 0, @ProtoNumber(11) @JvmField val picHeight: Int = 0, @ProtoNumber(12) @JvmField val picType: Int = 0, @ProtoNumber(13) @JvmField val buildVer: String = "", @ProtoNumber(14) @JvmField val innerIp: Int = 0, @ProtoNumber(15) @JvmField val appPicType: Int = 0, @ProtoNumber(16) @JvmField val originalPic: Int = 0, @ProtoNumber(17) @JvmField val fileIndex: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val dstUin: Long = 0L, @ProtoNumber(19) @JvmField val srvUpload: Int = 0, @ProtoNumber(20) @JvmField val transferUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ImgReq @Serializable internal class TryUpImgRsp( @ProtoNumber(1) @JvmField val fileId: Long = 0L, @ProtoNumber(2) @JvmField val result: Int = 0, @ProtoNumber(3) @JvmField val failMsg: String = "", @ProtoNumber(4) @JvmField val boolFileExit: Boolean = false, @ProtoNumber(5) @JvmField val msgImgInfo: ImgInfo? = null, @ProtoNumber(6) @JvmField val uint32UpIp: List<Int> = emptyList(), @ProtoNumber(7) @JvmField val uint32UpPort: List<Int> = emptyList(), @ProtoNumber(8) @JvmField val upUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val fileid: Long = 0L, @ProtoNumber(10) @JvmField val upOffset: Long = 0L, @ProtoNumber(11) @JvmField val blockSize: Long = 0L, @ProtoNumber(12) @JvmField val boolNewBigChan: Boolean = false, @ProtoNumber(26) @JvmField val msgUpIp6: List<IPv6Info> = emptyList(), @ProtoNumber(27) @JvmField val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(1001) @JvmField val msgInfo4busi: TryUpInfo4Busi? = null, ) : ProtoBuf @Serializable internal class TryUpInfo4Busi( @ProtoNumber(1) @JvmField val downDomain: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val thumbDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val originalDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val bigDownUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fileResid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class TryUpPttReq( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val srcUin: Long = 0L, @ProtoNumber(3) @JvmField val fileId: Long = 0L, @ProtoNumber(4) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fileSize: Long = 0L, @ProtoNumber(6) @JvmField val fileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val srcTerm: Int = 0, @ProtoNumber(8) @JvmField val platformType: Int = 0, @ProtoNumber(9) @JvmField val buType: Int = 0, @ProtoNumber(10) @JvmField val buildVer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val innerIp: Int = 0, @ProtoNumber(12) @JvmField val voiceLength: Int = 0, @ProtoNumber(13) @JvmField val boolNewUpChan: Boolean = false, @ProtoNumber(14) @JvmField val codec: Int = 0, @ProtoNumber(15) @JvmField val voiceType: Int = 0, @ProtoNumber(16) @JvmField val buId: Int = 0, ) : ProtoBuf @Serializable internal class TryUpPttRsp( @ProtoNumber(1) @JvmField val fileId: Long = 0L, @ProtoNumber(2) @JvmField val result: Int = 0, @ProtoNumber(3) @JvmField val failMsg: ByteArray? = null, @ProtoNumber(4) @JvmField val boolFileExit: Boolean = false, @ProtoNumber(5) @JvmField val uint32UpIp: List<Int> = emptyList(), @ProtoNumber(6) @JvmField val uint32UpPort: List<Int> = emptyList(), @ProtoNumber(7) @JvmField val upUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val fileid: Long = 0L, @ProtoNumber(9) @JvmField val upOffset: Long = 0L, @ProtoNumber(10) @JvmField val blockSize: Long = 0L, @ProtoNumber(11) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val channelType: Int = 0, @ProtoNumber(26) @JvmField val msgUpIp6: List<IPv6Info> = emptyList(), @ProtoNumber(27) @JvmField val clientIp6: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x857.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class GroupOpenSysMsg : ProtoBuf { @Serializable internal class LightApp( @ProtoNumber(1) @JvmField val app: String = "", @ProtoNumber(2) @JvmField val view: String = "", @ProtoNumber(3) @JvmField val desc: String = "", @ProtoNumber(4) @JvmField val prompt: String = "", @ProtoNumber(5) @JvmField val ver: String = "", @ProtoNumber(6) @JvmField val meta: String = "", @ProtoNumber(7) @JvmField val config: String = "", @ProtoNumber(8) @JvmField val source: Source? = null, ) : ProtoBuf @Serializable internal class RichMsg( @ProtoNumber(1) @JvmField val title: String = "", @ProtoNumber(2) @JvmField val desc: String = "", @ProtoNumber(3) @JvmField val brief: String = "", @ProtoNumber(4) @JvmField val cover: String = "", @ProtoNumber(5) @JvmField val url: String = "", @ProtoNumber(6) @JvmField val source: Source? = null, ) : ProtoBuf @Serializable internal class Sender( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nick: String = "", @ProtoNumber(3) @JvmField val avatar: String = "", @ProtoNumber(4) @JvmField val url: String = "", ) : ProtoBuf @Serializable internal class Source( @ProtoNumber(1) @JvmField val name: String = "", @ProtoNumber(2) @JvmField val icon: String = "", @ProtoNumber(3) @JvmField val url: String = "", ) : ProtoBuf @Serializable internal class SysMsgBody( @ProtoNumber(1) @JvmField val groupId: Long = 0L, @ProtoNumber(2) @JvmField val appid: Long = 0L, @ProtoNumber(3) @JvmField val sender: Sender? = null, @ProtoNumber(4) @JvmField val msgType: Int = 0, @ProtoNumber(5) @JvmField val content: String = "", @ProtoNumber(6) @JvmField val richMsg: RichMsg? = null, @ProtoNumber(7) @JvmField val lightApp: LightApp? = null, ) : ProtoBuf } @Serializable internal class TroopTips0x857 : ProtoBuf { @Serializable internal class AIOGrayTipsInfo( @ProtoNumber(1) @JvmField val optUint32ShowLastest: Int = 0, @ProtoNumber(2) @JvmField val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val optUint32Remind: Int = 0, @ProtoNumber(4) @JvmField val optBytesBrief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val receiverUin: Long = 0L, @ProtoNumber(6) @JvmField val reliaoAdminOpt: Int = 0, @ProtoNumber(7) @JvmField val robotGroupOpt: Int = 0, ) : ProtoBuf @Serializable internal class AIOTopTipsInfo( @ProtoNumber(1) @JvmField val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val optUint32Icon: Int = 0, @ProtoNumber(3) @JvmField val optEnumAction: Int /* enum */ = 1, @ProtoNumber(4) @JvmField val optBytesUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val optBytesData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val optBytesDataI: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val optBytesDataA: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val optBytesDataP: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class FloatedTipsInfo( @ProtoNumber(1) @JvmField val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GeneralGrayTipInfo( @ProtoNumber(1) @JvmField val busiType: Long = 0L, @ProtoNumber(2) @JvmField val busiId: Long = 0L, @ProtoNumber(3) @JvmField val ctrlFlag: Int = 0, @ProtoNumber(4) @JvmField val c2cType: Int = 0, @ProtoNumber(5) @JvmField val serviceType: Int = 0, @ProtoNumber(6) @JvmField val templId: Long = 0L, @ProtoNumber(7) @JvmField val msgTemplParam: List<TemplParam> = emptyList(), @ProtoNumber(8) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val tipsSeqId: Long = 0L, @ProtoNumber(100) @JvmField val pbReserv: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GoldMsgTipsElem( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val billno: String = "", @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val amount: Int = 0, @ProtoNumber(5) @JvmField val total: Int = 0, @ProtoNumber(6) @JvmField val interval: Int = 0, @ProtoNumber(7) @JvmField val finish: Int = 0, @ProtoNumber(8) @JvmField val uin: List<Long> = emptyList(), @ProtoNumber(9) @JvmField val action: Int = 0, ) : ProtoBuf @Serializable internal class GrayData( @ProtoNumber(1) @JvmField val allRead: Int = 0, @ProtoNumber(2) @JvmField val feedId: String = "", ) : ProtoBuf @Serializable internal class GroupAnnounceTBCInfo( @ProtoNumber(1) @JvmField val feedsId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val groupId: Long = 0L, @ProtoNumber(3) @JvmField val action: Int = 0, ) : ProtoBuf @Serializable internal class GroupAsyncNotify( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgSeq: Long = 0L, ) : ProtoBuf @Serializable internal class GroupInfoChange( @ProtoNumber(1) @JvmField val groupHonorSwitch: Int = 0, @ProtoNumber(2) @JvmField val groupMemberLevelSwitch: Int = 0, @ProtoNumber(3) @JvmField val groupFlagext4: Int = 0, @ProtoNumber(4) @JvmField val appealDeadline: Int = 0, @ProtoNumber(5) @JvmField val groupFlag: Int = 0, @ProtoNumber(7) @JvmField val groupFlagext3: Int = 0, @ProtoNumber(8) @JvmField val groupClassExt: Int = 0, @ProtoNumber(9) @JvmField val groupInfoExtSeq: Int = 0, ) : ProtoBuf @Serializable internal class GroupNotifyInfo( @ProtoNumber(1) @JvmField val optUint32AutoPullFlag: Int = 0, @ProtoNumber(2) @JvmField val optBytesFeedsId: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class InstCtrl( @ProtoNumber(1) @JvmField val msgSendToInst: List<InstInfo> = emptyList(), @ProtoNumber(2) @JvmField val msgExcludeInst: List<InstInfo> = emptyList(), @ProtoNumber(3) @JvmField val msgFromInst: InstInfo? = null, ) : ProtoBuf @Serializable internal class InstInfo( @ProtoNumber(1) @JvmField val apppid: Int = 0, @ProtoNumber(2) @JvmField val instid: Int = 0, @ProtoNumber(3) @JvmField val platform: Int = 0, @ProtoNumber(4) @JvmField val openAppid: Int = 0, @ProtoNumber(5) @JvmField val productid: Int = 0, @ProtoNumber(6) @JvmField val ssoBid: Int = 0, @ProtoNumber(7) @JvmField val guid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val verMin: Int = 0, @ProtoNumber(9) @JvmField val verMax: Int = 0, ) : ProtoBuf @Serializable internal class LbsShareChangePushInfo( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val groupId: Long = 0L, @ProtoNumber(5) @JvmField val operUin: Long = 0L, @ProtoNumber(6) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val msgSeq: Long = 0L, @ProtoNumber(8) @JvmField val joinNums: Int = 0, @ProtoNumber(99) @JvmField val pushType: Int = 0, @ProtoNumber(100) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class LuckyBagNotify( @ProtoNumber(1) @JvmField val msgTips: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MediaChangePushInfo( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val groupId: Long = 0L, @ProtoNumber(5) @JvmField val operUin: Long = 0L, @ProtoNumber(6) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val msgSeq: Long = 0L, @ProtoNumber(8) @JvmField val joinNums: Int = 0, @ProtoNumber(9) @JvmField val msgPerSetting: PersonalSetting? = null, @ProtoNumber(10) @JvmField val playMode: Int = 0, @ProtoNumber(11) @JvmField val isJoinWhenStart: Boolean = false, @ProtoNumber(99) @JvmField val mediaType: Int = 0, @ProtoNumber(100) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf { @Serializable internal class PersonalSetting( @ProtoNumber(1) @JvmField val themeId: Int = 0, @ProtoNumber(2) @JvmField val playerId: Int = 0, @ProtoNumber(3) @JvmField val fontId: Int = 0, ) : ProtoBuf } @Serializable internal class MessageBoxInfo( @ProtoNumber(1) @JvmField val optBytesContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val optBytesTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val optBytesButton: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MessageRecallReminder( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val recalledMsgList: List<MessageMeta> = emptyList(), @ProtoNumber(4) @JvmField val reminderContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val userdef: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val groupType: Int = 0, @ProtoNumber(7) @JvmField val opType: Int = 0, @ProtoNumber(8) @JvmField val adminUin: Long = 0L, @ProtoNumber(9) @JvmField val msgWordingInfo: WithDrawWordingInfo? = null, ) : ProtoBuf { @Serializable internal class MessageMeta( @ProtoNumber(1) @JvmField val seq: Int = 0, @ProtoNumber(2) @JvmField val time: Int = 0, @ProtoNumber(3) @JvmField val msgRandom: Int = 0, @ProtoNumber(4) @JvmField val msgType: Int = 0, @ProtoNumber(5) @JvmField val msgFlag: Int = 0, @ProtoNumber(6) @JvmField val authorUin: Long = 0L, @ProtoNumber(7) @JvmField val isAnonyMsg: Int = 0, ) : ProtoBuf @Serializable internal class WithDrawWordingInfo( @ProtoNumber(1) @JvmField val int32ItemId: Int = 0, @ProtoNumber(2) @JvmField val itemName: String = "", ) : ProtoBuf } @Serializable internal class MiniAppNotify( @ProtoNumber(1) @JvmField val msg: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NotifyMsgBody( @ProtoNumber(1) @JvmField val optEnumType: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val optUint64MsgTime: Long = 0L, @ProtoNumber(3) @JvmField val optUint64MsgExpires: Long = 0L, @ProtoNumber(4) @JvmField val optUint64GroupCode: Long = 0L, @ProtoNumber(5) @JvmField val optMsgGraytips: AIOGrayTipsInfo? = null, @ProtoNumber(6) @JvmField val optMsgMessagebox: MessageBoxInfo? = null, @ProtoNumber(7) @JvmField val optMsgFloatedtips: FloatedTipsInfo? = null, @ProtoNumber(8) @JvmField val optMsgToptips: AIOTopTipsInfo? = null, @ProtoNumber(9) @JvmField val optMsgRedtips: RedGrayTipsInfo? = null, @ProtoNumber(10) @JvmField val optMsgGroupNotify: GroupNotifyInfo? = null, @ProtoNumber(11) @JvmField val optMsgRecall: MessageRecallReminder? = null, @ProtoNumber(12) @JvmField val optMsgThemeNotify: ThemeStateNotify? = null, @ProtoNumber(13) @JvmField val serviceType: Int = 0, @ProtoNumber(14) @JvmField val optMsgObjmsgUpdate: NotifyObjmsgUpdate? = null, @ProtoNumber(15) @JvmField val optMsgWerewolfPush: WereWolfPush? = null, //@ProtoNumber(16) @JvmField val optStcmGameState: ApolloGameStatus.STCMGameMessage? = null, //@ProtoNumber(17) @JvmField val aplloMsgPush: ApolloPushMsgInfo.STPushMsgElem? = null, @ProtoNumber(18) @JvmField val optMsgGoldtips: GoldMsgTipsElem? = null, @ProtoNumber(20) @JvmField val optMsgMiniappNotify: MiniAppNotify? = null, @ProtoNumber(21) @JvmField val optUint64SenderUin: Long = 0L, @ProtoNumber(22) @JvmField val optMsgLuckybagNotify: LuckyBagNotify? = null, @ProtoNumber(23) @JvmField val optMsgTroopformtipsPush: TroopFormGrayTipsInfo? = null, @ProtoNumber(24) @JvmField val optMsgMediaPush: MediaChangePushInfo? = null, @ProtoNumber(26) @JvmField val optGeneralGrayTip: GeneralGrayTipInfo? = null, @ProtoNumber(27) @JvmField val optMsgVideoPush: VideoChangePushInfo? = null, @ProtoNumber(28) @JvmField val optLbsShareChangePlusInfo: LbsShareChangePushInfo? = null, @ProtoNumber(29) @JvmField val optMsgSingPush: SingChangePushInfo? = null, @ProtoNumber(30) @JvmField val optMsgGroupInfoChange: GroupInfoChange? = null, @ProtoNumber(31) @JvmField val optGroupAnnounceTbcInfo: GroupAnnounceTBCInfo? = null, @ProtoNumber(32) @JvmField val optQqVedioGamePushInfo: QQVedioGamePushInfo? = null, @ProtoNumber(33) @JvmField val optQqGroupDigestMsg: QQGroupDigestMsg? = null, @ProtoNumber(34) @JvmField val optStudyRoomMemberMsg: StudyRoomMemberChangePush? = null, @ProtoNumber(35) @JvmField val optQqLiveNotify: QQVaLiveNotifyMsg? = null, @ProtoNumber(36) @JvmField val optGroupAsyncNotidy: GroupAsyncNotify? = null, @ProtoNumber(37) @JvmField val optUint64GroupCurMsgSeq: Long = 0L, @ProtoNumber(38) @JvmField val optGroupDigestMsgSummary: QQGroupDigestMsgSummary? = null ) : ProtoBuf @Serializable internal class NotifyObjmsgUpdate( @ProtoNumber(1) @JvmField val objmsgId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val updateType: Int = 0, @ProtoNumber(3) @JvmField val extMsg: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class QQGroupDigestMsg( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val msgSeq: Int = 0, @ProtoNumber(3) @JvmField val msgRandom: Int = 0, @ProtoNumber(4) @JvmField val opType: Int = 0, @ProtoNumber(5) @JvmField val msgSender: Long = 0L, @ProtoNumber(6) @JvmField val digestOper: Long = 0L, @ProtoNumber(7) @JvmField val opTime: Int = 0, @ProtoNumber(8) @JvmField val lastestMsgSeq: Int = 0, @ProtoNumber(9) @JvmField val operNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val senderNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val extInfo: Int = 0, ) : ProtoBuf @Serializable internal class QQGroupDigestMsgSummary( @ProtoNumber(1) @JvmField val digestOper: Long = 0L, @ProtoNumber(2) @JvmField val opType: Int = 0, @ProtoNumber(3) @JvmField val opTime: Int = 0, @ProtoNumber(4) @JvmField val digestNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val succCnt: Int = 0, @ProtoNumber(6) @JvmField val summaryInfo: List<QQGroupDigestSummaryInfo> = emptyList(), ) : ProtoBuf @Serializable internal class QQGroupDigestSummaryInfo( @ProtoNumber(1) @JvmField val msgSeq: Int = 0, @ProtoNumber(2) @JvmField val msgRandom: Int = 0, @ProtoNumber(3) @JvmField val errorCode: Int = 0, ) : ProtoBuf @Serializable internal class QQVaLiveNotifyMsg( @ProtoNumber(1) @JvmField val uid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val notifyType: Int = 0, @ProtoNumber(3) @JvmField val ext1: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val ext2: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val ext3: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class QQVedioGamePushInfo( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val operUin: Long = 0L, @ProtoNumber(4) @JvmField val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RedGrayTipsInfo( @ProtoNumber(1) @JvmField val optUint32ShowLastest: Int = 0, @ProtoNumber(2) @JvmField val senderUin: Long = 0L, @ProtoNumber(3) @JvmField val receiverUin: Long = 0L, @ProtoNumber(4) @JvmField val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val authkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(7) @JvmField val sint32Msgtype: Int = 0, @ProtoNumber(8) @JvmField val luckyFlag: Int = 0, @ProtoNumber(9) @JvmField val hideFlag: Int = 0, @ProtoNumber(10) @JvmField val pcBody: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val icon: Int = 0, @ProtoNumber(12) @JvmField val luckyUin: Long = 0L, @ProtoNumber(13) @JvmField val time: Int = 0, @ProtoNumber(14) @JvmField val random: Int = 0, @ProtoNumber(15) @JvmField val broadcastRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(16) @JvmField val idiom: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(17) @JvmField val idiomSeq: Int = 0, @ProtoNumber(18) @JvmField val idiomAlpha: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val jumpurl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20) @JvmField val subchannel: Int = 0, @ProtoNumber(21) @JvmField val poemRule: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val optUint64GroupCode: Long = 0L, @ProtoNumber(2) @JvmField val uint64Memberuins: List<Long> = emptyList(), @ProtoNumber(3) @JvmField val optUint32Offline: Int = 0, @ProtoNumber(4) @JvmField val msgInstCtrl: InstCtrl? = null, @ProtoNumber(5) @JvmField val optBytesMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val optUint32BusiType: Int = 0, ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val optUint64GroupCode: Long = 0L, ) : ProtoBuf @Serializable internal class SingChangePushInfo( @ProtoNumber(1) @JvmField val seq: Long = 0L, @ProtoNumber(2) @JvmField val actionType: Int = 0, @ProtoNumber(3) @JvmField val groupId: Long = 0L, @ProtoNumber(4) @JvmField val operUin: Long = 0L, @ProtoNumber(5) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val joinNums: Int = 0, ) : ProtoBuf @Serializable internal class StudyRoomMemberChangePush( @ProtoNumber(1) @JvmField val memberCount: Int = 0 ) : ProtoBuf @Serializable internal class TemplParam( @ProtoNumber(1) @JvmField val name: String = "", @ProtoNumber(2) @JvmField val value: String = "", ) : ProtoBuf @Serializable internal class ThemeStateNotify( @ProtoNumber(1) @JvmField val state: Int = 0, @ProtoNumber(2) @JvmField val feedsId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val themeName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val actionUin: Long = 0L, @ProtoNumber(5) @JvmField val createUin: Long = 0L, ) : ProtoBuf @Serializable internal class TroopFormGrayTipsInfo( @ProtoNumber(1) @JvmField val writerUin: Long = 0L, @ProtoNumber(2) @JvmField val creatorUin: Long = 0L, @ProtoNumber(3) @JvmField val richContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val optBytesUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val creatorNick: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class VideoChangePushInfo( @ProtoNumber(1) @JvmField val seq: Long = 0L, @ProtoNumber(2) @JvmField val actionType: Int = 0, @ProtoNumber(3) @JvmField val groupId: Long = 0L, @ProtoNumber(4) @JvmField val operUin: Long = 0L, @ProtoNumber(5) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val joinNums: Int = 0, @ProtoNumber(7) @JvmField val joinState: Int = 0, @ProtoNumber(100) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class WereWolfPush( @ProtoNumber(1) @JvmField val pushType: Int = 0, @ProtoNumber(2) @JvmField val gameRoom: Long = 0L, @ProtoNumber(3) @JvmField val enumGameState: Int = 0, @ProtoNumber(4) @JvmField val gameRound: Int = 0, @ProtoNumber(5) @JvmField val roles: List<Role> = emptyList(), @ProtoNumber(6) @JvmField val speaker: Long = 0L, @ProtoNumber(7) @JvmField val judgeUin: Long = 0L, @ProtoNumber(8) @JvmField val judgeWords: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val enumOperation: Int = 0, @ProtoNumber(10) @JvmField val srcUser: Long = 0L, @ProtoNumber(11) @JvmField val dstUser: Long = 0L, @ProtoNumber(12) @JvmField val deadUsers: List<Long> = emptyList(), @ProtoNumber(13) @JvmField val gameResult: Int = 0, @ProtoNumber(14) @JvmField val timeoutSec: Int = 0, @ProtoNumber(15) @JvmField val killConfirmed: Int = 0, @ProtoNumber(16) @JvmField val judgeNickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(17) @JvmField val votedTieUsers: List<Long> = emptyList(), ) : ProtoBuf { @Serializable internal class GameRecord( @ProtoNumber(1) @JvmField val total: Int = 0, @ProtoNumber(2) @JvmField val win: Int = 0, @ProtoNumber(3) @JvmField val lose: Int = 0, @ProtoNumber(4) @JvmField val draw: Int = 0, ) : ProtoBuf @Serializable internal class Role( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val enumType: Int = 0, @ProtoNumber(3) @JvmField val enumState: Int = 0, @ProtoNumber(4) @JvmField val canSpeak: Int = 0, @ProtoNumber(5) @JvmField val canListen: Int = 0, @ProtoNumber(6) @JvmField val position: Int = 0, @ProtoNumber(7) @JvmField val canVote: Int = 0, @ProtoNumber(8) @JvmField val canVoted: Int = 0, @ProtoNumber(9) @JvmField val alreadyChecked: Int = 0, @ProtoNumber(10) @JvmField val alreadySaved: Int = 0, @ProtoNumber(11) @JvmField val alreadyPoisoned: Int = 0, @ProtoNumber(12) @JvmField val playerState: Int = 0, @ProtoNumber(13) @JvmField val enumDeadOp: Int = 0, @ProtoNumber(14) @JvmField val enumOperation: Int = 0, @ProtoNumber(15) @JvmField val dstUser: Long = 0L, @ProtoNumber(16) @JvmField val operationRound: Int = 0, @ProtoNumber(17) @JvmField val msgGameRecord: GameRecord? = null, @ProtoNumber(18) @JvmField val isWerewolf: Int = 0, @ProtoNumber(19) @JvmField val defendedUser: Long = 0L, @ProtoNumber(20) @JvmField val isSheriff: Int = 0, ) : ProtoBuf } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Cmd0x858.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class Oidb0x858 : ProtoBuf { @Serializable internal class GoldMsgTipsElem( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val billno: String = "", @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val amount: Int = 0, @ProtoNumber(5) @JvmField val total: Int = 0, @ProtoNumber(6) @JvmField val interval: Int = 0, @ProtoNumber(7) @JvmField val finish: Int = 0, @ProtoNumber(8) @JvmField val uin: List<Long> = emptyList(), @ProtoNumber(9) @JvmField val action: Int = 0, ) : ProtoBuf @Serializable internal class MessageRecallReminder( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val recalledMsgList: List<MessageMeta> = emptyList(), @ProtoNumber(4) @JvmField val reminderContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val userdef: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf { @Serializable internal class MessageMeta( @ProtoNumber(1) @JvmField val seq: Int = 0, @ProtoNumber(2) @JvmField val time: Int = 0, @ProtoNumber(3) @JvmField val msgRandom: Int = 0, ) : ProtoBuf } @Serializable internal class NotifyMsgBody( @ProtoNumber(1) @JvmField val optEnumType: Int /* enum */ = 5, @ProtoNumber(2) @JvmField val optUint64MsgTime: Long = 0L, @ProtoNumber(3) @JvmField val optUint64MsgExpires: Long = 0L, @ProtoNumber(4) @JvmField val optUint64ConfUin: Long = 0L, @ProtoNumber(5) @JvmField val optMsgRedtips: RedGrayTipsInfo? = null, @ProtoNumber(6) @JvmField val optMsgRecallReminder: MessageRecallReminder? = null, @ProtoNumber(7) @JvmField val optMsgObjUpdate: NotifyObjmsgUpdate? = null, // @SerialId(8) @JvmField val optStcmGameState: ApolloGameStatus.STCMGameMessage? = null, // @SerialId(9) @JvmField val aplloMsgPush: ApolloPushMsgInfo.STPushMsgElem? = null, @ProtoNumber(10) @JvmField val optMsgGoldtips: GoldMsgTipsElem? = null, ) : ProtoBuf @Serializable internal class NotifyObjmsgUpdate( @ProtoNumber(1) @JvmField val objmsgId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val updateType: Int = 0, @ProtoNumber(3) @JvmField val extMsg: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RedGrayTipsInfo( @ProtoNumber(1) @JvmField val optUint32ShowLastest: Int = 0, @ProtoNumber(2) @JvmField val senderUin: Long = 0L, @ProtoNumber(3) @JvmField val receiverUin: Long = 0L, @ProtoNumber(4) @JvmField val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val authkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(7) @JvmField val sint32Msgtype: Int = 0, @ProtoNumber(8) @JvmField val luckyFlag: Int = 0, @ProtoNumber(9) @JvmField val hideFlag: Int = 0, @ProtoNumber(10) @JvmField val pcBody: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val icon: Int = 0, @ProtoNumber(12) @JvmField val luckyUin: Long = 0L, @ProtoNumber(13) @JvmField val time: Int = 0, @ProtoNumber(14) @JvmField val random: Int = 0, @ProtoNumber(15) @JvmField val broadcastRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(16) @JvmField val idiom: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(17) @JvmField val idiomSeq: Int = 0, @ProtoNumber(18) @JvmField val idiomAlpha: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val jumpurl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Define.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class Common : ProtoBuf { @Serializable internal class BindInfo( @ProtoNumber(1) @JvmField val friUin: Long = 0L, @ProtoNumber(2) @JvmField val friNick: String = "", @ProtoNumber(3) @JvmField val time: Long = 0L, @ProtoNumber(4) @JvmField val bindStatus: Int = 0, ) : ProtoBuf @Serializable internal class MedalInfo( @ProtoNumber(1) @JvmField val id: Int = 0, @ProtoNumber(2) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val seq: Long = 0, @ProtoNumber(5) @JvmField val name: String = "", @ProtoNumber(6) @JvmField val newflag: Int = 0, @ProtoNumber(7) @JvmField val time: Long = 0L, @ProtoNumber(8) @JvmField val msgBindFri: BindInfo? = null, @ProtoNumber(11) @JvmField val desc: String = "", @ProtoNumber(31) @JvmField val level: Int = 0, @ProtoNumber(36) @JvmField val taskinfos: List<MedalTaskInfo> = emptyList(), @ProtoNumber(40) @JvmField val point: Int = 0, @ProtoNumber(41) @JvmField val pointLevel2: Int = 0, @ProtoNumber(42) @JvmField val pointLevel3: Int = 0, @ProtoNumber(43) @JvmField val seqLevel2: Long = 0, @ProtoNumber(44) @JvmField val seqLevel3: Long = 0, @ProtoNumber(45) @JvmField val timeLevel2: Long = 0L, @ProtoNumber(46) @JvmField val timeLevel3: Long = 0L, @ProtoNumber(47) @JvmField val descLevel2: String = "", @ProtoNumber(48) @JvmField val descLevel3: String = "", @ProtoNumber(49) @JvmField val endtime: Int = 0, @ProtoNumber(50) @JvmField val detailUrl: String = "", @ProtoNumber(51) @JvmField val detailUrl2: String = "", @ProtoNumber(52) @JvmField val detailUrl3: String = "", @ProtoNumber(53) @JvmField val taskDesc: String = "", @ProtoNumber(54) @JvmField val taskDesc2: String = "", @ProtoNumber(55) @JvmField val taskDesc3: String = "", @ProtoNumber(56) @JvmField val levelCount: Int = 0, @ProtoNumber(57) @JvmField val noProgress: Int = 0, @ProtoNumber(58) @JvmField val resource: String = "", @ProtoNumber(59) @JvmField val fromuinLevel: Int = 0, @ProtoNumber(60) @JvmField val unread: Int = 0, @ProtoNumber(61) @JvmField val unread2: Int = 0, @ProtoNumber(62) @JvmField val unread3: Int = 0, ) : ProtoBuf @Serializable internal class MedalTaskInfo( @ProtoNumber(1) @JvmField val taskid: Int = 0, @ProtoNumber(32) @JvmField val int32TaskValue: Int = 0, @ProtoNumber(33) @JvmField val tarValue: Int = 0, @ProtoNumber(34) @JvmField val tarValueLevel2: Int = 0, @ProtoNumber(35) @JvmField val tarValueLevel3: Int = 0, ) : ProtoBuf } @Serializable internal class AppointDefine : ProtoBuf { @Serializable internal class ADFeedContent( @ProtoNumber(1) @JvmField val msgUserInfo: UserInfo? = null, @ProtoNumber(2) @JvmField val strPicUrl: List<String> = emptyList(), @ProtoNumber(3) @JvmField val msgText: RichText? = null, @ProtoNumber(4) @JvmField val attendInfo: String = "", @ProtoNumber(5) @JvmField val actionUrl: String = "", @ProtoNumber(6) @JvmField val publishTime: Int = 0, @ProtoNumber(7) @JvmField val msgHotTopicList: HotTopicList? = null, @ProtoNumber(8) @JvmField val moreUrl: String = "", @ProtoNumber(9) @JvmField val recordDuration: String = "", ) : ProtoBuf @Serializable internal class RichText( @ProtoNumber(1) @JvmField val msgElems: List<Elem> = emptyList(), ) : ProtoBuf @Serializable internal class RankEvent( @ProtoNumber(1) @JvmField val listtype: Int = 0, @ProtoNumber(2) @JvmField val notifytype: Int = 0, @ProtoNumber(3) @JvmField val eventtime: Int = 0, @ProtoNumber(4) @JvmField val seq: Int = 0, @ProtoNumber(5) @JvmField val notifyTips: String = "", ) : ProtoBuf @Serializable internal class Wifi( @ProtoNumber(1) @JvmField val mac: Long = 0L, @ProtoNumber(2) @JvmField val int32Rssi: Int = 0, ) : ProtoBuf @Serializable internal class InterestItem( @ProtoNumber(1) @JvmField val tagId: Long = 0L, @ProtoNumber(2) @JvmField val tagName: String = "", @ProtoNumber(3) @JvmField val tagIconUrl: String = "", @ProtoNumber(4) @JvmField val tagHref: String = "", @ProtoNumber(5) @JvmField val tagBackColor: String = "", @ProtoNumber(6) @JvmField val tagFontColor: String = "", @ProtoNumber(7) @JvmField val tagVid: String = "", @ProtoNumber(8) @JvmField val tagType: Int = 0, @ProtoNumber(9) @JvmField val addTime: Int = 0, @ProtoNumber(10) @JvmField val tagCategory: String = "", @ProtoNumber(11) @JvmField val tagOtherUrl: String = "", @ProtoNumber(12) @JvmField val bid: Int = 0, ) : ProtoBuf @Serializable internal class ShopID( @ProtoNumber(1) @JvmField val shopid: String = "", @ProtoNumber(2) @JvmField val sp: Int = 0, ) : ProtoBuf @Serializable internal class FeedComment( @ProtoNumber(1) @JvmField val commentId: String = "", @ProtoNumber(2) @JvmField val feedId: String = "", @ProtoNumber(3) @JvmField val msgPublisherInfo: StrangerInfo? = null, @ProtoNumber(4) @JvmField val time: Int = 0, @ProtoNumber(6) @JvmField val msgReplyInfo: ReplyInfo? = null, @ProtoNumber(7) @JvmField val flag: Int = 0, @ProtoNumber(8) @JvmField val msgContent: RichText? = null, @ProtoNumber(9) @JvmField val hot: Int = 0, ) : ProtoBuf @Serializable internal class ADFeed( @ProtoNumber(1) @JvmField val taskId: Int = 0, @ProtoNumber(2) @JvmField val style: Int = 0, @ProtoNumber(3) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class Cell( @ProtoNumber(1) @JvmField val int32Mcc: Int = -1, @ProtoNumber(2) @JvmField val int32Mnc: Int = -1, @ProtoNumber(3) @JvmField val int32Lac: Int = -1, @ProtoNumber(4) @JvmField val int32Cellid: Int = -1, @ProtoNumber(5) @JvmField val int32Rssi: Int = 0, ) : ProtoBuf @Serializable internal class RecentVistorEvent( @ProtoNumber(1) @JvmField val eventtype: Int = 0, @ProtoNumber(2) @JvmField val eventTinyid: Long = 0L, @ProtoNumber(3) @JvmField val unreadCount: Int = 0, ) : ProtoBuf @Serializable internal class OrganizerInfo( @ProtoNumber(1) @JvmField val hostName: String = "", @ProtoNumber(2) @JvmField val hostUrl: String = "", @ProtoNumber(3) @JvmField val hostCover: String = "", ) : ProtoBuf @Serializable internal class InterestTag( @ProtoNumber(1) @JvmField val tagType: Int = 0, @ProtoNumber(2) @JvmField val msgTagList: List<InterestItem> = emptyList(), ) : ProtoBuf @Serializable internal class AppointInfoEx( @ProtoNumber(1) @JvmField val feedsPicUrl: String = "", @ProtoNumber(2) @JvmField val feedsUrl: String = "", @ProtoNumber(3) @JvmField val detailTitle: String = "", @ProtoNumber(4) @JvmField val detailDescribe: String = "", @ProtoNumber(5) @JvmField val showPublisher: Int = 0, @ProtoNumber(6) @JvmField val detailPicUrl: String = "", @ProtoNumber(7) @JvmField val detailUrl: String = "", @ProtoNumber(8) @JvmField val showAttend: Int = 0, ) : ProtoBuf @Serializable internal class DateComment( @ProtoNumber(1) @JvmField val commentId: String = "", @ProtoNumber(2) @JvmField val msgAppointId: AppointID? = null, @ProtoNumber(3) @JvmField val msgPublisherInfo: StrangerInfo? = null, @ProtoNumber(4) @JvmField val time: Int = 0, @ProtoNumber(6) @JvmField val msgReplyInfo: ReplyInfo? = null, @ProtoNumber(7) @JvmField val flag: Int = 0, @ProtoNumber(8) @JvmField val msgContent: RichText? = null, ) : ProtoBuf @Serializable internal class AppointContent( @ProtoNumber(1) @JvmField val appointSubject: Int = 0, @ProtoNumber(2) @JvmField val payType: Int = 0, @ProtoNumber(3) @JvmField val appointDate: Int = 0, @ProtoNumber(4) @JvmField val appointGender: Int = 0, @ProtoNumber(5) @JvmField val appointIntroduce: String = "", @ProtoNumber(6) @JvmField val msgAppointAddress: AddressInfo? = null, @ProtoNumber(7) @JvmField val msgTravelInfo: TravelInfo? = null, ) : ProtoBuf @Serializable internal class FeedInfo( @ProtoNumber(1) @JvmField val feedType: Long = 0L, @ProtoNumber(2) @JvmField val feedId: String = "", @ProtoNumber(3) @JvmField val msgFeedContent: FeedContent? = null, @ProtoNumber(4) @JvmField val msgTopicInfo: NearbyTopic? = null, @ProtoNumber(5) @JvmField val publishTime: Long = 0, @ProtoNumber(6) @JvmField val praiseCount: Int = 0, @ProtoNumber(7) @JvmField val praiseFlag: Int = 0, @ProtoNumber(8) @JvmField val msgPraiseUser: List<StrangerInfo> = emptyList(), @ProtoNumber(9) @JvmField val commentCount: Int = 0, @ProtoNumber(10) @JvmField val msgCommentList: List<FeedComment> = emptyList(), @ProtoNumber(11) @JvmField val commentRetAll: Int = 0, @ProtoNumber(12) @JvmField val hotFlag: Int = 0, @ProtoNumber(13) @JvmField val svrReserved: Long = 0L, @ProtoNumber(14) @JvmField val msgHotEntry: HotEntry? = null, ) : ProtoBuf @Serializable internal class HotTopicList( @ProtoNumber(1) @JvmField val topicList: List<HotTopic> = emptyList(), ) : ProtoBuf @Serializable internal class FeedContent( @ProtoNumber(1) @JvmField val strPicUrl: List<String> = emptyList(), @ProtoNumber(2) @JvmField val msgText: RichText? = null, @ProtoNumber(3) @JvmField val hrefUrl: String = "", @ProtoNumber(5) @JvmField val groupName: String = "", @ProtoNumber(6) @JvmField val groupBulletin: String = "", @ProtoNumber(7) @JvmField val feedType: Int = 0, @ProtoNumber(8) @JvmField val poiId: String = "", @ProtoNumber(9) @JvmField val poiTitle: String = "", @ProtoNumber(20) @JvmField val effectiveTime: Int = 0, @ProtoNumber(21) @JvmField val expiationTime: Int = 0, @ProtoNumber(22) @JvmField val msgLocale: LocaleInfo? = null, @ProtoNumber(23) @JvmField val feedsIndex: Int = 0, @ProtoNumber(24) @JvmField val msgAd: ADFeed? = null, @ProtoNumber(25) @JvmField val privateData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class TravelInfo( @ProtoNumber(1) @JvmField val msgDepartLocale: LocaleInfo? = null, @ProtoNumber(2) @JvmField val msgDestination: LocaleInfo? = null, @ProtoNumber(3) @JvmField val vehicle: Int = 0, @ProtoNumber(4) @JvmField val partnerCount: Int = 0, @ProtoNumber(5) @JvmField val placePicUrl: String = "", @ProtoNumber(6) @JvmField val placeUrl: String = "", ) : ProtoBuf @Serializable internal class RecentFreshFeed( @ProtoNumber(1) @JvmField val freshFeedInfo: List<FreshFeedInfo> = emptyList(), @ProtoNumber(2) @JvmField val uid: Long = 0L, ) : ProtoBuf @Serializable internal class GPS( @ProtoNumber(1) @JvmField val int32Lat: Int = 900000000, @ProtoNumber(2) @JvmField val int32Lon: Int = 900000000, @ProtoNumber(3) @JvmField val int32Alt: Int = -10000000, @ProtoNumber(4) @JvmField val int32Type: Int = 0, ) : ProtoBuf @Serializable internal class AppointID( @ProtoNumber(1) @JvmField val requestId: String = "", ) : ProtoBuf @Serializable internal class LocaleInfo( @ProtoNumber(1) @JvmField val name: String = "", @ProtoNumber(2) @JvmField val country: String = "", @ProtoNumber(3) @JvmField val province: String = "", @ProtoNumber(4) @JvmField val city: String = "", @ProtoNumber(5) @JvmField val region: String = "", @ProtoNumber(6) @JvmField val poi: String = "", @ProtoNumber(7) @JvmField val msgGps: GPS? = null, @ProtoNumber(8) @JvmField val address: String = "", ) : ProtoBuf @Serializable internal class LBSInfo( @ProtoNumber(1) @JvmField val msgGps: GPS? = null, @ProtoNumber(2) @JvmField val msgWifis: List<Wifi> = emptyList(), @ProtoNumber(3) @JvmField val msgCells: List<Cell> = emptyList(), ) : ProtoBuf @Serializable internal class FeedEvent( @ProtoNumber(1) @JvmField val eventId: Long = 0L, @ProtoNumber(2) @JvmField val time: Int = 0, @ProtoNumber(3) @JvmField val eventtype: Int = 0, @ProtoNumber(4) @JvmField val msgUserInfo: StrangerInfo? = null, @ProtoNumber(5) @JvmField val msgFeedInfo: FeedInfo? = null, @ProtoNumber(6) @JvmField val eventTips: String = "", @ProtoNumber(7) @JvmField val msgComment: FeedComment? = null, @ProtoNumber(8) @JvmField val cancelEventId: Long = 0L, ) : ProtoBuf @Serializable internal class FeedsCookie( @ProtoNumber(1) @JvmField val strList: List<String> = emptyList(), @ProtoNumber(2) @JvmField val pose: Int = 0, @ProtoNumber(3) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val uint64Topics: List<Long> = emptyList(), ) : ProtoBuf @Serializable internal class NearbyTopic( @ProtoNumber(1) @JvmField val topicId: Long = 0L, @ProtoNumber(2) @JvmField val topic: String = "", @ProtoNumber(3) @JvmField val foreword: String = "", @ProtoNumber(4) @JvmField val createTime: Int = 0, @ProtoNumber(5) @JvmField val updateTime: Int = 0, @ProtoNumber(6) @JvmField val hotFlag: Int = 0, @ProtoNumber(7) @JvmField val buttonStyle: Int = 0, @ProtoNumber(8) @JvmField val buttonSrc: String = "", @ProtoNumber(9) @JvmField val backgroundSrc: String = "", @ProtoNumber(10) @JvmField val attendeeInfo: String = "", @ProtoNumber(11) @JvmField val index: Int = 0, @ProtoNumber(12) @JvmField val publishScope: Int = 0, @ProtoNumber(13) @JvmField val effectiveTime: Int = 0, @ProtoNumber(14) @JvmField val expiationTime: Int = 0, @ProtoNumber(15) @JvmField val pushedUsrCount: Int = 0, @ProtoNumber(16) @JvmField val timerangeLeft: Int = 0, @ProtoNumber(17) @JvmField val timerangeRight: Int = 0, @ProtoNumber(18) @JvmField val area: String = "", ) : ProtoBuf @Serializable internal class NearbyEvent( @ProtoNumber(1) @JvmField val eventtype: Int = 0, @ProtoNumber(2) @JvmField val msgRankevent: RankEvent? = null, @ProtoNumber(3) @JvmField val eventUin: Long = 0L, @ProtoNumber(4) @JvmField val eventTinyid: Long = 0L, ) : ProtoBuf @Serializable internal class Feed( @ProtoNumber(1) @JvmField val msgUserInfo: PublisherInfo? = null, @ProtoNumber(2) @JvmField val msgFeedInfo: FeedInfo? = null, @ProtoNumber(3) @JvmField val ownerFlag: Int = 0, ) : ProtoBuf @Serializable internal class ActivityInfo( @ProtoNumber(2) @JvmField val name: String = "", @ProtoNumber(3) @JvmField val cover: String = "", @ProtoNumber(4) @JvmField val url: String = "", @ProtoNumber(5) @JvmField val startTime: Int = 0, @ProtoNumber(6) @JvmField val endTime: Int = 0, @ProtoNumber(7) @JvmField val locName: String = "", @ProtoNumber(8) @JvmField val enroll: Long = 0L, @ProtoNumber(9) @JvmField val createUin: Long = 0L, @ProtoNumber(10) @JvmField val createTime: Int = 0, @ProtoNumber(11) @JvmField val organizerInfo: OrganizerInfo = OrganizerInfo(), @ProtoNumber(12) @JvmField val flag: Long? = null, ) : ProtoBuf @Serializable internal class HotEntry( @ProtoNumber(1) @JvmField val openFlag: Int = 0, @ProtoNumber(2) @JvmField val restTime: Int = 0, @ProtoNumber(3) @JvmField val foreword: String = "", @ProtoNumber(4) @JvmField val backgroundSrc: String = "", ) : ProtoBuf @Serializable internal class UserFeed( @ProtoNumber(1) @JvmField val msgUserInfo: PublisherInfo? = null, @ProtoNumber(2) @JvmField val msgFeedInfo: FeedInfo? = null, @ProtoNumber(3) @JvmField val ownerFlag: Int = 0, @ProtoNumber(4) @JvmField val msgActivityInfo: ActivityInfo? = null, ) : ProtoBuf @Serializable internal class Elem( @ProtoNumber(1) @JvmField val content: String = "", @ProtoNumber(2) @JvmField val msgFaceInfo: Face? = null, ) : ProtoBuf @Serializable internal class HotFreshFeedList( @ProtoNumber(1) @JvmField val msgFeeds: List<HotUserFeed> = emptyList(), @ProtoNumber(2) @JvmField val updateTime: Int = 0, ) : ProtoBuf @Serializable internal class RptInterestTag( @ProtoNumber(1) @JvmField val interestTags: List<InterestTag> = emptyList(), ) : ProtoBuf @Serializable internal class AddressInfo( @ProtoNumber(1) @JvmField val companyZone: String = "", @ProtoNumber(2) @JvmField val companyName: String = "", @ProtoNumber(3) @JvmField val companyAddr: String = "", @ProtoNumber(4) @JvmField val companyPicUrl: String = "", @ProtoNumber(5) @JvmField val companyUrl: String = "", @ProtoNumber(6) @JvmField val msgCompanyId: ShopID? = null, ) : ProtoBuf @Serializable internal class PublisherInfo( @ProtoNumber(1) @JvmField val tinyid: Long = 0L, @ProtoNumber(2) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val age: Int = 0, @ProtoNumber(4) @JvmField val gender: Int = 0, @ProtoNumber(5) @JvmField val constellation: String = "", @ProtoNumber(6) @JvmField val profession: Int = 0, @ProtoNumber(7) @JvmField val distance: String = "", @ProtoNumber(8) @JvmField val marriage: Int = 0, @ProtoNumber(9) @JvmField val vipinfo: String = "", @ProtoNumber(10) @JvmField val recommend: Int = 0, @ProtoNumber(11) @JvmField val godflag: Int = 0, @ProtoNumber(12) @JvmField val chatflag: Int = 0, @ProtoNumber(13) @JvmField val chatupCount: Int = 0, @ProtoNumber(14) @JvmField val charm: Int = 0, @ProtoNumber(15) @JvmField val charmLevel: Int = 0, @ProtoNumber(16) @JvmField val pubNumber: Int = 0, @ProtoNumber(17) @JvmField val msgCommonLabel: CommonLabel? = null, @ProtoNumber(18) @JvmField val recentVistorTime: Int = 0, @ProtoNumber(19) @JvmField val strangerDeclare: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20) @JvmField val friendUin: Long = 0L, @ProtoNumber(21) @JvmField val historyFlag: Int = 0, @ProtoNumber(22) @JvmField val followflag: Long = 0L, ) : ProtoBuf @Serializable internal class HotUserFeed( @ProtoNumber(1) @JvmField val feedId: String = "", @ProtoNumber(2) @JvmField val praiseCount: Int = 0, @ProtoNumber(3) @JvmField val publishUid: Long = 0L, @ProtoNumber(4) @JvmField val publishTime: Int = 0, ) : ProtoBuf @Serializable internal class FreshFeedInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val time: Int = 0, @ProtoNumber(3) @JvmField val feedId: String = "", @ProtoNumber(4) @JvmField val feedType: Long = 0L, ) : ProtoBuf @Serializable internal class CommonLabel( @ProtoNumber(1) @JvmField val lableId: Int = 0, @ProtoNumber(2) @JvmField val lableMsgPre: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val lableMsgLast: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val interstName: List<ByteArray> = emptyList(), @ProtoNumber(5) @JvmField val interstType: List<Int> = emptyList(), ) : ProtoBuf @Serializable internal class Face( @ProtoNumber(1) @JvmField val index: Int = 0, ) : ProtoBuf @Serializable internal class StrangerInfo( @ProtoNumber(1) @JvmField val tinyid: Long = 0L, @ProtoNumber(2) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val age: Int = 0, @ProtoNumber(4) @JvmField val gender: Int = 0, @ProtoNumber(5) @JvmField val dating: Int = 0, @ProtoNumber(6) @JvmField val listIdx: Int = 0, @ProtoNumber(7) @JvmField val constellation: String = "", @ProtoNumber(8) @JvmField val profession: Int = 0, @ProtoNumber(9) @JvmField val marriage: Int = 0, @ProtoNumber(10) @JvmField val vipinfo: String = "", @ProtoNumber(11) @JvmField val recommend: Int = 0, @ProtoNumber(12) @JvmField val godflag: Int = 0, @ProtoNumber(13) @JvmField val charm: Int = 0, @ProtoNumber(14) @JvmField val charmLevel: Int = 0, @ProtoNumber(15) @JvmField val uin: Long = 0L, ) : ProtoBuf @Serializable internal class HotTopic( @ProtoNumber(1) @JvmField val id: Long = 0L, @ProtoNumber(2) @JvmField val title: String = "", @ProtoNumber(3) @JvmField val topicType: Long = 0L, @ProtoNumber(4) @JvmField val total: Long = 0L, @ProtoNumber(5) @JvmField val times: Long = 0L, @ProtoNumber(6) @JvmField val historyTimes: Long = 0L, @ProtoNumber(7) @JvmField val bgUrl: String = "", @ProtoNumber(8) @JvmField val url: String = "", @ProtoNumber(9) @JvmField val extraInfo: String = "", ) : ProtoBuf @Serializable internal class DateEvent( @ProtoNumber(1) @JvmField val eventId: Long = 0L, @ProtoNumber(2) @JvmField val time: Int = 0, @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val msgUserInfo: StrangerInfo? = null, @ProtoNumber(5) @JvmField val msgDateInfo: AppointInfo? = null, @ProtoNumber(6) @JvmField val attendIdx: Int = 0, @ProtoNumber(7) @JvmField val eventTips: String = "", @ProtoNumber(8) @JvmField val msgComment: DateComment? = null, @ProtoNumber(9) @JvmField val cancelEventId: Long = 0L, ) : ProtoBuf @Serializable internal class AppointInfo( @ProtoNumber(1) @JvmField val msgAppointId: AppointID? = null, @ProtoNumber(2) @JvmField val msgAppointment: AppointContent? = null, @ProtoNumber(3) @JvmField val appointStatus: Int = 0, @ProtoNumber(4) @JvmField val joinWording: String = "", @ProtoNumber(5) @JvmField val viewWording: String = "", @ProtoNumber(6) @JvmField val unreadCount: Int = 0, @ProtoNumber(7) @JvmField val owner: Int = 0, @ProtoNumber(8) @JvmField val join: Int = 0, @ProtoNumber(9) @JvmField val view: Int = 0, @ProtoNumber(10) @JvmField val commentWording: String = "", @ProtoNumber(11) @JvmField val commentNum: Int = 0, @ProtoNumber(12) @JvmField val attendStatus: Int = 0, @ProtoNumber(13) @JvmField val msgAppointmentEx: AppointInfoEx? = null, ) : ProtoBuf @Serializable internal class UserInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val age: Int = 0, @ProtoNumber(4) @JvmField val gender: Int = 0, @ProtoNumber(5) @JvmField val avatar: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ReplyInfo( @ProtoNumber(1) @JvmField val commentId: String = "", @ProtoNumber(2) @JvmField val msgStrangerInfo: StrangerInfo? = null, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Exciting.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField @Serializable internal class GroupFileUploadExt( @JvmField @ProtoNumber(1) val u1: Int, @JvmField @ProtoNumber(2) val u2: Int, @JvmField @ProtoNumber(100) val entry: GroupFileUploadEntry, @JvmField @ProtoNumber(3) val u3: Int, ) : ProtoBuf @Serializable internal class GroupFileUploadEntry( @JvmField @ProtoNumber(100) val business: ExcitingBusiInfo, @JvmField @ProtoNumber(200) val fileEntry: ExcitingFileEntry, @JvmField @ProtoNumber(300) val clientInfo: ExcitingClientInfo, @JvmField @ProtoNumber(400) val fileNameInfo: ExcitingFileNameInfo, @JvmField @ProtoNumber(500) val host: ExcitingHostConfig, ) : ProtoBuf @Serializable internal class ExcitingBusiInfo( @JvmField @ProtoNumber(1) val busId: Int, @JvmField @ProtoNumber(100) val senderUin: Long, @JvmField @ProtoNumber(200) val receiverUin: Long, // maybe @JvmField @ProtoNumber(400) val groupCode: Long, // maybe ) : ProtoBuf @Serializable internal class ExcitingFileEntry( @JvmField @ProtoNumber(100) val fileSize: Long, @JvmField @ProtoNumber(200) val md5: ByteArray, @JvmField @ProtoNumber(300) val sha1: ByteArray, @JvmField @ProtoNumber(600) val fileId: ByteArray, @JvmField @ProtoNumber(700) val uploadKey: ByteArray, ) : ProtoBuf @Serializable internal class ExcitingClientInfo( @JvmField @ProtoNumber(100) val clientType: Int, // maybe @JvmField @ProtoNumber(200) val appId: String, @JvmField @ProtoNumber(300) val terminalType: Int, @JvmField @ProtoNumber(400) val clientVer: String, @JvmField @ProtoNumber(600) val unknown: Int, ) : ProtoBuf @Serializable internal class ExcitingFileNameInfo( @JvmField @ProtoNumber(100) val filename: String, ) : ProtoBuf @Serializable internal class ExcitingHostConfig( @JvmField @ProtoNumber(200) val hosts: List<ExcitingHostInfo>, ) : ProtoBuf @Serializable internal class ExcitingHostInfo( @JvmField @ProtoNumber(1) val url: ExcitingUrlInfo, @JvmField @ProtoNumber(2) val port: Int, ) : ProtoBuf @Serializable internal class ExcitingUrlInfo( @JvmField @ProtoNumber(1) val unknown: Int, @JvmField @ProtoNumber(2) val host: String, ) : ProtoBuf ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/FrdSysMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField internal class FrdSysMsg { @Serializable internal class AddFrdSNInfo( @JvmField @ProtoNumber(1) val notSeeDynamic: Int = 0, @JvmField @ProtoNumber(2) val setSn: Int = 0, ) : ProtoBuf @Serializable internal class AddFriendVerifyInfo( @JvmField @ProtoNumber(1) val type: Int = 0, @JvmField @ProtoNumber(2) val url: String = "", @JvmField @ProtoNumber(3) val verifyInfo: String = "", ) : ProtoBuf @Serializable internal class AddtionInfo( @JvmField @ProtoNumber(1) val poke: Int = 0, @JvmField @ProtoNumber(2) val format: Int = 0, @JvmField @ProtoNumber(3) val entityCategory: String = "", @JvmField @ProtoNumber(4) val entityName: String = "", @JvmField @ProtoNumber(5) val entityUrl: String = "", ) : ProtoBuf @Serializable internal class DiscussInfo( @JvmField @ProtoNumber(1) val discussUin: Long = 0L, @JvmField @ProtoNumber(2) val discussName: String = "", @JvmField @ProtoNumber(3) val discussNick: String = "", ) : ProtoBuf @Serializable internal class EimInfo( @JvmField @ProtoNumber(1) val eimFuin: Long = 0L, @JvmField @ProtoNumber(2) val eimId: String = "", @JvmField @ProtoNumber(3) val eimTelno: String = "", @JvmField @ProtoNumber(4) val groupId: Long = 0L, ) : ProtoBuf @Serializable internal class FriendHelloInfo( @JvmField @ProtoNumber(1) val sourceName: String = "", ) : ProtoBuf @Serializable internal class FriendMiscInfo( @JvmField @ProtoNumber(1) val fromuinNick: String = "", ) : ProtoBuf @Serializable internal class FriendSysMsg( @JvmField @ProtoNumber(11) val msgGroupExt: GroupInfoExt? = null, @JvmField @ProtoNumber(12) val msgIntiteInfo: InviteInfo? = null, @JvmField @ProtoNumber(13) val msgSchoolInfo: SchoolInfo? = null, @JvmField @ProtoNumber(100) val doubtFlag: Int = 0, ) : ProtoBuf @Serializable internal class GroupInfo( @JvmField @ProtoNumber(1) val groupUin: Long = 0L, @JvmField @ProtoNumber(2) val groupName: String = "", @JvmField @ProtoNumber(3) val groupNick: String = "", ) : ProtoBuf @Serializable internal class GroupInfoExt( @JvmField @ProtoNumber(1) val notifyType: Int = 0, @JvmField @ProtoNumber(2) val groupCode: Long = 0L, @JvmField @ProtoNumber(3) val fromGroupadmlist: Int = 0, ) : ProtoBuf @Serializable internal class InviteInfo( @JvmField @ProtoNumber(1) val recommendUin: Long = 0L, ) : ProtoBuf @Serializable internal class MsgEncodeFlag( @JvmField @ProtoNumber(1) val isUtf8: Int = 0, ) : ProtoBuf @Serializable internal class SchoolInfo( @JvmField @ProtoNumber(1) val schoolId: String = "", @JvmField @ProtoNumber(2) val schoolName: String = "", ) : ProtoBuf @Serializable internal class TongXunLuNickInfo( @JvmField @ProtoNumber(1) val fromuin: Long = 0L, @JvmField @ProtoNumber(2) val touin: Long = 0L, @JvmField @ProtoNumber(3) val tongxunluNickname: String = "", ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/FriendListCommon.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class Vec0xd50 : ProtoBuf { @Serializable internal class ExtSnsFrdData( @ProtoNumber(1) @JvmField val frdUin: Long = 0L, @ProtoNumber(91001) @JvmField val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(101001) @JvmField val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(141001) @JvmField val mutualmarkScore: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(151001) @JvmField val ksingSwitch: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(181001) @JvmField val lbsShare: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgUpdateData: List<ExtSnsFrdData> = emptyList(), @ProtoNumber(11) @JvmField val over: Int = 0, @ProtoNumber(12) @JvmField val nextStart: Int = 0, @ProtoNumber(13) @JvmField val uint64UnfinishedUins: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val appid: Long = 0L, @ProtoNumber(2) @JvmField val maxPkgSize: Int = 0, @ProtoNumber(3) @JvmField val startTime: Int = 0, @ProtoNumber(4) @JvmField val startIndex: Int = 0, @ProtoNumber(5) @JvmField val reqNum: Int = 0, @ProtoNumber(6) @JvmField val uinList: List<Long> = emptyList(), @ProtoNumber(91001) @JvmField val reqMusicSwitch: Int = 0, @ProtoNumber(101001) @JvmField val reqMutualmarkAlienation: Int = 0, @ProtoNumber(141001) @JvmField val reqMutualmarkScore: Int = 0, @ProtoNumber(151001) @JvmField val reqKsingSwitch: Int = 0, @ProtoNumber(181001) @JvmField val reqMutualmarkLbsshare: Int = 0 ) : ProtoBuf @Serializable internal class KSingRelationInfo( @ProtoNumber(1) @JvmField val flag: Int = 0 ) : ProtoBuf } @Serializable internal class Vec0xd6b : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val maxPkgSize: Int = 0, @ProtoNumber(2) @JvmField val startTime: Int = 0, @ProtoNumber(11) @JvmField val uinList: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(11) @JvmField val msgMutualmarkData: List<MutualMarkData> = emptyList(), @ProtoNumber(12) @JvmField val uint64UnfinishedUins: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class MutualMarkData( @ProtoNumber(1) @JvmField val frdUin: Long = 0L, @ProtoNumber(2) @JvmField val result: Int = 0 // @SerialId(11) @JvmField val mutualmarkInfo: List<Mutualmark.MutualMark> = emptyList() ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Group.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class GroupLabel : ProtoBuf { @Serializable internal class Label( @ProtoNumber(1) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val enumType: Int /* enum */ = 1, @ProtoNumber(3) @JvmField val textColor: Color? = null, @ProtoNumber(4) @JvmField val edgingColor: Color? = null, @ProtoNumber(5) @JvmField val labelAttr: Int = 0, @ProtoNumber(6) @JvmField val labelType: Int = 0, ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val error: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val groupInfo: List<GroupInfo> = emptyList(), ) : ProtoBuf @Serializable internal class SourceId( @ProtoNumber(1) @JvmField val sourceId: Int = 0, ) : ProtoBuf @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val int32Result: Int = 0, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val groupLabel: List<Label> = emptyList(), ) : ProtoBuf @Serializable internal class Color( @ProtoNumber(1) @JvmField val r: Int = 0, @ProtoNumber(2) @JvmField val g: Int = 0, @ProtoNumber(3) @JvmField val b: Int = 0, ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val sourceId: SourceId? = null, @ProtoNumber(2) @JvmField val uinInfo: UinInfo? = null, @ProtoNumber(3) @JvmField val numberLabel: Int = 5, @ProtoNumber(4) @JvmField val groupCode: List<Long> = emptyList(), @ProtoNumber(5) @JvmField val labelStyle: Int = 0, ) : ProtoBuf @Serializable internal class UinInfo( @ProtoNumber(1) @JvmField val int64Longitude: Long = 0L, @ProtoNumber(2) @JvmField val int64Latitude: Long = 0L, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/GroupFileCommon.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class GroupFileCommon : ProtoBuf { @Serializable internal class FeedsInfo( @JvmField @ProtoNumber(1) val busId: Int = 0, @JvmField @ProtoNumber(2) val fileId: String = "", @JvmField @ProtoNumber(3) val msgRandom: Int = 0, @JvmField @ProtoNumber(4) val ext: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(5) val feedFlag: Int = 0, @JvmField @ProtoNumber(6) val msgCtrl: MsgCtrl.MsgCtrl? = null ) : ProtoBuf @Serializable internal class FeedsResult( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val detail: String = "", @JvmField @ProtoNumber(3) val fileId: String = "", @JvmField @ProtoNumber(4) val busId: Int = 0, @JvmField @ProtoNumber(5) val deadTime: Int = 0 ) : ProtoBuf @Serializable internal class FileInfo( @JvmField @ProtoNumber(1) val fileId: String = "", @JvmField @ProtoNumber(2) val fileName: String = "", @JvmField @ProtoNumber(3) val fileSize: Long = 0L, @JvmField @ProtoNumber(4) val busId: Int = 0, @JvmField @ProtoNumber(5) val uploadedSize: Long = 0L, @JvmField @ProtoNumber(6) val uploadTime: Int = 0, @JvmField @ProtoNumber(7) val deadTime: Int = 0, @JvmField @ProtoNumber(8) val modifyTime: Int = 0, @JvmField @ProtoNumber(9) val downloadTimes: Int = 0, @JvmField @ProtoNumber(10) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(11) val sha3: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(12) val md5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(13) val localPath: String = "", @JvmField @ProtoNumber(14) val uploaderName: String = "", @JvmField @ProtoNumber(15) val uploaderUin: Long = 0L, @JvmField @ProtoNumber(16) val parentFolderId: String = "", @JvmField @ProtoNumber(17) val safeType: Int = 0, @JvmField @ProtoNumber(20) val fileBlobExt: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(21) val ownerUin: Long = 0L, @JvmField @ProtoNumber(22) val feedId: String = "", @JvmField @ProtoNumber(23) val reservedField: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class FileInfoTmem( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val files: List<FileInfo> = emptyList() ) : ProtoBuf @Serializable internal class FileItem( @JvmField @ProtoNumber(1) val type: Int = 0, @JvmField @ProtoNumber(2) val folderInfo: FolderInfo? = null, @JvmField @ProtoNumber(3) val fileInfo: FileInfo? = null ) : ProtoBuf @Serializable internal class FolderInfo( @JvmField @ProtoNumber(1) val folderId: String = "", // uuid @JvmField @ProtoNumber(2) val parentFolderId: String = "", @JvmField @ProtoNumber(3) val folderName: String = "", @JvmField @ProtoNumber(4) val createTime: Int = 0, @JvmField @ProtoNumber(5) val modifyTime: Int = 0, @JvmField @ProtoNumber(6) val createUin: Long = 0L, @JvmField @ProtoNumber(7) val creatorName: String = "", @JvmField @ProtoNumber(8) val totalFileCount: Int = 0, @JvmField @ProtoNumber(9) val modifyUin: Long = 0L, @JvmField @ProtoNumber(10) val modifyName: String = "", @JvmField @ProtoNumber(11) val usedSpace: Long = 0L ) : ProtoBuf @Serializable internal class FolderInfoTmem( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val folders: List<FolderInfo> = emptyList() ) : ProtoBuf @Serializable internal class OverwriteInfo( @JvmField @ProtoNumber(1) val fileId: String = "", @JvmField @ProtoNumber(2) val downloadTimes: Int = 0 ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Highway.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField /** * v8.5.5 */ @Serializable internal class BdhExtinfo : ProtoBuf { @Serializable internal class CommFileExtReq( @JvmField @ProtoNumber(1) val actionType: Int = 0, @JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class CommFileExtRsp( @JvmField @ProtoNumber(1) val int32Retcode: Int = 0, @JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PicInfo( @JvmField @ProtoNumber(1) val idx: Int = 0, @JvmField @ProtoNumber(2) val size: Int = 0, @JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(4) val type: Int = 0, ) : ProtoBuf @Serializable internal class QQVoiceExtReq( @JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val fmt: Int = 0, @JvmField @ProtoNumber(3) val rate: Int = 0, @JvmField @ProtoNumber(4) val bits: Int = 0, @JvmField @ProtoNumber(5) val channel: Int = 0, @JvmField @ProtoNumber(6) val pinyin: Int = 0, ) : ProtoBuf @Serializable internal class QQVoiceExtRsp( @JvmField @ProtoNumber(1) val qid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val int32Retcode: Int = 0, @JvmField @ProtoNumber(3) val msgResult: List<QQVoiceResult> = emptyList(), ) : ProtoBuf @Serializable internal class QQVoiceResult( @JvmField @ProtoNumber(1) val text: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val pinyin: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val source: Int = 0, ) : ProtoBuf @Serializable internal class ShortVideoReqExtInfo( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val sessionId: Long = 0L, @JvmField @ProtoNumber(3) val msgThumbinfo: PicInfo? = null, @JvmField @ProtoNumber(4) val msgVideoinfo: VideoInfo? = null, @JvmField @ProtoNumber(5) val msgShortvideoSureReq: ShortVideoSureReqInfo? = null, @JvmField @ProtoNumber(6) val boolIsMergeCmdBeforeData: Boolean = false, ) : ProtoBuf @Serializable internal class ShortVideoRspExtInfo( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val sessionId: Long = 0L, @JvmField @ProtoNumber(3) val int32Retcode: Int = 0, @JvmField @ProtoNumber(4) val errinfo: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(5) val msgThumbinfo: PicInfo? = null, @JvmField @ProtoNumber(6) val msgVideoinfo: VideoInfo? = null, @JvmField @ProtoNumber(7) val msgShortvideoSureRsp: ShortVideoSureRspInfo? = null, @JvmField @ProtoNumber(8) val retryFlag: Int = 0, ) : ProtoBuf @Serializable internal class ShortVideoSureReqInfo( @JvmField @ProtoNumber(1) val fromuin: Long = 0L, @JvmField @ProtoNumber(2) val chatType: Int = 0, @JvmField @ProtoNumber(3) val touin: Long = 0L, @JvmField @ProtoNumber(4) val groupCode: Long = 0L, @JvmField @ProtoNumber(5) val clientType: Int = 0, @JvmField @ProtoNumber(6) val msgThumbinfo: PicInfo? = null, @JvmField @ProtoNumber(7) val msgMergeVideoinfo: List<VideoInfo> = emptyList(), @JvmField @ProtoNumber(8) val msgDropVideoinfo: List<VideoInfo> = emptyList(), @JvmField @ProtoNumber(9) val businessType: Int = 0, @JvmField @ProtoNumber(10) val subBusinessType: Int = 0, ) : ProtoBuf @Serializable internal class ShortVideoSureRspInfo( @JvmField @ProtoNumber(1) val fileid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val msgVideoinfo: VideoInfo? = null, @JvmField @ProtoNumber(4) val mergeCost: Int = 0, ) : ProtoBuf @Serializable internal class StoryVideoExtReq : ProtoBuf @Serializable internal class StoryVideoExtRsp( @JvmField @ProtoNumber(1) val int32Retcode: Int = 0, @JvmField @ProtoNumber(2) val msg: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val cdnUrl: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(4) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(5) val fileId: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class UploadPicExtInfo( @JvmField @ProtoNumber(1) val fileResid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val thumbDownloadUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class VideoInfo( @JvmField @ProtoNumber(1) val idx: Int = 0, @JvmField @ProtoNumber(2) val size: Int = 0, @JvmField @ProtoNumber(3) val binMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(4) val format: Int = 0, @JvmField @ProtoNumber(5) val resLen: Int = 0, @JvmField @ProtoNumber(6) val resWidth: Int = 0, @JvmField @ProtoNumber(7) val time: Int = 0, @JvmField @ProtoNumber(8) val starttime: Long = 0L, @JvmField @ProtoNumber(9) val isAudio: Int = 0, ) : ProtoBuf } @Serializable internal class CSDataHighwayHead : ProtoBuf { @Serializable internal class C2CCommonExtendinfo( @JvmField @ProtoNumber(1) val infoId: Int = 0, @JvmField @ProtoNumber(2) val msgFilterExtendinfo: FilterExtendinfo? = null, ) : ProtoBuf @Serializable internal class DataHighwayHead( @JvmField @ProtoNumber(1) val version: Int = 0, @JvmField @ProtoNumber(2) val uin: String = "", @JvmField @ProtoNumber(3) val command: String = "", @JvmField @ProtoNumber(4) val seq: Int = 0, @JvmField @ProtoNumber(5) val retryTimes: Int? = null, // = 0, @JvmField @ProtoNumber(6) val appid: Int? = null, // = 0, @JvmField @ProtoNumber(7) val dataflag: Int? = null, // = 0, @JvmField @ProtoNumber(8) val commandId: Int? = null, // = 0, @JvmField @ProtoNumber(9) val buildVer: String = "", @JvmField @ProtoNumber(10) val localeId: Int = 0, @JvmField @ProtoNumber(11) val envId: Int = 0, ) : ProtoBuf @Serializable internal class DataHole( @JvmField @ProtoNumber(1) val begin: Long = 0L, @JvmField @ProtoNumber(2) val end: Long = 0L, ) : ProtoBuf @Serializable internal class FilterExtendinfo( @JvmField @ProtoNumber(1) val filterFlag: Int = 0, @JvmField @ProtoNumber(2) val msgImageFilterRequest: ImageFilterRequest? = null, ) : ProtoBuf @Serializable internal class FilterStyle( @JvmField @ProtoNumber(1) val styleId: Int = 0, @JvmField @ProtoNumber(2) val styleName: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ImageFilterRequest( @JvmField @ProtoNumber(1) val sessionId: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val clientIp: Int = 0, @JvmField @ProtoNumber(3) val uin: Long = 0L, @JvmField @ProtoNumber(4) val style: FilterStyle? = null, @JvmField @ProtoNumber(5) val width: Int = 0, @JvmField @ProtoNumber(6) val height: Int = 0, @JvmField @ProtoNumber(7) val imageData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ImageFilterResponse( @JvmField @ProtoNumber(1) val retCode: Int = 0, @JvmField @ProtoNumber(2) val imageData: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val costTime: Int = 0, ) : ProtoBuf @Serializable internal class LoginSigHead( @JvmField @ProtoNumber(1) val loginsigType: Int = 0, @JvmField @ProtoNumber(2) val loginsig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NewServiceTicket( @JvmField @ProtoNumber(1) val signature: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val ukey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PicInfoExt( @JvmField @ProtoNumber(1) val picWidth: Int = 0, @JvmField @ProtoNumber(2) val picHeight: Int = 0, @JvmField @ProtoNumber(3) val picFlag: Int = 0, @JvmField @ProtoNumber(4) val busiType: Int = 0, @JvmField @ProtoNumber(5) val srcTerm: Int = 0, @JvmField @ProtoNumber(6) val platType: Int = 0, @JvmField @ProtoNumber(7) val netType: Int = 0, @JvmField @ProtoNumber(8) val imgType: Int = 0, @JvmField @ProtoNumber(9) val appPicType: Int = 0, ) : ProtoBuf @Serializable internal class PicRspExtInfo( @JvmField @ProtoNumber(1) val skey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val clientIp: Int = 0, @JvmField @ProtoNumber(3) val upOffset: Long = 0L, @JvmField @ProtoNumber(4) val blockSize: Long = 0L, ) : ProtoBuf @Serializable internal class QueryHoleRsp( @JvmField @ProtoNumber(1) val result: Int = 0, @JvmField @ProtoNumber(2) val dataHole: List<DataHole> = emptyList(), @JvmField @ProtoNumber(3) val boolCompFlag: Boolean = false, ) : ProtoBuf @Serializable internal class ReqDataHighwayHead( @JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null, @JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null, @JvmField @ProtoNumber(3) val reqExtendinfo: ByteArray? = null, // = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(4) val timestamp: Long = 0L, @JvmField @ProtoNumber(5) val msgLoginSigHead: LoginSigHead? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val msgQueryHoleRsp: QueryHoleRsp? = null, ) : ProtoBuf @Serializable internal class RspDataHighwayHead( @JvmField @ProtoNumber(1) val msgBasehead: DataHighwayHead? = null, @JvmField @ProtoNumber(2) val msgSeghead: SegHead? = null, @JvmField @ProtoNumber(3) val errorCode: Int = 0, @JvmField @ProtoNumber(4) val allowRetry: Int = 0, @JvmField @ProtoNumber(5) val cachecost: Int = 0, @JvmField @ProtoNumber(6) val htcost: Int = 0, @JvmField @ProtoNumber(7) val rspExtendinfo: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val timestamp: Long = 0L, @JvmField @ProtoNumber(9) val range: Long = 0L, @JvmField @ProtoNumber(10) val isReset: Int = 0, ) : ProtoBuf @Serializable internal class SegHead( @JvmField @ProtoNumber(1) val serviceid: Int = 0, @JvmField @ProtoNumber(2) val filesize: Long = 0L, @JvmField @ProtoNumber(3) val dataoffset: Long = 0L, @JvmField @ProtoNumber(4) val datalength: Int = 0, @JvmField @ProtoNumber(5) val rtcode: Int? = null, // = 0, @JvmField @ProtoNumber(6) val serviceticket: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val flag: Int? = null, // = 0, @JvmField @ProtoNumber(8) val md5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(9) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val cacheAddr: Int = 0, @JvmField @ProtoNumber(11) val queryTimes: Int = 0, @JvmField @ProtoNumber(12) val updateCacheip: Int = 0, ) : ProtoBuf } @Serializable internal class HwConfigPersistentPB : ProtoBuf { @Serializable internal class HwConfigItemPB( @JvmField @ProtoNumber(1) val key: String = "", @JvmField @ProtoNumber(2) val endPointList: List<HwEndPointPB> = emptyList(), ) : ProtoBuf @Serializable internal class HwConfigPB( @JvmField @ProtoNumber(1) val configItemList: List<HwConfigItemPB> = emptyList(), @JvmField @ProtoNumber(2) val netSegConfList: List<HwNetSegConfPB> = emptyList(), @JvmField @ProtoNumber(3) val shortVideoNetConf: List<HwNetSegConfPB> = emptyList(), @JvmField @ProtoNumber(4) val configItemListIp6: List<HwConfigItemPB> = emptyList(), ) : ProtoBuf @Serializable internal class HwEndPointPB( @JvmField @ProtoNumber(1) val host: String = "", @JvmField @ProtoNumber(2) val int32Port: Int = 0, @JvmField @ProtoNumber(3) val int64Timestampe: Long = 0L, ) : ProtoBuf @Serializable internal class HwNetSegConfPB( @JvmField @ProtoNumber(1) val int64NetType: Long = 0L, @JvmField @ProtoNumber(2) val int64SegSize: Long = 0L, @JvmField @ProtoNumber(3) val int64SegNum: Long = 0L, @JvmField @ProtoNumber(4) val int64CurConnNum: Long = 0L, ) : ProtoBuf } @Serializable internal class HwSessionInfoPersistentPB : ProtoBuf { @Serializable internal class HwSessionInfoPB( @JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } @Serializable internal class Subcmd0x501 : ProtoBuf { @Serializable internal class ReqBody( @JvmField @ProtoNumber(1281) val msgSubcmd0x501ReqBody: SubCmd0x501ReqBody? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1281) val msgSubcmd0x501RspBody: SubCmd0x501Rspbody? = null, ) : ProtoBuf @Serializable internal class SubCmd0x501ReqBody( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val idcId: Int = 0, @JvmField @ProtoNumber(3) val appid: Int = 0, @JvmField @ProtoNumber(4) val loginSigType: Int = 0, @JvmField @ProtoNumber(5) val loginSigTicket: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val requestFlag: Int = 0, @JvmField @ProtoNumber(7) val uint32ServiceTypes: List<Int> = emptyList(), @JvmField @ProtoNumber(8) val bid: Int = 0, @JvmField @ProtoNumber(9) val term: Int = 0, @JvmField @ProtoNumber(10) val plat: Int = 0, @JvmField @ProtoNumber(11) val net: Int = 0, @JvmField @ProtoNumber(12) val caller: Int = 0, ) : ProtoBuf @Serializable internal class SubCmd0x501Rspbody( @JvmField @ProtoNumber(1) val httpconnSigSession: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val msgHttpconnAddrs: List<SrvAddrs> = emptyList(), @JvmField @ProtoNumber(4) val preConnection: Int = 0, @JvmField @ProtoNumber(5) val csConn: Int = 0, @JvmField @ProtoNumber(6) val msgIpLearnConf: IpLearnConf? = null, @JvmField @ProtoNumber(7) val msgDynTimeoutConf: DynTimeOutConf? = null, @JvmField @ProtoNumber(8) val msgOpenUpConf: OpenUpConf? = null, @JvmField @ProtoNumber(9) val msgDownloadEncryptConf: DownloadEncryptConf? = null, @JvmField @ProtoNumber(10) val msgShortVideoConf: ShortVideoConf? = null, @JvmField @ProtoNumber(11) val msgPtvConf: PTVConf? = null, @JvmField @ProtoNumber(12) val shareType: Int = 0, @JvmField @ProtoNumber(13) val shareChannel: Int = 0, @JvmField @ProtoNumber(14) val fmtPolicy: Int = 0, @JvmField @ProtoNumber(15) val bigdataPolicy: Int = 0, @JvmField @ProtoNumber(16) val connAttemptDelay: Int = 0, ) : ProtoBuf { @Serializable internal class DownloadEncryptConf( @JvmField @ProtoNumber(1) val boolEnableEncryptRequest: Boolean = false, @JvmField @ProtoNumber(2) val boolEnableEncryptedPic: Boolean = false, @JvmField @ProtoNumber(3) val ctrlFlag: Int = 0, ) : ProtoBuf @Serializable internal class DynTimeOutConf( @JvmField @ProtoNumber(1) val tbase2g: Int = 0, @JvmField @ProtoNumber(2) val tbase3g: Int = 0, @JvmField @ProtoNumber(3) val tbase4g: Int = 0, @JvmField @ProtoNumber(4) val tbaseWifi: Int = 0, @JvmField @ProtoNumber(5) val torg2g: Int = 0, @JvmField @ProtoNumber(6) val torg3g: Int = 0, @JvmField @ProtoNumber(7) val torg4g: Int = 0, @JvmField @ProtoNumber(8) val torgWifi: Int = 0, @JvmField @ProtoNumber(9) val maxTimeout: Int = 0, @JvmField @ProtoNumber(10) val enableDynTimeout: Int = 0, @JvmField @ProtoNumber(11) val maxTimeout2g: Int = 0, @JvmField @ProtoNumber(12) val maxTimeout3g: Int = 0, @JvmField @ProtoNumber(13) val maxTimeout4g: Int = 0, @JvmField @ProtoNumber(14) val maxTimeoutWifi: Int = 0, @JvmField @ProtoNumber(15) val hbTimeout2g: Int = 0, @JvmField @ProtoNumber(16) val hbTimeout3g: Int = 0, @JvmField @ProtoNumber(17) val hbTimeout4g: Int = 0, @JvmField @ProtoNumber(18) val hbTimeoutWifi: Int = 0, @JvmField @ProtoNumber(19) val hbTimeoutDefault: Int = 0, ) : ProtoBuf @Serializable internal class Ip6Addr( @JvmField @ProtoNumber(1) val type: Int = 0, @JvmField @ProtoNumber(2) val ip6: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val port: Int = 0, @JvmField @ProtoNumber(4) val area: Int = 0, @JvmField @ProtoNumber(5) val sameIsp: Int = 0, ) : ProtoBuf @Serializable internal class IpAddr( @JvmField @ProtoNumber(1) val type: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @JvmField @ProtoNumber(2) val ip: Int = 0, @JvmField @ProtoNumber(3) val port: Int = 0, @JvmField @ProtoNumber(4) val area: Int = 0, @JvmField @ProtoNumber(5) val sameIsp: Int = 0, ) : ProtoBuf { fun decode(): Pair<Int, Int> = ip to port } @Serializable internal class IpLearnConf( @JvmField @ProtoNumber(1) val refreshCachedIp: Int = 0, @JvmField @ProtoNumber(2) val enableIpLearn: Int = 0, ) : ProtoBuf @Serializable internal class NetSegConf( @JvmField @ProtoNumber(1) val netType: Int = 0, @JvmField @ProtoNumber(2) val segsize: Int = 0, @JvmField @ProtoNumber(3) val segnum: Int = 0, @JvmField @ProtoNumber(4) val curconnnum: Int = 0, ) : ProtoBuf @Serializable internal class OpenUpConf( @JvmField @ProtoNumber(1) val boolEnableOpenup: Boolean = false, @JvmField @ProtoNumber(2) val preSendSegnum: Int = 0, @JvmField @ProtoNumber(3) val preSendSegnum3g: Int = 0, @JvmField @ProtoNumber(4) val preSendSegnum4g: Int = 0, @JvmField @ProtoNumber(5) val preSendSegnumWifi: Int = 0, ) : ProtoBuf @Serializable internal class PTVConf( @JvmField @ProtoNumber(1) val channelType: Int = 0, @JvmField @ProtoNumber(2) val msgNetsegconf: List<NetSegConf> = emptyList(), @JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false, ) : ProtoBuf @Serializable internal class ShortVideoConf( @JvmField @ProtoNumber(1) val channelType: Int = 0, @JvmField @ProtoNumber(2) val msgNetsegconf: List<NetSegConf> = emptyList(), @JvmField @ProtoNumber(3) val boolOpenHardwareCodec: Boolean = false, @JvmField @ProtoNumber(4) val boolSendAheadSignal: Boolean = false, ) : ProtoBuf @Serializable internal data class SrvAddrs( @JvmField @ProtoNumber(1) val serviceType: Int = 0, @JvmField @ProtoNumber(2) val msgAddrs: List<IpAddr> = emptyList(), @JvmField @ProtoNumber(3) val fragmentSize: Int = 0, @JvmField @ProtoNumber(4) val msgNetsegconf: List<NetSegConf> = emptyList(), @JvmField @ProtoNumber(5) val msgAddrsV6: List<Ip6Addr> = emptyList(), ) : ProtoBuf } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/HummerCommelem.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class HummerCommelem : ProtoBuf { @Serializable internal class MsgElemInfoServtype1( @ProtoNumber(1) @JvmField val rewardId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val senderUin: Long = 0L, @ProtoNumber(3) @JvmField val picType: Int = 0, @ProtoNumber(4) @JvmField val rewardMoney: Int = 0, @ProtoNumber(5) @JvmField val url: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val createTimestamp: Int = 0, @ProtoNumber(8) @JvmField val status: Int = 0, @ProtoNumber(9) @JvmField val size: Int = 0, @ProtoNumber(10) @JvmField val videoDuration: Int = 0, @ProtoNumber(11) @JvmField val seq: Long = 0L, @ProtoNumber(12) @JvmField val rewardTypeExt: Int = 0 ) : ProtoBuf @Serializable internal class MsgElemInfoServtype11( @ProtoNumber(1) @JvmField val resID: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val resMD5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val reserveInfo1: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val reserveInfo2: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val doodleDataOffset: Int = 0, @ProtoNumber(6) @JvmField val doodleGifId: Int = 0, @ProtoNumber(7) @JvmField val doodleUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val doodleMd5: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype13( @ProtoNumber(1) @JvmField val sysHeadId: Int = 0, @ProtoNumber(2) @JvmField val headFlag: Int = 0 ) : ProtoBuf @Serializable internal class MsgElemInfoServtype14( @ProtoNumber(1) @JvmField val id: Int = 0, @ProtoNumber(2) @JvmField val reserveInfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype15( @ProtoNumber(1) @JvmField val vid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val cover: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val summary: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val createTime: Long = 0L, @ProtoNumber(6) @JvmField val commentContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val author: Long = 0L, @ProtoNumber(8) @JvmField val ctrVersion: Int = 0 ) : ProtoBuf @Serializable internal class MsgElemInfoServtype16( @ProtoNumber(1) @JvmField val uid: Long = 0L, @ProtoNumber(2) @JvmField val unionID: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val storyID: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val md5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val thumbUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val doodleUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val videoWidth: Int = 0, @ProtoNumber(8) @JvmField val videoHeight: Int = 0, @ProtoNumber(9) @JvmField val sourceName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val sourceActionType: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val sourceActionData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val ctrVersion: Int = 0 ) : ProtoBuf @Serializable internal class MsgElemInfoServtype18( @ProtoNumber(1) @JvmField val currentAmount: Long = 0L, @ProtoNumber(2) @JvmField val totalAmount: Long = 0L, @ProtoNumber(3) @JvmField val listid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val authKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val number: Int = 0 ) : ProtoBuf @Serializable internal class MsgElemInfoServtype19( @ProtoNumber(1) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype2( @ProtoNumber(1) @JvmField val pokeType: Int = 0, @ProtoNumber(2) @JvmField val pokeSummary: String = "", @ProtoNumber(3) @JvmField val doubleHit: Int = 0, @ProtoNumber(4) @JvmField val vaspokeId: Int = 0, @ProtoNumber(5) @JvmField val vaspokeName: String = "", @ProtoNumber(6) @JvmField val vaspokeMinver: String = "", @ProtoNumber(7) @JvmField val pokeStrength: Int = 0, @ProtoNumber(8) @JvmField val msgType: Int = 0, @ProtoNumber(9) @JvmField val faceBubbleCount: Int = 0, @ProtoNumber(10) @JvmField val pokeFlag: Int = 0 ) : ProtoBuf @Serializable internal class MsgElemInfoServtype20( @ProtoNumber(1) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype21( @ProtoNumber(1) @JvmField val topicId: Int = 0, @ProtoNumber(2) @JvmField val confessorUin: Long = 0L, @ProtoNumber(3) @JvmField val confessorNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val confessorSex: Int = 0, @ProtoNumber(5) @JvmField val sysmsgFlag: Int = 0, @ProtoNumber(6) @JvmField val c2cConfessCtx: C2CConfessContext? = null, @ProtoNumber(7) @JvmField val topic: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val confessTime: Long = 0L, @ProtoNumber(9) @JvmField val groupConfessMsg: GroupConfessMsg? = null, @ProtoNumber(10) @JvmField val groupConfessCtx: GroupConfessContext? = null ) : ProtoBuf { @Serializable internal class C2CConfessContext( @ProtoNumber(1) @JvmField val confessorUin: Long = 0L, @ProtoNumber(2) @JvmField val confessToUin: Long = 0L, @ProtoNumber(3) @JvmField val sendUin: Long = 0L, @ProtoNumber(4) @JvmField val confessorNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val confess: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val bgType: Int = 0, @ProtoNumber(7) @JvmField val topicId: Int = 0, @ProtoNumber(8) @JvmField val confessTime: Long = 0L, @ProtoNumber(9) @JvmField val confessorSex: Int = 0, @ProtoNumber(10) @JvmField val bizType: Int = 0, @ProtoNumber(11) @JvmField val confessNum: Int = 0, @ProtoNumber(12) @JvmField val confessToSex: Int = 0 ) : ProtoBuf @Serializable internal class GroupConfessContext( @ProtoNumber(1) @JvmField val confessorUin: Long = 0L, @ProtoNumber(2) @JvmField val confessToUin: Long = 0L, @ProtoNumber(3) @JvmField val sendUin: Long = 0L, @ProtoNumber(4) @JvmField val confessorSex: Int = 0, @ProtoNumber(5) @JvmField val confessToNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val topic: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val topicId: Int = 0, @ProtoNumber(8) @JvmField val confessTime: Long = 0L, @ProtoNumber(9) @JvmField val confessToNickType: Int = 0, @ProtoNumber(10) @JvmField val confessorNick: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupConfessItem( @ProtoNumber(1) @JvmField val topicId: Int = 0, @ProtoNumber(2) @JvmField val confessToUin: Long = 0L, @ProtoNumber(3) @JvmField val confessToNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val topic: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val confessToNickType: Int = 0 ) : ProtoBuf @Serializable internal class GroupConfessMsg( @ProtoNumber(1) @JvmField val confessTime: Long = 0L, @ProtoNumber(2) @JvmField val confessorUin: Long = 0L, @ProtoNumber(3) @JvmField val confessorSex: Int = 0, @ProtoNumber(4) @JvmField val sysmsgFlag: Int = 0, @ProtoNumber(5) @JvmField val confessItems: List<GroupConfessItem> = emptyList(), @ProtoNumber(6) @JvmField val totalTopicCount: Int = 0 ) : ProtoBuf } @Serializable internal class MsgElemInfoServtype23( @ProtoNumber(1) @JvmField val faceType: Int = 0, @ProtoNumber(2) @JvmField val faceBubbleCount: Int = 0, @ProtoNumber(3) @JvmField val faceSummary: String = "", @ProtoNumber(4) @JvmField val flag: Int = 0, @ProtoNumber(5) @JvmField val others: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype24( @ProtoNumber(1) @JvmField val limitChatEnter: LimitChatEnter? = null, @ProtoNumber(2) @JvmField val limitChatExit: LimitChatExit? = null ) : ProtoBuf { @Serializable internal class LimitChatEnter( @ProtoNumber(1) @JvmField val tipsWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val leftChatTime: Int = 0, @ProtoNumber(3) @JvmField val matchTs: Long = 0L, @ProtoNumber(4) @JvmField val matchExpiredTime: Int = 0, @ProtoNumber(5) @JvmField val c2cExpiredTime: Int = 0, @ProtoNumber(6) @JvmField val readyTs: Long = 0L, @ProtoNumber(7) @JvmField val matchNick: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class LimitChatExit( @ProtoNumber(1) @JvmField val exitMethod: Int = 0, @ProtoNumber(2) @JvmField val matchTs: Long = 0L ) : ProtoBuf } @Serializable internal class MsgElemInfoServtype27( @ProtoNumber(1) @JvmField val videoFile: ImMsgBody.VideoFile? = null ) : ProtoBuf @Serializable internal class MsgElemInfoServtype29( @ProtoNumber(1) @JvmField val luckybagMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype3( @ProtoNumber(1) @JvmField val flashTroopPic: ImMsgBody.CustomFace? = null, @ProtoNumber(2) @JvmField val flashC2cPic: ImMsgBody.NotOnlineImage? = null ) : ProtoBuf @Serializable internal class MsgElemInfoServtype31( @ProtoNumber(1) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val ext: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype33( @ProtoNumber(1) @JvmField val index: Int = 0, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val compat: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val buf: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MsgElemInfoServtype37( @ProtoNumber(1) @JvmField val packId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val stickerId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val qsId: Int = 0, @ProtoNumber(4) @JvmField val sourceType: Int = 0, @ProtoNumber(5) @JvmField val stickerType: Int = 0, @ProtoNumber(6) @JvmField val resultId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val surpriseId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val randomType: Int = 0 ) : ProtoBuf @Serializable internal class MsgElemInfoServtype4( @ProtoNumber(1) @JvmField val imsgType: Int = 0, @ProtoNumber(4) @JvmField val stStoryAioObjMsg: StoryAioObjMsg? = null ) : ProtoBuf @Serializable internal class MsgElemInfoServtype5( @ProtoNumber(1) @JvmField val vid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val cover: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val summary: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val createTime: Long = 0L, @ProtoNumber(6) @JvmField val commentContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val author: Long = 0L ) : ProtoBuf @Serializable internal class MsgElemInfoServtype8( @ProtoNumber(1) @JvmField val wifiDeliverGiftMsg: ImMsgBody.DeliverGiftMsg? = null ) : ProtoBuf @Serializable internal class MsgElemInfoServtype9( @ProtoNumber(1) @JvmField val anchorStatus: Int = 0, @ProtoNumber(2) @JvmField val jumpSchema: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val anchorNickname: String = "", @ProtoNumber(4) @JvmField val anchorHeadUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val liveTitle: String = "" ) : ProtoBuf @Serializable internal class StoryAioObjMsg( @ProtoNumber(1) @JvmField val uiUrl: String = "", @ProtoNumber(2) @JvmField val jmpUrl: String = "" ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/HummerResv12.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField @Serializable internal class MarketFaceExtPb : ProtoBuf { @Serializable internal class ResvAttr( @JvmField @ProtoNumber(1) val supportSize: List<SupportSize> = emptyList(), @JvmField @ProtoNumber(2) val sourceType: Int = 0, @JvmField @ProtoNumber(3) val sourceName: String = "", @JvmField @ProtoNumber(4) val sourceJumpUrl: String = "", @JvmField @ProtoNumber(5) val sourceTypeName: String = "", @JvmField @ProtoNumber(6) val startTime: Int = 0, @JvmField @ProtoNumber(7) val endTime: Int = 0, @JvmField @ProtoNumber(8) val emojiType: Int = 0, @JvmField @ProtoNumber(9) val apngSupportSize: List<SupportSize> = emptyList(), @JvmField @ProtoNumber(10) val hasIpProduct: Int = 0, ) : ProtoBuf @Serializable internal class SupportSize( @JvmField @ProtoNumber(1) val width: Int = 0, @JvmField @ProtoNumber(2) val height: Int = 0, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/HummerResv21.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class HummerResv21 : ProtoBuf { @Serializable internal class FileImgInfo( @JvmField @ProtoNumber(1) val fileWidth: Int = 0, @JvmField @ProtoNumber(2) val fileHeight: Int = 0, ) : ProtoBuf @Serializable internal class ForwardExtFileInfo( @JvmField @ProtoNumber(1) val fileType: Int = 0, @JvmField @ProtoNumber(2) val senderUin: Long = 0L, @JvmField @ProtoNumber(3) val receiverUin: Long = 0L, @JvmField @ProtoNumber(4) val fileUuid: String = "", @JvmField @ProtoNumber(5) val fileName: String = "", @JvmField @ProtoNumber(6) val fileSize: Long = 0L, @JvmField @ProtoNumber(7) val fileSha1: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(9) val int64DeadTime: Long = 0L, @JvmField @ProtoNumber(10) val imgWidth: Int = 0, @JvmField @ProtoNumber(11) val imgHeight: Int = 0, @JvmField @ProtoNumber(12) val videoDuration: Long = 0L, @JvmField @ProtoNumber(13) val busId: Int = 0, ) : ProtoBuf @Serializable internal class ResvAttr( @JvmField @ProtoNumber(1) val fileImageInfo: FileImgInfo? = null, @JvmField @ProtoNumber(2) val forwardExtFileInfo: ForwardExtFileInfo? = null, ) : ProtoBuf @Serializable internal class XtfSenderInfo( @JvmField @ProtoNumber(1) val lanIp: Int = 0, @JvmField @ProtoNumber(2) val lanPort: Int = 0, @JvmField @ProtoNumber(3) val lanSrkey: Long = 0L, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/HummerResv3.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField /** * v8.5.5 */ @Serializable internal class CustomFaceExtPb : ProtoBuf { @Serializable internal class AnimationImageShow( @JvmField @ProtoNumber(1) val int32EffectId: Int = 0, @JvmField @ProtoNumber(2) val animationParam: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ResvAttr( @ProtoNumber(1) override val imageBizType: Int = 0, @JvmField @ProtoNumber(2) val customfaceType: Int = 0, @JvmField @ProtoNumber(3) val emojiPackageid: Int = 0, @JvmField @ProtoNumber(4) val emojiId: Int = 0, @JvmField @ProtoNumber(5) val text: String = "", @JvmField @ProtoNumber(6) val doutuSuppliers: String = "", @JvmField @ProtoNumber(7) val msgImageShow: AnimationImageShow? = null, @ProtoNumber(9) override val textSummary: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val emojiFrom: Int = 0, @JvmField @ProtoNumber(11) val emojiSource: String = "", @JvmField @ProtoNumber(12) val emojiWebUrl: String = "", @JvmField @ProtoNumber(13) val emojiIconUrl: String = "", @JvmField @ProtoNumber(14) val emojiMarketFaceName: String = "", @JvmField @ProtoNumber(15) val source: Int = 0, @JvmField @ProtoNumber(16) val cameraCaptureTemplateinfo: String = "", @JvmField @ProtoNumber(17) val cameraCaptureMaterialname: String = "", @JvmField @ProtoNumber(18) val adEmoJumpUrl: String = "", @JvmField @ProtoNumber(19) val adEmoDescStr: String = "", ) : ProtoBuf, ImgExtPbResvAttrCommon } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/HummerResv6.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class NotOnlineImageExtPb : ProtoBuf { @Serializable internal class ResvAttr( @ProtoNumber(1) override val imageBizType: Int = 0, @JvmField @ProtoNumber(2) val customfaceType: Int = 0, @JvmField @ProtoNumber(3) val emojiPackageid: Int = 0, @JvmField @ProtoNumber(4) val emojiId: Int = 0, @JvmField @ProtoNumber(5) val text: String = "", @JvmField @ProtoNumber(6) val doutuSuppliers: String = "", @ProtoNumber(8) override val textSummary: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val emojiFrom: Int = 0, @JvmField @ProtoNumber(11) val emojiSource: String = "", @JvmField @ProtoNumber(12) val emojiWebUrl: String = "", @JvmField @ProtoNumber(13) val emojiIconUrl: String = "", @JvmField @ProtoNumber(14) val emojiMarketFaceName: String = "", @JvmField @ProtoNumber(15) val source: Int = 0 ) : ProtoBuf, ImgExtPbResvAttrCommon } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/ImageRequest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.jvm.JvmField internal interface ImgReq : ProtoBuf // cmd0x352$ReqBody @Serializable internal class GetImgUrlReq( @ProtoNumber(1) @JvmField val srcUni: Int, @ProtoNumber(2) @JvmField val dstUni: Int, @ProtoNumber(3) @JvmField val fileResID: String, //UUID /** * UUID例子: 没有找到 */ @ProtoNumber(4) @JvmField val urlFlag: Int = 1, //5 unknown, 好像没用 @ProtoNumber(6) @JvmField val urlType: Int = 4, @ProtoNumber(7) @JvmField val requestTerm: Int = 5, //确定 @ProtoNumber(8) @JvmField val requestPlatformType: Int = 9, //确定 @ProtoNumber(9) @JvmField val srcFileType: Int = 1, //2=ftn,1=picplatform,255 @ProtoNumber(10) @JvmField val innerIP: Int = 0, //确定 @ProtoNumber(11) @JvmField val addressBook: Int = 0, //[ChatType.internalID]== 1006为1[为CONTACT时] 我觉得发0没问题 @ProtoNumber(12) @JvmField val buType: Int = 1, //确定 @ProtoNumber(13) @JvmField val buildVer: String = "8.2.7.4410", //版本号 @ProtoNumber(14) @JvmField val timestamp: Int = currentTimeSeconds().toInt(), //(pic_up_timestamp) @ProtoNumber(15) @JvmField val requestTransferType: Int = 1, ) : ImgReq ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/ImgExtPbResvAttrCommon.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import net.mamoe.mirai.internal.utils.io.ProtoBuf internal interface ImgExtPbResvAttrCommon: ProtoBuf { val textSummary: ByteArray val imageBizType: Int } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/LongMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class LongMsg : ProtoBuf { @Serializable internal class MsgDeleteReq( @ProtoNumber(1) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val msgType: Int = 0, ) : ProtoBuf @Serializable internal class MsgDeleteRsp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgDownReq( @ProtoNumber(1) @JvmField val srcUin: Int = 0, @ProtoNumber(2) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val msgType: Int = 0, @ProtoNumber(4) @JvmField val needCache: Int = 0, ) : ProtoBuf @Serializable internal class MsgDownRsp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val msgContent: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgUpReq( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val dstUin: Long = 0L, @ProtoNumber(3) @JvmField val msgId: Int = 0, @ProtoNumber(4) @JvmField val msgContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val storeType: Int = 0, @ProtoNumber(6) @JvmField val msgUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val needCache: Int = 0, ) : ProtoBuf @Serializable internal class MsgUpRsp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val msgId: Int = 0, @ProtoNumber(3) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val subcmd: Int = 0, @ProtoNumber(2) @JvmField val termType: Int = 0, @ProtoNumber(3) @JvmField val platformType: Int = 0, @ProtoNumber(4) @JvmField val msgUpReq: List<LongMsg.MsgUpReq> = emptyList(), @ProtoNumber(5) @JvmField val msgDownReq: List<LongMsg.MsgDownReq> = emptyList(), @ProtoNumber(6) @JvmField val msgDelReq: List<LongMsg.MsgDeleteReq> = emptyList(), @ProtoNumber(10) @JvmField val agentType: Int = 0, ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val subcmd: Int = 0, @ProtoNumber(2) @JvmField val msgUpRsp: List<LongMsg.MsgUpRsp> = emptyList(), @ProtoNumber(3) @JvmField val msgDownRsp: List<LongMsg.MsgDownRsp> = emptyList(), @ProtoNumber(4) @JvmField val msgDelRsp: List<LongMsg.MsgDeleteRsp> = emptyList(), ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Msg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import io.ktor.utils.io.core.* import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.inflate import net.mamoe.mirai.utils.isSameType import net.mamoe.mirai.utils.structureToStringIfAvailable import kotlin.jvm.JvmField @Serializable internal class ImCommon : ProtoBuf { @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val groupId: Long = 0L, @ProtoNumber(2) @JvmField val groupType: Int /* enum */ = 1, ) : ProtoBuf @Serializable internal class Signature( @ProtoNumber(1) @JvmField val keyType: Int = 0, @ProtoNumber(2) @JvmField val sessionAppId: Int = 0, @ProtoNumber(3) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class Token( @ProtoNumber(1) @JvmField val buf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val c2cType: Int = 0, @ProtoNumber(3) @JvmField val serviceType: Int = 0, ) : ProtoBuf @Serializable internal class User( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val appId: Int = 0, @ProtoNumber(3) @JvmField val instanceId: Int = 0, @ProtoNumber(4) @JvmField val appType: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(5) @JvmField val clientIp: Int = 0, @ProtoNumber(6) @JvmField val version: Int = 0, @ProtoNumber(7) @JvmField val phoneNumber: String = "", @ProtoNumber(8) @JvmField val platformId: Int = 0, @ProtoNumber(9) @JvmField val language: Int = 0, @ProtoNumber(10) @JvmField val equipKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } @Serializable internal class ImImagent : ProtoBuf { @Serializable internal class ImAgentHead( @ProtoNumber(1) @JvmField val command: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val seq: Int = 0, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val err: String = "", @ProtoNumber(5) @JvmField val echoBuf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val reqUser: ImCommon.User? = null, @ProtoNumber(7) @JvmField val reqInfo: Requestinfo? = null, @ProtoNumber(8) @JvmField val signature: Signature? = null, @ProtoNumber(9) @JvmField val subCmd: Int = 0, @ProtoNumber(10) @JvmField val serverIp: Int = 0, ) : ProtoBuf @Serializable internal class ImAgentPackage( @ProtoNumber(1) @JvmField val head: ImAgentHead? = null, @ProtoNumber(11) @JvmField val msgSendReq: ImMsg.MsgSendReq? = null, @ProtoNumber(12) @JvmField val msgSendResp: ImMsg.MsgSendResp? = null, ) : ProtoBuf @Serializable internal class Requestinfo( @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(1) @JvmField val reqIp: Int = 0, @ProtoNumber(2) @JvmField val reqPort: Int = 0, @ProtoNumber(3) @JvmField val reqFlag: Int = 0, ) : ProtoBuf @Serializable internal class Signature( @ProtoNumber(1) @JvmField val keyType: Int = 0, @ProtoNumber(2) @JvmField val sessionAppId: Int = 0, @ProtoNumber(3) @JvmField val sessionKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } @Serializable internal class ImMsg : ProtoBuf { @Serializable internal class C2C( @ProtoNumber(1) @JvmField val sender: ImCommon.User? = null, @ProtoNumber(2) @JvmField val receiver: ImCommon.User? = null, @ProtoNumber(3) @JvmField val c2cRelation: C2CRelation? = null, ) : ProtoBuf @Serializable internal class C2CRelation( @ProtoNumber(1) @JvmField val c2cType: Int /* enum */ = 0, @ProtoNumber(2) @JvmField val groupInfo: ImCommon.GroupInfo? = null, @ProtoNumber(3) @JvmField val token: ImCommon.Token? = null, ) : ProtoBuf @Serializable internal class ContentHead( @ProtoNumber(1) @JvmField val pkgNum: Int = 1, @ProtoNumber(2) @JvmField val pkgIndex: Int = 0, @ProtoNumber(3) @JvmField val seq: Int = 0, @ProtoNumber(4) @JvmField val dateTime: Int = 0, @ProtoNumber(5) @JvmField val msgType: Int = 0, @ProtoNumber(6) @JvmField val divSeq: Int = 0, @ProtoNumber(7) @JvmField val msgdbUin: Long = 0L, @ProtoNumber(8) @JvmField val msgdbSeq: Int = 0, @ProtoNumber(9) @JvmField val wordMsgSeq: Int = 0, @ProtoNumber(10) @JvmField val msgRand: Int = 0, ) : ProtoBuf @Serializable internal class Group( @ProtoNumber(1) @JvmField val sender: ImCommon.User? = null, @ProtoNumber(2) @JvmField val receiver: ImCommon.User? = null, @ProtoNumber(3) @JvmField val groupInfo: ImCommon.GroupInfo? = null, ) : ProtoBuf @Serializable internal class Msg( @ProtoNumber(1) @JvmField val head: MsgHead? = null, @ProtoNumber(2) @JvmField val body: ImMsgBody.MsgBody? = null, ) : ProtoBuf @Serializable internal class MsgHead( @ProtoNumber(1) @JvmField val routingHead: RoutingHead? = null, @ProtoNumber(2) @JvmField val contentHead: ContentHead? = null, @ProtoNumber(3) @JvmField val gbkTmpMsgBody: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgSendReq( @ProtoNumber(1) @JvmField val msg: Msg? = null, @ProtoNumber(2) @JvmField val buMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val msgTailId: Int = 0, @ProtoNumber(4) @JvmField val connMsgFlag: Int = 0, @ProtoNumber(5) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgSendResp @Serializable internal class RoutingHead( @ProtoNumber(1) @JvmField val c2c: C2C? = null, @ProtoNumber(2) @JvmField val group: Group? = null, ) : ProtoBuf } @Serializable internal class ImMsgBody : ProtoBuf { @Serializable internal class AnonymousGroupMsg( @ProtoNumber(1) @JvmField val flags: Int = 0, @ProtoNumber(2) @JvmField val anonId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val anonNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val headPortrait: Int = 0, @ProtoNumber(5) @JvmField val expireTime: Int = 0, @ProtoNumber(6) @JvmField val bubbleId: Int = 0, @ProtoNumber(7) @JvmField val rankColor: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ApolloActMsg( @ProtoNumber(1) @JvmField val actionId: Int = 0, @ProtoNumber(2) @JvmField val actionName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val actionText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val flag: Int = 0, @ProtoNumber(5) @JvmField val peerUin: Int = 0, @ProtoNumber(6) @JvmField val senderTs: Int = 0, @ProtoNumber(7) @JvmField val peerTs: Int = 0, @ProtoNumber(8) @JvmField val int32SenderStatus: Int = 0, @ProtoNumber(9) @JvmField val int32PeerStatus: Int = 0, @ProtoNumber(10) @JvmField val diytextId: Int = 0, @ProtoNumber(11) @JvmField val diytextContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val inputText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ArkAppElem( @ProtoNumber(1) @JvmField val appName: String = "", @ProtoNumber(2) @JvmField val minVersion: String = "", @ProtoNumber(3) @JvmField val xmlTemplate: String = "", @ProtoNumber(4) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class Attr( @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(1) @JvmField val codePage: Int = -1, @ProtoNumber(2) @JvmField val time: Int = 1, @ProtoNumber(3) @JvmField val random: Int = 0, @ProtoNumber(4) @JvmField val color: Int = 0, @ProtoNumber(5) @JvmField val size: Int = 10, @ProtoNumber(6) @JvmField val effect: Int = 7, @ProtoNumber(7) @JvmField val charSet: Int = 78, @ProtoNumber(8) @JvmField val pitchAndFamily: Int = 90, @ProtoNumber(9) @JvmField val fontName: String = "Times New Roman", @ProtoNumber(10) @JvmField val reserveData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class BitAppMsg( @ProtoNumber(1) @JvmField val buf: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class BlessingMessage( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val exFlag: Int = 0, ) : ProtoBuf @Serializable internal class CommonElem( @ProtoNumber(1) @JvmField val serviceType: Int = 0, @ProtoNumber(2) @JvmField val pbElem: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val businessType: Int = 0, ) : ProtoBuf @Serializable internal class ConferenceTipsInfo( @ProtoNumber(1) @JvmField val sessionType: Int = 0, @ProtoNumber(2) @JvmField val sessionUin: Long = 0L, @ProtoNumber(3) @JvmField val text: String = "", ) : ProtoBuf @Serializable internal class CrmElem( @ProtoNumber(1) @JvmField val crmBuf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val qidianFlag: Int = 0, @ProtoNumber(4) @JvmField val pushFlag: Int = 0, @ProtoNumber(5) @JvmField val countFlag: Int = 0, ) : ProtoBuf @Serializable internal class CustomElem( @ProtoNumber(1) @JvmField val desc: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val enumType: Int /* enum */ = 1, @ProtoNumber(4) @JvmField val ext: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val sound: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class CustomFace( @ProtoNumber(1) @JvmField val guid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val filePath: String = "", @ProtoNumber(3) @JvmField val shortcut: String = "", @ProtoNumber(4) @JvmField val buffer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val flag: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val oldData: ByteArray? = null, @ProtoNumber(7) @JvmField val fileId: Int = 0, @ProtoNumber(8) @JvmField val serverIp: Int = 0, @ProtoNumber(9) @JvmField val serverPort: Int = 0, @ProtoNumber(10) @JvmField val fileType: Int = 0, @ProtoNumber(11) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val useful: Int = 0, @ProtoNumber(13) override val picMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) override val thumbUrl: String = "", @ProtoNumber(15) @JvmField val bigUrl: String = "", @ProtoNumber(16) override val origUrl: String = "", @ProtoNumber(17) @JvmField val bizType: Int = 0, @ProtoNumber(18) @JvmField val repeatIndex: Int = 0, @ProtoNumber(19) @JvmField val repeatImage: Int = 0, @ProtoNumber(20) @JvmField val imageType: Int = 0, @ProtoNumber(21) @JvmField val index: Int = 0, @ProtoNumber(22) @JvmField val width: Int = 0, @ProtoNumber(23) @JvmField val height: Int = 0, @ProtoNumber(24) @JvmField val source: Int = 0, @ProtoNumber(25) @JvmField val size: Int = 0, @ProtoNumber(26) @JvmField val origin: Int = 0, @ProtoNumber(27) @JvmField val thumbWidth: Int = 0, @ProtoNumber(28) @JvmField val thumbHeight: Int = 0, @ProtoNumber(29) @JvmField val showLen: Int = 0, @ProtoNumber(30) @JvmField val downloadLen: Int = 0, @ProtoNumber(31) override val _400Url: String = "", @ProtoNumber(32) @JvmField val _400Width: Int = 0, @ProtoNumber(33) @JvmField val _400Height: Int = 0, @ProtoNumber(34) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf, NotOnlineImageOrCustomFace @Serializable internal class DeliverGiftMsg( @ProtoNumber(1) @JvmField val grayTipContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val animationPackageId: Int = 0, @ProtoNumber(3) @JvmField val animationPackageUrlA: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val animationPackageUrlI: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val remindBrief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val giftId: Int = 0, @ProtoNumber(7) @JvmField val giftCount: Int = 0, @ProtoNumber(8) @JvmField val animationBrief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val senderUin: Long = 0L, @ProtoNumber(10) @JvmField val receiverUin: Long = 0L, @ProtoNumber(11) @JvmField val stmessageTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val stmessageSubtitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val stmessageMessage: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val stmessageGiftpicid: Int = 0, @ProtoNumber(15) @JvmField val stmessageComefrom: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(16) @JvmField val stmessageExflag: Int = 0, @ProtoNumber(17) @JvmField val toAllGiftId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val comefromLink: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20) @JvmField val receiverName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(21) @JvmField val receiverPic: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(22) @JvmField val stmessageGifturl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class EIMInfo( @ProtoNumber(1) @JvmField val rootId: Long = 0L, @ProtoNumber(2) @JvmField val flag: Int = 0, ) : ProtoBuf @Serializable internal class Elem( @ProtoNumber(1) @JvmField val text: Text? = null, @ProtoNumber(2) @JvmField val face: Face? = null, @ProtoNumber(3) @JvmField val onlineImage: OnlineImage? = null, @ProtoNumber(4) @JvmField val notOnlineImage: NotOnlineImage? = null, @ProtoNumber(5) @JvmField val transElemInfo: TransElem? = null, @ProtoNumber(6) @JvmField val marketFace: MarketFace? = null, @ProtoNumber(7) @JvmField val elemFlags: ElemFlags? = null, @ProtoNumber(8) @JvmField val customFace: CustomFace? = null, @ProtoNumber(9) @JvmField val elemFlags2: ElemFlags2? = null, @ProtoNumber(10) @JvmField val funFace: FunFace? = null, @ProtoNumber(11) @JvmField val secretFile: SecretFileMsg? = null, @ProtoNumber(12) @JvmField val richMsg: RichMsg? = null, @ProtoNumber(13) @JvmField val groupFile: GroupFile? = null, @ProtoNumber(14) @JvmField val pubGroup: PubGroup? = null, @ProtoNumber(15) @JvmField val marketTrans: MarketTrans? = null, @ProtoNumber(16) @JvmField val extraInfo: ExtraInfo? = null, @ProtoNumber(17) @JvmField val shakeWindow: ShakeWindow? = null, @ProtoNumber(18) @JvmField val pubAccount: PubAccount? = null, @ProtoNumber(19) @JvmField val videoFile: VideoFile? = null, @ProtoNumber(20) @JvmField val tipsInfo: TipsInfo? = null, @ProtoNumber(21) @JvmField val anonGroupMsg: AnonymousGroupMsg? = null, @ProtoNumber(22) @JvmField val qqLiveOld: QQLiveOld? = null, @ProtoNumber(23) @JvmField val lifeOnline: LifeOnlineAccount? = null, // @ProtoNumber(24) @JvmField val qqwalletMsg: QQWalletMsg? = null, @ProtoNumber(25) @JvmField val crmElem: CrmElem? = null, @ProtoNumber(26) @JvmField val conferenceTipsInfo: ConferenceTipsInfo? = null, @ProtoNumber(27) @JvmField val redbagInfo: RedBagInfo? = null, @ProtoNumber(28) @JvmField val lowVersionTips: LowVersionTips? = null, @ProtoNumber(29) @JvmField val bankcodeCtrlInfo: ByteArray? = null, @ProtoNumber(30) @JvmField val nearByMsg: NearByMessageType? = null, @ProtoNumber(31) @JvmField val customElem: CustomElem? = null, @ProtoNumber(32) @JvmField val locationInfo: LocationInfo? = null, @ProtoNumber(33) @JvmField val pubAccInfo: PubAccInfo? = null, @ProtoNumber(34) @JvmField val smallEmoji: SmallEmoji? = null, @ProtoNumber(35) @JvmField val fsjMsgElem: FSJMessageElem? = null, @ProtoNumber(36) @JvmField val arkApp: ArkAppElem? = null, @ProtoNumber(37) @JvmField val generalFlags: GeneralFlags? = null, @ProtoNumber(38) @JvmField val hcFlashPic: CustomFace? = null, @ProtoNumber(39) @JvmField val deliverGiftMsg: DeliverGiftMsg? = null, @ProtoNumber(40) @JvmField val bitappMsg: BitAppMsg? = null, @ProtoNumber(41) @JvmField val openQqData: OpenQQData? = null, @ProtoNumber(42) @JvmField val apolloMsg: ApolloActMsg? = null, @ProtoNumber(43) @JvmField val groupPubAccInfo: GroupPubAccountInfo? = null, @ProtoNumber(44) @JvmField val blessMsg: BlessingMessage? = null, @ProtoNumber(45) @JvmField val srcMsg: SourceMsg? = null, @ProtoNumber(46) @JvmField val lolaMsg: LolaMsg? = null, @ProtoNumber(47) @JvmField val groupBusinessMsg: GroupBusinessMsg? = null, @ProtoNumber(48) @JvmField val msgWorkflowNotify: WorkflowNotifyMsg? = null, @ProtoNumber(49) @JvmField val patElem: PatsElem? = null, @ProtoNumber(50) @JvmField val groupPostElem: GroupPostElem? = null, @ProtoNumber(51) @JvmField val lightApp: LightAppElem? = null, @ProtoNumber(52) @JvmField val eimInfo: EIMInfo? = null, @ProtoNumber(53) @JvmField val commonElem: CommonElem? = null, ) : ProtoBuf { override fun toString(): String { return this.structureToStringIfAvailable() ?: super.toString() } } @Serializable internal class ElemFlags( @ProtoNumber(1) @JvmField val flags1: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val businessData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ElemFlags2( @ProtoNumber(1) @JvmField val colorTextId: Int = 0, @ProtoNumber(2) @JvmField val msgId: Long = 0L, @ProtoNumber(3) @JvmField val whisperSessionId: Int = 0, @ProtoNumber(4) @JvmField val pttChangeBit: Int = 0, @ProtoNumber(5) @JvmField val vipStatus: Int = 0, @ProtoNumber(6) @JvmField val compatibleId: Int = 0, @ProtoNumber(7) @JvmField val insts: List<Inst> = emptyList(), @ProtoNumber(8) @JvmField val msgRptCnt: Int = 0, @ProtoNumber(9) @JvmField val srcInst: Inst? = null, @ProtoNumber(10) @JvmField val longtitude: Int = 0, @ProtoNumber(11) @JvmField val latitude: Int = 0, @ProtoNumber(12) @JvmField val customFont: Int = 0, @ProtoNumber(13) @JvmField val pcSupportDef: PcSupportDef? = null, @ProtoNumber(14) @JvmField val crmFlags: Int = 0, ) : ProtoBuf { @Serializable internal class Inst( @ProtoNumber(1) @JvmField val appId: Int = 0, @ProtoNumber(2) @JvmField val instId: Int = 0, ) } @Serializable internal class ExtraInfo( @ProtoNumber(1) @JvmField val nick: String = "", @ProtoNumber(2) @JvmField val groupCard: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val level: Int = 0, @ProtoNumber(4) @JvmField val flags: Int = 0, @ProtoNumber(5) @JvmField val groupMask: Int = 0, @ProtoNumber(6) @JvmField val msgTailId: Int = 0, @ProtoNumber(7) @JvmField val senderTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val apnsTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val uin: Long = 0L, @ProtoNumber(10) @JvmField val msgStateFlag: Int = 0, @ProtoNumber(11) @JvmField val apnsSoundType: Int = 0, @ProtoNumber(12) @JvmField val newGroupFlag: Int = 0, ) : ProtoBuf @Serializable internal class Face( @ProtoNumber(1) @JvmField val index: Int = 0, @ProtoNumber(2) @JvmField val old: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val buf: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class FSJMessageElem( @ProtoNumber(1) @JvmField val msgType: Int = 0, ) : ProtoBuf @Serializable internal class FunFace( @ProtoNumber(1) @JvmField val msgTurntable: Turntable? = null, @ProtoNumber(2) @JvmField val msgBomb: Bomb? = null, ) { @Serializable internal class Bomb( @ProtoNumber(1) @JvmField val boolBurst: Boolean = false, ) @Serializable internal class Turntable( @ProtoNumber(1) @JvmField val uint64UinList: List<Long> = emptyList(), @ProtoNumber(2) @JvmField val hitUin: Long = 0L, @ProtoNumber(3) @JvmField val hitUinNick: String = "", ) } @Serializable internal class GeneralFlags( @ProtoNumber(1) @JvmField val bubbleDiyTextId: Int = 0, @ProtoNumber(2) @JvmField val groupFlagNew: Int = 0, @ProtoNumber(3) @JvmField val uin: Long = 0L, @ProtoNumber(4) @JvmField val rpId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val prpFold: Int = 0, @ProtoNumber(6) @JvmField val longTextFlag: Int = 0, @ProtoNumber(7) @JvmField val longTextResid: String = "", @ProtoNumber(8) @JvmField val groupType: Int = 0, @ProtoNumber(9) @JvmField val toUinFlag: Int = 0, @ProtoNumber(10) @JvmField val glamourLevel: Int = 0, @ProtoNumber(11) @JvmField val memberLevel: Int = 0, @ProtoNumber(12) @JvmField val groupRankSeq: Long = 0L, @ProtoNumber(13) @JvmField val olympicTorch: Int = 0, @ProtoNumber(14) @JvmField val babyqGuideMsgCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val uin32ExpertFlag: Int = 0, @ProtoNumber(16) @JvmField val bubbleSubId: Int = 0, @ProtoNumber(17) @JvmField val pendantId: Long = 0L, @ProtoNumber(18) @JvmField val rpIndex: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, // 78 00 F8 01 00 C8 02 00 ) : ProtoBuf @Serializable internal class GroupBusinessMsg( @ProtoNumber(1) @JvmField val flags: Int = 0, @ProtoNumber(2) @JvmField val headUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val headClkUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val nickColor: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val rank: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val rankColor: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val rankBgcolor: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GroupFile( @ProtoNumber(1) @JvmField val filename: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fileSize: Long = 0L, @ProtoNumber(3) @JvmField val fileId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val batchId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val mark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val sequence: Long = 0L, @ProtoNumber(8) @JvmField val batchItemId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val feedMsgTime: Int = 0, @ProtoNumber(10) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GroupPostElem( @ProtoNumber(1) @JvmField val transType: Int = 0, @ProtoNumber(2) @JvmField val transMsg: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GroupPubAccountInfo( @ProtoNumber(1) @JvmField val pubAccount: Long = 0L, ) : ProtoBuf @Serializable internal class LifeOnlineAccount( @ProtoNumber(1) @JvmField val uniqueId: Long = 0L, @ProtoNumber(2) @JvmField val op: Int = 0, @ProtoNumber(3) @JvmField val showTime: Int = 0, @ProtoNumber(4) @JvmField val report: Int = 0, @ProtoNumber(5) @JvmField val ack: Int = 0, @ProtoNumber(6) @JvmField val bitmap: Long = 0L, @ProtoNumber(7) @JvmField val gdtImpData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val gdtCliData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val viewId: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class LightAppElem( @NestedStructure(LightAppElemDesensitizer::class) @ProtoNumber(1) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf internal object LightAppElemDesensitizer : NestedStructureDesensitizer<LightAppElem, ByteArray> { // unzip override fun deserialize(context: LightAppElem, byteArray: ByteArray): ByteArray { if (byteArray.isEmpty()) return byteArray return when (byteArray[0].toInt()) { 0 -> byteArrayOf(0) + byteArray.decodeToString(startIndex = 1).toByteArray() 1 -> byteArrayOf(0) + byteArray.inflate(offset = 1) else -> error("unknown compression flag=${byteArray[0]}") } } } @Serializable internal class LocationInfo( @ProtoNumber(1) @JvmField val longitude: Double = 0.0, @ProtoNumber(2) @JvmField val latitude: Double = 0.0, @ProtoNumber(3) @JvmField val desc: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class LolaMsg( @ProtoNumber(1) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val encodeContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val longMsgUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val downloadKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class LowVersionTips( @ProtoNumber(1) @JvmField val businessId: Int = 0, @ProtoNumber(2) @JvmField val sessionType: Int = 0, @ProtoNumber(3) @JvmField val sessionUin: Long = 0L, @ProtoNumber(4) @JvmField val senderUin: Long = 0L, @ProtoNumber(5) @JvmField val text: String = "", ) : ProtoBuf @Serializable internal data class MarketFace( @ProtoNumber(1) @JvmField var faceName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val itemType: Int = 0, @ProtoNumber(3) @JvmField val faceInfo: Int = 0, @ProtoNumber(4) @JvmField val faceId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val tabId: Int = 0, @ProtoNumber(6) @JvmField val subType: Int = 0, @ProtoNumber(7) @JvmField val key: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val param: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val mediaType: Int = 0, @ProtoNumber(10) @JvmField val imageWidth: Int = 0, @ProtoNumber(11) @JvmField val imageHeight: Int = 0, @ProtoNumber(12) @JvmField val mobileParam: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf { @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (!faceName.contentEquals(other.faceName)) return false if (itemType != other.itemType) return false if (faceInfo != other.faceInfo) return false if (!faceId.contentEquals(other.faceId)) return false if (tabId != other.tabId) return false if (subType != other.subType) return false if (!key.contentEquals(other.key)) return false if (!param.contentEquals(other.param)) return false if (mediaType != other.mediaType) return false if (imageWidth != other.imageWidth) return false if (imageHeight != other.imageHeight) return false if (!mobileParam.contentEquals(other.mobileParam)) return false if (!pbReserve.contentEquals(other.pbReserve)) return false return true } override fun hashCode(): Int { var result = faceName.contentHashCode() result = 31 * result + itemType result = 31 * result + faceInfo result = 31 * result + faceId.contentHashCode() result = 31 * result + tabId result = 31 * result + subType result = 31 * result + key.contentHashCode() result = 31 * result + param.contentHashCode() result = 31 * result + mediaType result = 31 * result + imageWidth result = 31 * result + imageHeight result = 31 * result + mobileParam.contentHashCode() result = 31 * result + pbReserve.contentHashCode() return result } } @Serializable internal class MarketTrans( @ProtoNumber(1) @JvmField val int32Flag: Int = 0, @ProtoNumber(2) @JvmField val xml: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val ability: Int = 0, @ProtoNumber(5) @JvmField val minAbility: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val richText: RichText = RichText(), @ProtoNumber(2) @JvmField val msgContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val msgEncryptContent: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgBodySubtype4( @ProtoNumber(1) @JvmField val msgNotOnlineFile: NotOnlineFile? = null, @ProtoNumber(2) @JvmField val msgTime: Int = 0, ) : ProtoBuf @Serializable internal class NearByMessageType( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val identifyType: Int = 0, ) : ProtoBuf @Serializable internal class NotOnlineFile( @ProtoNumber(1) @JvmField val fileType: Int = 0, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val fileSize: Long = 0L, @ProtoNumber(7) @JvmField val note: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val reserved: Int = 0, @ProtoNumber(9) @JvmField val subcmd: Int = 0, @ProtoNumber(10) @JvmField val microCloud: Int = 0, @ProtoNumber(11) @JvmField val bytesFileUrls: List<ByteArray> = emptyList(), @ProtoNumber(12) @JvmField val downloadFlag: Int = 0, @ProtoNumber(50) @JvmField val dangerEvel: Int = 0, @ProtoNumber(51) @JvmField val lifeTime: Int = 0, @ProtoNumber(52) @JvmField val uploadTime: Int = 0, @ProtoNumber(53) @JvmField val absFileType: Int = 0, @ProtoNumber(54) @JvmField val clientType: Int = 0, @ProtoNumber(55) @JvmField val expireTime: Int = 0, @ProtoNumber(56) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NewTechImageType( @ProtoNumber(1) @JvmField val i: Int = 0, @ProtoNumber(2) @JvmField val type: Int = 0, @ProtoNumber(3) @JvmField val j: Int = 0, @ProtoNumber(4) @JvmField val k: Int = 0, ) : ProtoBuf @Serializable internal class NewTechImageFileInfo( @ProtoNumber(1) @JvmField val size: Long = 0L, @ProtoNumber(2) @JvmField val md5: String = "", @ProtoNumber(3) @JvmField val sha1: String = "", @ProtoNumber(4) @JvmField val filePath: String = "", @ProtoNumber(5) @JvmField val imageType: NewTechImageType, @ProtoNumber(6) @JvmField val imageWidth: Int = 0, @ProtoNumber(7) @JvmField val imageHeight: Int = 0, @ProtoNumber(8) @JvmField val i: Int = 0, @ProtoNumber(9) @JvmField val j: Int = 0, ) : ProtoBuf @Serializable internal class NewTechImageMsgInfo( @ProtoNumber(1) @JvmField val imageInfo: NewTechImageFileInfo, @ProtoNumber(2) @JvmField val fileId: String = "", @ProtoNumber(3) @JvmField val i: Int = 0, @ProtoNumber(4) @JvmField val timestamp: Long = 0L, @ProtoNumber(5) @JvmField val friendOrGroup: Int = 0, @ProtoNumber(6) @JvmField val j: Int = 0, ) : ProtoBuf @Serializable internal class NewTechImageSpec( @ProtoNumber(1) @JvmField val origin: String = "&spec=0", @ProtoNumber(2) @JvmField val large: String = "&spec=720", @ProtoNumber(3) @JvmField val small: String = "&spec=198", ) : ProtoBuf @Serializable internal class NewTechImageNoKeyDownloadInfo( @ProtoNumber(1) @JvmField val noKeyUrl: String = "", @ProtoNumber(2) @JvmField val spec: NewTechImageSpec, @ProtoNumber(3) @JvmField val domain: String = "multimedia.nt.qq.com.cn", ) : ProtoBuf @Serializable internal class NewTechImageGroupInfo( @ProtoNumber(1) @JvmField val groupId: Long = 0L, ) : ProtoBuf @Serializable internal class NewTechImageFriendInfo( @ProtoNumber(1) @JvmField val i: Int = 0, @ProtoNumber(2) @JvmField val j: String = "", ) : ProtoBuf @Serializable internal class NewTechImageSenderInfo( @ProtoNumber(101) @JvmField val i: Int = 0, @ProtoNumber(102) @JvmField val j: Int = 0, @ProtoNumber(200) @JvmField val k: Int = 0, @ProtoNumber(201) @JvmField val friendInfo: NewTechImageFriendInfo?, @ProtoNumber(202) @JvmField val groupInfo: NewTechImageGroupInfo?, ) : ProtoBuf @Serializable internal class NewTechImageInfoMain( @ProtoNumber(1) @JvmField val msgInfo: NewTechImageMsgInfo, @ProtoNumber(2) @JvmField val noKeyDownloadInfo: NewTechImageNoKeyDownloadInfo, @ProtoNumber(5) @JvmField val k: Int = 0, @ProtoNumber(6) @JvmField val senderInfo: NewTechImageSenderInfo, ) : ProtoBuf @Serializable internal class NewTechImageMetaInfoSenderMetaSenderUnknown( @ProtoNumber(1) @JvmField val i: Int = 0, @ProtoNumber(2) @JvmField val j: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val k: Int = 0, @ProtoNumber(4) @JvmField val l: Int = 0, @ProtoNumber(5) @JvmField val m: Int = 0, @ProtoNumber(7) @JvmField val n: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NewTechImageMetaInfoSenderMeta( @ProtoNumber(1) @JvmField val i: Int = 0, @ProtoNumber(3) @JvmField val j: Int = 0, @ProtoNumber(4) @JvmField val k: Int = 0, @ProtoNumber(9) @JvmField val displayStr: String = "", @ProtoNumber(10) @JvmField val l: Int = 0, @ProtoNumber(12) @JvmField val m: ByteArray? = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val n: ByteArray? = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val o: ByteArray? = EMPTY_BYTE_ARRAY, @ProtoNumber(20) @JvmField val friendUnknown: NewTechImageMetaInfoSenderMetaSenderUnknown?, @ProtoNumber(21) @JvmField val groupUnknown: NewTechImageMetaInfoSenderMetaSenderUnknown?, @ProtoNumber(30) @JvmField val origUrl: String = "", @ProtoNumber(31) @JvmField val md5Upper: String = "", ) : ProtoBuf @Serializable internal class NewTechImageMetaInfoMain( @ProtoNumber(1) @JvmField val isEmoji: Int = 0, @ProtoNumber(2) @JvmField val displayStr: String = "", @ProtoNumber(11) @JvmField val friendMeta: NewTechImageMetaInfoSenderMeta?, @ProtoNumber(12) @JvmField val groupMeta: NewTechImageMetaInfoSenderMeta?, @ProtoNumber(1001) @JvmField val i: Int = 0, @ProtoNumber(1002) @JvmField val j: Int = 0, @ProtoNumber(1003) @JvmField val k: Int? = 0, ) : ProtoBuf @Serializable internal class NewTechImageMetaInfoUnknown2( @ProtoNumber(3) @JvmField val i: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NewTechImageMetaInfoUnknown3( @ProtoNumber(11) @JvmField val i: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val j: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NewTechImageMetaInfo( @ProtoNumber(1) @JvmField val main: NewTechImageMetaInfoMain, @ProtoNumber(2) @JvmField val unknown1: NewTechImageMetaInfoUnknown2, @ProtoNumber(3) @JvmField val unknown2: NewTechImageMetaInfoUnknown3, ) : ProtoBuf @Serializable internal class NewTechImageInfo( @ProtoNumber(1) @JvmField val info: NewTechImageInfoMain, @ProtoNumber(2) @JvmField val meta: NewTechImageMetaInfo, ) : ProtoBuf interface NotOnlineImageOrCustomFace { val thumbUrl: String val origUrl: String val _400Url: String val picMd5: ByteArray } @Serializable internal class NotOnlineImage( @ProtoNumber(1) @JvmField val filePath: String = "", @ProtoNumber(2) @JvmField val fileLen: Long = 0L, // originally int @ProtoNumber(3) @JvmField val downloadPath: String = "", @ProtoNumber(4) @JvmField val oldVerSendFile: ByteArray? = null, @ProtoNumber(5) @JvmField val imgType: Int = 0, @ProtoNumber(6) @JvmField val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) override val picMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val picHeight: Int = 0, @ProtoNumber(9) @JvmField val picWidth: Int = 0, @ProtoNumber(10) @JvmField val resId: String = "", @ProtoNumber(11) @JvmField val flag: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) override val thumbUrl: String = "", @ProtoNumber(13) @JvmField val original: Int = 0, @ProtoNumber(14) @JvmField val bigUrl: String = "", @ProtoNumber(15) override val origUrl: String = "", @ProtoNumber(16) @JvmField val bizType: Int = 0, @ProtoNumber(17) @JvmField val result: Int = 0, @ProtoNumber(18) @JvmField val index: Int = 0, @ProtoNumber(19) @JvmField val opFaceBuf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20) @JvmField val oldPicMd5: Boolean = false, @ProtoNumber(21) @JvmField val thumbWidth: Int = 0, @ProtoNumber(22) @JvmField val thumbHeight: Int = 0, @ProtoNumber(23) @JvmField val fileId: Int = 0, @ProtoNumber(24) @JvmField val showLen: Int = 0, @ProtoNumber(25) @JvmField val downloadLen: Int = 0, @ProtoNumber(26) override val _400Url: String = "", @ProtoNumber(27) @JvmField val _400Width: Int = 0, @ProtoNumber(28) @JvmField val _400Height: Int = 0, @ProtoNumber(29) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf, NotOnlineImageOrCustomFace @Serializable internal class OnlineImage( @ProtoNumber(1) @JvmField val guid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val filePath: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class OpenQQData( @ProtoNumber(1) @JvmField val carQqData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PatsElem( @ProtoNumber(1) @JvmField val patType: Int = 0, @ProtoNumber(2) @JvmField val patCount: Int = 0, ) : ProtoBuf @Serializable internal class PcSupportDef( @ProtoNumber(1) @JvmField val pcPtlBegin: Int = 0, @ProtoNumber(2) @JvmField val pcPtlEnd: Int = 0, @ProtoNumber(3) @JvmField val macPtlBegin: Int = 0, @ProtoNumber(4) @JvmField val macPtlEnd: Int = 0, @ProtoNumber(5) @JvmField val ptlsSupport: List<Int> = emptyList(), @ProtoNumber(6) @JvmField val ptlsNotSupport: List<Int> = emptyList(), ) : ProtoBuf @Serializable internal class Ptt( @ProtoNumber(1) @JvmField val fileType: Int = 0, @ProtoNumber(2) @JvmField val srcUin: Long = 0L, @ProtoNumber(3) @JvmField val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val fileSize: Int = 0, @ProtoNumber(7) @JvmField val reserve: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val fileId: Int = 0, @ProtoNumber(9) @JvmField val serverIp: Int = 0, @ProtoNumber(10) @JvmField val serverPort: Int = 0, @ProtoNumber(11) @JvmField val boolValid: Boolean = false, @ProtoNumber(12) @JvmField val signature: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val shortcut: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val magicPttIndex: Int = 0, @ProtoNumber(16) @JvmField val voiceSwitch: Int = 0, @ProtoNumber(17) @JvmField val pttUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val groupFileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val time: Int = 0, @ProtoNumber(20) @JvmField var downPara: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(29) @JvmField val format: Int = 0, @ProtoNumber(30) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(31) @JvmField val bytesPttUrls: List<ByteArray> = emptyList(), @ProtoNumber(32) @JvmField val downloadFlag: Int = 0, ) : ProtoBuf { override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (fileType != other.fileType) return false if (srcUin != other.srcUin) return false if (!fileUuid.contentEquals(other.fileUuid)) return false if (!fileMd5.contentEquals(other.fileMd5)) return false if (!fileName.contentEquals(other.fileName)) return false if (fileSize != other.fileSize) return false if (!reserve.contentEquals(other.reserve)) return false if (fileId != other.fileId) return false if (serverIp != other.serverIp) return false if (serverPort != other.serverPort) return false if (boolValid != other.boolValid) return false if (!signature.contentEquals(other.signature)) return false if (!shortcut.contentEquals(other.shortcut)) return false if (!fileKey.contentEquals(other.fileKey)) return false if (magicPttIndex != other.magicPttIndex) return false if (voiceSwitch != other.voiceSwitch) return false if (!pttUrl.contentEquals(other.pttUrl)) return false if (!groupFileKey.contentEquals(other.groupFileKey)) return false if (time != other.time) return false if (!downPara.contentEquals(other.downPara)) return false if (format != other.format) return false if (!pbReserve.contentEquals(other.pbReserve)) return false if (bytesPttUrls != other.bytesPttUrls) return false if (downloadFlag != other.downloadFlag) return false return true } override fun hashCode(): Int { var result = fileType result = 31 * result + srcUin.hashCode() result = 31 * result + fileUuid.contentHashCode() result = 31 * result + fileMd5.contentHashCode() result = 31 * result + fileName.contentHashCode() result = 31 * result + fileSize result = 31 * result + reserve.contentHashCode() result = 31 * result + fileId result = 31 * result + serverIp result = 31 * result + serverPort result = 31 * result + boolValid.hashCode() result = 31 * result + signature.contentHashCode() result = 31 * result + shortcut.contentHashCode() result = 31 * result + fileKey.contentHashCode() result = 31 * result + magicPttIndex result = 31 * result + voiceSwitch result = 31 * result + pttUrl.contentHashCode() result = 31 * result + groupFileKey.contentHashCode() result = 31 * result + time result = 31 * result + downPara.contentHashCode() result = 31 * result + format result = 31 * result + pbReserve.contentHashCode() result = 31 * result + bytesPttUrls.hashCode() result = 31 * result + downloadFlag return result } } @Serializable internal class PubAccInfo( @ProtoNumber(1) @JvmField val isInterNum: Int = 0, @ProtoNumber(2) @JvmField val ingMsgTemplateId: String = "", @ProtoNumber(3) @JvmField val ingLongMsgUrl: String = "", @ProtoNumber(4) @JvmField val downloadKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PubAccount( @ProtoNumber(1) @JvmField val buf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val pubAccountUin: Long = 0L, ) : ProtoBuf @Serializable internal class PubGroup( @ProtoNumber(1) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val gender: Int = 0, @ProtoNumber(3) @JvmField val age: Int = 0, @ProtoNumber(4) @JvmField val distance: Int = 0, ) : ProtoBuf @Serializable internal class QQLiveOld( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val showText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val param: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val introduce: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class QQWalletAioBody( @ProtoNumber(1) @JvmField val senduin: Long = 0L, @ProtoNumber(2) @JvmField val sender: QQWalletAioElem? = null, @ProtoNumber(3) @JvmField val receiver: QQWalletAioElem? = null, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(4) @JvmField val sint32Channelid: Int = 0, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(5) @JvmField val sint32Templateid: Int = 0, @ProtoNumber(6) @JvmField val resend: Int = 0, @ProtoNumber(7) @JvmField val msgPriority: Int = 0, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(8) @JvmField val sint32Redtype: Int = 0, @ProtoNumber(9) @JvmField val billno: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val authkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(11) @JvmField val sint32Sessiontype: Int = 0, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(12) @JvmField val sint32Msgtype: Int = 0, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(13) @JvmField val sint32Envelopeid: Int = 0, @ProtoNumber(14) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(15) @JvmField val sint32Conftype: Int = 0, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(16) @JvmField val sint32MsgFrom: Int = 0, @ProtoNumber(17) @JvmField val pcBody: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val ingIndex: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val redchannel: Int = 0, @ProtoNumber(20) @JvmField val grapUin: List<Long> = emptyList(), @ProtoNumber(21) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class QQWalletAioElem( @ProtoNumber(1) @JvmField val background: Int = 0, @ProtoNumber(2) @JvmField val icon: Int = 0, @ProtoNumber(3) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val subtitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val linkurl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val blackstripe: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val notice: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val titleColor: Int = 0, @ProtoNumber(10) @JvmField val subtitleColor: Int = 0, @ProtoNumber(11) @JvmField val actionsPriority: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val nativeIos: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val nativeAndroid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val iconurl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(16) @JvmField val contentColor: Int = 0, @ProtoNumber(17) @JvmField val contentBgcolor: Int = 0, @ProtoNumber(18) @JvmField val aioImageLeft: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val aioImageRight: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20) @JvmField val cftImage: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(21) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class QQWalletMsg( @ProtoNumber(1) @JvmField val aioBody: QQWalletAioBody? = null, ) : ProtoBuf @Serializable internal class RedBagInfo( @ProtoNumber(1) @JvmField val redbagType: Int = 0, ) : ProtoBuf @Serializable internal class RichMsg( @ProtoNumber(1) @JvmField val template1: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val serviceId: Int = 0, @ProtoNumber(3) @JvmField val msgResid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val rand: Int = 0, @ProtoNumber(5) @JvmField val seq: Int = 0, @ProtoNumber(6) @JvmField val flags: Int = 0, ) : ProtoBuf @Serializable internal class RichText( @ProtoNumber(1) @JvmField val attr: Attr? = null, @ProtoNumber(2) @JvmField val elems: List<Elem> = listOf(), @ProtoNumber(3) @JvmField val notOnlineFile: NotOnlineFile? = null, @ProtoNumber(4) @JvmField val ptt: Ptt? = null, @ProtoNumber(5) @JvmField val tmpPtt: TmpPtt? = null, @ProtoNumber(6) @JvmField val trans211TmpMsg: Trans211TmpMsg? = null, ) : ProtoBuf @Serializable internal class SecretFileMsg( @ProtoNumber(1) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fromUin: Long = 0L, @ProtoNumber(3) @JvmField val toUin: Long = 0L, @ProtoNumber(4) @JvmField val status: Int = 0, @ProtoNumber(5) @JvmField val ttl: Int = 0, @ProtoNumber(6) @JvmField val type: Int = 0, @ProtoNumber(7) @JvmField val encryptPreheadLength: Int = 0, @ProtoNumber(8) @JvmField val encryptType: Int = 0, @ProtoNumber(9) @JvmField val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val readTimes: Int = 0, @ProtoNumber(11) @JvmField val leftTime: Int = 0, @ProtoNumber(12) @JvmField val notOnlineImage: NotOnlineImage? = null, @ProtoNumber(13) @JvmField val elemFlags2: ElemFlags2? = null, @ProtoNumber(14) @JvmField val opertype: Int = 0, @ProtoNumber(15) @JvmField val fromphonenum: String = "", ) : ProtoBuf @Serializable internal class ShakeWindow( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val reserve: Int = 0, @ProtoNumber(3) @JvmField val uin: Long = 0L, ) : ProtoBuf @Serializable internal class SmallEmoji( @ProtoNumber(1) @JvmField val packIdSum: Int = 0, @ProtoNumber(2) @JvmField val imageType: Int = 0, ) : ProtoBuf @Serializable internal class SourceMsg( @ProtoNumber(1) @JvmField val origSeqs: IntArray = intArrayOf(), @ProtoNumber(2) @JvmField val senderUin: Long = 0L, @ProtoNumber(3) @JvmField val time: Int = 0, @ProtoNumber(4) @JvmField val flag: Int = 0, @ProtoNumber(5) @JvmField val elems: List<Elem> = emptyList(), @ProtoNumber(6) @JvmField val type: Int = 0, @ProtoNumber(7) @JvmField val richMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @NestedStructure(SrcMsgDesensitizer::class) @ProtoNumber(9) @JvmField val srcMsg: ByteArray? = null, @ProtoNumber(10) @JvmField val toUin: Long = 0L, @ProtoNumber(11) @JvmField val troopName: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf internal object SrcMsgDesensitizer : NestedStructureDesensitizer<SourceMsg, MsgComm.Msg> { override fun deserialize(context: SourceMsg, byteArray: ByteArray): MsgComm.Msg { return byteArray.loadAs(MsgComm.Msg.serializer()) } } @Serializable internal class Text( @ProtoNumber(1) @JvmField val str: String = "", @ProtoNumber(2) @JvmField val link: String = "", @ProtoNumber(3) @JvmField val attr6Buf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val attr7Buf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val buf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class TipsInfo( @ProtoNumber(1) @JvmField val text: String = "", ) : ProtoBuf @Serializable internal class TmpPtt( @ProtoNumber(1) @JvmField val fileType: Int = 0, @ProtoNumber(2) @JvmField val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val fileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fileSize: Int = 0, @ProtoNumber(6) @JvmField val pttTimes: Int = 0, @ProtoNumber(7) @JvmField val userType: Int = 0, @ProtoNumber(8) @JvmField val ptttransFlag: Int = 0, @ProtoNumber(9) @JvmField val busiType: Int = 0, @ProtoNumber(10) @JvmField val msgId: Long = 0L, @ProtoNumber(30) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(31) @JvmField val pttEncodeData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class Trans211TmpMsg( @ProtoNumber(1) @JvmField val msgBody: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val c2cCmd: Int = 0, ) : ProtoBuf @Serializable internal class TransElem( @ProtoNumber(1) @JvmField val elemType: Int = 0, @ProtoNumber(2) @JvmField val elemValue: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class VideoFile( @ProtoNumber(1) @JvmField val fileUuid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val fileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val fileFormat: Int = 0, @ProtoNumber(5) @JvmField val fileTime: Int = 0, @ProtoNumber(6) @JvmField val fileSize: Int = 0, @ProtoNumber(7) @JvmField val thumbWidth: Int = 0, @ProtoNumber(8) @JvmField val thumbHeight: Int = 0, @ProtoNumber(9) @JvmField val thumbFileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val source: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val thumbFileSize: Int = 0, @ProtoNumber(12) @JvmField val busiType: Int = 0, @ProtoNumber(13) @JvmField val fromChatType: Int = 0, @ProtoNumber(14) @JvmField val toChatType: Int = 0, @ProtoNumber(15) @JvmField val boolSupportProgressive: Boolean = false, @ProtoNumber(16) @JvmField val fileWidth: Int = 0, @ProtoNumber(17) @JvmField val fileHeight: Int = 0, @ProtoNumber(18) @JvmField val subBusiType: Int = 0, @ProtoNumber(19) @JvmField val videoAttr: Int = 0, @ProtoNumber(20) @JvmField val bytesThumbFileUrls: List<ByteArray> = emptyList(), @ProtoNumber(21) @JvmField val bytesVideoFileUrls: List<ByteArray> = emptyList(), @ProtoNumber(22) @JvmField val thumbDownloadFlag: Int = 0, @ProtoNumber(23) @JvmField val videoDownloadFlag: Int = 0, @ProtoNumber(24) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class WorkflowNotifyMsg( @ProtoNumber(1) @JvmField val extMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val createUin: Long = 0L, ) : ProtoBuf } @Serializable internal class ImMsgHead : ProtoBuf { @Serializable internal class C2CHead( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val fromUin: Long = 0L, @ProtoNumber(3) @JvmField val ccType: Int = 0, @ProtoNumber(4) @JvmField val ccCmd: Int = 0, @ProtoNumber(5) @JvmField val authPicSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val authSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val authBuf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val serverTime: Int = 0, @ProtoNumber(9) @JvmField val clientTime: Int = 0, @ProtoNumber(10) @JvmField val rand: Int = 0, @ProtoNumber(11) @JvmField val ingPhoneNumber: String = "", ) : ProtoBuf @Serializable internal class CSHead( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val command: Int = 0, @ProtoNumber(3) @JvmField val seq: Int = 0, @ProtoNumber(4) @JvmField val version: Int = 0, @ProtoNumber(5) @JvmField val retryTimes: Int = 0, @ProtoNumber(6) @JvmField val clientType: Int = 0, @ProtoNumber(7) @JvmField val pubno: Int = 0, @ProtoNumber(8) @JvmField val localid: Int = 0, @ProtoNumber(9) @JvmField val timezone: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(10) @JvmField val clientIp: Int = 0, @ProtoNumber(11) @JvmField val clientPort: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(12) @JvmField val connIp: Int = 0, @ProtoNumber(13) @JvmField val connPort: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(14) @JvmField val interfaceIp: Int = 0, @ProtoNumber(15) @JvmField val interfacePort: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(16) @JvmField val actualIp: Int = 0, @ProtoNumber(17) @JvmField val flag: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(18) @JvmField val timestamp: Int = 0, @ProtoNumber(19) @JvmField val subcmd: Int = 0, @ProtoNumber(20) @JvmField val result: Int = 0, @ProtoNumber(21) @JvmField val appId: Int = 0, @ProtoNumber(22) @JvmField val instanceId: Int = 0, @ProtoNumber(23) @JvmField val sessionId: Long = 0L, @ProtoNumber(24) @JvmField val idcId: Int = 0, ) : ProtoBuf @Serializable internal class DeltaHead( @ProtoNumber(1) @JvmField val totalLen: Long = 0L, @ProtoNumber(2) @JvmField val offset: Long = 0L, @ProtoNumber(3) @JvmField val ackOffset: Long = 0L, @ProtoNumber(4) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val ackCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val result: Int = 0, @ProtoNumber(7) @JvmField val flags: Int = 0, ) : ProtoBuf @Serializable internal class Head( @ProtoNumber(1) @JvmField val headType: Int = 0, @ProtoNumber(2) @JvmField val msgCsHead: CSHead? = null, @ProtoNumber(3) @JvmField val msgS2cHead: S2CHead? = null, @ProtoNumber(4) @JvmField val msgHttpconnHead: HttpConnHead? = null, @ProtoNumber(5) @JvmField val paintFlag: Int = 0, @ProtoNumber(6) @JvmField val msgLoginSig: LoginSig? = null, @ProtoNumber(7) @JvmField val msgDeltaHead: DeltaHead? = null, @ProtoNumber(8) @JvmField val msgC2cHead: C2CHead? = null, @ProtoNumber(9) @JvmField val msgSconnHead: SConnHead? = null, @ProtoNumber(10) @JvmField val msgInstCtrl: InstCtrl? = null, ) : ProtoBuf @Serializable internal class HttpConnHead( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val command: Int = 0, @ProtoNumber(3) @JvmField val subCommand: Int = 0, @ProtoNumber(4) @JvmField val seq: Int = 0, @ProtoNumber(5) @JvmField val version: Int = 0, @ProtoNumber(6) @JvmField val retryTimes: Int = 0, @ProtoNumber(7) @JvmField val clientType: Int = 0, @ProtoNumber(8) @JvmField val pubNo: Int = 0, @ProtoNumber(9) @JvmField val localId: Int = 0, @ProtoNumber(10) @JvmField val timeZone: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(11) @JvmField val clientIp: Int = 0, @ProtoNumber(12) @JvmField val clientPort: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(13) @JvmField val qzhttpIp: Int = 0, @ProtoNumber(14) @JvmField val qzhttpPort: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(15) @JvmField val sppIp: Int = 0, @ProtoNumber(16) @JvmField val sppPort: Int = 0, @ProtoNumber(17) @JvmField val flag: Int = 0, @ProtoNumber(18) @JvmField val key: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(19) @JvmField val compressType: Int = 0, @ProtoNumber(20) @JvmField val originSize: Int = 0, @ProtoNumber(21) @JvmField val errorCode: Int = 0, @ProtoNumber(22) @JvmField val msgRedirect: RedirectMsg? = null, @ProtoNumber(23) @JvmField val commandId: Int = 0, @ProtoNumber(24) @JvmField val serviceCmdid: Int = 0, @ProtoNumber(25) @JvmField val msgOidbhead: TransOidbHead? = null, ) : ProtoBuf @Serializable internal class InstCtrl( @ProtoNumber(1) @JvmField val msgSendToInst: List<InstInfo> = emptyList(), @ProtoNumber(2) @JvmField val msgExcludeInst: List<InstInfo> = emptyList(), @ProtoNumber(3) @JvmField val msgFromInst: InstInfo? = InstInfo(), ) : ProtoBuf @Serializable internal class InstInfo( @ProtoNumber(1) @JvmField val apppid: Int = 0, @ProtoNumber(2) @JvmField val instid: Int = 0, @ProtoNumber(3) @JvmField val platform: Int = 0, @ProtoNumber(10) @JvmField val enumDeviceType: Int /* enum */ = 0, ) : ProtoBuf @Serializable internal class LoginSig( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RedirectMsg( @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(1) @JvmField val lastRedirectIp: Int = 0, @ProtoNumber(2) @JvmField val lastRedirectPort: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(3) @JvmField val redirectIp: Int = 0, @ProtoNumber(4) @JvmField val redirectPort: Int = 0, @ProtoNumber(5) @JvmField val redirectCount: Int = 0, ) : ProtoBuf @Serializable internal class S2CHead( @ProtoNumber(1) @JvmField val subMsgtype: Int = 0, @ProtoNumber(2) @JvmField val msgType: Int = 0, @ProtoNumber(3) @JvmField val fromUin: Long = 0L, @ProtoNumber(4) @JvmField val msgId: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(5) @JvmField val relayIp: Int = 0, @ProtoNumber(6) @JvmField val relayPort: Int = 0, @ProtoNumber(7) @JvmField val toUin: Long = 0L, ) : ProtoBuf @Serializable internal class SConnHead : ProtoBuf @Serializable internal class TransOidbHead( @ProtoNumber(1) @JvmField val command: Int = 0, @ProtoNumber(2) @JvmField val serviceType: Int = 0, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val errorMsg: String = "", ) : ProtoBuf } @Serializable internal class ImReceipt : ProtoBuf { @Serializable internal class MsgInfo( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, @ProtoNumber(3) @JvmField val msgSeq: Int = 0, @ProtoNumber(4) @JvmField val msgRandom: Int = 0, ) : ProtoBuf @Serializable internal data class ReceiptInfo( @ProtoNumber(1) @JvmField val readTime: Long = 0L, ) : ProtoBuf @Serializable internal class ReceiptReq( @ProtoNumber(1) @JvmField val command: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val msgInfo: MsgInfo? = null, ) : ProtoBuf @Serializable internal class ReceiptResp( @ProtoNumber(1) @JvmField val command: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val receiptInfo: ReceiptInfo? = null, ) : ProtoBuf } @Serializable internal class ObjMsg : ProtoBuf { @Serializable internal class MsgContentInfo( @ProtoNumber(1) @JvmField val contentInfoId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val msgFile: MsgFile? = null, ) : ProtoBuf { @Serializable internal class MsgFile( @ProtoNumber(1) @JvmField val busId: Int = 0, @ProtoNumber(2) @JvmField val filePath: String = "", // actually uuid @ProtoNumber(3) @JvmField val fileSize: Long = 0L, @ProtoNumber(4) @JvmField val fileName: String = "", @ProtoNumber(5) @JvmField val int64DeadTime: Long = 0L, @ProtoNumber(6) @JvmField val fileSha1: ByteArray = EMPTY_BYTE_ARRAY, // empty @ProtoNumber(7) @JvmField val ext: String = "", // originally bytes ) : ProtoBuf } @Serializable internal class MsgPic( @ProtoNumber(1) @JvmField val smallPicUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val originalPicUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val localPicId: Int = 0, ) : ProtoBuf @Serializable internal class ObjMsg( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val bytesAbstact: List<ByteArray> = emptyList(), @ProtoNumber(5) @JvmField val titleExt: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val msgPic: List<MsgPic> = emptyList(), @ProtoNumber(7) @JvmField val msgContentInfo: List<MsgContentInfo> = emptyList(), @ProtoNumber(8) @JvmField val reportIdShow: Int = 0, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/MsgCommon.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField /** * msf.msgcomm.msg_comm */ @Serializable internal class MsgComm : ProtoBuf { @Serializable internal class AppShareInfo( @ProtoNumber(1) @JvmField val appshareId: Int = 0, @ProtoNumber(2) @JvmField val appshareCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val appshareResource: PluginInfo? = null, ) : ProtoBuf @Serializable internal class C2CTmpMsgHead( @ProtoNumber(1) @JvmField val c2cType: Int = 0, @ProtoNumber(2) @JvmField val serviceType: Int = 0, @ProtoNumber(3) @JvmField val groupUin: Long = 0L, @ProtoNumber(4) @JvmField val groupCode: Long = 0L, @ProtoNumber(5) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val sigType: Int = 0, @ProtoNumber(7) @JvmField val fromPhone: String = "", @ProtoNumber(8) @JvmField val toPhone: String = "", @ProtoNumber(9) @JvmField val lockDisplay: Int = 0, @ProtoNumber(10) @JvmField val directionFlag: Int = 0, @ProtoNumber(11) @JvmField val reserved: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ContentHead( @ProtoNumber(1) @JvmField val pkgNum: Int = 0, @ProtoNumber(2) @JvmField val pkgIndex: Int = 0, @ProtoNumber(3) @JvmField val divSeq: Int = 0, @ProtoNumber(4) @JvmField val autoReply: Int = 0, ) : ProtoBuf @Serializable internal class DiscussInfo( @ProtoNumber(1) @JvmField val discussUin: Long = 0L, @ProtoNumber(2) @JvmField val discussType: Int = 0, @ProtoNumber(3) @JvmField val discussInfoSeq: Long = 0L, @ProtoNumber(4) @JvmField val discussRemark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val discussName: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ExtGroupKeyInfo( @ProtoNumber(1) @JvmField val curMaxSeq: Int = 0, @ProtoNumber(2) @JvmField val curTime: Long = 0L, ) : ProtoBuf @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val groupType: Int = 0, @ProtoNumber(3) @JvmField val groupInfoSeq: Long = 0L, @ProtoNumber(4) @JvmField val groupCard: String = "", @ProtoNumber(5) @JvmField val groupRank: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val groupLevel: Int = 0, @ProtoNumber(7) @JvmField val groupCardType: Int = 0, @ProtoNumber(8) @JvmField val groupName: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class Msg( @ProtoNumber(1) @JvmField val msgHead: MsgHead, @ProtoNumber(2) @JvmField val contentHead: ContentHead? = null, @ProtoNumber(3) @JvmField val msgBody: ImMsgBody.MsgBody, @ProtoNumber(4) @JvmField val appshareInfo: AppShareInfo? = null, ) : ProtoBuf @Serializable internal class MsgHead( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, @ProtoNumber(3) @JvmField val msgType: Int = 0, @ProtoNumber(4) @JvmField val c2cCmd: Int = 0, @ProtoNumber(5) @JvmField val msgSeq: Int = 0, @ProtoNumber(6) @JvmField val msgTime: Int = 0, @ProtoNumber(7) var msgUid: Long = 0L, @ProtoNumber(8) @JvmField val c2cTmpMsgHead: C2CTmpMsgHead? = null, @ProtoNumber(9) @JvmField val groupInfo: GroupInfo? = null, /** * 1: 群消息 by pc tim * 1001: 群消息 sent by android phone * * * 3116: music share, ANDROID_PHONE 发送 */ @ProtoNumber(10) @JvmField val fromAppid: Int = 0, @ProtoNumber(11) @JvmField val fromInstid: Int = 0, @ProtoNumber(12) @JvmField val userActive: Int = 0, @ProtoNumber(13) @JvmField val discussInfo: DiscussInfo? = null, @ProtoNumber(14) @JvmField val fromNick: String = "", @ProtoNumber(15) @JvmField val authUin: Long = 0L, @ProtoNumber(16) @JvmField val authNick: String = "", @ProtoNumber(17) @JvmField val msgFlag: Int = 0, @ProtoNumber(18) @JvmField val authRemark: String = "", @ProtoNumber(19) @JvmField val groupName: String = "", @ProtoNumber(20) @JvmField val mutiltransHead: MutilTransHead? = null, @ProtoNumber(21) @JvmField val msgInstCtrl: ImMsgHead.InstCtrl? = null, @ProtoNumber(22) @JvmField val publicAccountGroupSendFlag: Int = 0, @ProtoNumber(23) @JvmField val wseqInC2cMsghead: Int = 0, @ProtoNumber(24) @JvmField val cpid: Long = 0L, @ProtoNumber(25) @JvmField val extGroupKeyInfo: ExtGroupKeyInfo? = null, @ProtoNumber(26) @JvmField val multiCompatibleText: String = "", @ProtoNumber(27) @JvmField val authSex: Int = 0, @ProtoNumber(28) @JvmField val isSrcMsg: Boolean = false, ) : ProtoBuf @Serializable internal class MsgType0x210( @ProtoNumber(1) @JvmField val subMsgType: Int = 0, @ProtoNumber(2) @JvmField val msgContent: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MutilTransHead( @ProtoNumber(1) @JvmField val status: Int = 0, @ProtoNumber(2) @JvmField val msgId: Int = 0, ) : ProtoBuf @Serializable internal class PluginInfo( @ProtoNumber(1) @JvmField val resId: Int = 0, @ProtoNumber(2) @JvmField val pkgName: String = "", @ProtoNumber(3) @JvmField val newVer: Int = 0, @ProtoNumber(4) @JvmField val resType: Int = 0, @ProtoNumber(5) @JvmField val lanType: Int = 0, @ProtoNumber(6) @JvmField val priority: Int = 0, @ProtoNumber(7) @JvmField val resName: String = "", @ProtoNumber(8) @JvmField val resDesc: String = "", @ProtoNumber(9) @JvmField val resUrlBig: String = "", @ProtoNumber(10) @JvmField val resUrlSmall: String = "", @ProtoNumber(11) @JvmField val resConf: String = "", ) : ProtoBuf @Serializable internal class Uin2Nick( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nick: String = "", ) : ProtoBuf @Serializable internal class UinPairMsg( @ProtoNumber(1) @JvmField val lastReadTime: Int = 0, @ProtoNumber(2) @JvmField val peerUin: Long = 0L, @ProtoNumber(3) @JvmField val msgCompleted: Int = 0, @ProtoNumber(4) @JvmField val msg: List<Msg> = emptyList(), @ProtoNumber(5) @JvmField val unreadMsgNum: Int = 0, @ProtoNumber(8) @JvmField val c2cType: Int = 0, @ProtoNumber(9) @JvmField val serviceType: Int = 0, @ProtoNumber(10) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/MsgRevokeUserDef.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField internal class MsgRevokeUserDef : ProtoBuf { @Serializable internal class MsgInfoUserDef( @ProtoNumber(1) @JvmField val longMessageFlag: Int, @ProtoNumber(2) @JvmField val longMsgInfo: List<MsgInfoDef> = emptyList(), @ProtoNumber(3) @JvmField val fileUuid: List<String> = listOf() ) : ProtoBuf { @Serializable internal class MsgInfoDef( @ProtoNumber(1) @JvmField val msgSeq: Int = 0, @ProtoNumber(2) @JvmField val longMsgId: Int = 0, @ProtoNumber(3) @JvmField val longMsgNum: Int = 0, @ProtoNumber(4) @JvmField val longMsgIndex: Int = 0 ) : ProtoBuf } @Serializable internal class UinTypeUserDef( @ProtoNumber(1) @JvmField val fromUinType: Int, @ProtoNumber(2) @JvmField val fromGroupCode: Long = 0L, @ProtoNumber(3) @JvmField val fileUuid: List<String> = emptyList() ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/MsgSvc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.CheckableResponseB import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class MsgSvc : ProtoBuf { @Serializable internal class Grp( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, ) : ProtoBuf @Serializable internal class PbGetMsgResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val syncCookie: ByteArray? = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val syncFlag: SyncFlag = SyncFlag.CONTINUE, @ProtoNumber(5) @JvmField val uinPairMsgs: List<MsgComm.UinPairMsg> = emptyList(), @ProtoNumber(6) @JvmField val bindUin: Long = 0L, @ProtoNumber(7) @JvmField val msgRspType: Int = 0, @ProtoNumber(8) @JvmField val pubAccountCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val isPartialSync: Boolean = false, @ProtoNumber(10) @JvmField val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbGroupMsgWithDrawReq( @ProtoNumber(1) @JvmField val subCmd: Int, @ProtoNumber(2) @JvmField val groupType: Int, @ProtoNumber(3) @JvmField val groupCode: Long = 0L, @ProtoNumber(4) @JvmField val msgList: List<MessageInfo> = emptyList(), @ProtoNumber(5) @JvmField val userdef: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf { @Serializable internal class MessageInfo( @ProtoNumber(1) @JvmField val msgSeq: Int = 0, @ProtoNumber(2) @JvmField val msgRandom: Int = 0, @ProtoNumber(3) @JvmField val msgType: Int = 0, ) } @Serializable internal class PbGroupReadedReportReq( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val lastReadSeq: Long = 0L, ) : ProtoBuf @Serializable internal class BusinessWPATmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val sigt: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class C2C( @ProtoNumber(1) @JvmField val toUin: Long = 0L, ) : ProtoBuf @Serializable internal class PbGetGroupMsgReq( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val beginSeq: Long = 0L, @ProtoNumber(3) @JvmField val endSeq: Long = 0L, @ProtoNumber(4) @JvmField val filter: Int /* enum */ = 0, @ProtoNumber(5) @JvmField val memberSeq: Long = 0L, @ProtoNumber(6) @JvmField val publicGroup: Boolean = false, @ProtoNumber(7) @JvmField val shieldFlag: Int = 0, @ProtoNumber(8) @JvmField val saveTrafficFlag: Int = 0, ) : ProtoBuf @Serializable internal class PbBindUinMsgReadedConfirmReq( @ProtoNumber(1) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val bindUin: Long = 0L, ) : ProtoBuf @Serializable internal class AccostTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val reply: Boolean = false, ) : ProtoBuf @Serializable internal class PbDiscussReadedReportResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val confUin: Long = 0L, @ProtoNumber(4) @JvmField val memberSeq: Long = 0L, @ProtoNumber(5) @JvmField val confSeq: Long = 0L, ) : ProtoBuf @Serializable internal class NearByAssistantTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val reply: Boolean = false, ) : ProtoBuf @Serializable internal class MsgSendInfo( @ProtoNumber(1) @JvmField val receiver: Int = 0, ) : ProtoBuf @Serializable internal class PubGroupTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val groupUin: Long = 0L, ) : ProtoBuf @Serializable internal class AddressListTmp( @ProtoNumber(1) @JvmField val fromPhone: String = "", @ProtoNumber(2) @JvmField val toPhone: String = "", @ProtoNumber(3) @JvmField val toUin: Long = 0L, @ProtoNumber(4) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val fromContactSize: Int = 0, ) : ProtoBuf @Serializable internal class DisTmp( @ProtoNumber(1) @JvmField val disUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, ) @Serializable internal class PbMsgWithDrawResp( @ProtoNumber(1) @JvmField val c2cWithDraw: List<PbC2CMsgWithDrawResp> = emptyList(), @ProtoNumber(2) @JvmField val groupWithDraw: List<PbGroupMsgWithDrawResp> = emptyList(), ) : ProtoBuf @Serializable internal class AuthTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbMsgWithDrawReq( @ProtoNumber(1) @JvmField val c2cWithDraw: List<PbC2CMsgWithDrawReq> = emptyList(), @ProtoNumber(2) @JvmField val groupWithDraw: List<PbGroupMsgWithDrawReq> = emptyList(), ) : ProtoBuf internal enum class SyncFlag { START, CONTINUE, STOP } @Serializable internal class PbGetMsgReq( @ProtoNumber(1) @JvmField val syncFlag: SyncFlag, @ProtoNumber(2) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val rambleFlag: Int = 1, @ProtoNumber(4) @JvmField val latestRambleNumber: Int = 20, @ProtoNumber(5) @JvmField val otherRambleNumber: Int = 3, @ProtoNumber(6) @JvmField val onlineSyncFlag: Int = 1, @ProtoNumber(7) @JvmField val contextFlag: Int = 0, @ProtoNumber(8) @JvmField val whisperSessionId: Int = 0, @ProtoNumber(9) @JvmField val msgReqType: Int = 0, @ProtoNumber(10) @JvmField val pubaccountCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val msgCtrlBuf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val serverBuf: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbGetOneDayRoamMsgReq( @ProtoNumber(1) @JvmField val peerUin: Long = 0L, @ProtoNumber(2) @JvmField val lastMsgtime: Long = 0L, @ProtoNumber(3) @JvmField val random: Long = 0L, @ProtoNumber(4) @JvmField val readCnt: Int = 0, ) : ProtoBuf @Serializable internal class GrpTmp( @ProtoNumber(1) @JvmField val groupUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, ) : ProtoBuf @Serializable internal class PbGetDiscussMsgResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val discussUin: Long = 0L, @ProtoNumber(4) @JvmField val returnEndSeq: Long = 0L, @ProtoNumber(5) @JvmField val returnBeginSeq: Long = 0L, @ProtoNumber(6) @JvmField val msg: List<MsgComm.Msg> = emptyList(), @ProtoNumber(7) @JvmField val lastGetTime: Long = 0L, @ProtoNumber(8) @JvmField val discussInfoSeq: Long = 0L, ) : ProtoBuf @Serializable internal class CommTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val c2cType: Int = 0, @ProtoNumber(3) @JvmField val svrType: Int = 0, @ProtoNumber(4) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val reserved: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbGroupMsgWithDrawResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val subCmd: Int = 0, @ProtoNumber(4) @JvmField val groupType: Int = 0, @ProtoNumber(5) @JvmField val groupCode: Long = 0L, @ProtoNumber(6) @JvmField val failedMsgList: List<MessageResult> = emptyList(), @ProtoNumber(7) @JvmField val userdef: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf { @Serializable internal class MessageResult( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val msgSeq: Int = 0, @ProtoNumber(3) @JvmField val msgTime: Int = 0, @ProtoNumber(4) @JvmField val msgRandom: Int = 0, @ProtoNumber(5) @JvmField val errMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val msgType: Int = 0, ) : ProtoBuf } @Serializable internal class PbC2CReadedReportResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbC2CUnReadMsgNumReq : ProtoBuf @Serializable internal class PbC2CMsgWithDrawReq( @ProtoNumber(1) @JvmField val msgInfo: List<MsgInfo> = emptyList(), @ProtoNumber(2) @JvmField val longMessageFlag: Int, @ProtoNumber(3) @JvmField val reserved: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val subCmd: Int = 0, ) : ProtoBuf { @Serializable internal class MsgInfo( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, @ProtoNumber(3) @JvmField val msgSeq: Int = 0, @ProtoNumber(4) @JvmField val msgUid: Long = 0L, @ProtoNumber(5) @JvmField val msgTime: Long = 0L, @ProtoNumber(6) @JvmField val msgRandom: Int = 0, @ProtoNumber(7) @JvmField val pkgNum: Int = 0, @ProtoNumber(8) @JvmField val pkgIndex: Int = 0, @ProtoNumber(9) @JvmField val divSeq: Int = 0, @ProtoNumber(10) @JvmField val msgType: Int, @ProtoNumber(20) @JvmField val routingHead: RoutingHead? = null, ) } @Serializable internal class PbDelRoamMsgResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", ) : ProtoBuf @Serializable internal class Dis( @ProtoNumber(1) @JvmField val disUin: Long = 0L, ) : ProtoBuf @Serializable internal class TransSvrInfo( @ProtoNumber(1) @JvmField val subType: Int = 0, @ProtoNumber(2) @JvmField val int32RetCode: Int = 0, @ProtoNumber(3) @JvmField val errMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val transInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbPullGroupMsgSeqResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val groupInfoResp: List<GroupInfoResp> = emptyList(), ) : ProtoBuf { @Serializable internal class GroupInfoResp( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val memberSeq: Long = 0L, @ProtoNumber(3) @JvmField val groupSeq: Long = 0L, ) } @Serializable internal class PbSendMsgReq( @ProtoNumber(1) @JvmField val routingHead: RoutingHead? = null, @ProtoNumber(2) @JvmField val contentHead: MsgComm.ContentHead? = null, @ProtoNumber(3) @JvmField val msgBody: ImMsgBody.MsgBody = ImMsgBody.MsgBody(), @ProtoNumber(4) @JvmField val msgSeq: Int = 0, @ProtoNumber(5) @JvmField val msgRand: Int = 0, @ProtoNumber(6) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val appShare: MsgComm.AppShareInfo? = null, @ProtoNumber(8) @JvmField val msgVia: Int = 0, @ProtoNumber(9) @JvmField val dataStatist: Int = 0, @ProtoNumber(10) @JvmField val multiMsgAssist: MultiMsgAssist? = null, @ProtoNumber(11) @JvmField val inputNotifyInfo: PbInputNotifyInfo? = null, @ProtoNumber(12) @JvmField val msgCtrl: MsgCtrl.MsgCtrl? = null, @ProtoNumber(13) @JvmField val receiptReq: ImReceipt.ReceiptReq? = null, @ProtoNumber(14) @JvmField val multiSendSeq: Int = 0, ) : ProtoBuf @Serializable internal class TransMsg( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val c2cCmd: Int = 0, ) : ProtoBuf @Serializable internal class PbDeleteMsgResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", ) : ProtoBuf @Serializable internal class PbSearchRoamMsgInCloudResp( @ProtoNumber(1) @JvmField val msg: List<MsgComm.Msg> = emptyList(), @ProtoNumber(2) @JvmField val serializeRspbody: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbInputNotifyInfo( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val ime: Int = 0, @ProtoNumber(3) @JvmField val notifyFlag: Int = 0, @ProtoNumber(4) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val iosPushWording: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbUnReadMsgSeqResp( @ProtoNumber(1) @JvmField val c2cUnreadInfo: PbC2CUnReadMsgNumResp? = null, @ProtoNumber(2) @JvmField val binduinUnreadInfo: List<PbBindUinUnReadMsgNumResp> = emptyList(), @ProtoNumber(3) @JvmField val groupUnreadInfo: PbPullGroupMsgSeqResp? = null, @ProtoNumber(4) @JvmField val discussUnreadInfo: PbPullDiscussMsgSeqResp? = null, @ProtoNumber(5) @JvmField val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumResp? = null, ) : ProtoBuf @Serializable internal class PbDeleteMsgReq( @ProtoNumber(1) @JvmField val msgItems: List<MsgItem> = emptyList(), ) : ProtoBuf { @Serializable internal class MsgItem( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, @ProtoNumber(3) @JvmField val msgType: Int = 0, @ProtoNumber(4) @JvmField val msgSeq: Int = 0, @ProtoNumber(5) @JvmField val msgUid: Long = 0L, @ProtoNumber(7) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } @Serializable internal class MultiMsgAssist( @ProtoNumber(1) @JvmField val repeatedRouting: List<RoutingHead> = emptyList(), @ProtoNumber(2) @JvmField val msgUse: Int /* enum */ = 1, @ProtoNumber(3) @JvmField val tempId: Long = 0L, @ProtoNumber(4) @JvmField val vedioLen: Long = 0L, @ProtoNumber(5) @JvmField val redbagId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val redbagAmount: Long = 0L, @ProtoNumber(7) @JvmField val hasReadbag: Int = 0, @ProtoNumber(8) @JvmField val hasVedio: Int = 0, ) : ProtoBuf @Serializable internal class PbMsgReadedReportReq( @ProtoNumber(1) @JvmField val grpReadReport: List<PbGroupReadedReportReq> = emptyList(), @ProtoNumber(2) @JvmField val disReadReport: List<PbDiscussReadedReportReq> = emptyList(), @ProtoNumber(3) @JvmField val c2cReadReport: PbC2CReadedReportReq? = null, @ProtoNumber(4) @JvmField val bindUinReadReport: PbBindUinMsgReadedConfirmReq? = null, ) : ProtoBuf @Serializable internal class PbGetOneDayRoamMsgResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val peerUin: Long = 0L, @ProtoNumber(4) @JvmField val lastMsgtime: Long = 0L, @ProtoNumber(5) @JvmField val random: Long = 0L, @ProtoNumber(6) @JvmField val msg: List<MsgComm.Msg> = emptyList(), @ProtoNumber(7) @JvmField val iscomplete: Int = 0, ) : ProtoBuf @Serializable internal class PbBindUinGetMsgReq( @ProtoNumber(1) @JvmField val bindUin: Long = 0L, @ProtoNumber(2) @JvmField val bindUinSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val syncFlag: Int /* enum */ = 0, @ProtoNumber(4) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NearByDatingTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val reply: Boolean = false, ) : ProtoBuf @Serializable internal class BsnsTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RoutingHead( @ProtoNumber(1) @JvmField val c2c: C2C? = null, @ProtoNumber(2) @JvmField val grp: Grp? = null, @ProtoNumber(3) @JvmField val grpTmp: GrpTmp? = null, @ProtoNumber(4) @JvmField val dis: Dis? = null, @ProtoNumber(5) @JvmField val disTmp: DisTmp? = null, @ProtoNumber(6) @JvmField val wpaTmp: WPATmp? = null, @ProtoNumber(7) @JvmField val secretFile: SecretFileHead? = null, @ProtoNumber(8) @JvmField val publicPlat: PublicPlat? = null, @ProtoNumber(9) @JvmField val transMsg: TransMsg? = null, @ProtoNumber(10) @JvmField val addressList: AddressListTmp? = null, @ProtoNumber(11) @JvmField val richStatusTmp: RichStatusTmp? = null, @ProtoNumber(12) @JvmField val transCmd: TransCmd? = null, @ProtoNumber(13) @JvmField val accostTmp: AccostTmp? = null, @ProtoNumber(14) @JvmField val pubGroupTmp: PubGroupTmp? = null, @ProtoNumber(15) @JvmField val trans0x211: Trans0x211? = null, @ProtoNumber(16) @JvmField val businessWpaTmp: BusinessWPATmp? = null, @ProtoNumber(17) @JvmField val authTmp: AuthTmp? = null, @ProtoNumber(18) @JvmField val bsnsTmp: BsnsTmp? = null, @ProtoNumber(19) @JvmField val qqQuerybusinessTmp: QQQueryBusinessTmp? = null, @ProtoNumber(20) @JvmField val nearbyDatingTmp: NearByDatingTmp? = null, @ProtoNumber(21) @JvmField val nearbyAssistantTmp: NearByAssistantTmp? = null, @ProtoNumber(22) @JvmField val commTmp: CommTmp? = null, ) : ProtoBuf @Serializable internal class TransResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val respTag: Int = 0, @ProtoNumber(4) @JvmField val respBuff: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbSendMsgResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val sendTime: Int = 0, @ProtoNumber(4) @JvmField val svrbusyWaitTime: Int = 0, @ProtoNumber(5) @JvmField val msgSendInfo: MsgSendInfo? = null, @ProtoNumber(6) @JvmField val errtype: Int = 0, @ProtoNumber(7) @JvmField val transSvrInfo: TransSvrInfo? = null, @ProtoNumber(8) @JvmField val receiptResp: ImReceipt.ReceiptResp? = null, @ProtoNumber(9) @JvmField val textAnalysisResult: Int = 0, ) : ProtoBuf, Packet @Serializable internal class PbBindUinUnReadMsgNumResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val bindUin: Long = 0L, @ProtoNumber(4) @JvmField val msgNum: Int = 0, ) : ProtoBuf @Serializable internal class PbGetDiscussMsgReq( @ProtoNumber(1) @JvmField val discussUin: Long = 0L, @ProtoNumber(2) @JvmField val endSeq: Long = 0L, @ProtoNumber(3) @JvmField val beginSeq: Long = 0L, @ProtoNumber(4) @JvmField val lastGetTime: Long = 0L, @ProtoNumber(5) @JvmField val discussInfoSeq: Long = 0L, @ProtoNumber(6) @JvmField val filter: Int /* enum */ = 0, @ProtoNumber(7) @JvmField val memberSeq: Long = 0L, ) : ProtoBuf @Serializable internal class PbC2CMsgWithDrawResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val msgStatus: List<MsgStatus> = emptyList(), @ProtoNumber(4) @JvmField val subCmd: Int = 0, ) : ProtoBuf { @Serializable internal class MsgStatus( @ProtoNumber(1) @JvmField val msgInfo: PbC2CMsgWithDrawReq.MsgInfo? = null, @ProtoNumber(2) @JvmField val status: Int = 0, ) : ProtoBuf } @Serializable internal class SecretFileHead( @ProtoNumber(1) @JvmField val secretFileMsg: SubMsgType0xc1.MsgBody? = null, // @ProtoNumber(2) @JvmField val secretFileStatus: SubMsgType0x1a.MsgBody? = null ) @Serializable internal class PbGetRoamMsgReq( @ProtoNumber(1) @JvmField val peerUin: Long = 0L, @ProtoNumber(2) @JvmField val lastMsgtime: Long = 0L, @ProtoNumber(3) @JvmField val random: Long = 0L, @ProtoNumber(4) @JvmField val readCnt: Int = 0, @ProtoNumber(5) @JvmField val checkPwd: Int = 0, @ProtoNumber(6) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val pwd: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val subcmd: Int = 0, @ProtoNumber(9) @JvmField val beginMsgtime: Long = 0L, @ProtoNumber(10) @JvmField val reqType: Int = 0, ) : ProtoBuf @Serializable internal class TransCmd( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val msgType: Int = 0, ) : ProtoBuf @Serializable internal class PbMsgReadedReportResp( @ProtoNumber(1) @JvmField val grpReadReport: List<PbGroupReadedReportResp> = emptyList(), @ProtoNumber(2) @JvmField val disReadReport: List<PbDiscussReadedReportResp> = emptyList(), @ProtoNumber(3) @JvmField val c2cReadReport: PbC2CReadedReportResp? = null, @ProtoNumber(4) @JvmField val bindUinReadReport: PbBindUinMsgReadedConfirmResp? = null, ) : ProtoBuf @Serializable internal class PbThirdQQUnReadMsgNumResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val thirdqqRespInfo: List<ThirdQQRespInfo> = emptyList(), @ProtoNumber(4) @JvmField val interval: Int = 0, ) : ProtoBuf { @Serializable internal class ThirdQQRespInfo( @ProtoNumber(1) @JvmField val thirdUin: Long = 0L, @ProtoNumber(2) @JvmField val thirdUinCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val msgNum: Int = 0, @ProtoNumber(4) @JvmField val msgFlag: Int = 0, @ProtoNumber(5) @JvmField val redbagTime: Int = 0, @ProtoNumber(6) @JvmField val status: Int = 0, @ProtoNumber(7) @JvmField val lastMsgTime: Int = 0, ) : ProtoBuf } @Serializable internal class RichStatusTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class QQQueryBusinessTmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbDelRoamMsgReq( @ProtoNumber(1) @JvmField val c2cMsg: C2CMsg? = null, @ProtoNumber(2) @JvmField val grpMsg: GrpMsg? = null, @ProtoNumber(3) @JvmField val disMsg: DisMsg? = null, ) : ProtoBuf { @Serializable internal class GrpMsg( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val msgSeq: Long = 0L, ) : ProtoBuf @Serializable internal class C2CMsg( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val peerUin: Long = 0L, @ProtoNumber(3) @JvmField val msgTime: Int = 0, @ProtoNumber(4) @JvmField val msgRandom: Int = 0, @ProtoNumber(5) @JvmField val msgSeq: Int = 0, ) : ProtoBuf @Serializable internal class DisMsg( @ProtoNumber(1) @JvmField val discussUin: Long = 0L, @ProtoNumber(2) @JvmField val msgSeq: Long = 0L, ) : ProtoBuf } @Serializable internal class PbUnReadMsgSeqReq( @ProtoNumber(1) @JvmField val c2cUnreadInfo: PbC2CUnReadMsgNumReq? = null, @ProtoNumber(2) @JvmField val binduinUnreadInfo: List<PbBindUinUnReadMsgNumReq> = emptyList(), @ProtoNumber(3) @JvmField val groupUnreadInfo: PbPullGroupMsgSeqReq? = null, @ProtoNumber(4) @JvmField val discussUnreadInfo: PbPullDiscussMsgSeqReq? = null, @ProtoNumber(5) @JvmField val thirdqqUnreadInfo: PbThirdQQUnReadMsgNumReq? = null, ) : ProtoBuf @Serializable internal class PbPullDiscussMsgSeqResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val discussInfoResp: List<DiscussInfoResp> = emptyList(), ) : ProtoBuf { @Serializable internal class DiscussInfoResp( @ProtoNumber(1) @JvmField val confUin: Long = 0L, @ProtoNumber(2) @JvmField val memberSeq: Long = 0L, @ProtoNumber(3) @JvmField val confSeq: Long = 0L, ) : ProtoBuf } @Serializable internal class PbPullDiscussMsgSeqReq( @ProtoNumber(1) @JvmField val discussInfoReq: List<DiscussInfoReq> = emptyList(), ) : ProtoBuf { @Serializable internal class DiscussInfoReq( @ProtoNumber(1) @JvmField val confUin: Long = 0L, @ProtoNumber(2) @JvmField val lastSeq: Long = 0L, ) : ProtoBuf } @Serializable internal class WPATmp( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PublicPlat( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbBindUinMsgReadedConfirmResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val bindUin: Long = 0L, ) : ProtoBuf @Serializable internal class PbGetRoamMsgResp( @ProtoNumber(1) override val result: Int = 0, @ProtoNumber(2) override val errmsg: String = "", @ProtoNumber(3) @JvmField val peerUin: Long = 0L, @ProtoNumber(4) @JvmField val lastMsgtime: Long = 0L, @ProtoNumber(5) @JvmField val random: Long = 0L, @ProtoNumber(6) @JvmField val msg: List<MsgComm.Msg> = emptyList(), @ProtoNumber(7) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf, CheckableResponseB() @Serializable internal class PbDiscussReadedReportReq( @ProtoNumber(1) @JvmField val confUin: Long = 0L, @ProtoNumber(2) @JvmField val lastReadSeq: Long = 0L, ) : ProtoBuf @Serializable internal class PbC2CReadedReportReq( @ProtoNumber(1) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val pairInfo: List<UinPairReadInfo> = emptyList(), ) : ProtoBuf { @Serializable internal class UinPairReadInfo( @ProtoNumber(1) @JvmField val peerUin: Long = 0L, @ProtoNumber(2) @JvmField val lastReadTime: Int = 0, @ProtoNumber(3) @JvmField val crmSig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } @Serializable internal class Trans0x211( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val ccCmd: Int = 0, @ProtoNumber(3) @JvmField val instCtrl: ImMsgHead.InstCtrl? = null, @ProtoNumber(4) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val c2cType: Int = 0, @ProtoNumber(6) @JvmField val serviceType: Int = 0, ) : ProtoBuf @Serializable internal class PbSearchRoamMsgInCloudReq( @ProtoNumber(1) @JvmField val serializeReqbody: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbBindUinUnReadMsgNumReq( @ProtoNumber(1) @JvmField val bindUin: Long = 0L, @ProtoNumber(2) @JvmField val syncCookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbC2CUnReadMsgNumResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val msgNum: Int = 0, ) : ProtoBuf @Serializable internal class PbPullGroupMsgSeqReq( @ProtoNumber(1) @JvmField val groupInfoReq: List<GroupInfoReq> = emptyList(), ) : ProtoBuf { @Serializable internal class GroupInfoReq( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val lastSeq: Long = 0L, ) : ProtoBuf } @Serializable internal class TransReq( @ProtoNumber(1) @JvmField val command: Int = 0, @ProtoNumber(2) @JvmField val reqTag: Int = 0, @ProtoNumber(3) @JvmField val reqBuff: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbGroupReadedReportResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val groupCode: Long = 0L, @ProtoNumber(4) @JvmField val memberSeq: Long = 0L, @ProtoNumber(5) @JvmField val groupMsgSeq: Long = 0L, ) : ProtoBuf @Serializable internal class PbGetGroupMsgResp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val errmsg: String = "", @ProtoNumber(3) @JvmField val groupCode: Long = 0L, @ProtoNumber(4) @JvmField val returnBeginSeq: Long = 0L, @ProtoNumber(5) @JvmField val returnEndSeq: Long = 0L, @ProtoNumber(6) @JvmField val msg: List<MsgComm.Msg> = emptyList(), ) : ProtoBuf @Serializable internal class PbThirdQQUnReadMsgNumReq( @ProtoNumber(1) @JvmField val thirdqqReqInfo: List<ThirdQQReqInfo> = emptyList(), @ProtoNumber(2) @JvmField val source: Int = 0, ) : ProtoBuf { @Serializable internal class ThirdQQReqInfo( @ProtoNumber(1) @JvmField val thirdUin: Long = 0L, @ProtoNumber(2) @JvmField val thirdUinSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val thirdUinCookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } @Serializable internal class MsgCtrl { @Serializable internal class MsgCtrl( @ProtoNumber(1) @JvmField val msgFlag: Int = 0, @ProtoNumber(2) @JvmField val resvResvInfo: ResvResvInfo? = null, ) : ProtoBuf @Serializable internal class ResvResvInfo( @ProtoNumber(1) @JvmField val flag: Int = 0, @ProtoNumber(2) @JvmField val reserv1: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val reserv2: Long = 0L, @ProtoNumber(4) @JvmField val reserv3: Long = 0L, @ProtoNumber(5) @JvmField val createTime: Int = 0, @ProtoNumber(6) @JvmField val picHeight: Int = 0, @ProtoNumber(7) @JvmField val picWidth: Int = 0, @ProtoNumber(8) @JvmField val resvFlag: Int = 0, ) : ProtoBuf } @Serializable internal class SubMsgType0xc1 { @Serializable internal class NotOnlineImage( @ProtoNumber(1) @JvmField val filePath: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fileLen: Int = 0, @ProtoNumber(3) @JvmField val downloadPath: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val oldVerSendFile: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val imgType: Int = 0, @ProtoNumber(6) @JvmField val previewsImage: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val picMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val picHeight: Int = 0, @ProtoNumber(9) @JvmField val picWidth: Int = 0, @ProtoNumber(10) @JvmField val resId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val flag: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val downloadUrl: String = "", @ProtoNumber(13) @JvmField val original: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fromUin: Long = 0L, @ProtoNumber(3) @JvmField val toUin: Long = 0L, @ProtoNumber(4) @JvmField val status: Int = 0, @ProtoNumber(5) @JvmField val ttl: Int = 0, @ProtoNumber(6) @JvmField val type: Int = 0, @ProtoNumber(7) @JvmField val encryptPreheadLength: Int = 0, @ProtoNumber(8) @JvmField val encryptType: Int = 0, @ProtoNumber(9) @JvmField val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val readTimes: Int = 0, @ProtoNumber(11) @JvmField val leftTime: Int = 0, @ProtoNumber(12) @JvmField val notOnlineImage: NotOnlineImage? = null, ) : ProtoBuf } /* @Serializable internal class SubMsgType0x1a { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fromUin_int32: Int = 0, @ProtoNumber(3) @JvmField val toUin_int32: Int = 0, @ProtoNumber(4) @JvmField val status: Int = 0, @ProtoNumber(5) @JvmField val ttl: Int = 0, @ProtoNumber(6) @JvmField val ingDesc: String = "", @ProtoNumber(7) @JvmField val type: Int = 0, @ProtoNumber(8) @JvmField val captureTimes: Int = 0, @ProtoNumber(9) @JvmField val fromUin: Long = 0L, @ProtoNumber(10) @JvmField val toUin: Long = 0L ) : ProtoBuf }*/ ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/MsgTransmit.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class MsgTransmit : ProtoBuf { @Serializable internal class PbMultiMsgItem( @ProtoNumber(1) @JvmField val fileName: String = "", @ProtoNumber(2) @JvmField val buffer: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbMultiMsgNew( @ProtoNumber(1) @JvmField val msg: List<MsgComm.Msg> = emptyList(), ) : ProtoBuf @Serializable internal class PbMultiMsgTransmit( @ProtoNumber(1) @JvmField val msg: List<MsgComm.Msg> = emptyList(), @ProtoNumber(2) @JvmField val pbItemList: List<PbMultiMsgItem> = emptyList(), ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/MultiMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class MultiMsg : ProtoBuf { @Serializable internal class ExternMsg( @ProtoNumber(1) @JvmField val channelType: Int = 0, ) : ProtoBuf @Serializable internal class MultiMsgApplyDownReq( @ProtoNumber(1) @JvmField val msgResid: String = "", @ProtoNumber(2) @JvmField val msgType: Int = 0, @ProtoNumber(3) @JvmField val srcUin: Long = 0L, ) : ProtoBuf @Serializable internal class MultiMsgApplyDownRsp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val thumbDownPara: String = "", @ProtoNumber(3) @JvmField val msgKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val uint32DownIp: List<Int> = emptyList(), @ProtoNumber(5) @JvmField val uint32DownPort: List<Int> = emptyList(), @ProtoNumber(6) @JvmField val msgResid: String = "", @ProtoNumber(7) @JvmField val msgExternInfo: ExternMsg? = null, @ProtoNumber(8) @JvmField val bytesDownIpV6: List<ByteArray> = emptyList(), @ProtoNumber(9) @JvmField val uint32DownV6Port: List<Int> = emptyList(), ) : ProtoBuf @Serializable internal class MultiMsgApplyUpReq( @ProtoNumber(1) @JvmField val dstUin: Long = 0L, @ProtoNumber(2) @JvmField val msgSize: Long = 0L, @ProtoNumber(3) @JvmField val msgMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val msgType: Int = 0, @ProtoNumber(5) @JvmField val applyId: Int = 0, ) : ProtoBuf @Serializable internal class MultiMsgApplyUpRsp( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val msgResid: String = "", @ProtoNumber(3) @JvmField val msgUkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val uint32UpIp: List<Int> = emptyList(), @ProtoNumber(5) @JvmField val uint32UpPort: List<Int> = emptyList(), @ProtoNumber(6) @JvmField val blockSize: Long = 0L, @ProtoNumber(7) @JvmField val upOffset: Long = 0L, @ProtoNumber(8) @JvmField val applyId: Int = 0, @ProtoNumber(9) @JvmField val msgKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val msgSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val msgExternInfo: ExternMsg? = null, @ProtoNumber(12) @JvmField val bytesUpIpV6: List<ByteArray> = emptyList(), @ProtoNumber(13) @JvmField val uint32UpV6Port: List<Int> = emptyList(), ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val subcmd: Int = 0, @ProtoNumber(2) @JvmField val termType: Int = 0, @ProtoNumber(3) @JvmField val platformType: Int = 0, @ProtoNumber(4) @JvmField val netType: Int = 0, @ProtoNumber(5) @JvmField val buildVer: String = "", @ProtoNumber(6) @JvmField val multimsgApplyupReq: List<MultiMsg.MultiMsgApplyUpReq> = emptyList(), @ProtoNumber(7) @JvmField val multimsgApplydownReq: List<MultiMsg.MultiMsgApplyDownReq> = emptyList(), @ProtoNumber(8) @JvmField val buType: Int = 0, @ProtoNumber(9) @JvmField val reqChannelType: Int = 0, ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val subcmd: Int = 0, @ProtoNumber(2) @JvmField val multimsgApplyupRsp: List<MultiMsg.MultiMsgApplyUpRsp> = emptyList(), @ProtoNumber(3) @JvmField val multimsgApplydownRsp: List<MultiMsg.MultiMsgApplyDownRsp> = emptyList(), ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OIDB.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.capitalize import kotlin.jvm.JvmField internal class Oidb0x5d4 : ProtoBuf { @Serializable internal class DelResult( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val res: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val uinList: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val seq: Int = 0, @JvmField @ProtoNumber(2) val result: List<DelResult> = emptyList() ) : ProtoBuf } internal class Oidb0x5d2 : ProtoBuf { @Serializable internal class FriendInfo( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val gender: Int = 0, @JvmField @ProtoNumber(3) val age: Int = 0, @JvmField @ProtoNumber(4) val group: Int = 0, @JvmField @ProtoNumber(5) val login: Int = 0, @JvmField @ProtoNumber(6) val remark: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class FriendEntry( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val nick: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupInfo( @JvmField @ProtoNumber(1) val id: Int = 0, @JvmField @ProtoNumber(2) val name: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class LoginInfo( @JvmField @ProtoNumber(1) val id: Int = 0, @JvmField @ProtoNumber(2) val name: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val subCmd: Int = 0, @JvmField @ProtoNumber(2) val reqGetList: ReqGetList? = null, @JvmField @ProtoNumber(3) val reqGetInfo: ReqGetInfo? = null ) : ProtoBuf @Serializable internal class ReqGetInfo( @JvmField @ProtoNumber(1) val uinList: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class ReqGetList( @JvmField @ProtoNumber(1) val seq: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val subCmd: Int = 0, @JvmField @ProtoNumber(2) val rspGetList: RspGetList? = null, @JvmField @ProtoNumber(3) val rspGetInfo: RspGetInfo? = null ) : ProtoBuf @Serializable internal class RspGetInfo( @JvmField @ProtoNumber(1) val groupInfo: List<GroupInfo> = emptyList(), @JvmField @ProtoNumber(2) val loginInfo: List<LoginInfo> = emptyList(), @JvmField @ProtoNumber(3) val time: Int = 0, @JvmField @ProtoNumber(4) val frdInfo: List<FriendInfo> = emptyList(), @JvmField @ProtoNumber(5) val frdDelete: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class RspGetList( @JvmField @ProtoNumber(1) val seq: Int = 0, @JvmField @ProtoNumber(2) val list: List<FriendEntry> = emptyList() ) : ProtoBuf } internal class Oidb0x496 { @Serializable internal class AioKeyword( @JvmField @ProtoNumber(1) val keywords: List<AioKeywordInfo> = emptyList(), @JvmField @ProtoNumber(2) val rules: List<AioKeywordRuleInfo> = emptyList(), @JvmField @ProtoNumber(3) val version: Int = 0 ) : ProtoBuf @Serializable internal class AioKeywordInfo( @JvmField @ProtoNumber(1) val word: String = "", @JvmField @ProtoNumber(2) val ruleId: Int = 0 ) : ProtoBuf @Serializable internal class AioKeywordRuleInfo( @JvmField @ProtoNumber(1) val ruleId: Int = 0, @JvmField @ProtoNumber(2) val startTime: Int = 0, @JvmField @ProtoNumber(3) val endTime: Int = 0, @JvmField @ProtoNumber(4) val postionFlag: Int = 0, @JvmField @ProtoNumber(5) val matchGroupClass: List<Int> = emptyList(), @JvmField @ProtoNumber(6) val version: Int = 0 ) : ProtoBuf @Serializable internal class GroupMsgConfig( @JvmField @ProtoNumber(1) val boolUinEnable: Boolean = false, @JvmField @ProtoNumber(2) val maxAioMsg: Int = 0, @JvmField @ProtoNumber(3) val enableHelper: Int = 0, @JvmField @ProtoNumber(4) val groupMaxNumber: Int = 0, @JvmField @ProtoNumber(5) val nextUpdateTime: Int = 0 ) : ProtoBuf @Serializable internal class MsgSeqInfo( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val managerUinList: List<Long> = emptyList(), @JvmField @ProtoNumber(3) val updateTime: Long = 0L, @JvmField @ProtoNumber(4) val firstUnreadManagerMsgSeq: Long = 0L, @JvmField @ProtoNumber(5) val uint64ManagerMsgSeq: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val updateTime: Long = 0L, @JvmField @ProtoNumber(3) val managerUinList: Long = 0L, @JvmField @ProtoNumber(4) val firstUnreadManagerMsgSeq: Long = 0L, @JvmField @ProtoNumber(5) val justFetchMsgConfig: Int = 0, @JvmField @ProtoNumber(6) val type: Int = 0, @JvmField @ProtoNumber(7) val version: Int = 0, @JvmField @ProtoNumber(8) val aioKeywordVersion: Int = 0 ) : ProtoBuf @Serializable internal class Robot( @JvmField @ProtoNumber(1) val version: Int = 0, @JvmField @ProtoNumber(2) val uinRange: List<UinRange> = emptyList(), @JvmField @ProtoNumber(3) val fireKeywords: List<String> = emptyList(), @JvmField @ProtoNumber(4) val startKeywords: List<String> = emptyList(), @JvmField @ProtoNumber(5) val endKeywords: List<String> = emptyList(), @JvmField @ProtoNumber(6) val sessionTimeout: Int = 0, @JvmField @ProtoNumber(7) val subscribeCategories: List<RobotSubscribeCategory> = emptyList() ) : ProtoBuf @Serializable internal class RobotSubscribeCategory( @JvmField @ProtoNumber(1) val id: Int = 0, @JvmField @ProtoNumber(2) val name: String = "", @JvmField @ProtoNumber(3) val type: Int = 0, @JvmField @ProtoNumber(4) val nextWording: String = "", @JvmField @ProtoNumber(5) val nextContent: String = "" ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val msgSeqInfo: List<MsgSeqInfo> = emptyList(), @JvmField @ProtoNumber(2) val maxAioMsg: Long = 0L, @JvmField @ProtoNumber(3) val maxPositionMsg: Long = 0L, @JvmField @ProtoNumber(4) val msgGroupMsgConfig: GroupMsgConfig? = null, @JvmField @ProtoNumber(5) val robotConfig: Robot? = null, @JvmField @ProtoNumber(6) val aioKeywordConfig: AioKeyword? = null ) : ProtoBuf @Serializable internal class UinRange( @JvmField @ProtoNumber(1) val startUin: Long = 0L, @JvmField @ProtoNumber(2) val endUin: Long = 0L ) : ProtoBuf } @Serializable internal class Oidb0x8a0 : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val optUint64GroupCode: Long = 0L, @ProtoNumber(2) @JvmField val msgKickResult: List<KickResult> = emptyList() ) : ProtoBuf @Serializable internal class KickResult( @ProtoNumber(1) @JvmField val optUint32Result: Int = 0, @ProtoNumber(2) @JvmField val optUint64MemberUin: Long = 0L ) : ProtoBuf @Serializable internal class KickMemberInfo( @ProtoNumber(1) @JvmField val optUint32Operate: Int = 0, @ProtoNumber(2) @JvmField val optUint64MemberUin: Long = 0L, @ProtoNumber(3) @JvmField val optUint32Flag: Int = 0, @ProtoNumber(4) @JvmField val optBytesMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val optUint64GroupCode: Long = 0L, @ProtoNumber(2) @JvmField val msgKickList: List<KickMemberInfo> = emptyList(), @ProtoNumber(3) @JvmField val kickList: List<Long> = emptyList(), @ProtoNumber(4) @JvmField val kickFlag: Int = 0, @ProtoNumber(5) @JvmField val kickMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Oidb0x8fc : ProtoBuf { @Serializable internal class CardNameElem( @ProtoNumber(1) @JvmField val enumCardType: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class CommCardNameBuf( @ProtoNumber(1) @JvmField val richCardName: List<RichCardNameElem> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val showFlag: Int = 0, @ProtoNumber(3) @JvmField val memLevelInfo: List<MemberInfo> = emptyList(), @ProtoNumber(4) @JvmField val levelName: List<LevelName> = emptyList(), @ProtoNumber(5) @JvmField val updateTime: Int = 0, @ProtoNumber(6) @JvmField val officeMode: Int = 0, @ProtoNumber(7) @JvmField val groupOpenAppid: Int = 0, @ProtoNumber(8) @JvmField val msgClientInfo: ClientInfo? = null, @ProtoNumber(9) @JvmField val authKey: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class MemberInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val point: Int = 0, @ProtoNumber(3) @JvmField val activeDay: Int = 0, @ProtoNumber(4) @JvmField val level: Int = 0, @ProtoNumber(5) @JvmField val specialTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val specialTitleExpireTime: Int = 0, @ProtoNumber(7) @JvmField val uinName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val memberCardName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val phone: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val email: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val remark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val gender: Int = 0, @ProtoNumber(13) @JvmField val job: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val tribeLevel: Int = 0, @ProtoNumber(15) @JvmField val tribePoint: Int = 0, @ProtoNumber(16) @JvmField val richCardName: List<CardNameElem> = emptyList(), @ProtoNumber(17) @JvmField val commRichCardName: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RichCardNameElem( @ProtoNumber(1) @JvmField val ctrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val errInfo: String = "" ) : ProtoBuf @Serializable internal class ClientInfo( @ProtoNumber(1) @JvmField val implat: Int = 0, @ProtoNumber(2) @JvmField val ingClientver: String = "" ) : ProtoBuf @Serializable internal class LevelName( @ProtoNumber(1) @JvmField val level: Int = 0, @ProtoNumber(2) @JvmField val name: String = "" ) : ProtoBuf } @Serializable internal class Oidb0x88d : ProtoBuf { @Serializable internal class GroupExInfoOnly( @ProtoNumber(1) @JvmField val tribeId: Int = 0, @ProtoNumber(2) @JvmField val moneyForAddGroup: Int = 0 ) : ProtoBuf @Serializable internal class ReqGroupInfo( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val stgroupinfo: GroupInfo? = null, @ProtoNumber(3) @JvmField val lastGetGroupNameTime: Int = 0 ) : ProtoBuf @Serializable internal class RspGroupInfo( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val result: Int = 0, @ProtoNumber(3) @JvmField val stgroupinfo: GroupInfo? = null ) : ProtoBuf @Serializable internal class GroupGeoInfo( @ProtoNumber(1) @JvmField val owneruin: Long = 0L, @ProtoNumber(2) @JvmField val settime: Int = 0, @ProtoNumber(3) @JvmField val cityid: Int = 0, @ProtoNumber(4) @JvmField val int64Longitude: Long = 0L, @ProtoNumber(5) @JvmField val int64Latitude: Long = 0L, @ProtoNumber(6) @JvmField val geocontent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val poiId: Long = 0L ) : ProtoBuf @Serializable internal class TagRecord( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val tagId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val setTime: Long = 0L, @ProtoNumber(5) @JvmField val goodNum: Int = 0, @ProtoNumber(6) @JvmField val badNum: Int = 0, @ProtoNumber(7) @JvmField val tagLen: Int = 0, @ProtoNumber(8) @JvmField val tagValue: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val groupOwner: Long? = null, @ProtoNumber(2) @JvmField val groupCreateTime: Int? = null, @ProtoNumber(3) @JvmField val groupFlag: Int? = null, @ProtoNumber(4) @JvmField val groupFlagExt: Int? = null, @ProtoNumber(5) @JvmField val groupMemberMaxNum: Int? = null, @ProtoNumber(6) @JvmField val groupMemberNum: Int? = null, @ProtoNumber(7) @JvmField val groupOption: Int? = null, @ProtoNumber(8) @JvmField val groupClassExt: Int? = null, @ProtoNumber(9) @JvmField val groupSpecialClass: Int? = null, @ProtoNumber(10) @JvmField val groupLevel: Int? = null, @ProtoNumber(11) @JvmField val groupFace: Int? = null, @ProtoNumber(12) @JvmField val groupDefaultPage: Int? = null, @ProtoNumber(13) @JvmField val groupInfoSeq: Int? = null, @ProtoNumber(14) @JvmField val groupRoamingTime: Int? = null, @ProtoNumber(15) @JvmField val groupName: String? = null, @ProtoNumber(16) @JvmField val groupMemo: String? = null, @ProtoNumber(17) @JvmField val ingGroupFingerMemo: String? = null, @ProtoNumber(18) @JvmField val ingGroupClassText: String? = null, @ProtoNumber(19) @JvmField val groupAllianceCode: List<Int> = emptyList(), @ProtoNumber(20) @JvmField val groupExtraAdmNum: Int? = null, @ProtoNumber(21) @JvmField val groupUin: Long? = null, @ProtoNumber(22) @JvmField val groupCurMsgSeq: Int? = null, @ProtoNumber(23) @JvmField val groupLastMsgTime: Int? = null, @ProtoNumber(24) @JvmField val ingGroupQuestion: String? = null, @ProtoNumber(25) @JvmField val ingGroupAnswer: String? = null, @ProtoNumber(26) @JvmField val groupVisitorMaxNum: Int? = null, @ProtoNumber(27) @JvmField val groupVisitorCurNum: Int? = null, @ProtoNumber(28) @JvmField val levelNameSeq: Int? = null, @ProtoNumber(29) @JvmField val groupAdminMaxNum: Int? = null, @ProtoNumber(30) @JvmField val groupAioSkinTimestamp: Int? = null, @ProtoNumber(31) @JvmField val groupBoardSkinTimestamp: Int? = null, @ProtoNumber(32) @JvmField val ingGroupAioSkinUrl: String? = null, @ProtoNumber(33) @JvmField val ingGroupBoardSkinUrl: String? = null, @ProtoNumber(34) @JvmField val groupCoverSkinTimestamp: Int? = null, @ProtoNumber(35) @JvmField val ingGroupCoverSkinUrl: String? = null, @ProtoNumber(36) @JvmField val groupGrade: Int? = null, @ProtoNumber(37) @JvmField val activeMemberNum: Int? = null, @ProtoNumber(38) @JvmField val certificationType: Int? = null, @ProtoNumber(39) @JvmField val ingCertificationText: String? = null, @ProtoNumber(40) @JvmField val ingGroupRichFingerMemo: String? = null, @ProtoNumber(41) @JvmField val tagRecord: List<TagRecord> = emptyList(), @ProtoNumber(42) @JvmField val groupGeoInfo: GroupGeoInfo? = null, @ProtoNumber(43) @JvmField val headPortraitSeq: Int? = null, @ProtoNumber(44) @JvmField val msgHeadPortrait: GroupHeadPortrait? = null, @ProtoNumber(45) @JvmField val shutupTimestamp: Int? = null, @ProtoNumber(46) @JvmField val shutupTimestampMe: Int? = null, @ProtoNumber(47) @JvmField val createSourceFlag: Int? = null, @ProtoNumber(48) @JvmField val cmduinMsgSeq: Int? = null, @ProtoNumber(49) @JvmField val cmduinJoinTime: Int? = null, @ProtoNumber(50) @JvmField val cmduinUinFlag: Int? = null, @ProtoNumber(51) @JvmField val cmduinFlagEx: Int? = null, @ProtoNumber(52) @JvmField val cmduinNewMobileFlag: Int? = null, @ProtoNumber(53) @JvmField val cmduinReadMsgSeq: Int? = null, @ProtoNumber(54) @JvmField val cmduinLastMsgTime: Int? = null, @ProtoNumber(55) @JvmField val groupTypeFlag: Int? = null, @ProtoNumber(56) @JvmField val appPrivilegeFlag: Int? = null, @ProtoNumber(57) @JvmField val stGroupExInfo: GroupExInfoOnly? = null, @ProtoNumber(58) @JvmField val groupSecLevel: Int? = null, @ProtoNumber(59) @JvmField val groupSecLevelInfo: Int? = null, @ProtoNumber(60) @JvmField val cmduinPrivilege: Int? = null, @ProtoNumber(61) @JvmField val ingPoidInfo: ByteArray? = null, @ProtoNumber(62) @JvmField val cmduinFlagEx2: Int? = null, @ProtoNumber(63) @JvmField val confUin: Long? = null, @ProtoNumber(64) @JvmField val confMaxMsgSeq: Int? = null, @ProtoNumber(65) @JvmField val confToGroupTime: Int? = null, @ProtoNumber(66) @JvmField val passwordRedbagTime: Int? = null, @ProtoNumber(67) @JvmField val subscriptionUin: Long? = null, @ProtoNumber(68) @JvmField val memberListChangeSeq: Int? = null, @ProtoNumber(69) @JvmField val membercardSeq: Int? = null, @ProtoNumber(70) @JvmField val rootId: Long? = null, @ProtoNumber(71) @JvmField val parentId: Long? = null, @ProtoNumber(72) @JvmField val teamSeq: Int? = null, @ProtoNumber(73) @JvmField val historyMsgBeginTime: Long? = null, @ProtoNumber(74) @JvmField val inviteNoAuthNumLimit: Long? = null, @ProtoNumber(75) @JvmField val cmduinHistoryMsgSeq: Int? = null, @ProtoNumber(76) @JvmField val cmduinJoinMsgSeq: Int? = null, @ProtoNumber(77) @JvmField val groupFlagext3: Int? = null, @ProtoNumber(78) @JvmField val groupOpenAppid: Int? = null, @ProtoNumber(79) @JvmField val isConfGroup: Int? = null, @ProtoNumber(80) @JvmField val isModifyConfGroupFace: Int? = null, @ProtoNumber(81) @JvmField val isModifyConfGroupName: Int? = null, @ProtoNumber(82) @JvmField val noFingerOpenFlag: Int? = null, @ProtoNumber(83) @JvmField val noCodeFingerOpenFlag: Int? = null, @ProtoNumber(84) @JvmField val autoAgreeJoinGroupUserNumForNormalGroup: Int? = null, @ProtoNumber(85) @JvmField val autoAgreeJoinGroupUserNumForConfGroup: Int? = null, @ProtoNumber(86) @JvmField val isAllowConfGroupMemberNick: Int? = null, @ProtoNumber(87) @JvmField val isAllowConfGroupMemberAtAll: Int? = null, @ProtoNumber(88) @JvmField val isAllowConfGroupMemberModifyGroupName: Int? = null, @ProtoNumber(89) @JvmField val longGroupName: String? = null, @ProtoNumber(90) @JvmField val cmduinJoinRealMsgSeq: Int? = null, @ProtoNumber(91) @JvmField val isGroupFreeze: Int? = null, @ProtoNumber(92) @JvmField val msgLimitFrequency: Int? = null, @ProtoNumber(93) @JvmField val joinGroupAuth: ByteArray? = null, @ProtoNumber(94) @JvmField val hlGuildAppid: Int? = null, @ProtoNumber(95) @JvmField val hlGuildSubType: Int? = null, @ProtoNumber(96) @JvmField val hlGuildOrgid: Int? = null, @ProtoNumber(97) @JvmField val isAllowHlGuildBinary: Int? = null, @ProtoNumber(98) @JvmField val cmduinRingtoneId: Int? = null, @ProtoNumber(99) @JvmField val groupFlagext4: Int? = null, @ProtoNumber(100) @JvmField val groupFreezeReason: Int? = null, ) : ProtoBuf @Serializable internal class GroupHeadPortraitInfo( @ProtoNumber(1) @JvmField val uint32PicId: Int = 0, @ProtoNumber(2) @JvmField val leftX: Int = 0, @ProtoNumber(3) @JvmField val leftY: Int = 0, @ProtoNumber(4) @JvmField val rightX: Int = 0, @ProtoNumber(5) @JvmField val rightY: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val stzrspgroupinfo: List<RspGroupInfo> = emptyList(), @ProtoNumber(2) @JvmField val errorinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val stzreqgroupinfo: List<ReqGroupInfo> = emptyList(), @ProtoNumber(3) @JvmField val pcClientVersion: Int = 0 ) : ProtoBuf @Serializable internal class GroupHeadPortrait( @ProtoNumber(1) @JvmField val picCnt: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: List<GroupHeadPortraitInfo> = emptyList(), @ProtoNumber(3) @JvmField val defaultId: Int = 0, @ProtoNumber(4) @JvmField val verifyingPicCnt: Int = 0, @ProtoNumber(5) @JvmField val msgVerifyingpicInfo: List<GroupHeadPortraitInfo> = emptyList() ) : ProtoBuf } @Serializable internal class Oidb0x89a : ProtoBuf { @Serializable internal class GroupNewGuidelinesInfo( @ProtoNumber(1) @JvmField val boolEnabled: Boolean = false, @ProtoNumber(2) @JvmField val ingContent: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class Groupinfo( @ProtoNumber(1) @JvmField var groupExtAdmNum: Int? = null, @ProtoNumber(2) @JvmField var flag: Int? = null, @ProtoNumber(3) @JvmField var ingGroupName: ByteArray? = null, @ProtoNumber(4) @JvmField var ingGroupMemo: ByteArray? = null, @ProtoNumber(5) @JvmField var ingGroupFingerMemo: ByteArray? = null, @ProtoNumber(6) @JvmField var ingGroupAioSkinUrl: ByteArray? = null, @ProtoNumber(7) @JvmField var ingGroupBoardSkinUrl: ByteArray? = null, @ProtoNumber(8) @JvmField var ingGroupCoverSkinUrl: ByteArray? = null, @ProtoNumber(9) @JvmField var groupGrade: Int? = null, @ProtoNumber(10) @JvmField var activeMemberNum: Int? = null, @ProtoNumber(11) @JvmField var certificationType: Int? = null, @ProtoNumber(12) @JvmField var ingCertificationText: ByteArray? = null, @ProtoNumber(13) @JvmField var ingGroupRichFingerMemo: ByteArray? = null, @ProtoNumber(14) @JvmField var stGroupNewguidelines: GroupNewGuidelinesInfo? = null, @ProtoNumber(15) @JvmField var groupFace: Int? = null, @ProtoNumber(16) @JvmField var addOption: Int? = null, @ProtoNumber(17) @JvmField var shutupTime: Int? = null, @ProtoNumber(18) @JvmField var groupTypeFlag: Int? = null, @ProtoNumber(19) @JvmField var stringGroupTag: List<ByteArray> = emptyList(), @ProtoNumber(20) @JvmField var msgGroupGeoInfo: GroupGeoInfo? = null, @ProtoNumber(21) @JvmField var groupClassExt: Int? = null, @ProtoNumber(22) @JvmField var ingGroupClassText: ByteArray? = null, @ProtoNumber(23) @JvmField var appPrivilegeFlag: Int? = null, @ProtoNumber(24) @JvmField var appPrivilegeMask: Int? = null, @ProtoNumber(25) @JvmField var stGroupExInfo: GroupExInfoOnly? = null, @ProtoNumber(26) @JvmField var groupSecLevel: Int? = null, @ProtoNumber(27) @JvmField var groupSecLevelInfo: Int? = null, @ProtoNumber(28) @JvmField var subscriptionUin: Long? = null, @ProtoNumber(29) @JvmField var allowMemberInvite: Int? = null, @ProtoNumber(30) @JvmField var ingGroupQuestion: ByteArray? = null, @ProtoNumber(31) @JvmField var ingGroupAnswer: ByteArray? = null, @ProtoNumber(32) @JvmField var groupFlagext3: Int? = null, @ProtoNumber(33) @JvmField var groupFlagext3Mask: Int? = null, @ProtoNumber(34) @JvmField var groupOpenAppid: Int? = null, @ProtoNumber(35) @JvmField var noFingerOpenFlag: Int? = null, @ProtoNumber(36) @JvmField var noCodeFingerOpenFlag: Int? = null, @ProtoNumber(37) @JvmField var rootId: Long? = null, @ProtoNumber(38) @JvmField var msgLimitFrequency: Int? = null ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val errorinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupExInfoOnly( @ProtoNumber(1) @JvmField val tribeId: Int = 0, @ProtoNumber(2) @JvmField val moneyForAddGroup: Int = 0 ) : ProtoBuf @Serializable internal class GroupGeoInfo( @ProtoNumber(1) @JvmField val cityId: Int = 0, @ProtoNumber(2) @JvmField val longtitude: Long = 0L, @ProtoNumber(3) @JvmField val latitude: Long = 0L, @ProtoNumber(4) @JvmField val ingGeoContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val poiId: Long = 0L ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val stGroupInfo: Groupinfo? = null, @ProtoNumber(3) @JvmField val originalOperatorUin: Long = 0L, @ProtoNumber(4) @JvmField val reqGroupOpenAppid: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0x7cb : ProtoBuf { @Serializable internal class ConfigItem( @ProtoNumber(1) @JvmField val id: Int = 0, @ProtoNumber(2) @JvmField val config: String = "" ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val timeStamp: Int = 0, @ProtoNumber(2) @JvmField val timeGap: Int = 0, @ProtoNumber(3) @JvmField val commentConfigs: List<CommentConfig> = emptyList(), @ProtoNumber(4) @JvmField val attendTipsToA: String = "", @ProtoNumber(5) @JvmField val firstMsgTips: String = "", @ProtoNumber(6) @JvmField val cancleConfig: List<ConfigItem> = emptyList(), @ProtoNumber(7) @JvmField val msgDateRequest: DateRequest? = null, @ProtoNumber(8) @JvmField val msgHotLocale: List<ByteArray> = emptyList(),//List<AppointDefine.LocaleInfo> @ProtoNumber(9) @JvmField val msgTopicList: List<TopicConfig> = emptyList(), @ProtoNumber(10) @JvmField val travelMsgTips: String = "", @ProtoNumber(11) @JvmField val travelProfileTips: String = "", @ProtoNumber(12) @JvmField val travelAttenTips: String = "", @ProtoNumber(13) @JvmField val topicDefault: Int = 0 ) : ProtoBuf @Serializable internal class CommentConfig( @ProtoNumber(1) @JvmField val appointSubject: Int = 0, @ProtoNumber(2) @JvmField val msgConfigs: List<ConfigItem> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val timeStamp: Int = 0 ) : ProtoBuf @Serializable internal class DateRequest( @ProtoNumber(1) @JvmField val time: Int = 0, @ProtoNumber(2) @JvmField val errMsg: String = "" ) : ProtoBuf @Serializable internal class TopicConfig( @ProtoNumber(1) @JvmField val topicId: Int = 0, @ProtoNumber(2) @JvmField val topicName: String = "", @ProtoNumber(3) @JvmField val deadline: Int = 0, @ProtoNumber(4) @JvmField val errDeadline: String = "" ) : ProtoBuf } @Serializable internal class Oidb0x87a : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val country: String = "", @ProtoNumber(2) @JvmField val telephone: String = "", @ProtoNumber(3) @JvmField val resendInterval: Int = 0, @ProtoNumber(4) @JvmField val guid: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val country: String = "", @ProtoNumber(2) @JvmField val telephone: String = "", @ProtoNumber(3) @JvmField val guid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val enumButype: Int /* enum */ = 0 ) : ProtoBuf } @Serializable internal class GroupAppPb : ProtoBuf { @Serializable internal class ClientInfo( @ProtoNumber(1) @JvmField val platform: Int = 0, @ProtoNumber(2) @JvmField val version: String = "" ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val fullList: AppList? = null, @ProtoNumber(2) @JvmField val groupGrayList: AppList? = null, @ProtoNumber(3) @JvmField val redPointList: AppList? = null, @ProtoNumber(4) @JvmField val cacheInterval: Int = 0 ) : ProtoBuf @Serializable internal class AppList( @ProtoNumber(1) @JvmField val hash: String = "", @ProtoNumber(2) @JvmField val infos: List<AppInfo> = emptyList() ) : ProtoBuf @Serializable internal class AppInfo( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val icon: String = "", @ProtoNumber(3) @JvmField val name: String = "", @ProtoNumber(4) @JvmField val url: String = "", @ProtoNumber(5) @JvmField val isGray: Int = 0, @ProtoNumber(6) @JvmField val iconSimpleDay: String = "", @ProtoNumber(7) @JvmField val iconSimpleNight: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val client: ClientInfo? = null, @ProtoNumber(2) @JvmField val groupId: Long = 0L, @ProtoNumber(3) @JvmField val groupType: Int = 0, @ProtoNumber(4) @JvmField val fullListHash: String = "", @ProtoNumber(5) @JvmField val groupGrayListHash: String = "" ) : ProtoBuf } @Serializable internal class Oidb0xc34 : ProtoBuf { @Serializable internal class RspBody : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L ) : ProtoBuf } @Serializable internal class Cmd0x5fd : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgComment: AppointDefine.DateComment? = null, @ProtoNumber(2) @JvmField val maxFetchCount: Int = 0, @ProtoNumber(3) @JvmField val lastCommentId: String = "" ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgComment: List<AppointDefine.DateComment> = emptyList(), @ProtoNumber(2) @JvmField val errorTips: String = "", @ProtoNumber(3) @JvmField val clearCacheFlag: Int = 0, @ProtoNumber(4) @JvmField val commentWording: String = "", @ProtoNumber(5) @JvmField val commentNum: Int = 0 ) : ProtoBuf } @Serializable internal class Oidb0xbcb : ProtoBuf { @Serializable internal class CheckUrlReqItem( @ProtoNumber(1) @JvmField val url: String = "", @ProtoNumber(2) @JvmField val refer: String = "", @ProtoNumber(3) @JvmField val plateform: String = "", @ProtoNumber(4) @JvmField val qqPfTo: String = "", @ProtoNumber(5) @JvmField val msgType: Int = 0, @ProtoNumber(6) @JvmField val msgFrom: Int = 0, @ProtoNumber(7) @JvmField val msgChatid: Long = 0L, @ProtoNumber(8) @JvmField val serviceType: Long = 0L, @ProtoNumber(9) @JvmField val sendUin: Long = 0L, @ProtoNumber(10) @JvmField val reqType: String = "" ) : ProtoBuf @Serializable internal class CheckUrlRsp( @ProtoNumber(1) @JvmField val results: List<UrlCheckResult> = emptyList(), @ProtoNumber(2) @JvmField val nextReqDuration: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(9) @JvmField val notUseCache: Int = 0, @ProtoNumber(10) @JvmField val checkUrlReq: CheckUrlReq? = null ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val wording: String = "", @ProtoNumber(10) @JvmField val checkUrlRsp: CheckUrlRsp? = null ) : ProtoBuf @Serializable internal class CheckUrlReq( @ProtoNumber(1) @JvmField val url: List<String> = emptyList(), @ProtoNumber(2) @JvmField val refer: String = "", @ProtoNumber(3) @JvmField val plateform: String = "", @ProtoNumber(4) @JvmField val qqPfTo: String = "", @ProtoNumber(5) @JvmField val msgType: Int = 0, @ProtoNumber(6) @JvmField val msgFrom: Int = 0, @ProtoNumber(7) @JvmField val msgChatid: Long = 0L, @ProtoNumber(8) @JvmField val serviceType: Long = 0L, @ProtoNumber(9) @JvmField val sendUin: Long = 0L, @ProtoNumber(10) @JvmField val reqType: String = "", @ProtoNumber(11) @JvmField val originalUrl: String = "" ) : ProtoBuf @Serializable internal class UrlCheckResult( @ProtoNumber(1) @JvmField val url: String = "", @ProtoNumber(2) @JvmField val result: Int = 0, @ProtoNumber(3) @JvmField val jumpResult: Int = 0, @ProtoNumber(4) @JvmField val jumpUrl: String = "", @ProtoNumber(5) @JvmField val level: Int = 0, @ProtoNumber(6) @JvmField val subLevel: Int = 0, @ProtoNumber(7) @JvmField val umrtype: Int = 0, @ProtoNumber(8) @JvmField val retFrom: Int = 0, @ProtoNumber(9) @JvmField val operationBit: Long = 0L ) : ProtoBuf } @Serializable internal class Oidb0xbfe : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val receiveStatus: Int = 0, @ProtoNumber(2) @JvmField val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val flag: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L ) : ProtoBuf } @Serializable internal class Oidb0xbe8 : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val enumOpCode: Int /* enum */ = 1, @ProtoNumber(3) @JvmField val rspOfPopupFlag: Int = 0, @ProtoNumber(4) @JvmField val popupCountNow: Int = 0 ) : ProtoBuf @Serializable internal class PopupResult( @ProtoNumber(1) @JvmField val popupResult: Int = 0, @ProtoNumber(2) @JvmField val popupFieldid: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val enumOpCode: Int /* enum */ = 1, @ProtoNumber(3) @JvmField val reqOfPopupFlag: Int = 0, @ProtoNumber(4) @JvmField val rstOfPopupFlag: Int = 0, @ProtoNumber(5) @JvmField val mqq808WelcomepageFlag: Int = 0, @ProtoNumber(6) @JvmField val msgPopupResult: List<PopupResult> = emptyList() ) : ProtoBuf } @Serializable internal class Cmd0x7de : ProtoBuf { @Serializable internal class UserProfile( @ProtoNumber(1) @JvmField val msgPublisherInfo: AppointDefine.PublisherInfo? = null, @ProtoNumber(2) @JvmField val msgAppointsInfo: AppointDefine.AppointInfo? = null, @ProtoNumber(3) @JvmField val msgVistorInfo: List<AppointDefine.StrangerInfo> = emptyList() ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgHead: BusiRespHead? = null, @ProtoNumber(2) @JvmField val msgUserList: List<UserProfile> = emptyList(), @ProtoNumber(3) @JvmField val ended: Int = 0, @ProtoNumber(4) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class BusiRespHead( @ProtoNumber(1) @JvmField val int32Version: Int = 1, @ProtoNumber(2) @JvmField val int32Seq: Int = 0, @ProtoNumber(3) @JvmField val int32ReplyCode: Int = 0, @ProtoNumber(4) @JvmField val result: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgHead: BusiReqHead? = null, @ProtoNumber(2) @JvmField val msgLbsInfo: AppointDefine.LBSInfo? = null, @ProtoNumber(3) @JvmField val time: Int = 0, @ProtoNumber(4) @JvmField val subject: Int = 0, @ProtoNumber(5) @JvmField val gender: Int = 0, @ProtoNumber(6) @JvmField val ageLow: Int = 0, @ProtoNumber(7) @JvmField val ageUp: Int = 0, @ProtoNumber(8) @JvmField val profession: Int = 0, @ProtoNumber(9) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val msgDestination: AppointDefine.LocaleInfo? = null ) : ProtoBuf @Serializable internal class BusiReqHead( @ProtoNumber(1) @JvmField val int32Version: Int = 1, @ProtoNumber(2) @JvmField val int32Seq: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0x7a8 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val reqUin: Long = 0L, @ProtoNumber(11) @JvmField val onlyObtained: Int = 0, @ProtoNumber(12) @JvmField val readReport: Int = 0, @ProtoNumber(13) @JvmField val sortType: Int = 0, @ProtoNumber(14) @JvmField val onlyNew: Int = 0, @ProtoNumber(15) @JvmField val filterMedalIds: List<Int> = emptyList(), @ProtoNumber(16) @JvmField val onlySummary: Int = 0, @ProtoNumber(17) @JvmField val doScan: Int = 0, @ProtoNumber(18) @JvmField val startTimestamp: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val nick: String = "", @ProtoNumber(2) @JvmField val metalRank: Int = 0, @ProtoNumber(3) @JvmField val friCount: Int = 0, @ProtoNumber(4) @JvmField val metalCount: Int = 0, @ProtoNumber(5) @JvmField val metalTotal: Int = 0, @ProtoNumber(6) @JvmField val msgMedal: List<Common.MedalInfo> = emptyList(), @ProtoNumber(8) @JvmField val totalPoint: Int = 0, @ProtoNumber(9) @JvmField val int32NewCount: Int = 0, @ProtoNumber(10) @JvmField val int32UpgradeCount: Int = 0, @ProtoNumber(11) @JvmField val promptParams: String = "", @ProtoNumber(12) @JvmField val now: Int = 0 ) : ProtoBuf @Serializable internal class MedalNews( @ProtoNumber(1) @JvmField val friUin: Long = 0L, @ProtoNumber(2) @JvmField val friNick: String = "", @ProtoNumber(3) @JvmField val msgMedal: Common.MedalInfo? = null ) : ProtoBuf } @Serializable internal class Cmd0x5fe : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgAppointId: AppointDefine.AppointID? = null, @ProtoNumber(2) @JvmField val commentId: String = "", @ProtoNumber(3) @JvmField val fetchOldCount: Int = 0, @ProtoNumber(4) @JvmField val fetchNewCount: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgComment: List<AppointDefine.DateComment> = emptyList(), @ProtoNumber(2) @JvmField val errorTips: String = "", @ProtoNumber(3) @JvmField val fetchOldOver: Int = 0, @ProtoNumber(4) @JvmField val fetchNewOver: Int = 0 ) : ProtoBuf } @Serializable internal class Oidb0xc35 : ProtoBuf { @Serializable internal class RspBody : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgExposeInfo: List<ExposeItem> = emptyList() ) : ProtoBuf @Serializable internal class ExposeItem( @ProtoNumber(1) @JvmField val friend: Long = 0L, @ProtoNumber(2) @JvmField val pageId: Int = 0, @ProtoNumber(3) @JvmField val entranceId: Int = 0, @ProtoNumber(4) @JvmField val actionId: Int = 0, @ProtoNumber(5) @JvmField val exposeCount: Int = 0, @ProtoNumber(6) @JvmField val exposeTime: Int = 0, @ProtoNumber(7) @JvmField val algoBuffer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val addition: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Oidb0xc0d : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val completedTaskStamp: Long = 0L, @ProtoNumber(2) @JvmField val errMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val taskType: Int = 0, @ProtoNumber(3) @JvmField val taskPoint: Int = 0 ) : ProtoBuf } @Serializable internal class OidbSso : ProtoBuf { @Serializable internal class OIDBSSOPkg( @ProtoNumber(1) @JvmField val command: Int = 0, @ProtoNumber(2) @JvmField val serviceType: Int, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val bodybuffer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val errorMsg: String = "", @ProtoNumber(6) @JvmField val clientVersion: String = "" ) : ProtoBuf, Packet { fun checkSuccess(actionName: String) { check(result == 0) { "${actionName.capitalize()} failed. result=$result, errorMsg=$errorMsg" } } } } @Serializable internal class Cmd0xc83 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(101) @JvmField val fromUin: Long = 0L, @ProtoNumber(102) @JvmField val toUin: Long = 0L, @ProtoNumber(103) @JvmField val op: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(101) @JvmField val result: Int = 0, @ProtoNumber(102) @JvmField val retryInterval: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0xccb : ProtoBuf { @Serializable internal class GroupMsgInfo( @ProtoNumber(1) @JvmField val msgSeq: Int = 0, @ProtoNumber(2) @JvmField val roamFlag: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val destUin: Long = 0L, @ProtoNumber(3) @JvmField val groupCode: Long = 0L, @ProtoNumber(4) @JvmField val c2cMsg: List<C2cMsgInfo> = emptyList(), @ProtoNumber(5) @JvmField val groupMsg: List<GroupMsgInfo> = emptyList() ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val destUin: Long = 0L, @ProtoNumber(3) @JvmField val groupCode: Long = 0L, @ProtoNumber(4) @JvmField val c2cMsg: List<C2cMsgInfo> = emptyList(), @ProtoNumber(5) @JvmField val groupMsg: List<GroupMsgInfo> = emptyList(), @ProtoNumber(6) @JvmField val resId: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class C2cMsgInfo( @ProtoNumber(1) @JvmField val msgSeq: Int = 0, @ProtoNumber(2) @JvmField val msgTime: Int = 0, @ProtoNumber(3) @JvmField val msgRandom: Int = 0, @ProtoNumber(4) @JvmField val roamFlag: Int = 0 ) : ProtoBuf } @Serializable internal class Oidb0xd84 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val xmitinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val xmitinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Oidb0x5e1 : ProtoBuf { @Serializable internal class UdcUinData( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(4) @JvmField val openid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20002) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20003) @JvmField val country: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20004) @JvmField val province: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20009) @JvmField val gender: Int = 0, @ProtoNumber(20014) @JvmField val allow: Int = 0, @ProtoNumber(20015) @JvmField val faceId: Int = 0, @ProtoNumber(20020) @JvmField val city: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20027) @JvmField val commonPlace1: Int = 0, @ProtoNumber(20030) @JvmField val mss3Bitmapextra: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20031) @JvmField val birthday: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20032) @JvmField val cityId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(20033) @JvmField val lang1: Int = 0, @ProtoNumber(20034) @JvmField val lang2: Int = 0, @ProtoNumber(20035) @JvmField val lang3: Int = 0, @ProtoNumber(20041) @JvmField val cityZoneId: Int = 0, @ProtoNumber(20056) @JvmField val oin: Int = 0, @ProtoNumber(20059) @JvmField val bubbleId: Int = 0, @ProtoNumber(21001) @JvmField val mss2Identity: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(21002) @JvmField val mss1Service: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(21003) @JvmField val lflag: Int = 0, @ProtoNumber(21004) @JvmField val extFlag: Int = 0, @ProtoNumber(21006) @JvmField val basicSvrFlag: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(21007) @JvmField val basicCliFlag: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(24101) @JvmField val pengyouRealname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(24103) @JvmField val pengyouGender: Int = 0, @ProtoNumber(24118) @JvmField val pengyouFlag: Int = 0, @ProtoNumber(26004) @JvmField val fullBirthday: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(26005) @JvmField val fullAge: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(26010) @JvmField val simpleUpdateTime: Int = 0, @ProtoNumber(26011) @JvmField val mssUpdateTime: Int = 0, @ProtoNumber(27022) @JvmField val groupMemCreditFlag: Int = 0, @ProtoNumber(27025) @JvmField val faceAddonId: Long = 0L, @ProtoNumber(27026) @JvmField val musicGene: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(40323) @JvmField val fileShareBit: Int = 0, @ProtoNumber(40404) @JvmField val recommendPrivacyCtrl: Int = 0, @ProtoNumber(40505) @JvmField val oldFriendChat: Int = 0, @ProtoNumber(40602) @JvmField val businessBit: Int = 0, @ProtoNumber(41305) @JvmField val crmBit: Int = 0, @ProtoNumber(41810) @JvmField val forbidFileshareBit: Int = 0, @ProtoNumber(42333) @JvmField val userLoginGuardFace: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(11) @JvmField val msgUinData: List<UdcUinData> = emptyList(), @ProtoNumber(12) @JvmField val uint64UnfinishedUins: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uint64Uins: List<Long> = emptyList(), @ProtoNumber(2) @JvmField val startTime: Int = 0, @ProtoNumber(3) @JvmField val maxPackageSize: Int = 0, @ProtoNumber(4) @JvmField val bytesOpenid: List<ByteArray> = emptyList(), @ProtoNumber(5) @JvmField val appid: Int = 0, @ProtoNumber(20002) @JvmField val reqNick: Int = 0, @ProtoNumber(20003) @JvmField val reqCountry: Int = 0, @ProtoNumber(20004) @JvmField val reqProvince: Int = 0, @ProtoNumber(20009) @JvmField val reqGender: Int = 0, @ProtoNumber(20014) @JvmField val reqAllow: Int = 0, @ProtoNumber(20015) @JvmField val reqFaceId: Int = 0, @ProtoNumber(20020) @JvmField val reqCity: Int = 0, @ProtoNumber(20027) @JvmField val reqCommonPlace1: Int = 0, @ProtoNumber(20030) @JvmField val reqMss3Bitmapextra: Int = 0, @ProtoNumber(20031) @JvmField val reqBirthday: Int = 0, @ProtoNumber(20032) @JvmField val reqCityId: Int = 0, @ProtoNumber(20033) @JvmField val reqLang1: Int = 0, @ProtoNumber(20034) @JvmField val reqLang2: Int = 0, @ProtoNumber(20035) @JvmField val reqLang3: Int = 0, @ProtoNumber(20041) @JvmField val reqCityZoneId: Int = 0, @ProtoNumber(20056) @JvmField val reqOin: Int = 0, @ProtoNumber(20059) @JvmField val reqBubbleId: Int = 0, @ProtoNumber(21001) @JvmField val reqMss2Identity: Int = 0, @ProtoNumber(21002) @JvmField val reqMss1Service: Int = 0, @ProtoNumber(21003) @JvmField val reqLflag: Int = 0, @ProtoNumber(21004) @JvmField val reqExtFlag: Int = 0, @ProtoNumber(21006) @JvmField val reqBasicSvrFlag: Int = 0, @ProtoNumber(21007) @JvmField val reqBasicCliFlag: Int = 0, @ProtoNumber(24101) @JvmField val reqPengyouRealname: Int = 0, @ProtoNumber(24103) @JvmField val reqPengyouGender: Int = 0, @ProtoNumber(24118) @JvmField val reqPengyouFlag: Int = 0, @ProtoNumber(26004) @JvmField val reqFullBirthday: Int = 0, @ProtoNumber(26005) @JvmField val reqFullAge: Int = 0, @ProtoNumber(26010) @JvmField val reqSimpleUpdateTime: Int = 0, @ProtoNumber(26011) @JvmField val reqMssUpdateTime: Int = 0, @ProtoNumber(27022) @JvmField val reqGroupMemCreditFlag: Int = 0, @ProtoNumber(27025) @JvmField val reqFaceAddonId: Int = 0, @ProtoNumber(27026) @JvmField val reqMusicGene: Int = 0, @ProtoNumber(40323) @JvmField val reqFileShareBit: Int = 0, @ProtoNumber(40404) @JvmField val reqRecommendPrivacyCtrlBit: Int = 0, @ProtoNumber(40505) @JvmField val reqOldFriendChatBit: Int = 0, @ProtoNumber(40602) @JvmField val reqBusinessBit: Int = 0, @ProtoNumber(41305) @JvmField val reqCrmBit: Int = 0, @ProtoNumber(41810) @JvmField val reqForbidFileshareBit: Int = 0, @ProtoNumber(42333) @JvmField val userLoginGuardFace: Int = 0 ) : ProtoBuf } @Serializable internal class Oidb0xc90 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val communityBid: List<Long> = emptyList(), @ProtoNumber(2) @JvmField val page: Int = 0 ) : ProtoBuf @Serializable internal class CommunityWebInfo( @ProtoNumber(1) @JvmField val communityInfoItem: List<CommunityConfigInfo> = emptyList(), @ProtoNumber(2) @JvmField val page: Int = 0, @ProtoNumber(3) @JvmField val end: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val communityInfoItem: List<CommunityConfigInfo> = emptyList(), @ProtoNumber(2) @JvmField val jumpConcernCommunityUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val communityTitleWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val moreUrlWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val webCommunityInfo: CommunityWebInfo? = null, @ProtoNumber(6) @JvmField val jumpCommunityChannelUrl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class CommunityConfigInfo( @ProtoNumber(1) @JvmField val jumpHomePageUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val picUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val dynamicCount: Int = 0, @ProtoNumber(5) @JvmField val communityBid: Long = 0L, @ProtoNumber(6) @JvmField val followStatus: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0xd8a : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val retcode: Int = 0, @ProtoNumber(2) @JvmField val res: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val cmd: Int = 0, @ProtoNumber(3) @JvmField val body: String = "", @ProtoNumber(4) @JvmField val clientInfo: ClientInfo? = null ) : ProtoBuf @Serializable internal class ClientInfo( @ProtoNumber(1) @JvmField val implat: Int = 0, @ProtoNumber(2) @JvmField val ingClientver: String = "" ) : ProtoBuf } @Serializable internal class Oidb0xb6f : ProtoBuf { @Serializable internal class ReportFreqRspBody( @ProtoNumber(1) @JvmField val identity: Identity? = null, @ProtoNumber(4) @JvmField val remainTimes: Long = 0L, @ProtoNumber(5) @JvmField val expireTime: Int = 0 ) : ProtoBuf @Serializable internal class Identity( @ProtoNumber(1) @JvmField val apiName: String = "", @ProtoNumber(2) @JvmField val appid: Int = 0, @ProtoNumber(3) @JvmField val apptype: Int = 0, @ProtoNumber(4) @JvmField val bizid: Int = 0, @ProtoNumber(10) @JvmField val intExt1: Long = 0L, @ProtoNumber(20) @JvmField val ext1: String = "" ) : ProtoBuf @Serializable internal class ThresholdInfo( @ProtoNumber(1) @JvmField val thresholdPerMinute: Long = 0L, @ProtoNumber(2) @JvmField val thresholdPerDay: Long = 0L, @ProtoNumber(3) @JvmField val thresholdPerHour: Long = 0L, @ProtoNumber(4) @JvmField val thresholdPerWeek: Long = 0L ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val reportFreqRsp: ReportFreqRspBody? = null ) : ProtoBuf @Serializable internal class ReportFreqReqBody( @ProtoNumber(1) @JvmField val identity: Identity? = null, @ProtoNumber(2) @JvmField val invokeTimes: Long = 1L ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val reportFreqReq: ReportFreqReqBody? = null ) : ProtoBuf } @Serializable internal class Cmd0x7dc : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val seq: Int = 0, @ProtoNumber(2) @JvmField val wording: String = "", @ProtoNumber(3) @JvmField val msgAppointInfo: List<AppointDefine.AppointInfo> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val seq: Int = 0, @ProtoNumber(2) @JvmField val msgAppointment: AppointDefine.AppointContent? = null, @ProtoNumber(3) @JvmField val msgLbsInfo: AppointDefine.LBSInfo? = null, @ProtoNumber(4) @JvmField val overwrite: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0x7cd : ProtoBuf { @Serializable internal class AppointBrife( @ProtoNumber(1) @JvmField val msgPublisherInfo: AppointDefine.PublisherInfo? = null, @ProtoNumber(2) @JvmField val msgAppointsInfo: AppointDefine.AppointInfo? = null ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val stamp: Int = 0, @ProtoNumber(2) @JvmField val over: Int = 0, @ProtoNumber(3) @JvmField val next: Int = 0, @ProtoNumber(4) @JvmField val msgAppointsInfo: List<AppointBrife> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val stamp: Int = 0, @ProtoNumber(2) @JvmField val start: Int = 0, @ProtoNumber(3) @JvmField val want: Int = 0, @ProtoNumber(4) @JvmField val msgLbsInfo: AppointDefine.LBSInfo? = null, @ProtoNumber(5) @JvmField val msgAppointIds: List<AppointDefine.AppointID> = emptyList(), @ProtoNumber(6) @JvmField val appointOperation: Int = 0, @ProtoNumber(100) @JvmField val requestUin: Long = 0L ) : ProtoBuf } @Serializable internal class Oidb0xc0c : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val isTaskCompleted: Int = 0, @ProtoNumber(2) @JvmField val taskPoint: Int = 0, @ProtoNumber(3) @JvmField val guideWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val needShowProgress: Int = 0, @ProtoNumber(5) @JvmField val originalProgress: Int = 0, @ProtoNumber(6) @JvmField val nowProgress: Int = 0, @ProtoNumber(7) @JvmField val totalProgress: Int = 0, @ProtoNumber(8) @JvmField val needExecTask: Int = 0 ) : ProtoBuf @Serializable internal class VideoSrcType( @ProtoNumber(1) @JvmField val sourceType: Int = 0, @ProtoNumber(2) @JvmField val videoFromType: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val taskType: Int = 0, @ProtoNumber(3) @JvmField val rowkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val feedsId: Long = 0L, @ProtoNumber(5) @JvmField val msgVideoFromType: VideoSrcType? = null ) : ProtoBuf } @Serializable internal class Cmd0x5fb : ProtoBuf { @Serializable internal class ReqInfo( @ProtoNumber(3) @JvmField val time: Int = 0, @ProtoNumber(4) @JvmField val subject: Int = 0, @ProtoNumber(5) @JvmField val gender: Int = 0, @ProtoNumber(6) @JvmField val ageLow: Int = 0, @ProtoNumber(7) @JvmField val ageUp: Int = 0, @ProtoNumber(8) @JvmField val profession: Int = 0, @ProtoNumber(9) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val msgDestination: AppointDefine.LocaleInfo? = null ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgHead: BusiReqHead? = null, @ProtoNumber(2) @JvmField val msgLbsInfo: AppointDefine.LBSInfo? = null, @ProtoNumber(3) @JvmField val reqInfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class BusiRespHead( @ProtoNumber(1) @JvmField val int32Version: Int = 1, @ProtoNumber(2) @JvmField val int32Seq: Int = 0, @ProtoNumber(3) @JvmField val int32ReplyCode: Int = 0, @ProtoNumber(4) @JvmField val result: String = "" ) : ProtoBuf @Serializable internal class UserProfile( @ProtoNumber(1) @JvmField val int64Id: Long = 0L, @ProtoNumber(2) @JvmField val int32IdType: Int = 0, @ProtoNumber(3) @JvmField val url: String = "", @ProtoNumber(4) @JvmField val int32PicType: Int = 0, @ProtoNumber(5) @JvmField val int32SubPicType: Int = 0, @ProtoNumber(6) @JvmField val title: String = "", @ProtoNumber(7) @JvmField val content: String = "", @ProtoNumber(8) @JvmField val content2: String = "", @ProtoNumber(9) @JvmField val picUrl: String = "" ) : ProtoBuf @Serializable internal class BusiReqHead( @ProtoNumber(1) @JvmField val int32Version: Int = 1, @ProtoNumber(2) @JvmField val int32Seq: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgHead: BusiRespHead? = null, @ProtoNumber(2) @JvmField val msgUserList: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Oidb0xb61 : ProtoBuf { @Serializable internal class GetAppinfoReq( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val appType: Int = 0, @ProtoNumber(3) @JvmField val platform: Int = 0 ) : ProtoBuf @Serializable internal class GetPkgUrlReq( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val appType: Int = 0, @ProtoNumber(3) @JvmField val appVersion: Int = 0, @ProtoNumber(4) @JvmField val platform: Int = 0, @ProtoNumber(5) @JvmField val sysVersion: String = "", @ProtoNumber(6) @JvmField val qqVersion: String = "" ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val wording: String = "", @ProtoNumber(2) @JvmField val nextReqDuration: Int = 0, @ProtoNumber(10) @JvmField val getAppinfoRsp: GetAppinfoRsp? = null, @ProtoNumber(11) @JvmField val getMqqappUrlRsp: GetPkgUrlRsp? = null ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(10) @JvmField val getAppinfoReq: GetAppinfoReq? = null, @ProtoNumber(11) @JvmField val getMqqappUrlReq: GetPkgUrlReq? = null ) : ProtoBuf @Serializable internal class GetAppinfoRsp( @ProtoNumber(1) @JvmField val appinfo: Qqconnect.Appinfo? = null ) : ProtoBuf @Serializable internal class GetPkgUrlRsp( @ProtoNumber(1) @JvmField val appVersion: Int = 0, @ProtoNumber(2) @JvmField val pkgUrl: String = "" ) : ProtoBuf } @Serializable internal class Oidb0xb60 : ProtoBuf { @Serializable internal class GetPrivilegeReq( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val appType: Int = 3 ) : ProtoBuf @Serializable internal class CheckUrlReq( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val appType: Int = 0, @ProtoNumber(3) @JvmField val url: String = "" ) : ProtoBuf @Serializable internal class ClientInfo( @ProtoNumber(1) @JvmField val platform: Int = 0, @ProtoNumber(2) @JvmField val sdkVersion: String = "", @ProtoNumber(3) @JvmField val androidPackageName: String = "", @ProtoNumber(4) @JvmField val androidSignature: String = "", @ProtoNumber(5) @JvmField val iosBundleId: String = "", @ProtoNumber(6) @JvmField val pcSign: String = "" ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val wording: String = "", @ProtoNumber(10) @JvmField val getPrivilegeRsp: GetPrivilegeRsp? = null, @ProtoNumber(11) @JvmField val checkUrlRsp: CheckUrlRsp? = null ) : ProtoBuf @Serializable internal class CheckUrlRsp( @ProtoNumber(1) @JvmField val isAuthed: Boolean = false, @ProtoNumber(2) @JvmField val nextReqDuration: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val clientInfo: ClientInfo? = null, @ProtoNumber(10) @JvmField val getPrivilegeReq: GetPrivilegeReq? = null, @ProtoNumber(11) @JvmField val checkUrlReq: CheckUrlReq? = null ) : ProtoBuf @Serializable internal class GetPrivilegeRsp( @ProtoNumber(1) @JvmField val apiGroups: List<Int> = emptyList(), @ProtoNumber(2) @JvmField val nextReqDuration: Int = 0, @ProtoNumber(3) @JvmField val apiNames: List<String> = emptyList() ) : ProtoBuf } @Serializable internal class Cmd0x5fc : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val lastEventId: Long = 0L, @ProtoNumber(2) @JvmField val readEventId: Long = 0L, @ProtoNumber(3) @JvmField val fetchCount: Int = 0, @ProtoNumber(4) @JvmField val lastNearbyEventId: Long = 0L, @ProtoNumber(5) @JvmField val readNearbyEventId: Long = 0L, @ProtoNumber(6) @JvmField val fetchNearbyEventCount: Int = 0, @ProtoNumber(7) @JvmField val lastFeedEventId: Long = 0L, @ProtoNumber(8) @JvmField val readFeedEventId: Long = 0L, @ProtoNumber(9) @JvmField val fetchFeedEventCount: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgEventList: List<AppointDefine.DateEvent> = emptyList(), @ProtoNumber(2) @JvmField val actAppointIds: List<AppointDefine.AppointID> = emptyList(), @ProtoNumber(3) @JvmField val maxEventId: Long = 0L, @ProtoNumber(4) @JvmField val errorTips: String = "", @ProtoNumber(5) @JvmField val msgNearbyEventList: List<AppointDefine.NearbyEvent> = emptyList(), @ProtoNumber(6) @JvmField val msgFeedEventList: List<AppointDefine.FeedEvent> = emptyList(), @ProtoNumber(7) @JvmField val maxFreshEventId: Long = 0L ) : ProtoBuf } @Serializable internal class Oidb0xc33 : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val nextGap: Int = 0, @ProtoNumber(3) @JvmField val newUser: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody : ProtoBuf } @Serializable internal class Oidb0xc0b : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val isOpenCoinEntry: Int = 0, @ProtoNumber(2) @JvmField val canGetCoinCount: Int = 0, @ProtoNumber(3) @JvmField val coinIconUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val lastCompletedTaskStamp: Long = 0L, @ProtoNumber(6) @JvmField val cmsWording: List<KanDianCMSActivityInfo> = emptyList(), @ProtoNumber(7) @JvmField val lastCmsActivityStamp: Long = 0L, @ProtoNumber(8) @JvmField val msgKandianCoinRemind: KanDianCoinRemind? = null, @ProtoNumber(9) @JvmField val msgKandianTaskRemind: KanDianTaskRemind? = null ) : ProtoBuf @Serializable internal class KanDianCoinRemind( @ProtoNumber(1) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class KanDianTaskRemind( @ProtoNumber(1) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val taskType: Int = 0 ) : ProtoBuf @Serializable internal class KanDianCMSActivityInfo( @ProtoNumber(1) @JvmField val activityId: Long = 0L, @ProtoNumber(2) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val pictureUrl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L ) : ProtoBuf } @Serializable internal class Cmd0xc85 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(101) @JvmField val fromUin: Long = 0L, @ProtoNumber(102) @JvmField val toUin: Long = 0L, @ProtoNumber(103) @JvmField val op: Int = 0, @ProtoNumber(104) @JvmField val intervalDays: Int = 0 ) : ProtoBuf @Serializable internal class InteractionDetailInfo( @ProtoNumber(101) @JvmField val continuousRecordDays: Int = 0, @ProtoNumber(102) @JvmField val sendDayTime: Int = 0, @ProtoNumber(103) @JvmField val recvDayTime: Int = 0, @ProtoNumber(104) @JvmField val sendRecord: String = "", @ProtoNumber(105) @JvmField val recvRecord: String = "" ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(101) @JvmField val result: Int = 0, @ProtoNumber(102) @JvmField val recentInteractionTime: Int = 0, @ProtoNumber(103) @JvmField val interactionDetailInfo: InteractionDetailInfo? = null ) : ProtoBuf } @Serializable internal class Cmd0x7ce : ProtoBuf { @Serializable internal class AppintDetail( @ProtoNumber(1) @JvmField val msgPublisherInfo: AppointDefine.PublisherInfo? = null, @ProtoNumber(2) @JvmField val msgAppointsInfo: AppointDefine.AppointInfo? = null, @ProtoNumber(3) @JvmField val score: Int = 0, @ProtoNumber(4) @JvmField val joinOver: Int = 0, @ProtoNumber(5) @JvmField val joinNext: Int = 0, @ProtoNumber(6) @JvmField val msgStrangerInfo: List<AppointDefine.StrangerInfo> = emptyList(), @ProtoNumber(7) @JvmField val viewOver: Int = 0, @ProtoNumber(8) @JvmField val viewNext: Int = 0, @ProtoNumber(9) @JvmField val msgVistorInfo: List<AppointDefine.StrangerInfo> = emptyList(), @ProtoNumber(10) @JvmField val meJoin: Int = 0, @ProtoNumber(12) @JvmField val canProfile: Int = 0, @ProtoNumber(13) @JvmField val profileErrmsg: String = "", @ProtoNumber(14) @JvmField val canAio: Int = 0, @ProtoNumber(15) @JvmField val aioErrmsg: String = "", @ProtoNumber(16) @JvmField val sigC2C: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(17) @JvmField val uin: Long = 0L, @ProtoNumber(18) @JvmField val limited: Int = 0, @ProtoNumber(19) @JvmField val msgCommentList: List<AppointDefine.DateComment> = emptyList(), @ProtoNumber(20) @JvmField val commentOver: Int = 0, @ProtoNumber(23) @JvmField val meInvited: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgAppointsInfo: List<AppintDetail> = emptyList(), @ProtoNumber(2) @JvmField val secureFlag: Int = 0, @ProtoNumber(3) @JvmField val secureTips: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val appointIds: List<AppointDefine.AppointID> = emptyList(), @ProtoNumber(2) @JvmField val joinStart: Int = 0, @ProtoNumber(3) @JvmField val joinWant: Int = 0, @ProtoNumber(4) @JvmField val viewStart: Int = 0, @ProtoNumber(5) @JvmField val viewWant: Int = 0, @ProtoNumber(6) @JvmField val msgLbsInfo: AppointDefine.LBSInfo? = null, @ProtoNumber(7) @JvmField val uint64Uins: List<Long> = emptyList(), @ProtoNumber(8) @JvmField val viewCommentCount: Int = 0, @ProtoNumber(100) @JvmField val requestUin: Long = 0L ) : ProtoBuf } @Serializable internal class Cmd0x7db : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val wording: String = "", @ProtoNumber(2) @JvmField val msgAppointInfo: AppointDefine.AppointInfo? = null, @ProtoNumber(3) @JvmField val sigC2C: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val appointAction: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgAppointId: AppointDefine.AppointID? = null, @ProtoNumber(2) @JvmField val appointAction: Int = 0, @ProtoNumber(3) @JvmField val overwrite: Int = 0, @ProtoNumber(4) @JvmField val msgAppointIds: List<AppointDefine.AppointID> = emptyList() ) : ProtoBuf } @Serializable internal class Oidb0xc6c : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgGroupInfo: List<GroupInfo> = emptyList() ) : ProtoBuf @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val groupUin: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L ) : ProtoBuf @Serializable internal class RspBody : ProtoBuf } @Serializable internal class Oidb0xc05 : ProtoBuf { @Serializable internal class GetAuthAppListReq( @ProtoNumber(1) @JvmField val start: Int = 0, @ProtoNumber(2) @JvmField val limit: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val wording: String = "", @ProtoNumber(10) @JvmField val getCreateAppListRsp: GetCreateAppListRsp? = null, @ProtoNumber(11) @JvmField val getAuthAppListRsp: GetAuthAppListRsp? = null ) : ProtoBuf @Serializable internal class GetCreateAppListRsp( @ProtoNumber(1) @JvmField val totalCount: Int = 0, @ProtoNumber(2) @JvmField val appinfos: List<Qqconnect.Appinfo> = emptyList() ) : ProtoBuf @Serializable internal class GetAuthAppListRsp( @ProtoNumber(1) @JvmField val totalCount: Int = 0, @ProtoNumber(2) @JvmField val appinfos: List<Qqconnect.Appinfo> = emptyList(), @ProtoNumber(3) @JvmField val curIndex: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(10) @JvmField val getCreateAppListReq: GetCreateAppListReq? = null, @ProtoNumber(11) @JvmField val getAuthAppListReq: GetAuthAppListReq? = null ) : ProtoBuf @Serializable internal class GetCreateAppListReq( @ProtoNumber(1) @JvmField val start: Int = 0, @ProtoNumber(2) @JvmField val limit: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0x7da : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgAppointIds: List<AppointDefine.AppointID> = emptyList(), @ProtoNumber(2) @JvmField val appointOperation: Int = 0, @ProtoNumber(3) @JvmField val operationReason: Int = 0, @ProtoNumber(4) @JvmField val overwrite: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val wording: String = "", @ProtoNumber(2) @JvmField val msgAppointInfo: List<AppointDefine.AppointInfo> = emptyList(), @ProtoNumber(3) @JvmField val operationReason: Int = 0 ) : ProtoBuf } @Serializable internal class Qqconnect : ProtoBuf { @Serializable internal class MobileAppInfo( @ProtoNumber(11) @JvmField val androidAppInfo: List<AndroidAppInfo> = emptyList(), @ProtoNumber(12) @JvmField val iosAppInfo: List<IOSAppInfo> = emptyList() ) : ProtoBuf @Serializable internal class TemplateMsgConfig( @ProtoNumber(1) @JvmField val serviceMsgUin: Long = 0L, @ProtoNumber(2) @JvmField val publicMsgUin: Long = 0L, @ProtoNumber(3) @JvmField val campMsgUin: Long = 0L ) : ProtoBuf @Serializable internal class Appinfo( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val appType: Int = 0, @ProtoNumber(3) @JvmField val platform: Int = 0, @ProtoNumber(4) @JvmField val appName: String = "", @ProtoNumber(5) @JvmField val appKey: String = "", @ProtoNumber(6) @JvmField val appState: Int = 0, @ProtoNumber(7) @JvmField val iphoneUrlScheme: String = "", @ProtoNumber(8) @JvmField val androidPackName: String = "", @ProtoNumber(9) @JvmField val iconUrl: String = "", @ProtoNumber(10) @JvmField val sourceUrl: String = "", @ProtoNumber(11) @JvmField val iconSmallUrl: String = "", @ProtoNumber(12) @JvmField val iconMiddleUrl: String = "", @ProtoNumber(13) @JvmField val tencentDocsAppinfo: TencentDocsAppinfo? = null, @ProtoNumber(21) @JvmField val developerUin: Long = 0L, @ProtoNumber(22) @JvmField val appClass: Int = 0, @ProtoNumber(23) @JvmField val appSubclass: Int = 0, @ProtoNumber(24) @JvmField val remark: String = "", @ProtoNumber(25) @JvmField val iconMiniUrl: String = "", @ProtoNumber(26) @JvmField val authTime: Long = 0L, @ProtoNumber(27) @JvmField val appUrl: String = "", @ProtoNumber(28) @JvmField val universalLink: String = "", @ProtoNumber(29) @JvmField val qqconnectFeature: Int = 0, @ProtoNumber(30) @JvmField val isHatchery: Int = 0, @ProtoNumber(31) @JvmField val testUinList: List<Long> = emptyList(), @ProtoNumber(100) @JvmField val templateMsgConfig: TemplateMsgConfig? = null, @ProtoNumber(101) @JvmField val miniAppInfo: MiniAppInfo? = null, @ProtoNumber(102) @JvmField val webAppInfo: WebAppInfo? = null, @ProtoNumber(103) @JvmField val mobileAppInfo: MobileAppInfo? = null ) : ProtoBuf @Serializable internal class ConnectClientInfo( @ProtoNumber(1) @JvmField val platform: Int = 0, @ProtoNumber(2) @JvmField val sdkVersion: String = "", @ProtoNumber(3) @JvmField val systemName: String = "", @ProtoNumber(4) @JvmField val systemVersion: String = "", @ProtoNumber(21) @JvmField val androidPackageName: String = "", @ProtoNumber(22) @JvmField val androidSignature: String = "", @ProtoNumber(31) @JvmField val iosBundleId: String = "", @ProtoNumber(32) @JvmField val iosDeviceId: String = "", @ProtoNumber(33) @JvmField val iosAppToken: String = "", @ProtoNumber(41) @JvmField val pcSign: String = "" ) : ProtoBuf @Serializable internal class TencentDocsAppinfo( @ProtoNumber(1) @JvmField val openTypes: String = "", @ProtoNumber(2) @JvmField val opts: String = "", @ProtoNumber(3) @JvmField val ejs: String = "", @ProtoNumber(4) @JvmField val callbackUrlTest: String = "", @ProtoNumber(5) @JvmField val callbackUrl: String = "", @ProtoNumber(6) @JvmField val domain: String = "", @ProtoNumber(7) @JvmField val userinfoCallback: String = "", @ProtoNumber(8) @JvmField val userinfoCallbackTest: String = "" ) : ProtoBuf @Serializable internal class WebAppInfo( @ProtoNumber(1) @JvmField val websiteUrl: String = "", @ProtoNumber(2) @JvmField val provider: String = "", @ProtoNumber(3) @JvmField val icp: String = "", @ProtoNumber(4) @JvmField val callbackUrl: String = "" ) : ProtoBuf @Serializable internal class IOSAppInfo( @ProtoNumber(1) @JvmField val bundleId: String = "", @ProtoNumber(2) @JvmField val urlScheme: String = "", @ProtoNumber(3) @JvmField val storeId: String = "" ) : ProtoBuf @Serializable internal class MsgUinInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgType: Int = 0, @ProtoNumber(3) @JvmField val appid: Int = 0, @ProtoNumber(4) @JvmField val appType: Int = 0, @ProtoNumber(5) @JvmField val ctime: Int = 0, @ProtoNumber(6) @JvmField val mtime: Int = 0, @ProtoNumber(7) @JvmField val mpType: Int = 0, @ProtoNumber(100) @JvmField val nick: String = "", @ProtoNumber(101) @JvmField val faceUrl: String = "" ) : ProtoBuf @Serializable internal class MiniAppInfo( @ProtoNumber(1) @JvmField val superUin: Long = 0L, @ProtoNumber(11) @JvmField val ownerType: Int = 0, @ProtoNumber(12) @JvmField val ownerName: String = "", @ProtoNumber(13) @JvmField val ownerIdCardType: Int = 0, @ProtoNumber(14) @JvmField val ownerIdCard: String = "", @ProtoNumber(15) @JvmField val ownerStatus: Int = 0 ) : ProtoBuf @Serializable internal class AndroidAppInfo( @ProtoNumber(1) @JvmField val packName: String = "", @ProtoNumber(2) @JvmField val packSign: String = "", @ProtoNumber(3) @JvmField val apkDownUrl: String = "" ) : ProtoBuf } @Serializable internal class Sync : ProtoBuf { @Serializable internal class SyncAppointmentReq( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgAppointment: AppointDefine.AppointContent? = null, @ProtoNumber(3) @JvmField val msgGpsInfo: AppointDefine.GPS? = null ) : ProtoBuf @Serializable internal class SyncAppointmentRsp( @ProtoNumber(1) @JvmField val result: Int = 0 ) : ProtoBuf } @Serializable internal class Oidb0xc26 : ProtoBuf { @Serializable internal class RgoupLabel( @ProtoNumber(1) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val enumType: Int /* enum */ = 1, @ProtoNumber(3) @JvmField val textColor: RgroupColor? = null, @ProtoNumber(4) @JvmField val edgingColor: RgroupColor? = null, @ProtoNumber(5) @JvmField val labelAttr: Int = 0, @ProtoNumber(6) @JvmField val labelType: Int = 0 ) : ProtoBuf @Serializable internal class AddFriendSource( @ProtoNumber(1) @JvmField val source: Int = 0, @ProtoNumber(2) @JvmField val subSource: Int = 0 ) : ProtoBuf @Serializable internal class Label( @ProtoNumber(1) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val textColor: Color? = null, @ProtoNumber(3) @JvmField val edgingColor: Color? = null, @ProtoNumber(4) @JvmField val labelType: Int = 0 ) : ProtoBuf @Serializable internal class EntryDelay( @ProtoNumber(1) @JvmField val emEntry: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val delay: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgPersons: List<MayKnowPerson> = emptyList(), @ProtoNumber(2) @JvmField val entryInuse: List<Int> = emptyList(), @ProtoNumber(3) @JvmField val entryClose: List<Int> = emptyList(), @ProtoNumber(4) @JvmField val nextGap: Int = 0, @ProtoNumber(5) @JvmField val timestamp: Int = 0, @ProtoNumber(6) @JvmField val msgUp: Int = 0, @ProtoNumber(7) @JvmField val entryDelays: List<EntryDelay> = emptyList(), @ProtoNumber(8) @JvmField val listSwitch: Int = 0, @ProtoNumber(9) @JvmField val addPageListSwitch: Int = 0, @ProtoNumber(10) @JvmField val emRspDataType: Int /* enum */ = 1, @ProtoNumber(11) @JvmField val msgRgroupItems: List<RecommendInfo> = emptyList(), @ProtoNumber(12) @JvmField val boolIsNewuser: Boolean = false, @ProtoNumber(13) @JvmField val msgTables: List<TabInfo> = emptyList(), @ProtoNumber(14) @JvmField val cookies: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class TabInfo( @ProtoNumber(1) @JvmField val tabId: Int = 0, @ProtoNumber(2) @JvmField val recommendCount: Int = 0, @ProtoNumber(3) @JvmField val tableName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val iconUrlSelect: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val iconUrlUnselect: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val backgroundColorSelect: Color? = null, @ProtoNumber(7) @JvmField val backgroundColorUnselect: Color? = null ) : ProtoBuf @Serializable internal class MayKnowPerson( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgIosSource: AddFriendSource? = null, @ProtoNumber(3) @JvmField val msgAndroidSource: AddFriendSource? = null, @ProtoNumber(4) @JvmField val reason: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val additive: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val remark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val country: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val province: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val city: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val age: Int = 0, @ProtoNumber(12) @JvmField val catelogue: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val alghrithm: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val richbuffer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val qzone: Int = 0, @ProtoNumber(16) @JvmField val gender: Int = 0, @ProtoNumber(17) @JvmField val mobileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val token: String = "", @ProtoNumber(19) @JvmField val onlineState: Int = 0, @ProtoNumber(20) @JvmField val msgLabels: List<Label> = emptyList(), @ProtoNumber(21) @JvmField val sourceid: Int = 0 ) : ProtoBuf @Serializable internal class RecommendInfo( @ProtoNumber(1) @JvmField val woring: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val msgGroups: List<RgroupInfo> = emptyList() ) : ProtoBuf @Serializable internal class RgroupInfo( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val ownerUin: Long = 0L, @ProtoNumber(3) @JvmField val groupName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val groupMemo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val memberNum: Int = 0, @ProtoNumber(6) @JvmField val groupLabel: List<RgoupLabel> = emptyList(), @ProtoNumber(7) @JvmField val groupFlagExt: Int = 0, @ProtoNumber(8) @JvmField val groupFlag: Int = 0, @ProtoNumber(9) @JvmField val source: Int /* enum */ = 1, @ProtoNumber(10) @JvmField val tagWording: RgoupLabel? = null, @ProtoNumber(11) @JvmField val algorithm: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val joinGroupAuth: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val activity: Int = 0, @ProtoNumber(14) @JvmField val memberMaxNum: Int = 0, @ProtoNumber(15) @JvmField val int32UinPrivilege: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val filterUins: List<Long> = emptyList(), @ProtoNumber(2) @JvmField val phoneBook: Int = 0, @ProtoNumber(3) @JvmField val expectedUins: List<Long> = emptyList(), @ProtoNumber(4) @JvmField val emEntry: Int /* enum */ = 1, @ProtoNumber(5) @JvmField val fetchRgroup: Int = 0, @ProtoNumber(6) @JvmField val tabId: Int = 0, @ProtoNumber(7) @JvmField val want: Int = 80, @ProtoNumber(8) @JvmField val cookies: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RgroupColor( @ProtoNumber(1) @JvmField val r: Int = 0, @ProtoNumber(2) @JvmField val g: Int = 0, @ProtoNumber(3) @JvmField val b: Int = 0 ) : ProtoBuf @Serializable internal class Color( @ProtoNumber(1) @JvmField val r: Int = 0, @ProtoNumber(2) @JvmField val g: Int = 0, @ProtoNumber(3) @JvmField val b: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0xac6 : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val results: List<OperateResult> = emptyList(), @ProtoNumber(4) @JvmField val metalCount: Int = 0, @ProtoNumber(5) @JvmField val metalTotal: Int = 0, @ProtoNumber(9) @JvmField val int32NewCount: Int = 0, @ProtoNumber(10) @JvmField val int32UpgradeCount: Int = 0, @ProtoNumber(11) @JvmField val promptParams: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val medals: List<MedalReport> = emptyList(), @ProtoNumber(2) @JvmField val clean: Int = 0 ) : ProtoBuf @Serializable internal class MedalReport( @ProtoNumber(1) @JvmField val id: Int = 0, @ProtoNumber(2) @JvmField val level: Int = 0 ) : ProtoBuf @Serializable internal class OperateResult( @ProtoNumber(1) @JvmField val id: Int = 0, @ProtoNumber(2) @JvmField val int32Result: Int = 0, @ProtoNumber(3) @JvmField val errmsg: String = "" ) : ProtoBuf } @Serializable internal class Oidb0xd32 : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val openid: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val xmitinfo: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class XmitInfo( @ProtoNumber(1) @JvmField val signature: String = "", @ProtoNumber(2) @JvmField val appid: String = "", @ProtoNumber(3) @JvmField val groupid: String = "", @ProtoNumber(4) @JvmField val nonce: String = "", @ProtoNumber(5) @JvmField val timestamp: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0x7cf : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val stamp: Int = 0, @ProtoNumber(2) @JvmField val start: Int = 0, @ProtoNumber(3) @JvmField val want: Int = 0, @ProtoNumber(4) @JvmField val reqValidOnly: Int = 0, @ProtoNumber(5) @JvmField val msgAppointIds: List<AppointDefine.AppointID> = emptyList(), @ProtoNumber(6) @JvmField val appointOperation: Int = 0, @ProtoNumber(100) @JvmField val requestUin: Long = 0L ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val stamp: Int = 0, @ProtoNumber(2) @JvmField val over: Int = 0, @ProtoNumber(3) @JvmField val next: Int = 0, @ProtoNumber(4) @JvmField val msgAppointsInfo: List<AppointDefine.AppointInfo> = emptyList(), @ProtoNumber(5) @JvmField val unreadCount: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0xac7 : ProtoBuf { @Serializable internal class DeviceInfo( @ProtoNumber(1) @JvmField val din: Long = 0L, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val extd: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val cmd: Int = 0, @ProtoNumber(2) @JvmField val din: Long = 0L, @ProtoNumber(3) @JvmField val extd: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val msgBinderSig: BinderSig? = null ) : ProtoBuf @Serializable internal class ReceiveMessageDevices( @ProtoNumber(1) @JvmField val devices: List<DeviceInfo> = emptyList() ) : ProtoBuf @Serializable internal class BinderSig( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Cmd0x5fa : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgStrangerInfo: List<AppointDefine.StrangerInfo> = emptyList(), @ProtoNumber(2) @JvmField val reachStart: Int = 0, @ProtoNumber(3) @JvmField val reachEnd: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val appointIds: AppointDefine.AppointID? = null, @ProtoNumber(2) @JvmField val referIdx: Int = 0, @ProtoNumber(3) @JvmField val getReferRec: Int = 0, @ProtoNumber(4) @JvmField val reqNextCount: Int = 0, @ProtoNumber(5) @JvmField val reqPrevCount: Int = 0 ) : ProtoBuf } @Serializable internal class FavoriteCKVData : ProtoBuf { @Serializable internal class PicInfo( @ProtoNumber(1) @JvmField val uri: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val md5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val sha1: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val note: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val width: Int = 0, @ProtoNumber(7) @JvmField val height: Int = 0, @ProtoNumber(8) @JvmField val size: Int = 0, @ProtoNumber(9) @JvmField val type: Int = 0, @ProtoNumber(10) @JvmField val msgOwner: Author? = null, @ProtoNumber(11) @JvmField val picId: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class KandianFavoriteItem( @ProtoNumber(1) @JvmField val msgFavoriteExtInfo: KandianFavoriteBizData? = null, @ProtoNumber(2) @JvmField val bytesCid: List<ByteArray> = emptyList(), @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val status: Int = 0, @ProtoNumber(5) @JvmField val msgAuthor: Author? = null, @ProtoNumber(6) @JvmField val createTime: Long = 0L, @ProtoNumber(7) @JvmField val favoriteTime: Long = 0L, @ProtoNumber(8) @JvmField val modifyTime: Long = 0L, @ProtoNumber(9) @JvmField val dataSyncTime: Long = 0L, @ProtoNumber(10) @JvmField val msgFavoriteSummary: FavoriteSummary? = null ) : ProtoBuf @Serializable internal class LinkSummary( @ProtoNumber(1) @JvmField val uri: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val publisher: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val brief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val msgPicInfo: List<PicInfo> = emptyList(), @ProtoNumber(6) @JvmField val type: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val resourceUri: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class UserFavoriteList( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val modifyTs: Long = 0L, @ProtoNumber(100) @JvmField val msgFavoriteItems: List<FavoriteItem> = emptyList() ) : ProtoBuf @Serializable internal class FavoriteSummary( @ProtoNumber(2) @JvmField val msgLinkSummary: LinkSummary? = null ) : ProtoBuf @Serializable internal class FavoriteItem( @ProtoNumber(1) @JvmField val favoriteSource: Int = 0, @ProtoNumber(100) @JvmField val msgKandianFavoriteItem: KandianFavoriteItem? = null ) : ProtoBuf @Serializable internal class Author( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val numId: Long = 0L, @ProtoNumber(3) @JvmField val strId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val groupId: Long = 0L, @ProtoNumber(5) @JvmField val groupName: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class KandianFavoriteBizData( @ProtoNumber(1) @JvmField val rowkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val type: Int = 0, @ProtoNumber(3) @JvmField val videoDuration: Int = 0, @ProtoNumber(4) @JvmField val picNum: Int = 0, @ProtoNumber(5) @JvmField val accountId: Long = 0L, @ProtoNumber(6) @JvmField val accountName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val videoType: Int = 0, @ProtoNumber(8) @JvmField val feedsId: Long = 0L, @ProtoNumber(9) @JvmField val feedsType: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0x5ff : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val errorTips: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgAppointId: AppointDefine.AppointID? = null, @ProtoNumber(2) @JvmField val commentId: String = "" ) : ProtoBuf } @Serializable internal class Oidb0xccd : ProtoBuf { @Serializable internal class Result( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val errcode: Int = 0, @ProtoNumber(3) @JvmField val errmsg: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val int64Uin: Long = 0L, @ProtoNumber(2) @JvmField val appids: List<Int> = emptyList(), @ProtoNumber(3) @JvmField val platform: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val errcode: Int = 0, @ProtoNumber(2) @JvmField val results: List<Result> = emptyList() ) : ProtoBuf } @Serializable internal class Oidb0xc36 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uint64Uins: List<Long> = emptyList() ) : ProtoBuf @Serializable internal class RspBody : ProtoBuf } @Serializable internal class Oidb0x87c : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val country: String = "", @ProtoNumber(2) @JvmField val telephone: String = "", @ProtoNumber(3) @JvmField val smsCode: String = "", @ProtoNumber(4) @JvmField val guid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val enumButype: Int /* enum */ = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val country: String = "", @ProtoNumber(2) @JvmField val telephone: String = "", @ProtoNumber(3) @JvmField val keyType: Int = 0, @ProtoNumber(4) @JvmField val key: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val guid: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Cmd0xbf2 : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val phoneAddrBook: List<PhoneAddrBook> = emptyList(), @ProtoNumber(2) @JvmField val end: Int = 0, @ProtoNumber(3) @JvmField val nextIndex: Long = 0 ) : ProtoBuf @Serializable internal class PhoneAddrBook( @ProtoNumber(1) @JvmField val phone: String = "", @ProtoNumber(2) @JvmField val nick: String = "", @ProtoNumber(3) @JvmField val headUrl: String = "", @ProtoNumber(4) @JvmField val longNick: String = "" ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val startIndex: Long = 0L, @ProtoNumber(3) @JvmField val num: Long = 0L ) : ProtoBuf } @Serializable internal class Cmd0x6cd : ProtoBuf { @Serializable internal class RedpointInfo( @ProtoNumber(1) @JvmField val taskid: Int = 0, @ProtoNumber(2) @JvmField val curSeq: Long = 0L, @ProtoNumber(3) @JvmField val pullSeq: Long = 0L, @ProtoNumber(4) @JvmField val readSeq: Long = 0L, @ProtoNumber(5) @JvmField val pullTimes: Int = 0, @ProtoNumber(6) @JvmField val lastPullTime: Int = 0, @ProtoNumber(7) @JvmField val int32RemainedTime: Int = 0, @ProtoNumber(8) @JvmField val lastRecvTime: Int = 0, @ProtoNumber(9) @JvmField val fromId: Long = 0L, @ProtoNumber(10) @JvmField val enumRedpointType: Int /* enum */ = 1, @ProtoNumber(11) @JvmField val msgRedpointExtraInfo: RepointExtraInfo? = null, @ProtoNumber(12) @JvmField val configVersion: String = "", @ProtoNumber(13) @JvmField val doActivity: Int = 0, @ProtoNumber(14) @JvmField val msgUnreadMsg: List<MessageRec> = emptyList() ) : ProtoBuf @Serializable internal class PullRedpointReq( @ProtoNumber(1) @JvmField val taskid: Int = 0, @ProtoNumber(2) @JvmField val lastPullSeq: Long = 0L ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgRedpoint: List<RedpointInfo> = emptyList(), @ProtoNumber(2) @JvmField val unfinishedRedpoint: List<PullRedpointReq> = emptyList() ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val lastPullRedpoint: List<PullRedpointReq> = emptyList(), @ProtoNumber(2) @JvmField val unfinishedRedpoint: List<PullRedpointReq> = emptyList(), @ProtoNumber(3) @JvmField val msgPullSingleTask: PullRedpointReq? = null, @ProtoNumber(4) @JvmField val retMsgRec: Int = 0 ) : ProtoBuf @Serializable internal class MessageRec( @ProtoNumber(1) @JvmField val seq: Long = 0L, @ProtoNumber(2) @JvmField val time: Int = 0, @ProtoNumber(3) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class RepointExtraInfo( @ProtoNumber(1) @JvmField val count: Int = 0, @ProtoNumber(2) @JvmField val iconUrl: String = "", @ProtoNumber(3) @JvmField val tips: String = "", @ProtoNumber(4) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } @Serializable internal class Oidb0xd55 : ProtoBuf { @Serializable internal class CheckUserRsp( @ProtoNumber(1) @JvmField val openidUin: Long = 0L ) : ProtoBuf @Serializable internal class CheckMiniAppRsp : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val appid: Long = 0L, @ProtoNumber(2) @JvmField val appType: Int = 0, @ProtoNumber(3) @JvmField val srcId: Int = 0, @ProtoNumber(4) @JvmField val rawUrl: String = "", @ProtoNumber(11) @JvmField val checkAppSignReq: CheckAppSignReq? = null, @ProtoNumber(12) @JvmField val checkUserReq: CheckUserReq? = null, @ProtoNumber(13) @JvmField val checkMiniAppReq: CheckMiniAppReq? = null ) : ProtoBuf @Serializable internal class CheckAppSignReq( @ProtoNumber(1) @JvmField val clientInfo: Qqconnect.ConnectClientInfo? = null ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val wording: String = "", @ProtoNumber(11) @JvmField val checkAppSignRsp: CheckAppSignRsp? = null, @ProtoNumber(12) @JvmField val checkUserRsp: CheckUserRsp? = null, @ProtoNumber(13) @JvmField val checkMiniAppRsp: CheckMiniAppRsp? = null ) : ProtoBuf @Serializable internal class CheckUserReq( @ProtoNumber(1) @JvmField val openid: String = "", @ProtoNumber(2) @JvmField val needCheckSameUser: Int = 0 ) : ProtoBuf @Serializable internal class CheckMiniAppReq( @ProtoNumber(1) @JvmField val miniAppAppid: Long = 0L, @ProtoNumber(2) @JvmField val needCheckBind: Int = 0 ) : ProtoBuf @Serializable internal class CheckAppSignRsp( @ProtoNumber(1) @JvmField val iosAppToken: String = "", @ProtoNumber(2) @JvmField val iosUniversalLink: String = "", @ProtoNumber(11) @JvmField val optimizeSwitch: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0x8b4 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val gc: Long = 0L, @ProtoNumber(2) @JvmField val guin: Long = 0L, @ProtoNumber(3) @JvmField val flag: Int = 0, @ProtoNumber(21) @JvmField val dstUin: Long = 0L, @ProtoNumber(22) @JvmField val start: Int = 0, @ProtoNumber(23) @JvmField val cnt: Int = 0, @ProtoNumber(24) @JvmField val tag: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val gc: Long = 0L, @ProtoNumber(2) @JvmField val groupName: String = "", @ProtoNumber(3) @JvmField val faceUrl: String = "", @ProtoNumber(4) @JvmField val setDisplayTime: Int = 0, // @SerialId(5) @JvmField val groupLabel: List<GroupLabel.Label> = emptyList(), @ProtoNumber(6) @JvmField val textIntro: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val richIntro: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class TagInfo( @ProtoNumber(1) @JvmField val dstUin: Long = 0L, @ProtoNumber(2) @JvmField val start: Int = 0, @ProtoNumber(3) @JvmField val cnt: Int = 0, @ProtoNumber(4) @JvmField val timestamp: Int = 0, @ProtoNumber(5) @JvmField val _0x7ddSeq: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val flag: Int = 0, @ProtoNumber(21) @JvmField val tag: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(22) @JvmField val groupInfo: List<GroupInfo> = emptyList(), @ProtoNumber(23) @JvmField val textLabel: List<ByteArray> = emptyList() ) : ProtoBuf } @Serializable internal class Cmd0x682 : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgChatinfo: List<ChatInfo> = emptyList() ) : ProtoBuf @Serializable internal class ChatInfo( @ProtoNumber(1) @JvmField val touin: Long = 0L, @ProtoNumber(2) @JvmField val chatflag: Int = 0, @ProtoNumber(3) @JvmField val goldflag: Int = 0, @ProtoNumber(4) @JvmField val totalexpcount: Int = 0, @ProtoNumber(5) @JvmField val curexpcount: Int = 0, @ProtoNumber(6) @JvmField val totalFlag: Int = 0, @ProtoNumber(7) @JvmField val curdayFlag: Int = 0, @ProtoNumber(8) @JvmField val expressTipsMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val expressMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uint64Touinlist: List<Long> = emptyList() ) : ProtoBuf } @Serializable internal class Cmd0x6f5 : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val qqVersion: String = "", @ProtoNumber(2) @JvmField val qqPlatform: Int = 0 ) : ProtoBuf @Serializable internal class TaskInfo( @ProtoNumber(1) @JvmField val taskId: Int = 0, @ProtoNumber(2) @JvmField val appid: Int = 0, @ProtoNumber(3) @JvmField val passthroughLevel: Int = 0, @ProtoNumber(4) @JvmField val showLevel: Int = 0, @ProtoNumber(5) @JvmField val extra: Int = 0, @ProtoNumber(6) @JvmField val priority: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val configVersion: String = "", @ProtoNumber(2) @JvmField val taskInfo: List<TaskInfo> = emptyList() ) : ProtoBuf } @Serializable internal class Oidb0xb7e : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val topItem: List<DiandianTopConfig> = emptyList() ) : ProtoBuf @Serializable internal class DiandianTopConfig( @ProtoNumber(1) @JvmField val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val subTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val subTitleColor: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val picUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val type: Int = 0, @ProtoNumber(7) @JvmField val topicId: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody : ProtoBuf } @Serializable internal class Oidb0xc2f : ProtoBuf { @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val msgGetFollowUserRecommendListRsp: GetFollowUserRecommendListRsp? = null ) : ProtoBuf @Serializable internal class GetFollowUserRecommendListReq( @ProtoNumber(1) @JvmField val followedUin: Long = 0L ) : ProtoBuf @Serializable internal class RecommendAccountInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val accountType: Int = 0, @ProtoNumber(3) @JvmField val nickName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val headImgUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val isVip: Int = 0, @ProtoNumber(6) @JvmField val isStar: Int = 0, @ProtoNumber(7) @JvmField val recommendReason: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class GetFollowUserRecommendListRsp( @ProtoNumber(1) @JvmField val msgRecommendList: List<RecommendAccountInfo> = emptyList(), @ProtoNumber(2) @JvmField val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgGetFollowUserRecommendListReq: GetFollowUserRecommendListReq? = null ) : ProtoBuf } @Serializable internal class Cmd0x7ca : ProtoBuf { @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgAppointId: AppointDefine.AppointID? = null, @ProtoNumber(2) @JvmField val tinyid: Long = 0L, @ProtoNumber(3) @JvmField val opType: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val sigC2C: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val peerUin: Long = 0L, @ProtoNumber(3) @JvmField val errorWording: String = "", @ProtoNumber(4) @JvmField val opType: Int = 0 ) : ProtoBuf } @Serializable internal class Cmd0xd40 : ProtoBuf { @Serializable internal class DeviceInfo( @ProtoNumber(1) @JvmField val os: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val dev: DeviceInfo? = null, @ProtoNumber(2) @JvmField val src: Int = 0, @ProtoNumber(3) @JvmField val event: Int = 0, @ProtoNumber(4) @JvmField val redtype: Int = 0 ) : ProtoBuf @Serializable internal class RspBody : ProtoBuf } @Serializable internal class Cmd0x6ce : ProtoBuf { @Serializable internal class RspBody : ProtoBuf @Serializable internal class ReadRedpointReq( @ProtoNumber(1) @JvmField val taskid: Int = 0, @ProtoNumber(2) @JvmField val readSeq: Long = 0L, @ProtoNumber(3) @JvmField val appid: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val msgReadReq: List<ReadRedpointReq> = emptyList() ) : ProtoBuf } @Serializable internal class Cmd0xed3 : ProtoBuf { @Serializable internal class RspBody : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val msgSeq: Int = 0, @ProtoNumber(4) @JvmField val msgRandom: Int = 0, @ProtoNumber(5) @JvmField val aioUin: Long = 0L ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Oidb0x6d6.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.network.protocol.packet.chat.CheckableStruct import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class Oidb0x6d6 : ProtoBuf { @Serializable internal class DeleteFileReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val parentFolderId: String = "", @JvmField @ProtoNumber(5) val fileId: String = "", @JvmField @ProtoNumber(6) val msgdbSeq: Int = 0, @JvmField @ProtoNumber(7) val msgRand: Int = 0, ) : ProtoBuf @Serializable internal class DeleteFileRspBody( /** * -103: file not exist */ @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", ) : ProtoBuf, CheckableStruct @Serializable internal class DownloadFileReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val fileId: String = "", @JvmField @ProtoNumber(5) val boolThumbnailReq: Boolean = false, @JvmField @ProtoNumber(6) val urlType: Int = 0, @JvmField @ProtoNumber(7) val boolPreviewReq: Boolean = false, @JvmField @ProtoNumber(8) val src: Int = 0, ) : ProtoBuf @Serializable internal class DownloadFileRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val downloadIp: String = "", @JvmField @ProtoNumber(5) val downloadDns: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val sha3: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(9) val md5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val cookieVal: String = "", @JvmField @ProtoNumber(11) val saveFileName: String = "", @JvmField @ProtoNumber(12) val previewPort: Int = 0, @JvmField @ProtoNumber(13) val downloadDnsHttps: String = "", @JvmField @ProtoNumber(14) val previewPortHttps: Int = 0, ) : ProtoBuf, CheckableStruct @Serializable internal class MoveFileReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val fileId: String = "", @JvmField @ProtoNumber(5) val parentFolderId: String = "", @JvmField @ProtoNumber(6) val destFolderId: String = "", ) : ProtoBuf @Serializable internal class MoveFileRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val parentFolderId: String = "", ) : ProtoBuf, CheckableStruct @Serializable internal class RenameFileReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val fileId: String = "", @JvmField @ProtoNumber(5) val parentFolderId: String = "", @JvmField @ProtoNumber(6) val newFileName: String = "", ) : ProtoBuf @Serializable internal class RenameFileRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", ) : ProtoBuf, CheckableStruct @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val uploadFileReq: UploadFileReqBody? = null, @JvmField @ProtoNumber(2) val resendFileReq: ResendReqBody? = null, @JvmField @ProtoNumber(3) val downloadFileReq: DownloadFileReqBody? = null, @JvmField @ProtoNumber(4) val deleteFileReq: DeleteFileReqBody? = null, @JvmField @ProtoNumber(5) val renameFileReq: RenameFileReqBody? = null, @JvmField @ProtoNumber(6) val moveFileReq: MoveFileReqBody? = null, ) : ProtoBuf @Serializable internal class ResendReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val fileId: String = "", @JvmField @ProtoNumber(5) val sha: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ResendRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val uploadIp: String = "", @JvmField @ProtoNumber(5) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val checkKey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf, CheckableStruct @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val uploadFileRsp: UploadFileRspBody? = null, @JvmField @ProtoNumber(2) val resendFileRsp: ResendRspBody? = null, @JvmField @ProtoNumber(3) val downloadFileRsp: DownloadFileRspBody? = null, @JvmField @ProtoNumber(4) val deleteFileRsp: DeleteFileRspBody? = null, @JvmField @ProtoNumber(5) val renameFileRsp: RenameFileRspBody? = null, @JvmField @ProtoNumber(6) val moveFileRsp: MoveFileRspBody? = null, ) : ProtoBuf @Serializable internal class UploadFileReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val entrance: Int = 0, @JvmField @ProtoNumber(5) val parentFolderId: String = "", @JvmField @ProtoNumber(6) val fileName: String = "", @JvmField @ProtoNumber(7) val localPath: String = "", @JvmField @ProtoNumber(8) val fileSize: Long = 0L, @JvmField @ProtoNumber(9) val sha: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val sha3: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(11) val md5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(15) val boolSupportMultiUpload: Boolean = false, ) : ProtoBuf @Serializable internal class UploadFileRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val uploadIp: String = "", @JvmField @ProtoNumber(5) val serverDns: String = "", @JvmField @ProtoNumber(6) val busId: Int = 0, @JvmField @ProtoNumber(7) val fileId: String = "", @JvmField @ProtoNumber(8) val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(9) val checkKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val boolFileExist: Boolean = false, @JvmField @ProtoNumber(12) val uploadIpLanV4: List<String> = emptyList(), @JvmField @ProtoNumber(13) val uploadIpLanV6: List<String> = emptyList(), @JvmField @ProtoNumber(14) val uploadPort: Int = 0, ) : ProtoBuf, CheckableStruct } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Oidb0x6d7.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.network.protocol.packet.chat.CheckableStruct import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField internal class Oidb0x6d7 : ProtoBuf { @Serializable internal class CreateFolderReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val parentFolderId: String = "", @JvmField @ProtoNumber(4) val folderName: String = "", ) : ProtoBuf @Serializable internal class CreateFolderRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val folderInfo: GroupFileCommon.FolderInfo? = null, ) : ProtoBuf, CheckableStruct @Serializable internal class DeleteFolderReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val folderId: String = "", ) : ProtoBuf @Serializable internal class DeleteFolderRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", ) : ProtoBuf, CheckableStruct @Serializable internal class MoveFolderReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val folderId: String = "", @JvmField @ProtoNumber(4) val parentFolderId: String = "", @JvmField @ProtoNumber(5) val destFolderId: String = "", ) : ProtoBuf @Serializable internal class MoveFolderRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val folderInfo: GroupFileCommon.FolderInfo? = null, ) : ProtoBuf, CheckableStruct @Serializable internal class RenameFolderReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val folderId: String = "", @JvmField @ProtoNumber(4) val newFolderName: String = "", ) : ProtoBuf @Serializable internal class RenameFolderRspBody( @ProtoNumber(1) override val int32RetCode: Int = 0, @ProtoNumber(2) override val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val folderInfo: GroupFileCommon.FolderInfo? = null, ) : ProtoBuf, CheckableStruct @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val createFolderReq: CreateFolderReqBody? = null, @JvmField @ProtoNumber(2) val deleteFolderReq: DeleteFolderReqBody? = null, @JvmField @ProtoNumber(3) val renameFolderReq: RenameFolderReqBody? = null, @JvmField @ProtoNumber(4) val moveFolderReq: MoveFolderReqBody? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val createFolderRsp: CreateFolderRspBody? = null, @JvmField @ProtoNumber(2) val deleteFolderRsp: DeleteFolderRspBody? = null, @JvmField @ProtoNumber(3) val renameFolderRsp: RenameFolderRspBody? = null, @JvmField @ProtoNumber(4) val moveFolderRsp: MoveFolderRspBody? = null, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Oidb0x6d8.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class Oidb0x6d8 : ProtoBuf { @Serializable internal class FileTimeStamp( @JvmField @ProtoNumber(1) val uploadTime: Int = 0, @JvmField @ProtoNumber(2) val fileId: String = "", ) : ProtoBuf @Serializable internal class GetFileCountReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, ) : ProtoBuf @Serializable internal class GetFileCountRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val allFileCount: Int = 0, @JvmField @ProtoNumber(5) val boolFileTooMany: Boolean = false, @JvmField @ProtoNumber(6) val limitCount: Int = 0, @JvmField @ProtoNumber(7) val boolIsFull: Boolean = false, ) : ProtoBuf @Serializable internal class GetFileInfoReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val fileId: String = "", @JvmField @ProtoNumber(5) val fieldFlag: Int = 16777215, ) : ProtoBuf @Serializable internal class GetFileInfoRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val fileInfo: GroupFileCommon.FileInfo? = null, ) : ProtoBuf @Serializable internal class GetFileListReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val folderId: String = "", @JvmField @ProtoNumber(4) val startTimestamp: FileTimeStamp? = null, @JvmField @ProtoNumber(5) val fileCount: Int = 0, @JvmField @ProtoNumber(6) val maxTimestamp: FileTimeStamp? = null, @JvmField @ProtoNumber(7) val allFileCount: Int = 0, @JvmField @ProtoNumber(8) val reqFrom: Int = 0, @JvmField @ProtoNumber(9) val sortBy: Int = 0, @JvmField @ProtoNumber(10) val filterCode: Int = 0, @JvmField @ProtoNumber(11) val uin: Long = 0L, @JvmField @ProtoNumber(12) val fieldFlag: Int = 16777215, @JvmField @ProtoNumber(13) val startIndex: Int = 0, @JvmField @ProtoNumber(14) val context: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(15) val clientVersion: Int = 0, @JvmField @ProtoNumber(16) val whiteList: Int = 0, @JvmField @ProtoNumber(17) val sortOrder: Int = 0, @JvmField @ProtoNumber(18) val showOnlinedocFolder: Int = 0, ) : ProtoBuf @Serializable internal class GetFileListRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val boolIsEnd: Boolean = false, @JvmField @ProtoNumber(5) val itemList: List<Item> = emptyList(), @JvmField @ProtoNumber(6) val msgMaxTimestamp: FileTimeStamp? = null, @JvmField @ProtoNumber(7) val allFileCount: Int = 0, @JvmField @ProtoNumber(8) val filterCode: Int = 0, @JvmField @ProtoNumber(11) val boolSafeCheckFlag: Boolean = false, @JvmField @ProtoNumber(12) val safeCheckRes: Int = 0, @JvmField @ProtoNumber(13) val nextIndex: Int = 0, @JvmField @ProtoNumber(14) val context: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(15) val role: Int = 0, @JvmField @ProtoNumber(16) val openFlag: Int = 0, ) : ProtoBuf { @Serializable internal class Item( @JvmField @ProtoNumber(1) val type: Int = 0, // folder=2, @JvmField @ProtoNumber(2) val folderInfo: GroupFileCommon.FolderInfo? = null, @JvmField @ProtoNumber(3) val fileInfo: GroupFileCommon.FileInfo? = null, ) : ProtoBuf { val id get() = fileInfo?.fileId ?: folderInfo?.folderId val name get() = fileInfo?.fileName ?: folderInfo?.folderName } } @Serializable internal class GetFilePreviewReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val fileId: String = "", ) : ProtoBuf @Serializable internal class GetFilePreviewRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val int32ServerIp: Int = 0, @JvmField @ProtoNumber(5) val int32ServerPort: Int = 0, @JvmField @ProtoNumber(6) val downloadDns: String = "", @JvmField @ProtoNumber(7) val downloadUrl: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val cookieVal: String = "", @JvmField @ProtoNumber(9) val reservedField: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val downloadDnsHttps: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(11) val previewPortHttps: Int = 0, ) : ProtoBuf @Serializable internal class GetSpaceReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, ) : ProtoBuf @Serializable internal class GetSpaceRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val totalSpace: Long = 0L, @JvmField @ProtoNumber(5) val usedSpace: Long = 0L, @JvmField @ProtoNumber(6) val boolAllUpload: Boolean = false, ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val fileInfoReq: GetFileInfoReqBody? = null, @JvmField @ProtoNumber(2) val fileListInfoReq: GetFileListReqBody? = null, @JvmField @ProtoNumber(3) val groupFileCntReq: GetFileCountReqBody? = null, @JvmField @ProtoNumber(4) val groupSpaceReq: GetSpaceReqBody? = null, @JvmField @ProtoNumber(5) val filePreviewReq: GetFilePreviewReqBody? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val fileInfoRsp: GetFileInfoRspBody? = null, @JvmField @ProtoNumber(2) val fileListInfoRsp: GetFileListRspBody? = null, @JvmField @ProtoNumber(3) val groupFileCntRsp: GetFileCountRspBody? = null, @JvmField @ProtoNumber(4) val groupSpaceRsp: GetSpaceRspBody? = null, @JvmField @ProtoNumber(5) val filePreviewRsp: GetFilePreviewRspBody? = null, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Oidb0x6d9.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class Oidb0x6d9 : ProtoBuf { @Serializable internal class CopyFromReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val srcBusId: Int = 0, @JvmField @ProtoNumber(4) val srcParentFolder: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(5) val srcFilePath: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val dstBusId: Int = 0, @JvmField @ProtoNumber(7) val dstFolderId: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val fileSize: Long = 0L, @JvmField @ProtoNumber(9) val localPath: String = "", @JvmField @ProtoNumber(10) val fileName: String = "", @JvmField @ProtoNumber(11) val srcUin: Long = 0L, @JvmField @ProtoNumber(12) val md5: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class CopyFromRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val saveFilePath: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(5) val busId: Int = 0, ) : ProtoBuf @Serializable internal class CopyToReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val srcBusId: Int = 0, @JvmField @ProtoNumber(4) val srcFileId: String = "", @JvmField @ProtoNumber(5) val dstBusId: Int = 0, @JvmField @ProtoNumber(6) val dstUin: Long = 0L, @JvmField @ProtoNumber(40) val newFileName: String = "", @JvmField @ProtoNumber(100) val timCloudPdirKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(101) val timCloudPpdirKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(102) val timCloudExtensionInfo: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(103) val timFileExistOption: Int = 0, ) : ProtoBuf @Serializable internal class CopyToRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val saveFilePath: String = "", @JvmField @ProtoNumber(5) val busId: Int = 0, @JvmField @ProtoNumber(40) val fileName: String = "", ) : ProtoBuf @Serializable internal class FeedsReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val feedsInfoList: List<GroupFileCommon.FeedsInfo> = emptyList(), @JvmField @ProtoNumber(4) val multiSendSeq: Int = 0, ) : ProtoBuf @Serializable internal class FeedsRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val feedsResultList: List<GroupFileCommon.FeedsResult> = emptyList(), @JvmField @ProtoNumber(5) val svrbusyWaitTime: Int = 0, ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val transFileReq: TransFileReqBody? = null, @JvmField @ProtoNumber(2) val copyFromReq: CopyFromReqBody? = null, @JvmField @ProtoNumber(3) val copyToReq: CopyToReqBody? = null, @JvmField @ProtoNumber(5) val feedsInfoReq: FeedsReqBody? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val transFileRsp: TransFileRspBody? = null, @JvmField @ProtoNumber(2) val copyFromRsp: CopyFromRspBody? = null, @JvmField @ProtoNumber(3) val copyToRsp: CopyToRspBody? = null, @JvmField @ProtoNumber(5) val feedsInfoRsp: FeedsRspBody? = null, ) : ProtoBuf @Serializable internal class TransFileReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val appId: Int = 0, @JvmField @ProtoNumber(3) val busId: Int = 0, @JvmField @ProtoNumber(4) val fileId: String = "", ) : ProtoBuf @Serializable internal class TransFileRspBody( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val clientWording: String = "", @JvmField @ProtoNumber(4) val saveBusId: Int = 0, @JvmField @ProtoNumber(5) val saveFilePath: String = "", ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Oidb0x769.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class Oidb0x769 : ProtoBuf { @Serializable internal class Camera( @JvmField @ProtoNumber(1) val primary: Long = 0L, @JvmField @ProtoNumber(2) val secondary: Long = 0L, @JvmField @ProtoNumber(3) val flash: Boolean = false, ) : ProtoBuf @Serializable internal class Config( @JvmField @ProtoNumber(1) val type: Int = 0, @JvmField @ProtoNumber(2) val version: Int = 0, @JvmField @ProtoNumber(3) val contentList: List<String> = emptyList(), @JvmField @ProtoNumber(4) val debugMsg: String = "", @JvmField @ProtoNumber(5) val msgContentList: List<Content> = emptyList(), ) : ProtoBuf @Serializable internal class ConfigSeq( @JvmField @ProtoNumber(1) val type: Int = 0, @JvmField @ProtoNumber(2) val version: Int = 0, ) : ProtoBuf @Serializable internal class Content( @JvmField @ProtoNumber(1) val taskId: Int = 0, @JvmField @ProtoNumber(2) val compress: Int = 0, @JvmField @ProtoNumber(10) val content: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class CPU( @JvmField @ProtoNumber(1) val model: String = "", @JvmField @ProtoNumber(2) val cores: Int = 0, @JvmField @ProtoNumber(3) val frequency: Int = 0, ) : ProtoBuf @Serializable internal class DeviceInfo( @JvmField @ProtoNumber(1) val brand: String = "", @JvmField @ProtoNumber(2) val model: String = "", @JvmField @ProtoNumber(3) val os: OS? = null, @JvmField @ProtoNumber(4) val cpu: CPU? = null, @JvmField @ProtoNumber(5) val memory: Memory? = null, @JvmField @ProtoNumber(6) val storage: Storage? = null, @JvmField @ProtoNumber(7) val screen: Screen? = null, @JvmField @ProtoNumber(8) val camera: Camera? = null, ) : ProtoBuf @Serializable internal class Memory( @JvmField @ProtoNumber(1) val total: Long = 0L, @JvmField @ProtoNumber(2) val process: Long = 0L, ) : ProtoBuf @Serializable internal class OS( //1 IOS | 2 ANDROID | 3 OTHER @JvmField @ProtoNumber(1) val type: Int /* enum */ = 1, @JvmField @ProtoNumber(2) val version: String = "", @JvmField @ProtoNumber(3) val sdk: String = "", @JvmField @ProtoNumber(4) val kernel: String = "", @JvmField @ProtoNumber(5) val rom: String = "", ) : ProtoBuf @Serializable internal class QueryUinPackageUsageReq( @JvmField @ProtoNumber(1) val type: Int = 0, @JvmField @ProtoNumber(2) val uinFileSize: Long = 0L, ) : ProtoBuf @Serializable internal class QueryUinPackageUsageRsp( @JvmField @ProtoNumber(1) val status: Int = 0, @JvmField @ProtoNumber(2) val leftUinNum: Long = 0L, @JvmField @ProtoNumber(3) val maxUinNum: Long = 0L, @JvmField @ProtoNumber(4) val proportion: Int = 0, @JvmField @ProtoNumber(10) val uinPackageUsedList: List<UinPackageUsedInfo> = emptyList(), ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val configList: List<ConfigSeq> = emptyList(), @JvmField @ProtoNumber(2) val msgDeviceInfo: DeviceInfo? = null, @JvmField @ProtoNumber(3) val info: String = "", @JvmField @ProtoNumber(4) val province: String = "", @JvmField @ProtoNumber(5) val city: String = "", @JvmField @ProtoNumber(6) val reqDebugMsg: Int = 0, @JvmField @ProtoNumber(101) val queryUinPackageUsageReq: QueryUinPackageUsageReq? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val result: Int = 0, @JvmField @ProtoNumber(2) val configList: List<Config> = emptyList(), @JvmField @ProtoNumber(101) val queryUinPackageUsageRsp: QueryUinPackageUsageRsp? = null, ) : ProtoBuf @Serializable internal class Screen( @JvmField @ProtoNumber(1) val model: String = "", @JvmField @ProtoNumber(2) val width: Int = 0, @JvmField @ProtoNumber(3) val height: Int = 0, @JvmField @ProtoNumber(4) val dpi: Int = 0, @JvmField @ProtoNumber(5) val multiTouch: Boolean = false, ) : ProtoBuf @Serializable internal class Storage( @JvmField @ProtoNumber(1) val builtin: Long = 0L, @JvmField @ProtoNumber(2) val external: Long = 0L, ) : ProtoBuf @Serializable internal class UinPackageUsedInfo( @JvmField @ProtoNumber(1) val ruleId: Int = 0, @JvmField @ProtoNumber(2) val author: String = "", @JvmField @ProtoNumber(3) val url: String = "", @JvmField @ProtoNumber(4) val uinNum: Long = 0L, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/Oidb0xeac.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class Oidb0xeac : ProtoBuf { @Serializable internal class ArkMsg( @JvmField @ProtoNumber(1) val appName: String = "", @JvmField @ProtoNumber(2) val json: String = "", ) : ProtoBuf @Serializable internal class BatchReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val msgs: List<Oidb0xeac.MsgInfo> = emptyList(), ) : ProtoBuf @Serializable internal class BatchRspBody( @JvmField @ProtoNumber(1) val wording: String = "", @JvmField @ProtoNumber(2) val errorCode: Int = 0, @JvmField @ProtoNumber(3) val succCnt: Int = 0, @JvmField @ProtoNumber(4) val msgProcInfos: List<Oidb0xeac.MsgProcessInfo> = emptyList(), @JvmField @ProtoNumber(5) val digestTime: Int = 0, ) : ProtoBuf @Serializable internal class DigestMsg( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val msgSeq: Int = 0, @JvmField @ProtoNumber(3) val msgRandom: Int = 0, @JvmField @ProtoNumber(4) val msgContent: List<Oidb0xeac.MsgElem> = emptyList(), @JvmField @ProtoNumber(5) val textSize: Long = 0L, @JvmField @ProtoNumber(6) val picSize: Long = 0L, @JvmField @ProtoNumber(7) val videoSize: Long = 0L, @JvmField @ProtoNumber(8) val senderUin: Long = 0L, @JvmField @ProtoNumber(9) val senderTime: Int = 0, @JvmField @ProtoNumber(10) val addDigestUin: Long = 0L, @JvmField @ProtoNumber(11) val addDigestTime: Int = 0, @JvmField @ProtoNumber(12) val startTime: Int = 0, @JvmField @ProtoNumber(13) val latestMsgSeq: Int = 0, @JvmField @ProtoNumber(14) val opType: Int = 0, ) : ProtoBuf @Serializable internal class FaceMsg( @JvmField @ProtoNumber(1) val index: Int = 0, @JvmField @ProtoNumber(2) val text: String = "", ) : ProtoBuf @Serializable internal class GroupFileMsg( @JvmField @ProtoNumber(1) val fileName: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val busId: Int = 0, @JvmField @ProtoNumber(3) val fileId: String = "", @JvmField @ProtoNumber(4) val fileSize: Long = 0L, @JvmField @ProtoNumber(5) val deadTime: Long = 0L, @JvmField @ProtoNumber(6) val fileSha1: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val ext: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ImageMsg( @JvmField @ProtoNumber(1) val md5: String = "", @JvmField @ProtoNumber(2) val uuid: String = "", @JvmField @ProtoNumber(3) val imgType: Int = 0, @JvmField @ProtoNumber(4) val fileSize: Int = 0, @JvmField @ProtoNumber(5) val width: Int = 0, @JvmField @ProtoNumber(6) val height: Int = 0, @JvmField @ProtoNumber(101) val fileId: Int = 0, @JvmField @ProtoNumber(102) val serverIp: Int = 0, @JvmField @ProtoNumber(103) val serverPort: Int = 0, @JvmField @ProtoNumber(104) val filePath: String = "", @JvmField @ProtoNumber(201) val thumbUrl: String = "", @JvmField @ProtoNumber(202) val originalUrl: String = "", @JvmField @ProtoNumber(203) val resaveUrl: String = "", ) : ProtoBuf @Serializable internal class MsgElem( @JvmField @ProtoNumber(1) val msgType: Int = 0, @JvmField @ProtoNumber(11) val textMsg: Oidb0xeac.TextMsg? = null, @JvmField @ProtoNumber(12) val faceMsg: Oidb0xeac.FaceMsg? = null, @JvmField @ProtoNumber(13) val imageMsg: Oidb0xeac.ImageMsg? = null, @JvmField @ProtoNumber(14) val groupFileMsg: Oidb0xeac.GroupFileMsg? = null, @JvmField @ProtoNumber(15) val shareMsg: Oidb0xeac.ShareMsg? = null, @JvmField @ProtoNumber(16) val richMsg: Oidb0xeac.RichMsg? = null, @JvmField @ProtoNumber(17) val arkMsg: Oidb0xeac.ArkMsg? = null, ) : ProtoBuf @Serializable internal class MsgInfo( @JvmField @ProtoNumber(1) val msgSeq: Int = 0, @JvmField @ProtoNumber(2) val msgRandom: Int = 0, ) : ProtoBuf @Serializable internal class MsgProcessInfo( @JvmField @ProtoNumber(1) val msg: Oidb0xeac.MsgInfo? = null, @JvmField @ProtoNumber(2) val errorCode: Int = 0, @JvmField @ProtoNumber(3) val digestUin: Long = 0L, @JvmField @ProtoNumber(4) val digestTime: Int = 0, ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val groupCode: Long = 0L, @JvmField @ProtoNumber(2) val msgSeq: Int = 0, @JvmField @ProtoNumber(3) val msgRandom: Int = 0, ) : ProtoBuf @Serializable internal class RichMsg( @JvmField @ProtoNumber(1) val serviceId: Int = 0, @JvmField @ProtoNumber(2) val xml: String = "", @JvmField @ProtoNumber(3) val longMsgResid: String = "", ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val wording: String = "", @JvmField @ProtoNumber(2) val digestUin: Long = 0L, @JvmField @ProtoNumber(3) val digestTime: Int = 0, @JvmField @ProtoNumber(4) val msg: Oidb0xeac.DigestMsg? = null, @JvmField @ProtoNumber(10) val errorCode: Int = 0, ) : ProtoBuf @Serializable internal class ShareMsg( @JvmField @ProtoNumber(1) val type: String = "", @JvmField @ProtoNumber(2) val title: String = "", @JvmField @ProtoNumber(3) val summary: String = "", @JvmField @ProtoNumber(4) val brief: String = "", @JvmField @ProtoNumber(5) val url: String = "", @JvmField @ProtoNumber(6) val pictureUrl: String = "", @JvmField @ProtoNumber(7) val action: String = "", @JvmField @ProtoNumber(8) val source: String = "", @JvmField @ProtoNumber(9) val sourceUrl: String = "", ) : ProtoBuf @Serializable internal class TextMsg( @JvmField @ProtoNumber(1) val str: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OidbCmd0xb77.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField @Serializable internal class OidbCmd0xb77 : ProtoBuf { @Serializable internal class ArkJsonBody( @JvmField @ProtoNumber(10) val jsonStr: String = "", ) : ProtoBuf @Serializable internal class ArkMsgBody( @JvmField @ProtoNumber(1) val app: String = "", @JvmField @ProtoNumber(2) val view: String = "", @JvmField @ProtoNumber(3) val prompt: String = "", @JvmField @ProtoNumber(4) val ver: String = "", @JvmField @ProtoNumber(5) val desc: String = "", @JvmField @ProtoNumber(6) val featureId: Int = 0, @JvmField @ProtoNumber(10) val meta: String = "", @JvmField @ProtoNumber(11) val metaUrl1: String = "", @JvmField @ProtoNumber(12) val metaUrl2: String = "", @JvmField @ProtoNumber(13) val metaUrl3: String = "", @JvmField @ProtoNumber(14) val metaText1: String = "", @JvmField @ProtoNumber(15) val metaText2: String = "", @JvmField @ProtoNumber(16) val metaText3: String = "", @JvmField @ProtoNumber(20) val config: String = "", ) : ProtoBuf @Serializable internal class ArkV1MsgBody( @JvmField @ProtoNumber(1) val app: String = "", @JvmField @ProtoNumber(2) val view: String = "", @JvmField @ProtoNumber(3) val prompt: String = "", @JvmField @ProtoNumber(4) val ver: String = "", @JvmField @ProtoNumber(5) val desc: String = "", @JvmField @ProtoNumber(6) val featureId: Int = 0, @JvmField @ProtoNumber(10) val meta: String = "", @JvmField @ProtoNumber(11) val items: List<TemplateItem> = emptyList(), @JvmField @ProtoNumber(20) val config: String = "", ) : ProtoBuf @Serializable internal class ClientInfo( @JvmField @ProtoNumber(1) val platform: Int = 0, @JvmField @ProtoNumber(2) val sdkVersion: String = "", @JvmField @ProtoNumber(3) val androidPackageName: String = "", @JvmField @ProtoNumber(4) val androidSignature: String = "", @JvmField @ProtoNumber(5) val iosBundleId: String = "", @JvmField @ProtoNumber(6) val pcSign: String = "", ) : ProtoBuf @Serializable internal class ImageInfo( @JvmField @ProtoNumber(1) val md5: String = "", @JvmField @ProtoNumber(2) val uuid: String = "", @JvmField @ProtoNumber(3) val imgType: Int = 0, @JvmField @ProtoNumber(4) val fileSize: Int = 0, @JvmField @ProtoNumber(5) val width: Int = 0, @JvmField @ProtoNumber(6) val height: Int = 0, @JvmField @ProtoNumber(7) val original: Int = 0, @JvmField @ProtoNumber(101) val fileId: Int = 0, @JvmField @ProtoNumber(102) val serverIp: Int = 0, @JvmField @ProtoNumber(103) val serverPort: Int = 0, ) : ProtoBuf @Serializable internal class MiniAppMsgBody( @JvmField @ProtoNumber(1) val miniAppAppid: Long = 0L, @JvmField @ProtoNumber(2) val miniAppPath: String = "", @JvmField @ProtoNumber(3) val webPageUrl: String = "", @JvmField @ProtoNumber(4) val miniAppType: Int = 0, @JvmField @ProtoNumber(5) val title: String = "", @JvmField @ProtoNumber(6) val desc: String = "", @JvmField @ProtoNumber(10) val jsonStr: String = "", ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val appid: Long = 0L, @JvmField @ProtoNumber(2) val appType: Int = 0, @JvmField @ProtoNumber(3) val msgStyle: Int = 0, @JvmField @ProtoNumber(4) val senderUin: Long = 0L, @JvmField @ProtoNumber(5) val clientInfo: ClientInfo? = null, // @JvmField @ProtoNumber(6) val textMsg: String? = null, @JvmField @ProtoNumber(7) val extInfo: ExtInfo? = null, @JvmField @ProtoNumber(10) val sendType: Int = 0, @JvmField @ProtoNumber(11) val recvUin: Long = 0L, @JvmField @ProtoNumber(12) val richMsgBody: RichMsgBody? = null, @JvmField @ProtoNumber(13) val arkMsgBody: ArkMsgBody? = null, // @JvmField @ProtoNumber(14) val recvOpenid: String? = null, // don't be "" @JvmField @ProtoNumber(15) val arkv1MsgBody: ArkV1MsgBody? = null, @JvmField @ProtoNumber(16) val arkJsonBody: ArkJsonBody? = null, @JvmField @ProtoNumber(17) val xmlMsgBody: XmlMsgBody? = null, @JvmField @ProtoNumber(18) val miniAppMsgBody: MiniAppMsgBody? = null, ) : ProtoBuf @Serializable internal class ExtInfo( @ProtoNumber(1) @JvmField val customFeatureId: List<Int> = emptyList(), @ProtoNumber(2) @JvmField val apnsWording: String = "", @ProtoNumber(3) @JvmField val groupSaveDbFlag: Int = 0, @ProtoNumber(4) @JvmField val receiverAppId: Int = 0, @ProtoNumber(5) @JvmField val msgSeq: Long = 0L, ) : ProtoBuf @Serializable internal class RichMsgBody( @JvmField @ProtoNumber(1) val usingArk: Boolean = false, @JvmField @ProtoNumber(10) val title: String = "", @JvmField @ProtoNumber(11) val summary: String = "", @JvmField @ProtoNumber(12) val brief: String = "", @JvmField @ProtoNumber(13) val url: String = "", @JvmField @ProtoNumber(14) val pictureUrl: String = "", @JvmField @ProtoNumber(15) val action: String = "", @JvmField @ProtoNumber(16) val musicUrl: String = "", @JvmField @ProtoNumber(21) val imageInfo: ImageInfo? = null, ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val wording: String = "", @JvmField @ProtoNumber(2) val jumpResult: Int = 0, @JvmField @ProtoNumber(3) val jumpUrl: String = "", @JvmField @ProtoNumber(4) val level: Int = 0, @JvmField @ProtoNumber(5) val subLevel: Int = 0, @JvmField @ProtoNumber(6) val developMsg: String = "", ) : ProtoBuf, Packet @Serializable internal class TemplateItem( @JvmField @ProtoNumber(1) val key: String = "", @JvmField @ProtoNumber(2) val type: Int = 0, @JvmField @ProtoNumber(3) val value: String = "", ) : ProtoBuf @Serializable internal class XmlMsgBody( @JvmField @ProtoNumber(11) val serviceId: Int = 0, @JvmField @ProtoNumber(12) val xml: String = "", ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/OnlinePush.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class MsgOnlinePush { @Serializable internal class PbPushMsg( @ProtoNumber(1) @JvmField val msg: MsgComm.Msg, @ProtoNumber(2) @JvmField val svrip: Int = 0, @ProtoNumber(3) @JvmField val pushToken: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val pingFlag: Int = 0, @ProtoNumber(9) @JvmField val generalFlag: Int = 0, ) : ProtoBuf } @Serializable internal class OnlinePushTrans : ProtoBuf { @Serializable internal class ExtGroupKeyInfo( @ProtoNumber(1) @JvmField val curMaxSeq: Int = 0, @ProtoNumber(2) @JvmField val curTime: Long = 0L, ) : ProtoBuf @Serializable internal class PbMsgInfo( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, @ProtoNumber(3) @JvmField val msgType: Int = 0, @ProtoNumber(4) @JvmField val msgSubtype: Int = 0, @ProtoNumber(5) @JvmField val msgSeq: Int = 0, @ProtoNumber(6) @JvmField val msgUid: Long = 0L, @ProtoNumber(7) @JvmField val msgTime: Int = 0, @ProtoNumber(8) @JvmField val realMsgTime: Int = 0, @ProtoNumber(9) @JvmField val nickName: String = "", @ProtoNumber(10) @JvmField val msgData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val svrIp: Int = 0, @ProtoNumber(12) @JvmField val extGroupKeyInfo: ExtGroupKeyInfo? = null, @ProtoNumber(17) @JvmField val generalFlag: Int = 0, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/PbReserve.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class NotOnlineImage { @Serializable internal class ResvAttr( @JvmField @ProtoNumber(1) val imageBizType: Int = 0, @JvmField @ProtoNumber(2) val customfaceType: Int = 0, @JvmField @ProtoNumber(3) val emojiPackageid: Int = 0, @JvmField @ProtoNumber(4) val emojiId: Int = 0, @JvmField @ProtoNumber(5) val text: String = "", @JvmField @ProtoNumber(6) val doutuSuppliers: String = "", @JvmField @ProtoNumber(8) val textSummary: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val emojiFrom: Int = 0, @JvmField @ProtoNumber(11) val emojiSource: String = "", @JvmField @ProtoNumber(12) val emojiWebUrl: String = "", @JvmField @ProtoNumber(13) val emojiIconUrl: String = "", @JvmField @ProtoNumber(14) val emojiMarketFaceName: String = "", @JvmField @ProtoNumber(15) val source: Int = 0, @JvmField @ProtoNumber(16) val cameraCaptureTemplateinfo: String = "", @JvmField @ProtoNumber(17) val cameraCaptureMaterialname: String = "", @JvmField @ProtoNumber(18) val adEmoJumpUrl: String = "", @JvmField @ProtoNumber(19) val adEmoDescStr: String = "", ) : ProtoBuf } internal class CustomFace { @Serializable internal class AnimationImageShow( @JvmField @ProtoNumber(1) val int32EffectId: Int = 0, @JvmField @ProtoNumber(2) val animationParam: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ResvAttr( @JvmField @ProtoNumber(1) val imageBizType: Int = 0, @JvmField @ProtoNumber(2) val customfaceType: Int = 0, @JvmField @ProtoNumber(3) val emojiPackageid: Int = 0, @JvmField @ProtoNumber(4) val emojiId: Int = 0, @JvmField @ProtoNumber(5) val text: String = "", @JvmField @ProtoNumber(6) val doutuSuppliers: String = "", @JvmField @ProtoNumber(7) val msgImageShow: AnimationImageShow? = null, @JvmField @ProtoNumber(9) val textSummary: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val emojiFrom: Int = 0, @JvmField @ProtoNumber(11) val emojiSource: String = "", @JvmField @ProtoNumber(12) val emojiWebUrl: String = "", @JvmField @ProtoNumber(13) val emojiIconUrl: String = "", @JvmField @ProtoNumber(14) val emojiMarketFaceName: String = "", @JvmField @ProtoNumber(15) val source: Int = 0, @JvmField @ProtoNumber(16) val cameraCaptureTemplateinfo: String = "", @JvmField @ProtoNumber(17) val cameraCaptureMaterialname: String = "", @JvmField @ProtoNumber(18) val adEmoJumpUrl: String = "", @JvmField @ProtoNumber(19) val adEmoDescStr: String = "", ) : ProtoBuf } internal class Generalflags : ProtoBuf { @Serializable internal class ResvAttr( @ProtoNumber(1) @JvmField val globalGroupLevel: Int = 0, @ProtoNumber(2) @JvmField val nearbyCharmLevel: Int = 0, @ProtoNumber(3) @JvmField val redbagMsgSenderUin: Long = 0L, @ProtoNumber(4) @JvmField val titleId: Int = 0, @ProtoNumber(5) @JvmField val robotMsgFlag: Int = 0, @ProtoNumber(6) @JvmField val wantGiftSenderUin: Long = 0L, @ProtoNumber(7) @JvmField val stickerX: Float = 0.0f, @ProtoNumber(8) @JvmField val stickerY: Float = 0.0f, @ProtoNumber(9) @JvmField val stickerWidth: Float = 0.0f, @ProtoNumber(10) @JvmField val stickerHeight: Float = 0.0f, @ProtoNumber(11) @JvmField val stickerRotate: Int = 0, @ProtoNumber(12) @JvmField val stickerHostMsgseq: Long = 0L, @ProtoNumber(13) @JvmField val stickerHostMsguid: Long = 0L, @ProtoNumber(14) @JvmField val stickerHostTime: Long = 0L, @ProtoNumber(15) @JvmField val mobileCustomFont: Int = 0, @ProtoNumber(16) @JvmField val tailKey: Int = 0, @ProtoNumber(17) @JvmField val showTailFlag: Int = 0, @ProtoNumber(18) @JvmField val doutuMsgType: Int = 0, @ProtoNumber(19) @JvmField val doutuCombo: Int = 0, @ProtoNumber(20) @JvmField val customFeatureid: Int = 0, @ProtoNumber(21) @JvmField val goldenMsgType: Int = 0, @ProtoNumber(22) @JvmField val goldenMsgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(23) @JvmField val botMessageClassId: Int = 0, @ProtoNumber(24) @JvmField val subscriptionUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(25) @JvmField val pendantDiyId: Int = 0, @ProtoNumber(26) @JvmField val timedMessage: Int = 0, @ProtoNumber(27) @JvmField val holidayFlag: Int = 0, @ProtoNumber(29) @JvmField val kplInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(30) @JvmField val faceId: Int = 0, @ProtoNumber(31) @JvmField val diyFontTimestamp: Int = 0, @ProtoNumber(32) @JvmField val redEnvelopeType: Int = 0, @ProtoNumber(33) @JvmField val shortVideoId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(34) @JvmField val reqFontEffectId: Int = 0, @ProtoNumber(35) @JvmField val loveLanguageFlag: Int = 0, @ProtoNumber(36) @JvmField val aioSyncToStoryFlag: Int = 0, @ProtoNumber(37) @JvmField val uploadImageToQzoneFlag: Int = 0, @ProtoNumber(39) @JvmField val uploadImageToQzoneParam: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(40) @JvmField val groupConfessSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(41) @JvmField val subfontId: Long = 0L, @ProtoNumber(42) @JvmField val msgFlagType: Int = 0, @ProtoNumber(43) @JvmField val uint32CustomFeatureid: List<Int> = emptyList(), @ProtoNumber(44) @JvmField val richCardNameVer: Int = 0, @ProtoNumber(47) @JvmField val msgInfoFlag: Int = 0, @ProtoNumber(48) @JvmField val serviceMsgType: Int = 0, @ProtoNumber(49) @JvmField val serviceMsgRemindType: Int = 0, @ProtoNumber(50) @JvmField val serviceMsgName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(51) @JvmField val vipType: Int = 0, @ProtoNumber(52) @JvmField val vipLevel: Int = 0, @ProtoNumber(53) @JvmField val pbPttWaveform: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(54) @JvmField val userBigclubLevel: Int = 0, @ProtoNumber(55) @JvmField val userBigclubFlag: Int = 0, @ProtoNumber(56) @JvmField val nameplate: Int = 0, @ProtoNumber(57) @JvmField val autoReply: Int = 0, @ProtoNumber(58) @JvmField val reqIsBigclubHidden: Int = 0, @ProtoNumber(59) @JvmField val showInMsgList: Int = 0, @ProtoNumber(60) @JvmField val oacMsgExtend: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(61) @JvmField val groupMemberFlagEx2: Int = 0, @ProtoNumber(62) @JvmField val groupRingtoneId: Int = 0, @ProtoNumber(63) @JvmField val robotGeneralTrans: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(64) @JvmField val troopPobingTemplate: Int = 0, @ProtoNumber(65) @JvmField val hudongMark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(66) @JvmField val groupInfoFlagEx3: Int = 0, ) : ProtoBuf } @Serializable internal class ResvAttrForGiftMsg : ProtoBuf { @Serializable internal class ActivityGiftInfo( @ProtoNumber(1) @JvmField val isActivityGift: Int = 0, @ProtoNumber(2) @JvmField val textColor: String = "", @ProtoNumber(3) @JvmField val text: String = "", @ProtoNumber(4) @JvmField val url: String = "", ) : ProtoBuf @Serializable internal class InteractGift( @ProtoNumber(1) @JvmField val interactId: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ResvAttr( @ProtoNumber(1) @JvmField val int32SendScore: Int = 0, @ProtoNumber(2) @JvmField val int32RecvScore: Int = 0, @ProtoNumber(3) @JvmField val charmHeroism: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val buttonFlag: Int = 0, @ProtoNumber(5) @JvmField val objColor: Int = 0, @ProtoNumber(6) @JvmField val animationType: Int = 0, @ProtoNumber(7) @JvmField val msgInteractGift: InteractGift? = null, @ProtoNumber(8) @JvmField val activityGiftInfo: ActivityGiftInfo? = null, ) : ProtoBuf } @Serializable internal class SourceMsg : ProtoBuf { @Serializable internal class ResvAttr( @ProtoNumber(1) @JvmField val richMsg2: ByteArray? = null, @ProtoNumber(2) @JvmField val oriMsgtype: Int? = null, @ProtoNumber(3) @JvmField val origUids: List<Long>? = null, ) : ProtoBuf } @Serializable internal class VideoFile : ProtoBuf { @Serializable internal class ResvAttr( @ProtoNumber(1) @JvmField val hotvideoIcon: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val hotvideoTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val hotvideoUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val hotvideoIconSub: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val specialVideoType: Int = 0, @ProtoNumber(6) @JvmField val dynamicText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val msgTailType: Int = 0, @ProtoNumber(8) @JvmField val redEnvelopeType: Int = 0, @ProtoNumber(9) @JvmField val shortVideoId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val animojiModelId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val longVideoKandianType: Int = 0, @ProtoNumber(12) @JvmField val source: Int = 0, ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/PttShortVideo.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY @Serializable internal class PttShortVideo : ProtoBuf { @Serializable internal class ServerListInfo( @JvmField @ProtoNumber(1) val upIp: Int = 0, @JvmField @ProtoNumber(2) val upPort: Int = 0 ) : ProtoBuf @Serializable internal class CodecConfigReq( @JvmField @ProtoNumber(1) val platformChipinfo: String = "", @JvmField @ProtoNumber(2) val osVersion: String = "", @JvmField @ProtoNumber(3) val deviceName: String = "" ) : ProtoBuf @Serializable internal class DataHole( @JvmField @ProtoNumber(1) val begin: Long = 0L, @JvmField @ProtoNumber(2) val end: Long = 0L ) : ProtoBuf @Serializable internal class ExtensionReq( @JvmField @ProtoNumber(1) val subBusiType: Int = 0, @JvmField @ProtoNumber(2) val userCnt: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoAddr( @JvmField @ProtoNumber(1) val hostType: Int = 0, @JvmField @ProtoNumber(10) val strHost: List<String> = emptyList(), @JvmField @ProtoNumber(11) val urlArgs: String = "", @JvmField @ProtoNumber(21) val strHostIpv6: List<String> = emptyList(), @JvmField @ProtoNumber(22) val strDomain: List<String> = emptyList() ) : ProtoBuf @Serializable internal class PttShortVideoDeleteReq( @JvmField @ProtoNumber(1) val fromuin: Long = 0L, @JvmField @ProtoNumber(2) val touin: Long = 0L, @JvmField @ProtoNumber(3) val chatType: Int = 0, @JvmField @ProtoNumber(4) val clientType: Int = 0, @JvmField @ProtoNumber(5) val fileid: String = "", @JvmField @ProtoNumber(6) val groupCode: Long = 0L, @JvmField @ProtoNumber(7) val agentType: Int = 0, @JvmField @ProtoNumber(8) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(9) val businessType: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoDeleteResp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "" ) : ProtoBuf @Serializable internal class PttShortVideoDownloadReq( @JvmField @ProtoNumber(1) val fromuin: Long = 0L, @JvmField @ProtoNumber(2) val touin: Long = 0L, @JvmField @ProtoNumber(3) val chatType: Int = 0, @JvmField @ProtoNumber(4) val clientType: Int = 0, @JvmField @ProtoNumber(5) val fileid: String = "", @JvmField @ProtoNumber(6) val groupCode: Long = 0L, @JvmField @ProtoNumber(7) val agentType: Int = 0, @JvmField @ProtoNumber(8) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(9) val businessType: Int = 0, @JvmField @ProtoNumber(10) val fileType: Int = 0, @JvmField @ProtoNumber(11) val downType: Int = 0, @JvmField @ProtoNumber(12) val sceneType: Int = 0, @JvmField @ProtoNumber(13) val needInnerAddr: Int = 0, @JvmField @ProtoNumber(14) val reqTransferType: Int = 0, @JvmField @ProtoNumber(15) val reqHostType: Int = 0, @JvmField @ProtoNumber(20) val flagSupportLargeSize: Int = 0, @JvmField @ProtoNumber(30) val flagClientQuicProtoEnable: Int = 0, @JvmField @ProtoNumber(31) val targetCodecFormat: Int = 0, @JvmField @ProtoNumber(32) val msgCodecConfig: CodecConfigReq? = null, @JvmField @ProtoNumber(33) val sourceCodecFormat: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoDownloadResp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val sameAreaOutAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(4) val diffAreaOutAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(5) val downloadkey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val sameAreaInnerAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(8) val diffAreaInnerAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(9) val msgDownloadAddr: PttShortVideoAddr? = null, @JvmField @ProtoNumber(10) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(30) val flagServerQuicProtoEnable: Int = 0, @JvmField @ProtoNumber(31) val serverQuicPara: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(32) val codecFormat: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoFileInfo( @JvmField @ProtoNumber(1) val fileName: String = "", @JvmField @ProtoNumber(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val thumbFileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(4) val fileSize: Long = 0L, @JvmField @ProtoNumber(5) val fileResLength: Int = 0, @JvmField @ProtoNumber(6) val fileResWidth: Int = 0, @JvmField @ProtoNumber(7) val fileFormat: Int = 0, @JvmField @ProtoNumber(8) val fileTime: Int = 0, @JvmField @ProtoNumber(9) val thumbFileSize: Long = 0L, @JvmField @ProtoNumber(10) val decryptVideoMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(11) val decryptFileSize: Long = 0L, @JvmField @ProtoNumber(12) val decryptThumbMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(13) val decryptThumbSize: Long = 0L, @JvmField @ProtoNumber(14) val extend: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class PttShortVideoFileInfoExtend( @JvmField @ProtoNumber(1) val bitRate: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoIpList( @JvmField @ProtoNumber(1) val ip: Int = 0, @JvmField @ProtoNumber(2) val port: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoRetweetReq( @JvmField @ProtoNumber(1) val fromUin: Long = 0L, @JvmField @ProtoNumber(2) val toUin: Long = 0L, @JvmField @ProtoNumber(3) val fromChatType: Int = 0, @JvmField @ProtoNumber(4) val toChatType: Int = 0, @JvmField @ProtoNumber(5) val fromBusiType: Int = 0, @JvmField @ProtoNumber(6) val toBusiType: Int = 0, @JvmField @ProtoNumber(7) val clientType: Int = 0, @JvmField @ProtoNumber(8) val msgPttShortVideoFileInfo: PttShortVideoFileInfo? = null, @JvmField @ProtoNumber(9) val agentType: Int = 0, @JvmField @ProtoNumber(10) val fileid: String = "", @JvmField @ProtoNumber(11) val groupCode: Long = 0L, @JvmField @ProtoNumber(20) val flagSupportLargeSize: Int = 0, @JvmField @ProtoNumber(21) val codecFormat: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoRetweetResp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val sameAreaOutAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(4) val diffAreaOutAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(5) val fileid: String = "", @JvmField @ProtoNumber(6) val ukey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val fileExist: Int = 0, @JvmField @ProtoNumber(8) val sameAreaInnerAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(9) val diffAreaInnerAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(10) val dataHole: List<DataHole> = emptyList(), @JvmField @ProtoNumber(11) val isHotFile: Int = 0, @JvmField @ProtoNumber(12) val longVideoCarryWatchPointType: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoUploadReq( @JvmField @ProtoNumber(1) val fromuin: Long = 0L, @JvmField @ProtoNumber(2) val touin: Long = 0L, @JvmField @ProtoNumber(3) val chatType: Int = 0, @JvmField @ProtoNumber(4) val clientType: Int = 0, @JvmField @ProtoNumber(5) val msgPttShortVideoFileInfo: PttShortVideoFileInfo? = null, @JvmField @ProtoNumber(6) val groupCode: Long = 0L, @JvmField @ProtoNumber(7) val agentType: Int = 0, @JvmField @ProtoNumber(8) val businessType: Int = 0, @JvmField @ProtoNumber(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val subBusinessType: Int = 0, @JvmField @ProtoNumber(20) val flagSupportLargeSize: Int = 0, @JvmField @ProtoNumber(21) val codecFormat: Int = 0 ) : ProtoBuf @Serializable internal class PttShortVideoUploadResp( @JvmField @ProtoNumber(1) val int32RetCode: Int = 0, @JvmField @ProtoNumber(2) val retMsg: String = "", @JvmField @ProtoNumber(3) val sameAreaOutAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(4) val diffAreaOutAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(5) val fileid: String = "", @JvmField @ProtoNumber(6) val ukey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val fileExist: Int = 0, @JvmField @ProtoNumber(8) val sameAreaInnerAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(9) val diffAreaInnerAddr: List<PttShortVideoIpList> = emptyList(), @JvmField @ProtoNumber(10) val dataHole: List<DataHole> = emptyList(), @JvmField @ProtoNumber(11) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(12) val isHotFile: Int = 0, @JvmField @ProtoNumber(13) val longVideoCarryWatchPointType: Int = 0 ) : ProtoBuf @Serializable internal class QuicParameter( @JvmField @ProtoNumber(1) val enableQuic: Int = 0, @JvmField @ProtoNumber(2) val encryptionVer: Int = 1, @JvmField @ProtoNumber(3) val fecVer: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val seq: Int = 0, @JvmField @ProtoNumber(3) val msgPttShortVideoUploadReq: PttShortVideoUploadReq? = null, @JvmField @ProtoNumber(4) val msgPttShortVideoDownloadReq: PttShortVideoDownloadReq? = null, @JvmField @ProtoNumber(5) val msgShortVideoRetweetReq: List<PttShortVideoRetweetReq> = emptyList(), @JvmField @ProtoNumber(6) val msgShortVideoDeleteReq: List<PttShortVideoDeleteReq> = emptyList(), @JvmField @ProtoNumber(100) val msgExtensionReq: List<ExtensionReq> = emptyList() ) : ProtoBuf @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val seq: Int = 0, @JvmField @ProtoNumber(3) val msgPttShortVideoUploadResp: PttShortVideoUploadResp? = null, @JvmField @ProtoNumber(4) val msgPttShortVideoDownloadResp: PttShortVideoDownloadResp? = null, @JvmField @ProtoNumber(5) val msgShortVideoRetweetResp: List<PttShortVideoRetweetResp> = emptyList(), @JvmField @ProtoNumber(6) val msgShortVideoDeleteResp: List<PttShortVideoDeleteResp> = emptyList(), @JvmField @ProtoNumber(100) val changeChannel: Int = 0, @JvmField @ProtoNumber(101) val allowRetry: Int = 0 ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/SSOReserveField.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY internal class SSOReserveField { @Serializable internal class ReserveFields( @ProtoNumber(8) @JvmField val clientIpcookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val flag: Int = 0, @ProtoNumber(10) @JvmField val envId: Int = 0, @ProtoNumber(11) @JvmField val localeId: Int = 2052, @ProtoNumber(12) @JvmField val qimei: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val env: String = "", @ProtoNumber(14) @JvmField val newconnFlag: Int = 0, @ProtoNumber(15) @JvmField val traceParent: String = "", @ProtoNumber(16) @JvmField val uid: String = "", @ProtoNumber(18) @JvmField val imsi: Int = 0, @ProtoNumber(19) @JvmField val networkType: Int = 0, @ProtoNumber(20) @JvmField val ipStackType: Int = 0, @ProtoNumber(21) @JvmField val messageType: Int = 0, @ProtoNumber(22) @JvmField val trpcRsp: SsoTrpcResponse? = null, @ProtoNumber(23) @JvmField val transInfo: List<SsoMapEntry> = emptyList(), @ProtoNumber(24) @JvmField val secInfo: SsoSecureInfo? = null, @ProtoNumber(25) @JvmField val secSigFlag: Int = 0, @ProtoNumber(26) @JvmField val ntCoreVersion: Int = 0, @ProtoNumber(27) @JvmField val ssoRouteCost: Int = 0, @ProtoNumber(28) @JvmField val ssoIpOrigin: Int = 0, @ProtoNumber(30) @JvmField val presureToken: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class SsoMapEntry( @ProtoNumber(1) @JvmField val key: String = "", @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class SsoSecureInfo( @ProtoNumber(1) @JvmField val secSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val secDeviceToken: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val secExtra: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class SsoTrpcResponse( @ProtoNumber(1) @JvmField val ret: Int = 0, @ProtoNumber(2) @JvmField val funcRet: Int = 0, @ProtoNumber(3) @JvmField val errorMsg: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/StatSvcGetOnline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField internal class StatSvcGetOnline { @Serializable internal class Instance( @ProtoNumber(1) @JvmField val instanceId: Int = 0, @ProtoNumber(2) @JvmField val clientType: Int = 0 ) : ProtoBuf @Serializable internal class ReqBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val appid: Int = 0 ) : ProtoBuf @Serializable internal class RspBody( @ProtoNumber(1) @JvmField val errorCode: Int = 0, @ProtoNumber(2) @JvmField val errorMsg: String = "", @ProtoNumber(3) @JvmField val uin: Long = 0L, @ProtoNumber(4) @JvmField val appid: Int = 0, @ProtoNumber(5) @JvmField val timeInterval: Int = 0, @ProtoNumber(6) @JvmField val msgInstances: List<StatSvcGetOnline.Instance> = emptyList() ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/StatSvcSimpleGet.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField internal class StatSvcSimpleGet { @Serializable internal class RspBody( @JvmField @ProtoNumber(1) val errorCode: Int = 0, @JvmField @ProtoNumber(2) val errmsg: String = "", @JvmField @ProtoNumber(3) val helloInterval: Int = 0, @JvmField @ProtoNumber(4) val clientip: String = "", @JvmField @ProtoNumber(5) val clientBatteyGetInterval: Int = 0 ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/StructMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField internal class QPayReminderMsg : ProtoBuf { @Serializable internal class GetInfoReq( @ProtoNumber(1) @JvmField val scene: String = "", @ProtoNumber(2) @JvmField val subCmd: String = "", @ProtoNumber(3) @JvmField val infoDate: String = "", ) : ProtoBuf @Serializable internal class GetInfoRsp( @ProtoNumber(1) @JvmField val resultCode: Int = 0, @ProtoNumber(2) @JvmField val resultInfo: String = "", @ProtoNumber(3) @JvmField val urgency: Int = 0, @ProtoNumber(4) @JvmField val templateNo: Int = 0, @ProtoNumber(5) @JvmField val content: String = "", @ProtoNumber(6) @JvmField val infoDate: String = "", ) : ProtoBuf } internal class Structmsg : ProtoBuf { @Serializable internal class AddFrdSNInfo( @ProtoNumber(1) @JvmField val notSeeDynamic: Int = 0, @ProtoNumber(2) @JvmField val setSn: Int = 0, ) : ProtoBuf @Serializable internal class FlagInfo( @ProtoNumber(1) @JvmField val grpMsgKickAdmin: Int = 0, @ProtoNumber(2) @JvmField val grpMsgHiddenGrp: Int = 0, @ProtoNumber(3) @JvmField val grpMsgWordingDown: Int = 0, @ProtoNumber(4) @JvmField val frdMsgGetBusiCard: Int = 0, @ProtoNumber(5) @JvmField val grpMsgGetOfficialAccount: Int = 0, @ProtoNumber(6) @JvmField val grpMsgGetPayInGroup: Int = 0, @ProtoNumber(7) @JvmField val frdMsgDiscuss2ManyChat: Int = 0, @ProtoNumber(8) @JvmField val grpMsgNotAllowJoinGrpInviteNotFrd: Int = 0, @ProtoNumber(9) @JvmField val frdMsgNeedWaitingMsg: Int = 0, @ProtoNumber(10) @JvmField val frdMsgUint32NeedAllUnreadMsg: Int = 0, @ProtoNumber(11) @JvmField val grpMsgNeedAutoAdminWording: Int = 0, @ProtoNumber(12) @JvmField val grpMsgGetTransferGroupMsgFlag: Int = 0, @ProtoNumber(13) @JvmField val grpMsgGetQuitPayGroupMsgFlag: Int = 0, @ProtoNumber(14) @JvmField val grpMsgSupportInviteAutoJoin: Int = 0, @ProtoNumber(15) @JvmField val grpMsgMaskInviteAutoJoin: Int = 0, @ProtoNumber(16) @JvmField val grpMsgGetDisbandedByAdmin: Int = 0, @ProtoNumber(17) @JvmField val grpMsgGetC2cInviteJoinGroup: Int = 0, ) : ProtoBuf @Serializable internal class FriendInfo( @ProtoNumber(1) @JvmField val msgJointFriend: String = "", @ProtoNumber(2) @JvmField val msgBlacklist: String = "", ) : ProtoBuf @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val groupAuthType: Int = 0, @ProtoNumber(2) @JvmField val displayAction: Int = 0, @ProtoNumber(3) @JvmField val msgAlert: String = "", @ProtoNumber(4) @JvmField val msgDetailAlert: String = "", @ProtoNumber(5) @JvmField val msgOtherAdminDone: String = "", @ProtoNumber(6) @JvmField val appPrivilegeFlag: Int = 0, ) : ProtoBuf @Serializable internal class MsgInviteExt( @ProtoNumber(1) @JvmField val srcType: Int = 0, @ProtoNumber(2) @JvmField val srcCode: Long = 0L, @ProtoNumber(3) @JvmField val waitState: Int = 0, ) : ProtoBuf @Serializable internal class MsgPayGroupExt( @ProtoNumber(1) @JvmField val joinGrpTime: Long = 0L, @ProtoNumber(2) @JvmField val quitGrpTime: Long = 0L, ) : ProtoBuf @Serializable internal class ReqNextSystemMsg( @ProtoNumber(1) @JvmField val msgNum: Int = 0, @ProtoNumber(2) @JvmField val followingFriendSeq: Long = 0L, @ProtoNumber(3) @JvmField val followingGroupSeq: Long = 0L, @ProtoNumber(4) @JvmField val checktype: Int /* enum */ = 1, @ProtoNumber(5) @JvmField val flag: FlagInfo? = null, @ProtoNumber(6) @JvmField val language: Int = 0, @ProtoNumber(7) @JvmField val version: Int = 0, @ProtoNumber(8) @JvmField val friendMsgTypeFlag: Long = 0L, ) : ProtoBuf @Serializable internal class ReqSystemMsg( @ProtoNumber(1) @JvmField val msgNum: Int = 0, @ProtoNumber(2) @JvmField val latestFriendSeq: Long = 0L, @ProtoNumber(3) @JvmField val latestGroupSeq: Long = 0L, @ProtoNumber(4) @JvmField val version: Int = 0, @ProtoNumber(5) @JvmField val language: Int = 0, ) : ProtoBuf @Serializable internal class ReqSystemMsgAction( @ProtoNumber(1) @JvmField val msgType: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val msgSeq: Long = 0L, @ProtoNumber(3) @JvmField val reqUin: Long = 0L, @ProtoNumber(4) @JvmField val subType: Int = 0, @ProtoNumber(5) @JvmField val srcId: Int = 0, @ProtoNumber(6) @JvmField val subSrcId: Int = 0, @ProtoNumber(7) @JvmField val groupMsgType: Int = 0, @ProtoNumber(8) @JvmField val actionInfo: SystemMsgActionInfo? = null, @ProtoNumber(9) @JvmField val language: Int = 0, ) : ProtoBuf @Serializable internal class ReqSystemMsgNew( @ProtoNumber(1) @JvmField val msgNum: Int = 0, @ProtoNumber(2) @JvmField val latestFriendSeq: Long = 0L, @ProtoNumber(3) @JvmField val latestGroupSeq: Long = 0L, @ProtoNumber(4) @JvmField val version: Int = 0, @ProtoNumber(5) @JvmField val checktype: Int /* enum */ = 1, @ProtoNumber(6) @JvmField val flag: FlagInfo? = null, @ProtoNumber(7) @JvmField val language: Int = 0, @ProtoNumber(8) @JvmField val isGetFrdRibbon: Boolean = true, @ProtoNumber(9) @JvmField val isGetGrpRibbon: Boolean = true, @ProtoNumber(10) @JvmField val friendMsgTypeFlag: Long = 0L, ) : ProtoBuf @Serializable internal class ReqSystemMsgRead( @ProtoNumber(1) @JvmField val latestFriendSeq: Long = 0L, @ProtoNumber(2) @JvmField val latestGroupSeq: Long = 0L, @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val checktype: Int /* enum */ = 1, ) : ProtoBuf @Serializable internal class RspHead( @ProtoNumber(1) @JvmField val result: Int = 0, @ProtoNumber(2) @JvmField val msgFail: String = "", ) : ProtoBuf @Serializable internal class RspNextSystemMsg( @ProtoNumber(1) @JvmField val head: RspHead? = null, @ProtoNumber(2) @JvmField val msgs: List<StructMsg> = emptyList(), @ProtoNumber(3) @JvmField val followingFriendSeq: Long = 0L, @ProtoNumber(4) @JvmField val followingGroupSeq: Long = 0L, @ProtoNumber(5) @JvmField val checktype: Int /* enum */ = 1, @ProtoNumber(100) @JvmField val gameNick: String = "", @ProtoNumber(101) @JvmField val undecidForQim: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(102) @JvmField val unReadCount3: Int = 0, ) : ProtoBuf @Serializable internal class RspSystemMsg( @ProtoNumber(1) @JvmField val head: RspHead? = null, @ProtoNumber(2) @JvmField val msgs: List<StructMsg> = emptyList(), @ProtoNumber(3) @JvmField val unreadCount: Int = 0, @ProtoNumber(4) @JvmField val latestFriendSeq: Long = 0L, @ProtoNumber(5) @JvmField val latestGroupSeq: Long = 0L, @ProtoNumber(6) @JvmField val followingFriendSeq: Long = 0L, @ProtoNumber(7) @JvmField val followingGroupSeq: Long = 0L, @ProtoNumber(8) @JvmField val msgDisplay: String = "", ) : ProtoBuf @Serializable internal class RspSystemMsgAction( @ProtoNumber(1) @JvmField val head: RspHead? = null, @ProtoNumber(2) @JvmField val msgDetail: String = "", @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(5) @JvmField val msgInvalidDecided: String = "", @ProtoNumber(6) @JvmField val remarkResult: Int = 0, ) : ProtoBuf @Serializable internal class RspSystemMsgNew( @ProtoNumber(1) @JvmField val head: RspHead? = null, @ProtoNumber(2) @JvmField val unreadFriendCount: Int = 0, @ProtoNumber(3) @JvmField val unreadGroupCount: Int = 0, @ProtoNumber(4) @JvmField val latestFriendSeq: Long = 0L, @ProtoNumber(5) @JvmField val latestGroupSeq: Long = 0L, @ProtoNumber(6) @JvmField val followingFriendSeq: Long = 0L, @ProtoNumber(7) @JvmField val followingGroupSeq: Long = 0L, @ProtoNumber(9) @JvmField val friendmsgs: List<StructMsg> = emptyList(), @ProtoNumber(10) @JvmField val groupmsgs: List<StructMsg> = emptyList(), @ProtoNumber(11) @JvmField val msgRibbonFriend: StructMsg? = null, @ProtoNumber(12) @JvmField val msgRibbonGroup: StructMsg? = null, @ProtoNumber(13) @JvmField val msgDisplay: String = "", @ProtoNumber(14) @JvmField val grpMsgDisplay: String = "", @ProtoNumber(15) @JvmField val over: Int = 0, @ProtoNumber(20) @JvmField val checktype: Int /* enum */ = 1, @ProtoNumber(100) @JvmField val gameNick: String = "", @ProtoNumber(101) @JvmField val undecidForQim: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(102) @JvmField val unReadCount3: Int = 0, ) : ProtoBuf @Serializable internal class RspSystemMsgRead( @ProtoNumber(1) @JvmField val head: RspHead? = null, @ProtoNumber(2) @JvmField val type: Int = 0, @ProtoNumber(3) @JvmField val checktype: Int /* enum */ = 1, ) : ProtoBuf @Serializable internal class StructMsg( @ProtoNumber(1) @JvmField val version: Int = 0, @ProtoNumber(2) @JvmField val msgType: Int /* enum */ = 1, @ProtoNumber(3) @JvmField val msgSeq: Long = 0L, @ProtoNumber(4) @JvmField val msgTime: Long = 0L, @ProtoNumber(5) @JvmField val reqUin: Long = 0L, @ProtoNumber(6) @JvmField val unreadFlag: Int = 0, @ProtoNumber(50) @JvmField val msg: SystemMsg? = null, ) : ProtoBuf @Serializable internal class SystemMsg( @ProtoNumber(1) @JvmField val subType: Int = 0, @ProtoNumber(2) @JvmField val msgTitle: String = "", @ProtoNumber(3) @JvmField val msgDescribe: String = "", @ProtoNumber(4) @JvmField val msgAdditional: String = "", @ProtoNumber(5) @JvmField val msgSource: String = "", @ProtoNumber(6) @JvmField val msgDecided: String = "", @ProtoNumber(7) @JvmField val srcId: Int = 0, @ProtoNumber(8) @JvmField val subSrcId: Int = 0, @ProtoNumber(9) @JvmField val actions: List<SystemMsgAction> = emptyList(), @ProtoNumber(10) @JvmField val groupCode: Long = 0L, @ProtoNumber(11) @JvmField val actionUin: Long = 0L, @ProtoNumber(12) @JvmField val groupMsgType: Int = 0, @ProtoNumber(13) @JvmField val groupInviterRole: Int = 0, @ProtoNumber(14) @JvmField val friendInfo: FriendInfo? = null, @ProtoNumber(15) @JvmField val groupInfo: GroupInfo? = null, @ProtoNumber(16) @JvmField val actorUin: Long = 0L, @ProtoNumber(17) @JvmField val msgActorDescribe: String = "", @ProtoNumber(18) @JvmField val msgAdditionalList: String = "", @ProtoNumber(19) @JvmField val relation: Int = 0, @ProtoNumber(20) @JvmField val reqsubtype: Int = 0, @ProtoNumber(21) @JvmField val cloneUin: Long = 0L, @ProtoNumber(22) @JvmField val discussUin: Long = 0L, @ProtoNumber(23) @JvmField val eimGroupId: Long = 0L, @ProtoNumber(24) @JvmField val msgInviteExtinfo: MsgInviteExt? = null, @ProtoNumber(25) @JvmField val msgPayGroupExtinfo: MsgPayGroupExt? = null, @ProtoNumber(26) @JvmField val sourceFlag: Int = 0, @ProtoNumber(27) @JvmField val gameNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(28) @JvmField val gameMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(29) @JvmField val groupFlagext3: Int = 0, @ProtoNumber(30) @JvmField val groupOwnerUin: Long = 0L, @ProtoNumber(31) @JvmField val doubtFlag: Int = 0, @ProtoNumber(32) @JvmField val warningTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(33) @JvmField val nameMore: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(50) @JvmField val reqUinFaceid: Int = 0, @ProtoNumber(51) @JvmField val reqUinNick: String = "", @ProtoNumber(52) @JvmField val groupName: String = "", @ProtoNumber(53) @JvmField val actionUinNick: String = "", @ProtoNumber(54) @JvmField val msgQna: String = "", @ProtoNumber(55) @JvmField val msgDetail: String = "", @ProtoNumber(57) @JvmField val groupExtFlag: Int = 0, @ProtoNumber(58) @JvmField val actorUinNick: String = "", @ProtoNumber(59) @JvmField val picUrl: String = "", @ProtoNumber(60) @JvmField val cloneUinNick: String = "", @ProtoNumber(61) @JvmField val reqUinBusinessCard: String = "", @ProtoNumber(63) @JvmField val eimGroupIdName: String = "", @ProtoNumber(64) @JvmField val reqUinPreRemark: String = "", @ProtoNumber(65) @JvmField val actionUinQqNick: String = "", @ProtoNumber(66) @JvmField val actionUinRemark: String = "", @ProtoNumber(67) @JvmField val reqUinGender: Int = 0, @ProtoNumber(68) @JvmField val reqUinAge: Int = 0, @ProtoNumber(69) @JvmField val c2cInviteJoinGroupFlag: Int = 0, @ProtoNumber(101) @JvmField val cardSwitch: Int = 0, ) : ProtoBuf @Serializable internal class SystemMsgAction( @ProtoNumber(1) @JvmField val name: String = "", @ProtoNumber(2) @JvmField val result: String = "", @ProtoNumber(3) @JvmField val action: Int = 0, @ProtoNumber(4) @JvmField val actionInfo: SystemMsgActionInfo? = null, @ProtoNumber(5) @JvmField val detailName: String = "", ) : ProtoBuf @Serializable internal class SystemMsgActionInfo( @ProtoNumber(1) @JvmField val type: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(50) @JvmField val msg: String = "", @ProtoNumber(51) @JvmField val groupId: Int = 0, @ProtoNumber(52) @JvmField val remark: String = "", @ProtoNumber(53) @JvmField val blacklist: Boolean = false, @ProtoNumber(54) @JvmField val addFrdSNInfo: AddFrdSNInfo? = null, ) : ProtoBuf } @Serializable internal class Youtu : ProtoBuf { @Serializable internal class NameCardOcrRsp( @ProtoNumber(1) @JvmField val errorcode: Int = 0, @ProtoNumber(2) @JvmField val errormsg: String = "", @ProtoNumber(3) @JvmField val uin: String = "", @ProtoNumber(4) @JvmField val uinConfidence: Float = 0.0F, @ProtoNumber(5) @JvmField val phone: String = "", @ProtoNumber(6) @JvmField val phoneConfidence: Float = 0.0F, @ProtoNumber(7) @JvmField val name: String = "", @ProtoNumber(8) @JvmField val nameConfidence: Float = 0.0F, @ProtoNumber(9) @JvmField val image: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val sessionId: String = "", ) : ProtoBuf } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/SyncCookie.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.utils.io.ProtoBuf import kotlin.jvm.JvmField import kotlin.math.absoluteValue import kotlin.random.Random // COMMENTED ON 2020/7/25 @Serializable internal class SyncCookie( @ProtoNumber(1) @JvmField val time1: Long? = null, // 1580277992 @ProtoNumber(2) @JvmField val time: Long, // 1580277992 @ProtoNumber(3) @JvmField val unknown1: Long = Random.nextLong().absoluteValue, // 678328038 @ProtoNumber(4) @JvmField val unknown2: Long = Random.nextLong().absoluteValue, // 1687142153 @ProtoNumber(5) @JvmField val const1: Long = const1_, // 1458467940 @ProtoNumber(11) @JvmField val const2: Long = const2_, // 2683038258 @ProtoNumber(12) @JvmField val unknown3: Long = 0x1d, @ProtoNumber(13) @JvmField val lastSyncTime: Long? = null, @ProtoNumber(14) @JvmField val unknown4: Long = 0, ) : ProtoBuf private val const1_: Long = Random.nextLong().absoluteValue private val const2_: Long = Random.nextLong().absoluteValue /* @Serializable internal class SyncCookie( @SerialId(1) @JvmField val time1: Long? = null, // 1580277992 @SerialId(2) @JvmField val time: Long, // 1580277992 @SerialId(3) @JvmField val unknown1: Long = 678328038,// 678328038 @SerialId(4) @JvmField val unknown2: Long = 1687142153, // 1687142153 @SerialId(5) @JvmField val const1: Long = 1458467940, // 1458467940 @SerialId(11) @JvmField val const2: Long = 2683038258, // 2683038258 @SerialId(12) @JvmField val unknown3: Long = 0x1d, @SerialId(13) @JvmField val lastSyncTime: Long? = null, @SerialId(14) @JvmField val unknown4: Long = 0 ) : ProtoBuf */ ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/msgType0x210.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class SubMsgType0x43 : ProtoBuf { @Serializable internal class UpdateTips( @ProtoNumber(1) @JvmField val desc: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } internal class Submsgtype0x101 { internal class SubMsgType0x27 : ProtoBuf { @Serializable internal class ClientReport( @ProtoNumber(1) @JvmField val serviceId: Int = 0, @ProtoNumber(2) @JvmField val contentId: String = "", ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgPushPlatform: PushPlatform? = null, @ProtoNumber(2) @JvmField val msgClientReport: ClientReport? = null, ) : ProtoBuf @Serializable internal class PushPlatform( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val title: String = "", @ProtoNumber(3) @JvmField val desc: String = "", @ProtoNumber(4) @JvmField val targetUrl: String = "", @ProtoNumber(5) @JvmField val forwardType: Int = 0, @ProtoNumber(6) @JvmField val extDataString: String = "", @ProtoNumber(7) @JvmField val extData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x102 { internal class Submsgtype0x102 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val adId: String = "", ) : ProtoBuf } } internal class Submsgtype0x103 { internal class Submsgtype0x103 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val from: Long = 0L, @ProtoNumber(2) @JvmField val to: Long = 0L, @ProtoNumber(3) @JvmField val topicId: Int = 0, @ProtoNumber(11) @JvmField val curCount: Int = 0, @ProtoNumber(12) @JvmField val totalCount: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x104 { internal class Submsgtype0x104 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val from: Long = 0L, @ProtoNumber(2) @JvmField val to: Long = 0L, @ProtoNumber(3) @JvmField val topicId: Int = 0, @ProtoNumber(11) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x108 { internal class SubMsgType0x108 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val pushUin: Long = 0L, @ProtoNumber(3) @JvmField val likeCount: Int = 0, @ProtoNumber(4) @JvmField val pushTime: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x10f { internal class Submsgtype0x10f : ProtoBuf { @Serializable internal class KanDianCoinSettingWording( @ProtoNumber(1) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val pictureUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val isOpenCoinEntry: Int = 0, @ProtoNumber(2) @JvmField val canGetCoinCount: Int = 0, @ProtoNumber(3) @JvmField val coinIconUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val msgSettingWording: KanDianCoinSettingWording? = null, @ProtoNumber(5) @JvmField val lastCompletedTaskStamp: Long = 0L, @ProtoNumber(6) @JvmField val dstUin: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0x111 { internal class SubMsgType0x111 : ProtoBuf { @Serializable internal class AddFriendSource( @ProtoNumber(1) @JvmField val source: Int = 0, @ProtoNumber(2) @JvmField val subSource: Int = 0, ) : ProtoBuf @Serializable internal class Color( @ProtoNumber(1) @JvmField val r: Int = 0, @ProtoNumber(2) @JvmField val g: Int = 0, @ProtoNumber(3) @JvmField val b: Int = 0, ) : ProtoBuf @Serializable internal class Label( @ProtoNumber(1) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val textColor: Color? = null, @ProtoNumber(3) @JvmField val edgingColor: Color? = null, @ProtoNumber(4) @JvmField val labelType: Int = 0, ) : ProtoBuf @Serializable internal class MayKnowPerson( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgIosSource: AddFriendSource? = null, @ProtoNumber(3) @JvmField val msgAndroidSource: AddFriendSource? = null, @ProtoNumber(4) @JvmField val reason: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val additive: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val remark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val country: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val province: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val city: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val age: Int = 0, @ProtoNumber(12) @JvmField val catelogue: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val alghrithm: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val richbuffer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val qzone: Int = 0, @ProtoNumber(16) @JvmField val gender: Int = 0, @ProtoNumber(17) @JvmField val mobileName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val token: String = "", @ProtoNumber(19) @JvmField val msgLabels: List<Label> = emptyList(), ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Long = 0L, @ProtoNumber(2) @JvmField val msgAddRecommendPersons: List<MayKnowPerson> = emptyList(), @ProtoNumber(3) @JvmField val uint64DelUins: List<Long> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x113 { internal class SubMsgType0x113 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val int32AppId: Int = 0, @ProtoNumber(2) @JvmField val int32TaskId: Int = 0, @ProtoNumber(3) @JvmField val enumTaskOp: Int /* enum */ = 1, ) : ProtoBuf } } internal class Submsgtype0x115 { internal class SubMsgType0x115 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, @ProtoNumber(3) @JvmField val msgNotifyItem: NotifyItem? = null, @ProtoNumber(4) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NotifyItem( @ProtoNumber(1) @JvmField val ime: Int = 0, @ProtoNumber(2) @JvmField val timeout: Int = 0, @ProtoNumber(3) @JvmField val timestamp: Long = 0L, @ProtoNumber(4) @JvmField val eventType: Int = 0, @ProtoNumber(5) @JvmField val interval: Int = 0, @ProtoNumber(6) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x116 { internal class Submsgtype0x116 : ProtoBuf { @Serializable internal class MemberInfo( @ProtoNumber(1) @JvmField val memberUin: Long = 0L, @ProtoNumber(2) @JvmField val inviteTimestamp: Int = 0, @ProtoNumber(3) @JvmField val terminalType: Int = 0, @ProtoNumber(4) @JvmField val clientVersion: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgMemberJoin: List<MemberInfo> = emptyList(), @ProtoNumber(2) @JvmField val msgMemberQuit: List<MemberInfo> = emptyList(), @ProtoNumber(3) @JvmField val groupId: Int = 0, @ProtoNumber(4) @JvmField val roomId: Int = 0, @ProtoNumber(5) @JvmField val inviteListTotalCount: Int = 0, @ProtoNumber(6) @JvmField val enumEventType: Int /* enum */ = 1, ) : ProtoBuf } } internal class Submsgtype0x117 { internal class Submsgtype0x117 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val uint32MoudleId: List<Int> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x118 { internal class Submsgtype0x118 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val pushType: Int = 0, @ProtoNumber(2) @JvmField val pushData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val timestamp: Int = 0, @ProtoNumber(4) @JvmField val msgSystemNotify: SystemNotify? = null, ) : ProtoBuf @Serializable internal class SystemNotify( @ProtoNumber(1) @JvmField val msgSummary: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val filterFlag: Int = 0, @ProtoNumber(3) @JvmField val extendContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val ignorePcActive: Int = 0, @ProtoNumber(5) @JvmField val filterVersion: Int = 0, @ProtoNumber(6) @JvmField val countFlag: Int = 0, @ProtoNumber(7) @JvmField val filterVersionUpperlimitFlag: Int = 0, @ProtoNumber(8) @JvmField val filterVersionUpperlimit: Int = 0, @ProtoNumber(9) @JvmField val customSound: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val admnFlag: Int = 0, @ProtoNumber(11) @JvmField val ignoreWithoutContent: Int = 0, @ProtoNumber(12) @JvmField val msgTitle: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x119 { internal class SubMsgType0x119 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val writerUin: Long = 0L, @ProtoNumber(2) @JvmField val creatorUin: Long = 0L, @ProtoNumber(3) @JvmField val richContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val optBytesUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val creatorNick: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x11a { internal class Submsgtype0x11a : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val enumResult: Int /* enum */ = 0, @ProtoNumber(2) @JvmField val token: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val encryptKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val msgUserData: UserData? = null, @ProtoNumber(5) @JvmField val enumBizType: Int /* enum */ = 1, ) : ProtoBuf @Serializable internal class UserData( @ProtoNumber(1) @JvmField val ip: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fixed32Port: List<Int> = emptyList(), @ProtoNumber(3) @JvmField val ssid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val bssid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val enumPlatform: Int /* enum */ = 1, ) : ProtoBuf } } internal class Submsgtype0x11b { internal class Submsgtype0x11b : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val qrSig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val enumBizType: Int /* enum */ = 1, ) : ProtoBuf } } internal class Submsgtype0x11c { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val cmd: Int = 0, @ProtoNumber(2) @JvmField val timestamp: Int = 0, @ProtoNumber(3) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } internal class Submsgtype0x11e { internal class SubMsgType0x11e : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val reason: String = "", ) : ProtoBuf } } internal class Submsgtype0x11f { internal class SubMsgType0x11f : ProtoBuf { @Serializable internal class MediaUserInfo( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val joinState: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val aioType: Int = 0, @ProtoNumber(5) @JvmField val operUin: Long = 0L, @ProtoNumber(6) @JvmField val uint64ToUin: List<Long> = emptyList(), @ProtoNumber(7) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val msgSeq: Long = 0L, @ProtoNumber(9) @JvmField val msgMediaUin: List<MediaUserInfo> = emptyList(), @ProtoNumber(10) @JvmField val msgPerSetting: PersonalSetting? = null, @ProtoNumber(11) @JvmField val playMode: Int = 0, @ProtoNumber(99) @JvmField val mediaType: Int = 0, @ProtoNumber(100) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PersonalSetting( @ProtoNumber(1) @JvmField val themeId: Int = 0, @ProtoNumber(2) @JvmField val playerId: Int = 0, @ProtoNumber(3) @JvmField val fontId: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x120 { internal class SubMsgType0x120 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val srcAppId: Int = 0, @ProtoNumber(2) @JvmField val noticeType: Int = 0, @ProtoNumber(3) @JvmField val reserve1: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val reserve2: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val reserve3: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val noticeTime: Int = 0, @ProtoNumber(7) @JvmField val frdUin: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0x122 { internal class GrayTipsResv : ProtoBuf { @Serializable internal class ResvAttr( @ProtoNumber(1) @JvmField val friendBannedFlag: Int = 0, ) : ProtoBuf } internal class Submsgtype0x122 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val busiType: Long = 0L, @ProtoNumber(2) @JvmField val busiId: Long = 0L, @ProtoNumber(3) @JvmField val ctrlFlag: Int = 0, @ProtoNumber(4) @JvmField val c2cType: Int = 0, @ProtoNumber(5) @JvmField val serviceType: Int = 0, @ProtoNumber(6) @JvmField val templId: Long = 0L, @ProtoNumber(7) @JvmField val msgTemplParam: List<TemplParam> = emptyList(), @ProtoNumber(8) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val tipsSeqId: Long = 0L, @ProtoNumber(100) @JvmField val pbReserv: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class TemplParam( @ProtoNumber(1) @JvmField val name: String = "", @ProtoNumber(2) @JvmField val value: String = "", ) : ProtoBuf } } internal class Submsgtype0x123 { internal class Submsgtype0x123 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val busiType: Long = 0L, @ProtoNumber(2) @JvmField val busiId: Long = 0L, @ProtoNumber(3) @JvmField val ctrlFlag: Int = 0, @ProtoNumber(4) @JvmField val c2cType: Int = 0, @ProtoNumber(5) @JvmField val serviceType: Int = 0, @ProtoNumber(6) @JvmField val templId: Long = 0L, @ProtoNumber(7) @JvmField val templParam: List<TemplParam> = emptyList(), @ProtoNumber(8) @JvmField val templContent: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class TemplParam( @ProtoNumber(1) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x125 { internal class Submsgtype0x125 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val operUin: Long = 0L, @ProtoNumber(5) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val msgSeq: Long = 0L, @ProtoNumber(99) @JvmField val pushType: Int = 0, @ProtoNumber(100) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x126 { internal class Submsgtype0x126 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgSeq: Long = 0L, @ProtoNumber(2) @JvmField val msgType: Int = 0, @ProtoNumber(3) @JvmField val msgInfo: String = "", @ProtoNumber(100) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x127 { internal class Submsgtype0x127 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val seq: Long = 0L, @ProtoNumber(2) @JvmField val actionType: Int = 0, @ProtoNumber(3) @JvmField val friendUin: Long = 0L, @ProtoNumber(4) @JvmField val operUin: Long = 0L, @ProtoNumber(5) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val joinState: Int /* enum */ = 1, ) : ProtoBuf } } internal class Submsgtype0x128 { internal class Submsgtype0x128 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val matchUin: Long = 0L, @ProtoNumber(3) @JvmField val tipsWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val timeStamp: Long = 0L, @ProtoNumber(6) @JvmField val matchExpiredTime: Int = 0, @ProtoNumber(7) @JvmField val reportId: String = "", //@ProtoNumber(8) @JvmField val msgTag: Oidb0xe03.TagInfo? = null, //@ProtoNumber(9) @JvmField val msgMatchUinData: Oidb0xe03.MatchUinData? = null ) : ProtoBuf } } internal class Submsgtype0x129 { internal class Submsgtype0x129 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val seq: Long = 0L, @ProtoNumber(2) @JvmField val actionType: Int = 0, @ProtoNumber(3) @JvmField val friendUin: Long = 0L, @ProtoNumber(4) @JvmField val operUin: Long = 0L, @ProtoNumber(5) @JvmField val grayTips: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val joinState: Int /* enum */ = 1, ) : ProtoBuf } } internal class Submsgtype0x133 { internal class Submsgtype0x133 : ProtoBuf { @Serializable internal class FaceFriend( @ProtoNumber(1) @JvmField val friend: Long = 0L, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val type: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgFriends: List<FaceFriend> = emptyList(), @ProtoNumber(3) @JvmField val sessionId: String = "", ) : ProtoBuf } } internal class Submsgtype0x135 { @Serializable internal class ModulePushPb : ProtoBuf { @Serializable internal class Content( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val title: String = "", @ProtoNumber(3) @JvmField val desc: String = "", @ProtoNumber(4) @JvmField val msgImage: Image? = null, @ProtoNumber(5) @JvmField val msgForward: Forward? = null, @ProtoNumber(6) @JvmField val extData: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class Forward( @ProtoNumber(1) @JvmField val url: String = "", @ProtoNumber(2) @JvmField val type: Int = 0, ) : ProtoBuf @Serializable internal class Image( @ProtoNumber(1) @JvmField val url: String = "", @ProtoNumber(2) @JvmField val width: Int = 0, @ProtoNumber(3) @JvmField val height: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val int32ServiceId: Int = 0, @ProtoNumber(2) @JvmField val int32SubServiceId: Int = 0, @ProtoNumber(3) @JvmField val int32NotifyId: Int = 0, @ProtoNumber(4) @JvmField val int32PushId: Int = 0, @ProtoNumber(5) @JvmField val int32Type: Int = 0, @ProtoNumber(6) @JvmField val int32RecallFlag: Int = 0, @ProtoNumber(7) @JvmField val msgContent: Content? = null, ) : ProtoBuf } } @Serializable internal class Submsgtype0x13a : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val pushType: Int = 0, @ProtoNumber(2) @JvmField val pushData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val timestamp: Int = 0, @ProtoNumber(4) @JvmField val msgSystemNotify: SystemNotify? = null, ) : ProtoBuf @Serializable internal class SystemNotify( @ProtoNumber(1) @JvmField val msgSummary: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val filterFlag: Int = 0, @ProtoNumber(3) @JvmField val extendContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val ignorePcActive: Int = 0, @ProtoNumber(5) @JvmField val filterVersion: Int = 0, @ProtoNumber(6) @JvmField val countFlag: Int = 0, @ProtoNumber(7) @JvmField val filterVersionUpperlimitFlag: Int = 0, @ProtoNumber(8) @JvmField val filterVersionUpperlimit: Int = 0, @ProtoNumber(9) @JvmField val customSound: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val admnFlag: Int = 0, @ProtoNumber(11) @JvmField val ignoreWithoutContent: Int = 0, @ProtoNumber(12) @JvmField val msgTitle: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } @Serializable internal class Submsgtype0x13b : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val op: Int /* enum */ = 2, @ProtoNumber(2) @JvmField val muteFriend: Long = 0L, ) : ProtoBuf } /* internal class Submsgtype0x1a { internal class SubMsgType0x1a : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val fileKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fromUinInt: Int = 0, @ProtoNumber(3) @JvmField val toUinInt: Int = 0, @ProtoNumber(4) @JvmField val status: Int = 0, @ProtoNumber(5) @JvmField val ttl: Int = 0, @ProtoNumber(6) @JvmField val desc: String = "", @ProtoNumber(7) @JvmField val type: Int = 0, @ProtoNumber(8) @JvmField val captureTimes: Int = 0, @ProtoNumber(9) @JvmField val fromUin: Long = 0L, @ProtoNumber(10) @JvmField val toUin: Long = 0L ) : ProtoBuf } }*/ internal class Submsgtype0x26 { internal class Submsgtype0x26 : ProtoBuf { @Serializable internal class AppID( @ProtoNumber(1) @JvmField val appId: Long = 0L, ) : ProtoBuf @Serializable internal class AppNotifyContent( @ProtoNumber(1) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val optMsgAppNotifyUser: List<AppNotifyUser> = emptyList(), @ProtoNumber(3) @JvmField val onlineCount: Int = 0, @ProtoNumber(4) @JvmField val expireTs: Int = 0, @ProtoNumber(5) @JvmField val roomMode: Int = 0, @ProtoNumber(6) @JvmField val liveExtraMode: Int = 0, @ProtoNumber(7) @JvmField val gameId: Int = 0, ) : ProtoBuf @Serializable internal class AppNotifyUser( @ProtoNumber(1) @JvmField val optUint64Uin: Long = 0L, @ProtoNumber(2) @JvmField val optUint32Flag: Int = 0, ) : ProtoBuf @Serializable internal class AppTip( @ProtoNumber(1) @JvmField val tipInfoSeq: Int = 0, @ProtoNumber(2) @JvmField val icon: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val iconTimeStamp: Int = 0, @ProtoNumber(4) @JvmField val tooltip: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val reportidClick: Int = 0, @ProtoNumber(6) @JvmField val reportidShow: Int = 0, ) : ProtoBuf @Serializable internal class AppTipNotify( @ProtoNumber(1) @JvmField val msgAppTip: AppTip? = null, @ProtoNumber(2) @JvmField val action: Int = 0, @ProtoNumber(3) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val notifySeq: Int = 0, @ProtoNumber(5) @JvmField val neededTipInfoSeq: Int = 0, @ProtoNumber(6) @JvmField val optMsgAppNotifyContent: AppNotifyContent? = null, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val msgSubcmd0x1PushBody: List<SubCmd0x1UpdateAppUnreadNum> = emptyList(), @ProtoNumber(3) @JvmField val msgSubcmd0x2PushBody: SubCmd0x2UpdateAppList? = null, @ProtoNumber(4) @JvmField val msgSubcmd0x3PushBody: SubCmd0x3UpdateDiscussAppInfo? = null, @ProtoNumber(5) @JvmField val msgSubcmd0x4PushBody: SubCmd0x4UpdateApp? = null, ) : ProtoBuf { @Serializable internal class SubCmd0x1UpdateAppUnreadNum( @ProtoNumber(1) @JvmField val msgAppId: AppID? = null, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(3) @JvmField val sint32UnreadNum: Int = 0, @ProtoNumber(4) @JvmField val msgAppTipNotify: AppTipNotify? = null, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(5) @JvmField val sint32AlbumCnt: Int = 0, ) : ProtoBuf @Serializable internal class SubCmd0x2UpdateAppList( @ProtoNumber(1) @JvmField val msgAppId: List<AppID> = emptyList(), @ProtoNumber(2) @JvmField val uint32TimeStamp: List<Int> = emptyList(), @ProtoNumber(3) @JvmField val groupCode: Long = 0L, ) : ProtoBuf @Serializable internal class SubCmd0x3UpdateDiscussAppInfo( @ProtoNumber(1) @JvmField val msgAppId: AppID? = null, @ProtoNumber(2) @JvmField val confUin: Long = 0L, @ProtoNumber(3) @JvmField val msgAppTipNotify: AppTipNotify? = null, ) : ProtoBuf @Serializable internal class SubCmd0x4UpdateApp( @ProtoNumber(1) @JvmField val msgAppId: AppID? = null, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(3) @JvmField val sint32UnreadNum: Int = 0, ) : ProtoBuf } @Serializable internal class TransferCnt( @ProtoNumber(1) @JvmField val chainId: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0x27 { internal class SubMsgType0x27 : ProtoBuf { @Serializable internal class AddGroup( @ProtoNumber(1) @JvmField val groupid: Int = 0, @ProtoNumber(2) @JvmField val sortid: Int = 0, @ProtoNumber(3) @JvmField val groupname: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class AppointmentNotify( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val appointId: String = "", @ProtoNumber(3) @JvmField val notifytype: Int = 0, @ProtoNumber(4) @JvmField val tipsContent: String = "", @ProtoNumber(5) @JvmField val unreadCount: Int = 0, @ProtoNumber(6) @JvmField val joinWording: String = "", @ProtoNumber(7) @JvmField val viewWording: String = "", @ProtoNumber(8) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val eventInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val nearbyEventInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val feedEventInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class BinaryMsg( @ProtoNumber(1) @JvmField val opType: Int = 0, @ProtoNumber(2) @JvmField val opValue: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ChatMatchInfo( @ProtoNumber(1) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val matchUin: Long = 0L, @ProtoNumber(4) @JvmField val tipsWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val leftChatTime: Int = 0, @ProtoNumber(6) @JvmField val timeStamp: Long = 0L, @ProtoNumber(7) @JvmField val matchExpiredTime: Int = 0, @ProtoNumber(8) @JvmField val c2cExpiredTime: Int = 0, @ProtoNumber(9) @JvmField val matchCount: Int = 0, @ProtoNumber(10) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ConfMsgRoamFlag( @ProtoNumber(1) @JvmField val confid: Long = 0L, @ProtoNumber(2) @JvmField val flag: Int = 0, @ProtoNumber(3) @JvmField val timestamp: Long = 0L, ) : ProtoBuf @Serializable internal class DaRenNotify( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val loginDays: Int = 0, @ProtoNumber(3) @JvmField val days: Int = 0, @ProtoNumber(4) @JvmField val isYestodayLogin: Int = 0, @ProtoNumber(5) @JvmField val isTodayLogin: Int = 0, ) : ProtoBuf @Serializable internal class DelFriend( @ProtoNumber(1) @JvmField val uint64Uins: List<Long> = emptyList(), ) : ProtoBuf @Serializable internal class DelGroup( @ProtoNumber(1) @JvmField val groupid: Int = 0, ) : ProtoBuf @Serializable internal class FanpaiziNotify( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val fromNick: String = "", @ProtoNumber(3) @JvmField val tipsContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ForwardBody( @ProtoNumber(1) @JvmField val notifyType: Int = 0, @ProtoNumber(2) @JvmField val opType: Int = 0, @ProtoNumber(3) @JvmField val msgAddGroup: AddGroup? = null, @ProtoNumber(4) @JvmField val msgDelGroup: DelGroup? = null, @ProtoNumber(5) @JvmField val msgModGroupName: ModGroupName? = null, @ProtoNumber(6) @JvmField val msgModGroupSort: ModGroupSort? = null, @ProtoNumber(7) @JvmField val msgModFriendGroup: ModFriendGroup? = null, @ProtoNumber(8) @JvmField val msgModProfile: ModProfile? = null, @ProtoNumber(9) @JvmField val msgModFriendRemark: ModFriendRemark? = null, @ProtoNumber(10) @JvmField val msgModLongNick: ModLongNick? = null, @ProtoNumber(11) @JvmField val msgModCustomFace: ModCustomFace? = null, @ProtoNumber(12) @JvmField val msgModGroupProfile: ModGroupProfile? = null, @ProtoNumber(13) @JvmField val msgModGroupMemberProfile: ModGroupMemberProfile? = null, @ProtoNumber(14) @JvmField val msgDelFriend: DelFriend? = null, @ProtoNumber(15) @JvmField val msgRoamPriv: ModFrdRoamPriv? = null, @ProtoNumber(16) @JvmField val msgGrpMsgRoamFlag: GrpMsgRoamFlag? = null, @ProtoNumber(17) @JvmField val msgConfMsgRoamFlag: ConfMsgRoamFlag? = null, @ProtoNumber(18) @JvmField val msgModRichLongNick: ModLongNick? = null, @ProtoNumber(19) @JvmField val msgBinPkg: BinaryMsg? = null, @ProtoNumber(20) @JvmField val msgModFriendRings: ModSnsGeneralInfo? = null, @ProtoNumber(21) @JvmField val msgModConfProfile: ModConfProfile? = null, @ProtoNumber(22) @JvmField val msgModFriendFlag: SnsUpdateFlag? = null, @ProtoNumber(23) @JvmField val msgAppointmentNotify: AppointmentNotify? = null, @ProtoNumber(25) @JvmField val msgDarenNotify: DaRenNotify? = null, @ProtoNumber(26) @JvmField val msgNewComeinUserNotify: NewComeinUserNotify? = null, @ProtoNumber(200) @JvmField val msgPushSearchDev: PushSearchDev? = null, @ProtoNumber(201) @JvmField val msgPushReportDev: PushReportDev? = null, @ProtoNumber(202) @JvmField val msgQqPayPush: QQPayPush? = null, @ProtoNumber(203) @JvmField val redpointInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(204) @JvmField val msgHotFriendNotify: HotFriendNotify? = null, @ProtoNumber(205) @JvmField val msgPraiseRankNotify: PraiseRankNotify? = null, @ProtoNumber(210) @JvmField val msgCampusNotify: MQQCampusNotify? = null, @ProtoNumber(211) @JvmField val msgModRichLongNickEx: ModLongNick? = null, @ProtoNumber(212) @JvmField val msgChatMatchInfo: ChatMatchInfo? = null, @ProtoNumber(214) @JvmField val msgFrdCustomOnlineStatusChange: FrdCustomOnlineStatusChange? = null, @ProtoNumber(2000) @JvmField val msgFanpanziNotify: FanpaiziNotify? = null, ) : ProtoBuf @Serializable internal class FrdCustomOnlineStatusChange( @ProtoNumber(1) @JvmField val uin: Long = 0L, ) : ProtoBuf @Serializable internal class FriendGroup( @ProtoNumber(1) @JvmField val fuin: Long = 0L, @ProtoNumber(2) @JvmField val uint32OldGroupId: List<Int> = emptyList(), @ProtoNumber(3) @JvmField val uint32NewGroupId: List<Int> = emptyList(), ) : ProtoBuf @Serializable internal class FriendRemark( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val fuin: Long = 0L, @ProtoNumber(3) @JvmField val rmkName: String = "", @ProtoNumber(4) @JvmField val groupCode: Long = 0L, ) : ProtoBuf @Serializable internal class GPS( @ProtoNumber(1) @JvmField val int32Lat: Int = 900000000, @ProtoNumber(2) @JvmField val int32Lon: Int = 900000000, @ProtoNumber(3) @JvmField val int32Alt: Int = -10000000, @ProtoNumber(4) @JvmField val int32Type: Int = 0, ) : ProtoBuf @Serializable internal class GroupMemberProfileInfo( @ProtoNumber(1) @JvmField val field: Int = 0, @ProtoNumber(2) @JvmField val value: String = "", ) : ProtoBuf @Serializable internal class GroupProfileInfo( @ProtoNumber(1) @JvmField val field: Int = 0, @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GroupSort( @ProtoNumber(1) @JvmField val groupid: Int = 0, @ProtoNumber(2) @JvmField val sortid: Int = 0, ) : ProtoBuf @Serializable internal class GrpMsgRoamFlag( @ProtoNumber(1) @JvmField val groupcode: Long = 0L, @ProtoNumber(2) @JvmField val flag: Int = 0, @ProtoNumber(3) @JvmField val timestamp: Long = 0L, ) : ProtoBuf @Serializable internal class HotFriendNotify( @ProtoNumber(1) @JvmField val dstUin: Long = 0L, @ProtoNumber(2) @JvmField val praiseHotLevel: Int = 0, @ProtoNumber(3) @JvmField val chatHotLevel: Int = 0, @ProtoNumber(4) @JvmField val praiseHotDays: Int = 0, @ProtoNumber(5) @JvmField val chatHotDays: Int = 0, @ProtoNumber(6) @JvmField val closeLevel: Int = 0, @ProtoNumber(7) @JvmField val closeDays: Int = 0, @ProtoNumber(8) @JvmField val praiseFlag: Int = 0, @ProtoNumber(9) @JvmField val chatFlag: Int = 0, @ProtoNumber(10) @JvmField val closeFlag: Int = 0, @ProtoNumber(11) @JvmField val notifyTime: Long = 0L, @ProtoNumber(12) @JvmField val lastPraiseTime: Long = 0L, @ProtoNumber(13) @JvmField val lastChatTime: Long = 0L, @ProtoNumber(14) @JvmField val qzoneHotLevel: Int = 0, @ProtoNumber(15) @JvmField val qzoneHotDays: Int = 0, @ProtoNumber(16) @JvmField val qzoneFlag: Int = 0, @ProtoNumber(17) @JvmField val lastQzoneTime: Long = 0L, ) : ProtoBuf @Serializable internal class ModConfProfile( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val confUin: Int = 0, @ProtoNumber(3) @JvmField val msgProfileInfos: List<ProfileInfo> = emptyList(), ) : ProtoBuf @Serializable internal class ModCustomFace( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val groupCode: Long = 0L, @ProtoNumber(4) @JvmField val cmdUin: Long = 0L, ) : ProtoBuf @Serializable internal class ModFrdRoamPriv( @ProtoNumber(1) @JvmField val msgRoamPriv: List<OneRoamPriv> = emptyList(), ) : ProtoBuf @Serializable internal class ModFriendGroup( @ProtoNumber(1) @JvmField val msgFrdGroup: List<FriendGroup> = emptyList(), ) : ProtoBuf @Serializable internal class ModFriendRemark( @ProtoNumber(1) @JvmField val msgFrdRmk: List<FriendRemark> = emptyList(), ) : ProtoBuf @Serializable internal class ModGroupMemberProfile( @ProtoNumber(1) @JvmField val groupUin: Long = 0L, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val msgGroupMemberProfileInfos: List<GroupMemberProfileInfo> = emptyList(), @ProtoNumber(4) @JvmField val groupCode: Long = 0L, ) : ProtoBuf @Serializable internal class ModGroupName( @ProtoNumber(1) @JvmField val groupid: Int = 0, @ProtoNumber(2) @JvmField val groupname: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ModGroupProfile( @ProtoNumber(1) @JvmField val groupUin: Long = 0L, @ProtoNumber(2) @JvmField val msgGroupProfileInfos: List<GroupProfileInfo> = emptyList(), @ProtoNumber(3) @JvmField val groupCode: Long = 0L, @ProtoNumber(4) @JvmField val cmdUin: Long = 0L, ) : ProtoBuf @Serializable internal class ModGroupSort( @ProtoNumber(1) @JvmField val msgGroupsort: List<GroupSort> = emptyList(), ) : ProtoBuf @Serializable internal class ModLongNick( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ModProfile( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgProfileInfos: List<ProfileInfo> = emptyList(), ) : ProtoBuf @Serializable internal class ModSnsGeneralInfo( @ProtoNumber(1) @JvmField val msgSnsGeneralInfos: List<SnsUpateBuffer> = emptyList(), ) : ProtoBuf @Serializable internal class MQQCampusNotify( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val wording: String = "", @ProtoNumber(3) @JvmField val target: String = "", @ProtoNumber(4) @JvmField val type: Int = 0, @ProtoNumber(5) @JvmField val source: String = "", ) : ProtoBuf @Serializable internal class SubMsgType0x27MsgBody( @ProtoNumber(1) @JvmField val msgModInfos: List<ForwardBody> = emptyList(), ) : ProtoBuf @Serializable internal class NewComeinUser( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val isFrd: Int = 0, @ProtoNumber(3) @JvmField val remark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NewComeinUserNotify( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val boolStrongNotify: Boolean = false, @ProtoNumber(3) @JvmField val pushTime: Int = 0, @ProtoNumber(4) @JvmField val msgNewComeinUser: NewComeinUser? = null, @ProtoNumber(5) @JvmField val msgNewGroup: NewGroup? = null, @ProtoNumber(6) @JvmField val msgNewGroupUser: NewGroupUser? = null, ) : ProtoBuf @Serializable internal class NewGroup( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val groupName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val ownerUin: Long = 0L, @ProtoNumber(4) @JvmField val ownerNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val distance: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NewGroupUser( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val int32Sex: Int = 0, @ProtoNumber(3) @JvmField val int32Age: Int = 0, @ProtoNumber(4) @JvmField val nick: String = "", @ProtoNumber(5) @JvmField val distance: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class OneRoamPriv( @ProtoNumber(1) @JvmField val fuin: Long = 0L, @ProtoNumber(2) @JvmField val privTag: Int = 0, @ProtoNumber(3) @JvmField val privValue: Int = 0, ) : ProtoBuf @Serializable internal class PraiseRankNotify( @ProtoNumber(11) @JvmField val isChampion: Int = 0, @ProtoNumber(12) @JvmField val rankNum: Int = 0, @ProtoNumber(13) @JvmField val msg: String = "", ) : ProtoBuf @Serializable internal class ProfileInfo( @ProtoNumber(1) @JvmField val field: Int = 0, @ProtoNumber(2) @JvmField val value: String = "", ) : ProtoBuf @Serializable internal class PushReportDev( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(4) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val reportMaxNum: Int = 200, @ProtoNumber(6) @JvmField val sn: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PushSearchDev( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgGpsInfo: GPS? = null, @ProtoNumber(3) @JvmField val devTime: Int = 0, @ProtoNumber(4) @JvmField val pushTime: Int = 0, @ProtoNumber(5) @JvmField val din: Long = 0L, @ProtoNumber(6) @JvmField val data: String = "", ) : ProtoBuf @Serializable internal class QQPayPush( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val boolPayOk: Boolean = false, ) : ProtoBuf @Serializable internal class SnsUpateBuffer( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val code: Long = 0L, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(400) @JvmField val msgSnsUpdateItem: List<SnsUpdateItem> = emptyList(), @ProtoNumber(401) @JvmField val uint32Idlist: List<Int> = emptyList(), ) : ProtoBuf @Serializable internal class SnsUpdateFlag( @ProtoNumber(1) @JvmField val msgUpdateSnsFlag: List<SnsUpdateOneFlag> = emptyList(), ) : ProtoBuf @Serializable internal class SnsUpdateItem( @ProtoNumber(1) @JvmField val updateSnsType: Int = 0, @ProtoNumber(2) @JvmField val value: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class SnsUpdateOneFlag( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val id: Long = 0L, @ProtoNumber(3) @JvmField val flag: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x28 { internal class SubMsgType0x28 : ProtoBuf { @Serializable internal class FollowList( @ProtoNumber(1) @JvmField val puin: Long = 0L, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val seqno: Int = 0, @ProtoNumber(135) @JvmField val disableCancelChat: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val msgRspFollowlist: RspFollowList? = null, @ProtoNumber(3) @JvmField val msgRspTypelist: RspTypeList? = null, ) : ProtoBuf @Serializable internal class RspFollowList( @ProtoNumber(1) @JvmField val msgFollowlist: List<FollowList> = emptyList(), ) : ProtoBuf @Serializable internal class RspTypeList( @ProtoNumber(1) @JvmField val msgTypelist: List<TypeList> = emptyList(), ) : ProtoBuf @Serializable internal class TypeList( @ProtoNumber(1) @JvmField val puin: Long = 0L, @ProtoNumber(2) @JvmField val flag: Int = 0, @ProtoNumber(3) @JvmField val type: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x30 { internal class SubMsgType0x30 : ProtoBuf { @Serializable internal class BlockListNotify( @ProtoNumber(1) @JvmField val msgBlockUinInfo: List<BlockUinInfo> = emptyList(), @ProtoNumber(2) @JvmField val uint64DelUin: List<Long> = emptyList(), ) : ProtoBuf @Serializable internal class BlockUinInfo( @ProtoNumber(1) @JvmField val blockUin: Long = 0L, @ProtoNumber(2) @JvmField val sourceId: Int = 0, @ProtoNumber(3) @JvmField val sourceSubId: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val msgS2cBlocklistNotify: BlockListNotify? = null, ) : ProtoBuf } } internal class Submsgtype0x31 { internal class Submsgtype0x31 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val flag: Int = 0, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val bindUin: Long = 0L, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(4) @JvmField val time: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x35 { internal class Submsgtype0x35 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val bubbleTimestamp: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x3b { internal class Submsgtype0x3b : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val userShowFlag: Int = 0, @ProtoNumber(4) @JvmField val memberLevelChanged: Int = 0, @ProtoNumber(5) @JvmField val officemode: Int = 0, @ProtoNumber(7) @JvmField val memberLevelNewChanged: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x3d { internal class SttResultPush : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val msgPttResp: TransPttResp? = null, @ProtoNumber(3) @JvmField val msgPttShardResp: TransPttShardRsp? = null, ) : ProtoBuf @Serializable internal class TextRsp( @ProtoNumber(4) @JvmField val totalLen: Int = 0, @ProtoNumber(5) @JvmField val seq: Int = 0, @ProtoNumber(6) @JvmField val pos: Int = 0, @ProtoNumber(7) @JvmField val len: Int = 0, @ProtoNumber(8) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY ) : ProtoBuf @Serializable internal class TransPttResp( @ProtoNumber(1) @JvmField val sessionid: Long = 0L, @ProtoNumber(2) @JvmField val pttType: Int = 0, @ProtoNumber(3) @JvmField val errorCode: Int = 0, @ProtoNumber(4) @JvmField val totalLen: Int = 0, @ProtoNumber(5) @JvmField val seq: Int = 0, @ProtoNumber(6) @JvmField val pos: Int = 0, @ProtoNumber(7) @JvmField val len: Int = 0, @ProtoNumber(8) @JvmField val text: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val senderUin: Long = 0L, @ProtoNumber(10) @JvmField val receiverUin: Long = 0L, @ProtoNumber(11) @JvmField val fileID: Int = 0, @ProtoNumber(12) @JvmField val filemd5: String = "", @ProtoNumber(13) @JvmField val filePath: String = "", ) : ProtoBuf @Serializable internal class TransPttShardRsp( @ProtoNumber(1) @JvmField val sessionid: Long = 0L, @ProtoNumber(2) @JvmField val pttType: Int = 0, @ProtoNumber(3) @JvmField val errorCode: Int = 0, @ProtoNumber(9) @JvmField val senderUin: Long = 0L, @ProtoNumber(10) @JvmField val receiverUin: Long = 0L, @ProtoNumber(11) @JvmField val fileID: Int = 0, @ProtoNumber(12) @JvmField val filemd5: String = "", @ProtoNumber(13) @JvmField val filePath: String = "", @ProtoNumber(21) @JvmField val totalSeq: Int = 0, @ProtoNumber(22) @JvmField val boolIsTotalEnd: Boolean = false, @ProtoNumber(23) @JvmField val boolIsCurEnd: Boolean = false, @ProtoNumber(30) @JvmField val curTextRsp: TextRsp? = null, @ProtoNumber(31) @JvmField val allTextRsp: TextRsp? = null ) : ProtoBuf } } internal class Submsgtype0x3e { internal class Submsgtype0x3e : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subcmd: Int = 0, @ProtoNumber(2) @JvmField val random: Int = 0, @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val device: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val sid: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x3f { internal class SubMsgType0x3f : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgPubunikey: List<PubUniKey> = emptyList(), ) : ProtoBuf @Serializable internal class PubUniKey( @ProtoNumber(1) @JvmField val fromPubUin: Long = 0L, @ProtoNumber(2) @JvmField val qwMsgId: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0x40 { internal class SubMsgType0x40 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val vUuid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fromUin: Long = 0L, @ProtoNumber(3) @JvmField val toUin: Long = 0L, @ProtoNumber(4) @JvmField val state: Int = 0, @ProtoNumber(11) @JvmField val opertype: Int = 0, @ProtoNumber(12) @JvmField val fromphonenum: String = "", ) : ProtoBuf } } internal class Submsgtype0x41 { internal class MsgType0x210SubMsgType0x41 : ProtoBuf { @Serializable internal class GameRsultMsg( @ProtoNumber(1) @JvmField val gameName: String = "", @ProtoNumber(2) @JvmField val gamePic: String = "", @ProtoNumber(3) @JvmField val moreInfo: String = "", @ProtoNumber(4) @JvmField val msgGameRsts: List<UinResult> = emptyList(), @ProtoNumber(5) @JvmField val gameSubheading: String = "", @ProtoNumber(6) @JvmField val uin: Long = 0L, @ProtoNumber(7) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class UinResult( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val grade: Int = 0, @ProtoNumber(4) @JvmField val score: String = "", ) : ProtoBuf } } internal class Submsgtype0x42 { internal class Submsgtype0x42 : ProtoBuf { @Serializable internal class GameStatusSync( @ProtoNumber(1) @JvmField val gameAppid: Int = 0, @ProtoNumber(2) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x44 { internal class Submsgtype0x44 : ProtoBuf { @Serializable internal class ClearCountMsg( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val time: Int = 0, @ProtoNumber(3) @JvmField val processflag: Int = 0, @ProtoNumber(4) @JvmField val updateflag: Int = 0, ) : ProtoBuf @Serializable internal class DelMsgNotify( @ProtoNumber(1) @JvmField val sequence: Long = 0L ) : ProtoBuf @Serializable internal class FriendSyncMsg( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val fuin: Long = 0L, @ProtoNumber(3) @JvmField val processtype: Int = 0, @ProtoNumber(4) @JvmField val time: Int = 0, @ProtoNumber(5) @JvmField val processflag: Int = 0, @ProtoNumber(6) @JvmField val sourceid: Int = 0, @ProtoNumber(7) @JvmField val sourcesubid: Int = 0, @ProtoNumber(8) @JvmField val strWording: List<String> = emptyList(), ) : ProtoBuf @Serializable internal class GeneralNotify( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgSeq: Long = 0L ) : ProtoBuf @Serializable internal class GroupSyncMsg( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgSeq: Long = 0L, @ProtoNumber(3) @JvmField val grpCode: Long = 0L, @ProtoNumber(4) @JvmField val gaCode: Long = 0L, @ProtoNumber(5) @JvmField val optUin1: Long = 0L, @ProtoNumber(6) @JvmField val optUin2: Long = 0L, @ProtoNumber(7) @JvmField val msgBuf: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val authKey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val msgStatus: Int = 0, @ProtoNumber(10) @JvmField val actionUin: Long = 0L, @ProtoNumber(11) @JvmField val actionTime: Long = 0L, @ProtoNumber(12) @JvmField val curMaxMemCount: Int = 0, @ProtoNumber(13) @JvmField val nextMaxMemCount: Int = 0, @ProtoNumber(14) @JvmField val curMemCount: Int = 0, @ProtoNumber(15) @JvmField val reqSrcId: Int = 0, @ProtoNumber(16) @JvmField val reqSrcSubId: Int = 0, @ProtoNumber(17) @JvmField val inviterRole: Int = 0, @ProtoNumber(18) @JvmField val extAdminNum: Int = 0, @ProtoNumber(19) @JvmField val processflag: Int = 0, ) : ProtoBuf @Serializable internal class ModifySyncMsg( @ProtoNumber(1) @JvmField val time: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgFriendMsgSync: FriendSyncMsg? = null, @ProtoNumber(2) @JvmField val msgGroupMsgSync: GroupSyncMsg? = null, @ProtoNumber(3) @JvmField val msgCleanCountMsg: ClearCountMsg? = null, @ProtoNumber(4) @JvmField val msgModifyMsgSync: ModifySyncMsg? = null, @ProtoNumber(5) @JvmField val msgWaitingMsgSync: WaitingSyncMsg? = null, @ProtoNumber(7) @JvmField val msgDelMsgNotify: DelMsgNotify? = null, @ProtoNumber(8) @JvmField val msgGeneralNotify: GeneralNotify? = null ) : ProtoBuf @Serializable internal class WaitingSyncMsg( @ProtoNumber(1) @JvmField val time: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x48 { @Serializable internal class RecommendDeviceLock( @ProtoNumber(1) @JvmField val canCancel: Boolean = false, @ProtoNumber(2) @JvmField val wording: String = "", @ProtoNumber(3) @JvmField val title: String = "", @ProtoNumber(4) @JvmField val secondTitle: String = "", @ProtoNumber(5) @JvmField val thirdTitle: String = "", @ProtoNumber(6) @JvmField val wordingList: List<String> = emptyList(), ) : ProtoBuf } internal class Submsgtype0x4a { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val secCmd: Int = 0, @ProtoNumber(2) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } internal class Submsgtype0x4b { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val albumid: String = "", @ProtoNumber(2) @JvmField val coverUrl: String = "", @ProtoNumber(3) @JvmField val albumName: String = "", @ProtoNumber(4) @JvmField val opuin: Long = 0L, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(5) @JvmField val time: Int = 0, @ProtoNumber(6) @JvmField val picCnt: Int = 0, @ProtoNumber(7) @JvmField val pushMsgHelper: String = "", @ProtoNumber(8) @JvmField val pushMsgAlbum: String = "", @ProtoNumber(9) @JvmField val usrTotal: Int = 0, @ProtoNumber(10) @JvmField val uint64User: List<Long> = emptyList(), ) : ProtoBuf internal class Submsgtype0x4b : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val albumid: String = "", @ProtoNumber(2) @JvmField val coverUrl: String = "", @ProtoNumber(3) @JvmField val albumName: String = "", @ProtoNumber(4) @JvmField val opuin: Long = 0L, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(5) @JvmField val time: Int = 0, @ProtoNumber(6) @JvmField val picCnt: Int = 0, @ProtoNumber(7) @JvmField val pushMsgHelper: String = "", @ProtoNumber(8) @JvmField val pushMsgAlbum: String = "", @ProtoNumber(9) @JvmField val usrTotal: Int = 0, @ProtoNumber(10) @JvmField val uint64User: List<Long> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x4e { internal class Submsgtype0x4e : ProtoBuf { @Serializable internal class GroupBulletin( @ProtoNumber(1) @JvmField val msgContent: List<Content> = emptyList(), ) : ProtoBuf { @Serializable internal class Content( @ProtoNumber(1) @JvmField val feedid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(3) @JvmField val time: Int = 0, ) : ProtoBuf } @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val groupId: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val appid: Int = 0, @ProtoNumber(4) @JvmField val msgGroupBulletin: GroupBulletin? = null, ) : ProtoBuf } } internal class Submsgtype0x54 { internal class Submsgtype0x54 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val peerType: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val peerUin: Long = 0L, @ProtoNumber(3) @JvmField val taskList: List<TaskInfo> = emptyList(), ) : ProtoBuf { @Serializable internal class TaskInfo( @ProtoNumber(1) @JvmField val taskId: Int = 0, ) : ProtoBuf } } } internal class Submsgtype0x60 { internal class SubMsgType0x60 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val pushcmd: Int = 0, @ProtoNumber(2) @JvmField val int64Ts: Long = 0L, @ProtoNumber(3) @JvmField val ssid: String = "", @ProtoNumber(4) @JvmField val content: String = "", ) : ProtoBuf } } internal class Submsgtype0x63 { internal class Submsgtype0x63 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val roomid: Long = 0L, @ProtoNumber(2) @JvmField val seq: Int = 0, @ProtoNumber(3) @JvmField val url: String = "", @ProtoNumber(4) @JvmField val data: String = "", ) : ProtoBuf } } internal class Submsgtype0x65 { internal class SubMsgType0x65 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val cmd: Int = 0, @ProtoNumber(2) @JvmField val msgExpiredPkg: MsgExpiredPkg? = null, ) : ProtoBuf @Serializable internal class MsgExpiredPkg( @ProtoNumber(1) @JvmField val platform: Int = 0, @ProtoNumber(2) @JvmField val expirePkg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val predownPkg: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x66 { internal class Submsgtype0x66 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val pushData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val timestamp: Int = 0, @ProtoNumber(4) @JvmField val notifyText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val pushFlag: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x67 { internal class Submsgtype0x67 : ProtoBuf { @Serializable internal class GroupInfo( @ProtoNumber(1) @JvmField val groupCode: Long = 0L, @ProtoNumber(2) @JvmField val groupName: String = "", @ProtoNumber(3) @JvmField val groupMemo: String = "", @ProtoNumber(4) @JvmField val memberNum: Int = 0, @ProtoNumber(5) @JvmField val groupType: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgGrpinfo: List<GroupInfo> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x69 { @Serializable internal class Submsgtype0x69( @ProtoNumber(1) @JvmField val appid: Int = 0, @ProtoNumber(2) @JvmField val boolDisplayReddot: Boolean = false, @ProtoNumber(3) @JvmField val number: Int = 0, @ProtoNumber(4) @JvmField val reason: Int = 0, @ProtoNumber(5) @JvmField val lastTime: Int = 0, @ProtoNumber(6) @JvmField val cmdUin: Long = 0L, @ProtoNumber(7) @JvmField val faceUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val customBuffer: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val expireTime: Int = 0, @ProtoNumber(10) @JvmField val cmdUinType: Int = 0, @ProtoNumber(11) @JvmField val reportType: Int = 0, @ProtoNumber(12) @JvmField val boolTestEnv: Boolean = false, ) : ProtoBuf } internal class Submsgtype0x6b { internal class SubMsgType0x6b : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val toUin: Long = 0L, @ProtoNumber(2) @JvmField val tipsContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val yesText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val noText: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x6f { internal class SubMsgType0x6f : ProtoBuf { @Serializable internal class AddFriendSource( @ProtoNumber(1) @JvmField val source: Int = 0, @ProtoNumber(2) @JvmField val subSource: Int = 0, ) : ProtoBuf @Serializable internal class AddQimFriendNotifyToQQ( @ProtoNumber(1) @JvmField val opType: Int = 0, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val gender: Int = 0, @ProtoNumber(4) @JvmField val smartRemark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val longnick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val storysTotalNum: Long = 0L, @ProtoNumber(7) @JvmField val caresCount: Long = 0L, @ProtoNumber(8) @JvmField val fansCount: Long = 0L, @ProtoNumber(9) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val srcWording: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class AddQimNotLoginFrdNotifyToQQ( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val gender: Int = 0, @ProtoNumber(4) @JvmField val age: Int = 0, @ProtoNumber(5) @JvmField val coverstory: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val storysTotalNum: Long = 0L, @ProtoNumber(7) @JvmField val msgVideoInfo: List<VideoInfo> = emptyList(), @ProtoNumber(8) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val qqUin: Long = 0L, ) : ProtoBuf @Serializable internal class BirthdayReminderPush( @ProtoNumber(2004) @JvmField val reminderWording: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class FanpaiziNotify( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val fromNick: String = "", @ProtoNumber(3) @JvmField val tipsContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ForwardBody( @ProtoNumber(1) @JvmField val notifyType: Int = 0, @ProtoNumber(2) @JvmField val opType: Int = 0, @ProtoNumber(2000) @JvmField val msgFanpanziNotify: FanpaiziNotify? = null, @ProtoNumber(2001) @JvmField val msgMcardNotificationLike: MCardNotificationLike? = null, @ProtoNumber(2002) @JvmField val msgVipInfoNotify: VipInfoNotify? = null, @ProtoNumber(2003) @JvmField val msgPushLostDevFound: PushLostDevFound? = null, @ProtoNumber(2004) @JvmField val msgBirthdayReminderPush: BirthdayReminderPush? = null, @ProtoNumber(2005) @JvmField val msgPushLostDev: PushLostDevFound? = null, @ProtoNumber(2007) @JvmField val msgBabyqRewardInfo: RewardInfo? = null, @ProtoNumber(2008) @JvmField val msgHotFriendNotify: HotFriendNotify? = null, @ProtoNumber(2009) @JvmField val msgPushQimRecommend: QimRecomendMsg? = null, @ProtoNumber(2010) @JvmField val msgModQimFriend: QimFriendNotify? = null, @ProtoNumber(2011) @JvmField val msgModQimFriendToQq: QimFriendNotifyToQQ? = null, ) : ProtoBuf @Serializable internal class GPS( @ProtoNumber(1) @JvmField val int32Lat: Int = 900000000, @ProtoNumber(2) @JvmField val int32Lon: Int = 900000000, @ProtoNumber(3) @JvmField val int32Alt: Int = -10000000, @ProtoNumber(4) @JvmField val int32Type: Int = 0, ) : ProtoBuf @Serializable internal class HotFriendNotify( @ProtoNumber(1) @JvmField val dstUin: Long = 0L, @ProtoNumber(2) @JvmField val praiseHotLevel: Int = 0, @ProtoNumber(3) @JvmField val chatHotLevel: Int = 0, @ProtoNumber(4) @JvmField val praiseHotDays: Int = 0, @ProtoNumber(5) @JvmField val chatHotDays: Int = 0, @ProtoNumber(6) @JvmField val closeLevel: Int = 0, @ProtoNumber(7) @JvmField val closeDays: Int = 0, @ProtoNumber(8) @JvmField val praiseFlag: Int = 0, @ProtoNumber(9) @JvmField val chatFlag: Int = 0, @ProtoNumber(10) @JvmField val closeFlag: Int = 0, @ProtoNumber(11) @JvmField val notifyTime: Long = 0L, @ProtoNumber(12) @JvmField val lastPraiseTime: Long = 0L, @ProtoNumber(13) @JvmField val lastChatTime: Long = 0L, ) : ProtoBuf @Serializable internal class MCardNotificationLike( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val counterTotal: Int = 0, @ProtoNumber(3) @JvmField val counterNew: Int = 0, @ProtoNumber(4) @JvmField val wording: String = "", ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgModInfos: List<ForwardBody> = emptyList(), ) : ProtoBuf @Serializable internal class PushLostDevFound( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(3) @JvmField val devTime: Int = 0, @ProtoNumber(6) @JvmField val din: Long = 0L, ) : ProtoBuf @Serializable internal class QimFriendNotify( @ProtoNumber(1) @JvmField val opType: Int = 0, @ProtoNumber(2) @JvmField val uint64Uins: List<Long> = emptyList(), @ProtoNumber(3) @JvmField val fansUnreadCount: Long = 0L, @ProtoNumber(4) @JvmField val fansTotalCount: Long = 0L, @ProtoNumber(5) @JvmField val pushTime: Long = 0L, @ProtoNumber(6) @JvmField val bytesMobiles: List<ByteArray> = emptyList(), ) : ProtoBuf @Serializable internal class QimFriendNotifyToQQ( @ProtoNumber(1) @JvmField val notifyType: Int = 0, @ProtoNumber(2) @JvmField val msgAddNotifyToQq: AddQimFriendNotifyToQQ? = null, @ProtoNumber(3) @JvmField val msgUpgradeNotify: UpgradeQimFriendNotify? = null, @ProtoNumber(4) @JvmField val msgAddNotLoginFrdNotifyToQq: AddQimNotLoginFrdNotifyToQQ? = null, ) : ProtoBuf @Serializable internal class QimRecomendInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val reason: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val gender: Int = 0, @ProtoNumber(5) @JvmField val longnick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val alghbuff: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val age: Int = 0, @ProtoNumber(8) @JvmField val source: Int = 0, @ProtoNumber(9) @JvmField val sourceReason: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val msgIosSource: AddFriendSource? = null, @ProtoNumber(11) @JvmField val msgAndroidSource: AddFriendSource? = null, ) : ProtoBuf @Serializable internal class QimRecomendMsg( @ProtoNumber(1) @JvmField val msgRecomendList: List<QimRecomendInfo> = emptyList(), @ProtoNumber(2) @JvmField val timestamp: Long = 0L, ) : ProtoBuf @Serializable internal class RewardInfo( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val jmpUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val cookies: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val jmpWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val optWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val optUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val faceAddonId: Long = 0L, @ProtoNumber(9) @JvmField val iconUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val toastWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val reportType: Int = 0, ) : ProtoBuf @Serializable internal class UpgradeQimFriendNotify( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class VideoInfo( @ProtoNumber(1) @JvmField val vid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val videoCoverUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class VipInfoNotify( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val vipLevel: Int = 0, @ProtoNumber(3) @JvmField val vipIdentify: Int = 0, @ProtoNumber(4) @JvmField val ext: Int = 0, @ProtoNumber(5) @JvmField val extString: String = "", @ProtoNumber(6) @JvmField val redFlag: Int = 0, @ProtoNumber(7) @JvmField val disableRedEnvelope: Int = 0, @ProtoNumber(8) @JvmField val redpackId: Int = 0, @ProtoNumber(9) @JvmField val redpackName: String = "", ) : ProtoBuf } } internal class Submsgtype0x71 { internal class Submsgtype0x71 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgAppInfo: List<ReportAppInfo> = emptyList(), @ProtoNumber(2) @JvmField val uiUin: Long = 0L, ) : ProtoBuf @Serializable internal class RedDisplayInfo( @ProtoNumber(1) @JvmField val msgRedTypeInfo: List<RedTypeInfo> = emptyList(), @ProtoNumber(2) @JvmField val msgTabDisplayInfo: RedTypeInfo? = null, ) : ProtoBuf @Serializable internal class RedTypeInfo( @ProtoNumber(1) @JvmField val redType: Int = 0, @ProtoNumber(2) @JvmField val redContent: String = "", @ProtoNumber(3) @JvmField val redDesc: String = "", @ProtoNumber(4) @JvmField val redPriority: Int = 0, ) : ProtoBuf @Serializable internal class ReportAppInfo( @ProtoNumber(1) @JvmField val appId: Int = 0, @ProtoNumber(2) @JvmField val int32NewFlag: Int = 0, @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val buffer: String = "", @ProtoNumber(5) @JvmField val path: String = "", @ProtoNumber(6) @JvmField val pushRedTs: Int = 0, @ProtoNumber(7) @JvmField val mission: String = "", @ProtoNumber(8) @JvmField val int32Appset: Int = 0, @ProtoNumber(9) @JvmField val int32Num: Int = 0, @ProtoNumber(10) @JvmField val iconUrl: String = "", @ProtoNumber(11) @JvmField val int32IconFlag: Int = 0, @ProtoNumber(12) @JvmField val int32IconType: Int = 0, @ProtoNumber(13) @JvmField val duration: Int = 0, @ProtoNumber(14) @JvmField val msgVersionInfo: ReportVersion? = null, @ProtoNumber(15) @JvmField val androidAppId: Int = 0, @ProtoNumber(16) @JvmField val iosAppId: Int = 0, @ProtoNumber(17) @JvmField val androidPath: String = "", @ProtoNumber(18) @JvmField val iosPath: String = "", @ProtoNumber(19) @JvmField val int32MissionLevel: Int = 0, @ProtoNumber(20) @JvmField val msgDisplayDesc: RedDisplayInfo? = null, ) : ProtoBuf @Serializable internal class ReportVersion( @ProtoNumber(1) @JvmField val int32PlantId: Int = 0, @ProtoNumber(2) @JvmField val boolAllver: Boolean = false, @ProtoNumber(3) @JvmField val strVersion: List<String> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x72 { internal class SubMsgType0x72 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val urgency: Int = 0, @ProtoNumber(3) @JvmField val templateNo: Int = 0, @ProtoNumber(4) @JvmField val content: String = "", @ProtoNumber(5) @JvmField val infoDate: String = "", ) : ProtoBuf } } internal class Submsgtype0x76 { internal class SubMsgType0x76 : ProtoBuf { @Serializable internal class BirthdayNotify( @ProtoNumber(1) @JvmField val msgOneFriend: List<OneBirthdayFriend> = emptyList(), @ProtoNumber(2) @JvmField val reserved: Int = 0, @ProtoNumber(3) @JvmField val giftMsg: List<OneGiftMessage> = emptyList(), @ProtoNumber(4) @JvmField val topPicUrl: String = "", @ProtoNumber(5) @JvmField val extend: String = "", ) : ProtoBuf @Serializable internal class GeoGraphicNotify( @ProtoNumber(1) @JvmField val localCity: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val msgOneFriend: List<OneGeoGraphicFriend> = emptyList(), ) : ProtoBuf @Serializable internal class MemorialDayNotify( @ProtoNumber(1) @JvmField val anniversaryInfo: List<OneMemorialDayInfo> = emptyList(), ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val boolStrongNotify: Boolean = false, @ProtoNumber(3) @JvmField val pushTime: Int = 0, @ProtoNumber(4) @JvmField val msgGeographicNotify: GeoGraphicNotify? = null, @ProtoNumber(5) @JvmField val msgBirthdayNotify: BirthdayNotify? = null, @ProtoNumber(6) @JvmField val notifyWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val msgMemorialdayNotify: MemorialDayNotify? = null, ) : ProtoBuf @Serializable internal class OneBirthdayFriend( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val boolLunarBirth: Boolean = false, @ProtoNumber(3) @JvmField val birthMonth: Int = 0, @ProtoNumber(4) @JvmField val birthDate: Int = 0, @ProtoNumber(5) @JvmField val msgSendTime: Long = 0L, @ProtoNumber(6) @JvmField val birthYear: Int = 0, ) : ProtoBuf @Serializable internal class OneGeoGraphicFriend( @ProtoNumber(1) @JvmField val uin: Long = 0L, ) : ProtoBuf @Serializable internal class OneGiftMessage( @ProtoNumber(1) @JvmField val giftId: Int = 0, @ProtoNumber(2) @JvmField val giftName: String = "", @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val giftUrl: String = "", @ProtoNumber(5) @JvmField val price: Int = 0, @ProtoNumber(6) @JvmField val playCnt: Int = 0, @ProtoNumber(7) @JvmField val backgroundColor: String = "", ) : ProtoBuf @Serializable internal class OneMemorialDayInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val type: Long = 0, @ProtoNumber(3) @JvmField val memorialTime: Int = 0, @ProtoNumber(11) @JvmField val mainWordingNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val mainWordingEvent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val subWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val greetings: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val friendGender: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x78 { internal class Submsgtype0x78 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val version: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x7a { internal class Submsgtype0x7a : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fromUin: Long = 0L, @ProtoNumber(3) @JvmField val nick: String = "", @ProtoNumber(4) @JvmField val discussUin: Long = 0L, @ProtoNumber(5) @JvmField val discussNick: String = "", @ProtoNumber(6) @JvmField val seq: Long = 0L, @ProtoNumber(7) @JvmField val atTime: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0x7c { internal class Submsgtype0x7c : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val int32Cmd: Int = 0, @ProtoNumber(3) @JvmField val stringCmdExt: List<String> = emptyList(), @ProtoNumber(4) @JvmField val seq: Long = 0L, @ProtoNumber(5) @JvmField val stringSeqExt: List<String> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x7e { internal class Submsgtype0x7e : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val notice: String = "", @ProtoNumber(2) @JvmField val msgOnlinePush: WalletMsgPush? = null, ) : ProtoBuf @Serializable internal class WalletMsgPush( @ProtoNumber(1) @JvmField val action: Int = 0, @ProtoNumber(2) @JvmField val timestamp: Int = 0, @ProtoNumber(3) @JvmField val extend: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val serialno: String = "", @ProtoNumber(5) @JvmField val billno: String = "", @ProtoNumber(6) @JvmField val appinfo: String = "", @ProtoNumber(7) @JvmField val amount: Int = 0, @ProtoNumber(8) @JvmField val jumpurl: String = "", ) : ProtoBuf } } internal class Submsgtype0x83 { internal class SubMsgType0x83 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgParams: List<MsgParams> = emptyList(), @ProtoNumber(2) @JvmField val seq: Long = 0L, @ProtoNumber(3) @JvmField val groupId: Long = 0L, ) : ProtoBuf @Serializable internal class MsgParams( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val fromUin: Long = 0L, @ProtoNumber(3) @JvmField val toUin: Long = 0L, @ProtoNumber(4) @JvmField val dataString: String = "", @ProtoNumber(5) @JvmField val data: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf internal class MsgRep : ProtoBuf } } internal class Submsgtype0x85 { internal class SubMsgType0x85 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val showLastest: Int = 0, @ProtoNumber(2) @JvmField val senderUin: Long = 0L, @ProtoNumber(3) @JvmField val receiverUin: Long = 0L, @ProtoNumber(4) @JvmField val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val authkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val pcBody: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val icon: Int = 0, @ProtoNumber(9) @JvmField val random: Int = 0, @ProtoNumber(10) @JvmField val redSenderUin: Long = 0L, @ProtoNumber(11) @JvmField val type: Int = 0, @ProtoNumber(12) @JvmField val subType: Int = 0, @ProtoNumber(13) @JvmField val jumpurl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x86 { internal class SubMsgType0x86 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val notifyFlag: Int = 0, @ProtoNumber(2) @JvmField val notifyWording: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0x87 { internal class SubMsgType0x87 : ProtoBuf { @Serializable internal class CloneInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val remark: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val originNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val showInAio: Int = 0, @ProtoNumber(5) @JvmField val toUin: Long = 0L, @ProtoNumber(6) @JvmField val toNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val srcGender: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val friendMsgTypeFlag: Long = 0L, @ProtoNumber(2) @JvmField val msgMsgNotify: List<MsgNotify> = emptyList(), @ProtoNumber(3) @JvmField val msgMsgNotifyUnread: MsgNotifyUnread? = null, ) : ProtoBuf @Serializable internal class MsgNotify( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val fuin: Long = 0L, @ProtoNumber(3) @JvmField val time: Int = 0, @ProtoNumber(4) @JvmField val reqsubtype: Int = 0, @ProtoNumber(5) @JvmField val maxCount: Int = 0, @ProtoNumber(6) @JvmField val msgCloneInfo: CloneInfo? = null, ) : ProtoBuf @Serializable internal class MsgNotifyUnread( @ProtoNumber(1) @JvmField val unreadcount: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x89 { internal class Submsgtype0x89 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uiUin: Long = 0L, @ProtoNumber(2) @JvmField val pushRedTs: Int = 0, @ProtoNumber(3) @JvmField val msgNumRed: List<NumRedBusiInfo> = emptyList(), ) : ProtoBuf @Serializable internal class NumRedBusiInfo( @ProtoNumber(1) @JvmField val clientVerBegin: String = "", @ProtoNumber(2) @JvmField val clientVerEnd: String = "", @ProtoNumber(3) @JvmField val platId: Int = 0, @ProtoNumber(4) @JvmField val appId: Int = 0, @ProtoNumber(5) @JvmField val androidAppId: Int = 0, @ProtoNumber(6) @JvmField val iosAppId: Int = 0, @ProtoNumber(7) @JvmField val path: String = "", @ProtoNumber(8) @JvmField val androidPath: String = "", @ProtoNumber(9) @JvmField val iosPath: String = "", @ProtoNumber(10) @JvmField val missionid: String = "", @ProtoNumber(11) @JvmField val msgid: Long = 0L, @ProtoNumber(12) @JvmField val status: Int = 0, @ProtoNumber(13) @JvmField val expireTime: Int = 0, @ProtoNumber(14) @JvmField val int32Appset: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x8d { internal class SubMsgType0x8d : ProtoBuf { @Serializable internal class ChannelNotify( @ProtoNumber(1) @JvmField val channelId: Long = 0L, @ProtoNumber(2) @JvmField val channelName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val topArticleIdList: List<Long> = emptyList(), ) : ProtoBuf @Serializable internal class CommentFeeds( @ProtoNumber(1) @JvmField val feedsOwner: Long = 0L, @ProtoNumber(2) @JvmField val feedsId: Long = 0L, @ProtoNumber(3) @JvmField val commentUin: Long = 0L, @ProtoNumber(4) @JvmField val commentId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val replyUin: Long = 0L, @ProtoNumber(6) @JvmField val replyId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val commentInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val feedsSubject: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class DeleteComment( @ProtoNumber(1) @JvmField val feedsOwner: Long = 0L, @ProtoNumber(2) @JvmField val feedsId: Long = 0L, @ProtoNumber(3) @JvmField val commentUin: Long = 0L, @ProtoNumber(4) @JvmField val commentId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val replyUin: Long = 0L, @ProtoNumber(6) @JvmField val replyId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val deleteUin: Long = 0L, ) : ProtoBuf @Serializable internal class DeleteFeeds( @ProtoNumber(1) @JvmField val feedsOwner: Long = 0L, @ProtoNumber(2) @JvmField val feedsId: Long = 0L, @ProtoNumber(3) @JvmField val deleteUin: Long = 0L, ) : ProtoBuf @Serializable internal class LikeFeeds( @ProtoNumber(1) @JvmField val feedsOwner: Long = 0L, @ProtoNumber(2) @JvmField val feedsId: Long = 0L, @ProtoNumber(3) @JvmField val likeUin: Long = 0L, @ProtoNumber(4) @JvmField val feedsSubject: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgNotifyInfos: List<NotifyBody> = emptyList(), @ProtoNumber(2) @JvmField val redSpotNotifyBody: RedSpotNotifyBody? = null, ) : ProtoBuf @Serializable internal class NotifyBody( @ProtoNumber(1) @JvmField val notifyType: Int = 0, @ProtoNumber(2) @JvmField val seq: Int = 0, @ProtoNumber(3) @JvmField val pushTime: Int = 0, @ProtoNumber(10) @JvmField val msgPublishFeeds: PublishFeeds? = null, @ProtoNumber(11) @JvmField val msgCommentFeeds: CommentFeeds? = null, @ProtoNumber(12) @JvmField val msgLikeFeeds: LikeFeeds? = null, @ProtoNumber(13) @JvmField val msgDeleteFeeds: DeleteFeeds? = null, @ProtoNumber(14) @JvmField val msgDeleteComment: DeleteComment? = null, ) : ProtoBuf @Serializable internal class PublishFeeds( @ProtoNumber(1) @JvmField val feedsOwner: Long = 0L, @ProtoNumber(2) @JvmField val feedsId: Long = 0L, ) : ProtoBuf @Serializable internal class RedSpotNotifyBody( @ProtoNumber(1) @JvmField val time: Int = 0, @ProtoNumber(2) @JvmField val newChannelList: List<Long> = emptyList(), @ProtoNumber(3) @JvmField val guideWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val msgChannelNotify: ChannelNotify? = null, ) : ProtoBuf } } internal class Submsgtype0x8f { internal class Submsgtype0x8f : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgSourceId: SourceID? = null, @ProtoNumber(2) @JvmField val feedsId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val enumMsgType: Int /* enum */ = 1, @ProtoNumber(4) @JvmField val extMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val authorUin: Long = 0L, @ProtoNumber(6) @JvmField val confirmUin: Long = 0L, ) : ProtoBuf @Serializable internal class SourceID( @ProtoNumber(1) @JvmField val sourceType: Int = 0, @ProtoNumber(2) @JvmField val sourceCode: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0x90 { internal class SubMsgType0x90 : ProtoBuf { @Serializable internal class DpNotifyMsgBdoy( @ProtoNumber(1) @JvmField val pid: Int = 0, @ProtoNumber(2) @JvmField val din: Long = 0L, @ProtoNumber(3) @JvmField val msgNotifyInfo: List<NotifyItem> = emptyList(), @ProtoNumber(4) @JvmField val extendInfo: String = "", ) : ProtoBuf @Serializable internal class Head( @ProtoNumber(1) @JvmField val cmd: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgHead: Head? = null, @ProtoNumber(2) @JvmField val msgBody: PushBody? = null, ) : ProtoBuf @Serializable internal class NotifyItem( @ProtoNumber(1) @JvmField val propertyid: Int = 0, ) : ProtoBuf @Serializable internal class OccupyMicrophoneNotifyMsgBody( @ProtoNumber(1) @JvmField val uin: Int = 0, @ProtoNumber(2) @JvmField val din: Long = 0L, ) : ProtoBuf @Serializable internal class PushBody( @ProtoNumber(1) @JvmField val msgDpNotifyBody: DpNotifyMsgBdoy? = null, @ProtoNumber(2) @JvmField val msgOccupyMicrophoneBody: OccupyMicrophoneNotifyMsgBody? = null, ) : ProtoBuf } } internal class Submsgtype0x92 { internal class SubMsgType0x92 : ProtoBuf { @Serializable internal class CrmS2CMsgHead( @ProtoNumber(1) @JvmField val crmSubCmd: Int = 0, @ProtoNumber(2) @JvmField val headLen: Int = 0, @ProtoNumber(3) @JvmField val verNo: Int = 0, @ProtoNumber(4) @JvmField val kfUin: Long = 0L, @ProtoNumber(5) @JvmField val seq: Int = 0, @ProtoNumber(6) @JvmField val packNum: Int = 0, @ProtoNumber(7) @JvmField val curPack: Int = 0, @ProtoNumber(8) @JvmField val bufSig: String = "", ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val msgCrmCommonHead: CrmS2CMsgHead? = null, @ProtoNumber(100) @JvmField val msgPushEmanMsg: S2CPushEmanMsgToC? = null, ) : ProtoBuf { @Serializable internal class S2CPushEmanMsgToC( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val xml: String = "", ) : ProtoBuf } } } internal class Submsgtype0x93 { internal class Submsgtype0x93 : ProtoBuf { @Serializable internal class LiteMailIndexInfo( @ProtoNumber(1) @JvmField val feedsId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val msgSourceId: SourceID? = null, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgUmcChanged: UnreadMailCountChanged? = null, @ProtoNumber(3) @JvmField val msgStateChanged: StateChangeNotify? = null, ) : ProtoBuf @Serializable internal class SourceID( @ProtoNumber(1) @JvmField val sourceType: Int = 0, @ProtoNumber(2) @JvmField val sourceCode: Long = 0L, ) : ProtoBuf @Serializable internal class StateChangeNotify( @ProtoNumber(1) @JvmField val msgSourceId: SourceID? = null, @ProtoNumber(2) @JvmField val feedsId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val enumMsgType: Int /* enum */ = 1, @ProtoNumber(4) @JvmField val extMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val reqUin: Long = 0L, @ProtoNumber(6) @JvmField val msgLiteMailIndex: List<LiteMailIndexInfo> = emptyList(), ) : ProtoBuf @Serializable internal class UnreadMailCountChanged( @ProtoNumber(1) @JvmField val msgUmc: UnreadMailCountInfo? = null, ) : ProtoBuf @Serializable internal class UnreadMailCountInfo( @ProtoNumber(1) @JvmField val unreadCount: Int = 0, @ProtoNumber(2) @JvmField val dataVersion: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x94 { internal class Submsgtype0x94 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val taskId: Int = 0, @ProtoNumber(2) @JvmField val folderReddotFlag: Int = 0, @ProtoNumber(3) @JvmField val discoverReddotFlag: Int = 0, @ProtoNumber(4) @JvmField val startTs: Int = 0, @ProtoNumber(5) @JvmField val endTs: Int = 0, @ProtoNumber(6) @JvmField val periodOfValidity: Int = 0, @ProtoNumber(7) @JvmField val folderMsg: String = "", @ProtoNumber(8) @JvmField val discountReddotFlag: Int = 0, @ProtoNumber(9) @JvmField val nearbyReddotFlag: Int = 0, @ProtoNumber(10) @JvmField val mineReddotFlag: Int = 0, @ProtoNumber(11) @JvmField val onlyDiscoverReddotFlag: Int = 0, @ProtoNumber(12) @JvmField val onlyDiscountReddotFlag: Int = 0, @ProtoNumber(13) @JvmField val onlyNearbyReddotFlag: Int = 0, @ProtoNumber(14) @JvmField val onlyMineReddotFlag: Int = 0, @ProtoNumber(15) @JvmField val taskType: Int = 0, @ProtoNumber(16) @JvmField val taskInfo: String = "", @ProtoNumber(17) @JvmField val typeName: String = "", @ProtoNumber(18) @JvmField val typeColor: String = "", @ProtoNumber(19) @JvmField val jumpUrl: String = "", ) : ProtoBuf } } internal class Submsgtype0x96 { internal class Submsgtype0x96 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val pushMsg: String = "", @ProtoNumber(2) @JvmField val pushType: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x97 { internal class Submsgtype0x97 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val businessUin: String = "", @ProtoNumber(2) @JvmField val jsonContext: String = "", ) : ProtoBuf } } internal class Submsgtype0x98 { internal class Submsgtype0x98 : ProtoBuf { @Serializable internal class ModBlock( @ProtoNumber(1) @JvmField val op: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val subCmd: Int = 0, @ProtoNumber(3) @JvmField val msgModBlock: ModBlock? = null, ) : ProtoBuf } } internal class Submsgtype0x9b { internal class SubMsgType0x9b : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val appId: Long = 0L, @ProtoNumber(2) @JvmField val mainType: Int = 0, @ProtoNumber(3) @JvmField val subType: Int = 0, @ProtoNumber(4) @JvmField val extMsg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val workflowId: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PbOfficeNotify( @ProtoNumber(1) @JvmField val optUint32MyofficeFlag: Int = 0, @ProtoNumber(2) @JvmField val uint64Appid: List<Long> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x9d { internal class SubMsgType0x9d : ProtoBuf { @Serializable internal class ModuleUpdateNotify( @ProtoNumber(1) @JvmField val moduleId: Int = 0, @ProtoNumber(2) @JvmField val moduleVersion: Int = 0, @ProtoNumber(3) @JvmField val moduleState: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val lolaModuleUpdate: List<ModuleUpdateNotify> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0x9e { internal class SubmsgType0x9e : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val url: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val authKey: Int = 0, ) : ProtoBuf } } internal class Submsgtype0x9f { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val showLastest: Int = 0, @ProtoNumber(2) @JvmField val senderUin: Long = 0L, @ProtoNumber(3) @JvmField val receiverUin: Long = 0L, @ProtoNumber(4) @JvmField val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val authkey: ByteArray = EMPTY_BYTE_ARRAY, @ProtoType(ProtoIntegerType.SIGNED) @ProtoNumber(7) @JvmField val sint32Sessiontype: Int = 0, @ProtoNumber(8) @JvmField val groupUin: Long = 0L, ) : ProtoBuf } internal class Submsgtype0xa0 { internal class Submsgtype0xa0 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val isMassBlessOpen: Int = 0, ) : ProtoBuf } } internal class Submsgtype0xa1 { internal class Submsgtype0xa1 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val qid: Long = 0L, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(3) @JvmField val fixed32UpdateTime: Int = 0, @ProtoNumber(4) @JvmField val teamCreatedDestroied: Int = 0, @ProtoNumber(5) @JvmField val uint64OfficeFaceChangedUins: List<Long> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0xa2 { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val showLastest: Int = 0, @ProtoNumber(2) @JvmField val senderUin: Long = 0L, @ProtoNumber(3) @JvmField val receiverUin: Long = 0L, @ProtoNumber(4) @JvmField val senderRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val receiverRichContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val authkey: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } internal class Submsgtype0xa4 { internal class Submsgtype0xa4 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val brief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val url: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xa8 { internal class SubMsgType0xa8 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val actionType: Int = 0, @ProtoNumber(2) @JvmField val actionSubType: Int = 0, @ProtoNumber(3) @JvmField val msgSummary: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val extendContent: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xaa { internal class SubMsgType0xaa : ProtoBuf { @Serializable internal class GameTeamMsgBody( @ProtoNumber(1) @JvmField val gameTeamCmd: Int = 0, @ProtoNumber(2) @JvmField val msgTurnOverMessage: GameTeamTurnOverMessage? = null, @ProtoNumber(3) @JvmField val msgStartGameMessage: GameTeamStartGameMessage? = null, @ProtoNumber(4) @JvmField val msgUpdateTeamMessage: GameTeamUpdateTeamMessage? = null, ) : ProtoBuf @Serializable internal class GameTeamStartGameMessage( @ProtoNumber(1) @JvmField val gamedata: String = "", @ProtoNumber(2) @JvmField val platformType: Int = 0, @ProtoNumber(3) @JvmField val title: String = "", @ProtoNumber(4) @JvmField val summary: String = "", @ProtoNumber(5) @JvmField val picUrl: String = "", @ProtoNumber(6) @JvmField val appid: String = "", @ProtoNumber(7) @JvmField val appStoreId: String = "", @ProtoNumber(8) @JvmField val packageName: String = "", @ProtoNumber(9) @JvmField val createMsgTime: Long = 0L, @ProtoNumber(10) @JvmField val expire: Int = 0, @ProtoNumber(11) @JvmField val buildTeamTime: Long = 0L, ) : ProtoBuf @Serializable internal class GameTeamTurnOverMessage( @ProtoNumber(1) @JvmField val teamId: String = "", @ProtoNumber(2) @JvmField val sessionType: Int = 0, @ProtoNumber(3) @JvmField val sourceUin: String = "", @ProtoNumber(4) @JvmField val actionUin: String = "", @ProtoNumber(5) @JvmField val actionType: Int = 0, @ProtoNumber(6) @JvmField val currentCount: Int = 0, @ProtoNumber(7) @JvmField val totalCount: Int = 0, @ProtoNumber(8) @JvmField val createMsgTime: Long = 0L, @ProtoNumber(9) @JvmField val status: Int = 0, @ProtoNumber(10) @JvmField val expire: Int = 0, @ProtoNumber(11) @JvmField val buildTeamTime: Long = 0L, @ProtoNumber(12) @JvmField val leaderUin: String = "", @ProtoNumber(13) @JvmField val uin32LeaderStatus: Int = 0, @ProtoNumber(14) @JvmField val inviteSourceList: List<InviteSource> = emptyList(), ) : ProtoBuf @Serializable internal class GameTeamUpdateTeamMessage( @ProtoNumber(1) @JvmField val teamId: String = "", @ProtoNumber(2) @JvmField val gameId: String = "", @ProtoNumber(3) @JvmField val status: Int = 0, @ProtoNumber(4) @JvmField val modeImg: String = "", @ProtoNumber(5) @JvmField val currentCount: Int = 0, @ProtoNumber(6) @JvmField val createMsgTime: Long = 0L, @ProtoNumber(7) @JvmField val expire: Int = 0, @ProtoNumber(8) @JvmField val buildTeamTime: Long = 0L, @ProtoNumber(9) @JvmField val leaderUin: String = "", @ProtoNumber(10) @JvmField val uin32LeaderStatus: Int = 0, ) : ProtoBuf @Serializable internal class InviteSource( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val src: String = "", ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val cmd: Int = 0, @ProtoNumber(2) @JvmField val msgGameTeamMsg: GameTeamMsgBody? = null, @ProtoNumber(3) @JvmField val msgOnlineDocMsg: OnlineDocMsgBody? = null, ) : ProtoBuf @Serializable internal class OnlineDocMsgBody( @ProtoNumber(1) @JvmField val onlineDocCmd: Int = 0, @ProtoNumber(2) @JvmField val msgPushChangeTitleMessage: OnlineDocPushChangeTitleMessage? = null, @ProtoNumber(3) @JvmField val msgPushNewPadMessage: OnlineDocPushNewPadMessage? = null, @ProtoNumber(4) @JvmField val msgPushPreviewToEdit: OnlineDocPushPreviewToEditMessage? = null, ) : ProtoBuf @Serializable internal class OnlineDocPushChangeTitleMessage( @ProtoNumber(1) @JvmField val domainid: Int = 0, @ProtoNumber(2) @JvmField val localpadid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val lastEditorUin: Long = 0L, @ProtoNumber(5) @JvmField val lastEditorNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val lastEditTime: Long = 0L, ) : ProtoBuf @Serializable internal class OnlineDocPushNewPadMessage( @ProtoNumber(1) @JvmField val padUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val type: Int = 0, @ProtoNumber(3) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val createTime: Long = 0L, @ProtoNumber(5) @JvmField val creatorUin: Long = 0L, @ProtoNumber(6) @JvmField val creatorNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val lastEditorUin: Long = 0L, @ProtoNumber(8) @JvmField val lastEditorNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val lastEditTime: Long = 0L, @ProtoNumber(10) @JvmField val boolPinnedFlag: Boolean = false, @ProtoNumber(11) @JvmField val lastViewerUin: Long = 0L, @ProtoNumber(12) @JvmField val lastViewerNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val lastViewTime: Long = 0L, @ProtoNumber(14) @JvmField val lastPinnedTime: Long = 0L, @ProtoNumber(15) @JvmField val currentUserBrowseTime: Long = 0L, @ProtoNumber(16) @JvmField val hostuserUin: Long = 0L, @ProtoNumber(17) @JvmField val hostuserNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(18) @JvmField val lastAuthTime: Long = 0L, @ProtoNumber(19) @JvmField val policy: Int = 0, @ProtoNumber(20) @JvmField val rightFlag: Int = 0, @ProtoNumber(21) @JvmField val domainid: Int = 0, @ProtoNumber(22) @JvmField val localpadid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(23) @JvmField val lastUnpinnedTime: Long = 0L, @ProtoNumber(24) @JvmField val boolDeleteFlag: Boolean = false, @ProtoNumber(25) @JvmField val lastDeleteTime: Long = 0L, @ProtoNumber(26) @JvmField val thumbUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(27) @JvmField val pdid: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class OnlineDocPushPreviewToEditMessage( @ProtoNumber(1) @JvmField val version: Int = 0, @ProtoNumber(2) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val padUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val aioSession: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xab { internal class SubMsgType0xab : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val gc: Long = 0L, @ProtoNumber(3) @JvmField val rewardId: String = "", @ProtoNumber(4) @JvmField val rewardStatus: Int = 0, ) : ProtoBuf } } internal class Submsgtype0xae { internal class SubMsgType0xae : ProtoBuf { @Serializable internal class AddFriendSource( @ProtoNumber(1) @JvmField val source: Int = 0, @ProtoNumber(2) @JvmField val subSource: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val msgPeopleMayKonw: PushPeopleMayKnow? = null, @ProtoNumber(3) @JvmField val msgPersonsMayKnow: PushPeopleMayKnowV2? = null, ) : ProtoBuf @Serializable internal class PersonMayKnow( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val age: Int = 0, @ProtoNumber(4) @JvmField val sex: Int = 0, @ProtoNumber(5) @JvmField val mainReason: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val soureReason: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val alghrithm: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val source: Int = 0, @ProtoNumber(9) @JvmField val msgIosSource: AddFriendSource? = null, @ProtoNumber(10) @JvmField val msgAndroidSource: AddFriendSource? = null, @ProtoNumber(11) @JvmField val msg: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val gameSource: Int = 0, @ProtoNumber(13) @JvmField val roleName: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PushPeopleMayKnow( @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(1) @JvmField val fixed32Timestamp: Int = 0, @ProtoNumber(2) @JvmField val wordingMsg: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class PushPeopleMayKnowV2( @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(1) @JvmField val fixed32Timestamp: Int = 0, @ProtoNumber(2) @JvmField val msgFriendList: List<PersonMayKnow> = emptyList(), @ProtoNumber(3) @JvmField val roleName: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xb1 { internal class Submsgtype0xb1 : ProtoBuf { @Serializable internal class DealInviteInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val id: String = "", @ProtoNumber(4) @JvmField val dealResult: Int = 0, ) : ProtoBuf @Serializable internal class InviteInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val expireTime: Int = 0, @ProtoNumber(4) @JvmField val id: String = "", ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val notifyType: Int = 0, @ProtoNumber(2) @JvmField val inviteInfo: InviteInfo? = null, @ProtoNumber(3) @JvmField val univiteInfo: UninviteInfo? = null, @ProtoNumber(4) @JvmField val dealInfo: DealInviteInfo? = null, ) : ProtoBuf @Serializable internal class UninviteInfo( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val id: String = "", ) : ProtoBuf } } internal class Submsgtype0xb3 { class SubMsgType0xb3 { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val msgAddFrdNotify: PushAddFrdNotify, ) : ProtoBuf @Serializable internal class PushAddFrdNotify( @ProtoNumber(1) @JvmField val fuin: Long = 0L, @ProtoNumber(2) @JvmField val fuinBubbleId: Long = 0L, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(3) @JvmField val fixed32Timestamp: Int = 0, @ProtoNumber(4) @JvmField val wording: String = "", // 我们已经是好友啦,一起来聊天吧! @ProtoNumber(5) @JvmField val fuinNick: String = "", @ProtoNumber(6) @JvmField val sourceId: Int = 0, @ProtoNumber(7) @JvmField val subsourceId: Int = 0, @ProtoNumber(8) @JvmField val mobile: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val reqUin: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0xb5 { internal class SubMsgType0xb5 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val grayTipContent: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val animationPackageId: Int = 0, @ProtoNumber(3) @JvmField val animationPackageUrlA: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val animationPackageUrlI: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val remindBrief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val giftId: Int = 0, @ProtoNumber(7) @JvmField val giftCount: Int = 0, @ProtoNumber(8) @JvmField val animationBrief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val senderUin: Long = 0L, @ProtoNumber(10) @JvmField val receiverUin: Long = 0L, @ProtoNumber(11) @JvmField val stmessageTitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val stmessageSubtitle: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val stmessageMessage: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val stmessageGiftpicid: Int = 0, @ProtoNumber(15) @JvmField val stmessageComefrom: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(16) @JvmField val stmessageExflag: Int = 0, @ProtoNumber(17) @JvmField val toAllGiftId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10000) @JvmField val groupCode: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0xbe { internal class SubMsgType0xbe : ProtoBuf { @Serializable internal class Medal( @ProtoNumber(1) @JvmField val id: Int = 0, @ProtoNumber(2) @JvmField val level: Int = 0, @ProtoNumber(3) @JvmField val type: Int = 0, @ProtoNumber(4) @JvmField val iconUrl: String = "", @ProtoNumber(5) @JvmField val flashUrl: String = "", @ProtoNumber(6) @JvmField val name: String = "", ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val groupCode: Long = 0L, @ProtoNumber(3) @JvmField val notifyType: Int = 0, @ProtoNumber(4) @JvmField val onlineLevel: Int = 0, @ProtoNumber(5) @JvmField val msgMedalList: List<Medal> = emptyList(), ) : ProtoBuf } } /* internal class Submsgtype0xc1 { internal class Submsgtype0xc1 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val groupid: Long = 0L, @ProtoNumber(2) @JvmField val memberNum: Int = 0, @ProtoNumber(3) @JvmField val data: String = "" ) : ProtoBuf } } */ internal class Submsgtype0xc3 { internal class Submsgtype0xc3 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val pushData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val timestamp: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0xc5 { internal class Submsgtype0xc5 : ProtoBuf { @Serializable internal class BBInfo( @ProtoNumber(1) @JvmField val bbUin: Long = 0L, @ProtoNumber(2) @JvmField val src: Int = 0, ) : ProtoBuf @Serializable internal class BiuBody( @ProtoNumber(1) @JvmField val biuUin: Long = 0L, ) : ProtoBuf @Serializable internal class CommentInfo( @ProtoNumber(2) @JvmField val commentUin: Long = 0L, @ProtoNumber(3) @JvmField val commentId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val replyUin: Long = 0L, @ProtoNumber(5) @JvmField val replyId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val commentContent: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class LikeInfo( @ProtoNumber(2) @JvmField val likeUin: Long = 0L, @ProtoNumber(3) @JvmField val op: Int = 0, @ProtoNumber(4) @JvmField val replyId: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val bid: Int = 0, @ProtoNumber(2) @JvmField val source: Int = 0, @ProtoNumber(3) @JvmField val operatorType: Int /* enum */ = 1, @ProtoNumber(4) @JvmField val articleId: Long = 0L, @ProtoNumber(5) @JvmField val pushTime: Int = 0, @ProtoNumber(6) @JvmField val seq: Long = 0L, @ProtoNumber(7) @JvmField val msgid: String = "", @ProtoNumber(10) @JvmField val msgNotifyInfos: NotifyBody? = null, @ProtoNumber(11) @JvmField val diandianCookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NotifyBody( @ProtoNumber(1) @JvmField val msgStyleSheet: StyleSheet? = null, @ProtoNumber(10) @JvmField val msgCommentArticle: CommentInfo? = null, @ProtoNumber(11) @JvmField val msgLikeArticle: LikeInfo? = null, @ProtoNumber(12) @JvmField val msgBbInfo: BBInfo? = null, @ProtoNumber(13) @JvmField val redPointInfo: List<RedPointInfo> = emptyList(), ) : ProtoBuf @Serializable internal class RedPointInfo( @ProtoNumber(1) @JvmField val itemId: Int = 0, @ProtoNumber(2) @JvmField val redPointItemType: Int /* enum */ = 0, @ProtoNumber(3) @JvmField val url: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val effectTime: Long = 0L, @ProtoNumber(5) @JvmField val failureTime: Long = 0L, ) : ProtoBuf @Serializable internal class StyleSheet( @ProtoNumber(1) @JvmField val showFolder: Int = 0, @ProtoNumber(2) @JvmField val folderRedType: Int /* enum */ = 0, @ProtoNumber(3) @JvmField val orangeWord: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val summary: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val msgTipBody: TipsBody? = null, @ProtoNumber(6) @JvmField val showLockScreen: Int = 0, @ProtoNumber(7) @JvmField val msgType: Int /* enum */ = 0, @ProtoNumber(8) @JvmField val msgBiuBody: BiuBody? = null, @ProtoNumber(9) @JvmField val isLow: Int = 0, ) : ProtoBuf @Serializable internal class TipsBody( @ProtoNumber(1) @JvmField val tipsUiType: Int /* enum */ = 0, @ProtoNumber(2) @JvmField val uin: Long = 0L, @ProtoNumber(3) @JvmField val iconUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val schema: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val businessInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xc6 { internal class SubMsgType0xc6 : ProtoBuf { @Serializable internal class AccountExceptionAlertBody( @ProtoNumber(1) @JvmField val title: String = "", @ProtoNumber(2) @JvmField val content: String = "", @ProtoNumber(3) @JvmField val leftButtonText: String = "", @ProtoNumber(4) @JvmField val rightButtonText: String = "", @ProtoNumber(5) @JvmField val rightButtonLink: String = "", @ProtoNumber(6) @JvmField val leftButtonId: Int = 0, @ProtoNumber(7) @JvmField val rightButtonId: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val secCmd: Int = 0, @ProtoNumber(2) @JvmField val msgS2cAccountExceptionNotify: AccountExceptionAlertBody? = null, ) : ProtoBuf } } internal class Submsgtype0xc7 { internal class Submsgtype0xc7 : ProtoBuf { @Serializable internal class ForwardBody( @ProtoNumber(1) @JvmField val notifyType: Int = 0, @ProtoNumber(2) @JvmField val opType: Int = 0, @ProtoNumber(3000) @JvmField val msgHotFriendNotify: HotFriendNotify? = null, @ProtoNumber(4000) @JvmField val msgRelationalChainChange: RelationalChainChange? = null, ) : ProtoBuf @Serializable internal class FriendShipFlagNotify( @ProtoNumber(1) @JvmField val dstUin: Long = 0L, @ProtoNumber(2) @JvmField val level1: Int = 0, @ProtoNumber(3) @JvmField val level2: Int = 0, @ProtoNumber(4) @JvmField val continuityDays: Int = 0, @ProtoNumber(5) @JvmField val chatFlag: Int = 0, @ProtoNumber(6) @JvmField val lastChatTime: Long = 0L, @ProtoNumber(7) @JvmField val notifyTime: Long = 0L, @ProtoNumber(8) @JvmField val seq: Int = 0, ) : ProtoBuf @Serializable internal class HotFriendNotify( @ProtoNumber(1) @JvmField val dstUin: Long = 0L, @ProtoNumber(2) @JvmField val praiseHotLevel: Int = 0, @ProtoNumber(3) @JvmField val chatHotLevel: Int = 0, @ProtoNumber(4) @JvmField val praiseHotDays: Int = 0, @ProtoNumber(5) @JvmField val chatHotDays: Int = 0, @ProtoNumber(6) @JvmField val closeLevel: Int = 0, @ProtoNumber(7) @JvmField val closeDays: Int = 0, @ProtoNumber(8) @JvmField val praiseFlag: Int = 0, @ProtoNumber(9) @JvmField val chatFlag: Int = 0, @ProtoNumber(10) @JvmField val closeFlag: Int = 0, @ProtoNumber(11) @JvmField val notifyTime: Long = 0L, @ProtoNumber(12) @JvmField val lastPraiseTime: Long = 0L, @ProtoNumber(13) @JvmField val lastChatTime: Long = 0L, @ProtoNumber(14) @JvmField val qzoneHotLevel: Int = 0, @ProtoNumber(15) @JvmField val qzoneHotDays: Int = 0, @ProtoNumber(16) @JvmField val qzoneFlag: Int = 0, @ProtoNumber(17) @JvmField val lastQzoneTime: Long = 0L, @ProtoNumber(51) @JvmField val showRecheckEntry: Int = 0, @ProtoNumber(52) @JvmField val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(100) @JvmField val loverFlag: Int = 0, @ProtoNumber(200) @JvmField val keyHotLevel: Int = 0, @ProtoNumber(201) @JvmField val keyHotDays: Int = 0, @ProtoNumber(202) @JvmField val keyFlag: Int = 0, @ProtoNumber(203) @JvmField val lastKeyTime: Long = 0L, @ProtoNumber(204) @JvmField val keyWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(205) @JvmField val keyTransFlag: Int = 0, @ProtoNumber(206) @JvmField val loverKeyBusinessType: Int = 0, @ProtoNumber(207) @JvmField val loverKeySubBusinessType: Int = 0, @ProtoNumber(208) @JvmField val loverKeyMainWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(209) @JvmField val loverKeyLinkWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(300) @JvmField val boatLevel: Int = 0, @ProtoNumber(301) @JvmField val boatDays: Int = 0, @ProtoNumber(302) @JvmField val boatFlag: Int = 0, @ProtoNumber(303) @JvmField val lastBoatTime: Int = 0, @ProtoNumber(304) @JvmField val boatWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(400) @JvmField val notifyType: Int = 0, @ProtoNumber(401) @JvmField val msgFriendshipFlagNotify: FriendShipFlagNotify? = null, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgModInfos: List<ForwardBody> = emptyList(), ) : ProtoBuf @Serializable internal class RelationalChainChange( @ProtoNumber(1) @JvmField val appid: Long = 0L, @ProtoNumber(2) @JvmField val srcUin: Long = 0L, @ProtoNumber(3) @JvmField val dstUin: Long = 0L, @ProtoNumber(4) @JvmField val changeType: Int /* enum */ = 1, @ProtoNumber(5) @JvmField val msgRelationalChainInfoOld: RelationalChainInfo? = null, @ProtoNumber(6) @JvmField val msgRelationalChainInfoNew: RelationalChainInfo? = null, @ProtoNumber(7) @JvmField val msgToDegradeInfo: ToDegradeInfo? = null, @ProtoNumber(20) @JvmField val relationalChainInfos: List<RelationalChainInfos> = emptyList(), @ProtoNumber(100) @JvmField val uint32FeatureId: List<Int> = emptyList(), ) : ProtoBuf @Serializable internal class RelationalChainInfo( @ProtoNumber(1) @JvmField val type: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val attr: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(1002) @JvmField val intimateInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(91001) @JvmField val musicSwitch: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(101001) @JvmField val mutualmarkAlienation: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RelationalChainInfos( @ProtoNumber(1) @JvmField val msgRelationalChainInfoOld: RelationalChainInfo? = null, @ProtoNumber(2) @JvmField val msgRelationalChainInfoNew: RelationalChainInfo? = null, ) : ProtoBuf @Serializable internal class ToDegradeInfo( @ProtoNumber(1) @JvmField val toDegradeItem: List<ToDegradeItem> = emptyList(), @ProtoNumber(2) @JvmField val nick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val notifyTime: Long = 0L, ) : ProtoBuf @Serializable internal class ToDegradeItem( @ProtoNumber(1) @JvmField val type: Int /* enum */ = 1, @ProtoNumber(2) @JvmField val oldLevel: Int = 0, @ProtoNumber(3) @JvmField val newLevel: Int = 0, @ProtoNumber(11) @JvmField val continuityDays: Int = 0, @ProtoNumber(12) @JvmField val lastActionTime: Long = 0L, ) : ProtoBuf } } internal class Mutualmark { class Mutualmark : ProtoBuf { @Serializable internal class MutualmarkInfo( @ProtoNumber(1) @JvmField val lastActionTime: Long = 0L, @ProtoNumber(2) @JvmField val level: Int = 0, @ProtoNumber(3) @JvmField val lastChangeTime: Long = 0L, @ProtoNumber(4) @JvmField val continueDays: Int = 0, @ProtoNumber(5) @JvmField val wildcardWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val notifyTime: Long = 0L, @ProtoNumber(7) @JvmField val iconStatus: Long = 0L, @ProtoNumber(8) @JvmField val iconStatusEndTime: Long = 0L, @ProtoNumber(9) @JvmField val closeFlag: Int = 0, @ProtoNumber(10) @JvmField val resourceInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ResourceInfo17( @ProtoNumber(1) @JvmField val dynamicUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val staticUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val cartoonUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val cartoonMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val playCartoon: Int = 0, @ProtoNumber(6) @JvmField val word: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xc9 { class Submsgtype0xc9 : ProtoBuf { @Serializable internal class BusinessMsg( @ProtoNumber(1) @JvmField val msgType: Int /* enum */ = 0, @ProtoNumber(2) @JvmField val msgData: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val boolTabVisible: Boolean = false, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val fromUin: Long = 0L, @ProtoNumber(3) @JvmField val actionUin: Long = 0L, @ProtoNumber(4) @JvmField val source: Int /* enum */ = 0, @ProtoNumber(5) @JvmField val msgBusinessMsg: List<BusinessMsg> = emptyList(), @ProtoNumber(6) @JvmField val boolNewFriend: Boolean = false, ) : ProtoBuf } } internal class Submsgtype0xca { class Submsgtype0xca : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val msgList: List<MsgContent> = emptyList(), ) : ProtoBuf @Serializable internal class MsgContent( @ProtoNumber(1) @JvmField val tag: Long = 0L, @ProtoNumber(2) @JvmField val msgType: Long = 0L, @ProtoNumber(3) @JvmField val seq: Long = 0L, @ProtoNumber(4) @JvmField val content: String = "", @ProtoNumber(5) @JvmField val actionId: Long = 0L, @ProtoNumber(6) @JvmField val ts: Long = 0L, @ProtoNumber(7) @JvmField val expts: Long = 0L, @ProtoNumber(8) @JvmField val errorMsg: String = "", @ProtoNumber(9) @JvmField val showSpace: Long = 0L, @ProtoNumber(10) @JvmField val regionUrl: String = "", ) : ProtoBuf } } internal class Submsgtype0xcb { internal class SubMsgType0xcb : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val anchorStatus: Int = 0, @ProtoNumber(2) @JvmField val jumpSchema: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val anchorNickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val anchorHeadUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val liveWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val liveEndWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val c2cMsgWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val liveWordingType: Int = 0, @ProtoNumber(9) @JvmField val endWordingType: Int = 0, ) : ProtoBuf } } internal class Submsgtype0xcc { internal class SubMsgType0xcc : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val uin: Long = 0L, @ProtoNumber(4) @JvmField val unionId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val subType: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val feedId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val vid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val coverUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xce { internal class Submsgtype0xce : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val int64StartTime: Long = 0L, @ProtoNumber(2) @JvmField val int64EndTime: Long = 0L, @ProtoNumber(3) @JvmField val params: String = "", ) : ProtoBuf } } internal class Submsgtype0xcf { internal class Submsgtype0xcf : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val rsptype: Int = 0, @ProtoNumber(2) @JvmField val rspbody: String = "", ) : ProtoBuf } } internal class Submsgtype0xd0 { internal class SubMsgType0xd0 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val hotTopicId: Long = 0L, @ProtoNumber(4) @JvmField val hotTopicName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val bigVId: Long = 0L, @ProtoNumber(6) @JvmField val bigVUnionId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val pgcType: Int = 0, @ProtoNumber(8) @JvmField val pgcColumnUnionId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val link: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val subType: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(11) @JvmField val coverUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xd7 { internal class SubMsgType0xd7 : ProtoBuf { @Serializable internal class Content( @ProtoNumber(1) @JvmField val fromUser: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val plainText: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val buluoWord: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val richFreshWord: AppointDefine.RichText? = null, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val msgboxUnreadCount: Int = 0, @ProtoNumber(3) @JvmField val unreadCount: Int = 0, @ProtoNumber(4) @JvmField val msgContent: Content? = null, @ProtoNumber(5) @JvmField val timestamp: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0xda { internal class SubMsgType0xda : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val msgInfo: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val subType: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val versionCtrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val feedId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val unionId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val commentId: Int = 0, @ProtoNumber(8) @JvmField val iconUnionId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val coverUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(10) @JvmField val operType: Int = 0, @ProtoNumber(11) @JvmField val groupUnionid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(12) @JvmField val vid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(13) @JvmField val doodleUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(14) @JvmField val fromNick: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(15) @JvmField val vidUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(16) @JvmField val extInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xdb { internal class Submsgtype0xdb : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val rsptype: Int = 0, @ProtoNumber(2) @JvmField val rspbody: String = "", ) : ProtoBuf } } internal class Submsgtype0xdc { internal class Submsgtype0xdc : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgList: List<MsgContent> = emptyList(), @ProtoNumber(2) @JvmField val msgType: Int = 0, @ProtoNumber(3) @JvmField val msgList0x02: List<MsgContent> = emptyList(), @ProtoNumber(4) @JvmField val minQqVer: String = "", ) : ProtoBuf @Serializable internal class MsgContent( @ProtoNumber(1) @JvmField val masterPri: Long = 0L, @ProtoNumber(2) @JvmField val subPri: Long = 0L, @ProtoNumber(3) @JvmField val showTimes: Long = 0L, @ProtoNumber(4) @JvmField val showBegTs: Long = 0L, @ProtoNumber(5) @JvmField val expTs: Long = 0L, @ProtoNumber(6) @JvmField val msgSentTs: Long = 0L, @ProtoNumber(7) @JvmField val actionId: Long = 0L, @ProtoNumber(8) @JvmField val wording: String = "", @ProtoNumber(9) @JvmField val scheme: String = "", @ProtoNumber(10) @JvmField val regionUrl: String = "", @ProtoNumber(11) @JvmField val wordingColor: Long = 0L, @ProtoNumber(12) @JvmField val msgId: Long = 0L, @ProtoNumber(13) @JvmField val bubbleId: Long = 0L, @ProtoNumber(14) @JvmField val tips: String = "", @ProtoNumber(15) @JvmField val gameId: Long = 0L, ) : ProtoBuf } } internal class Submsgtype0xdd { internal class Submsgtype0xdd : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val uint64InviteUin: List<Long> = emptyList(), @ProtoNumber(3) @JvmField val inviteLeader: Long = 0L, @ProtoNumber(4) @JvmField val msgPoiInfo: WifiPOIInfo? = null, @ProtoNumber(5) @JvmField val msgPlayerState: List<PlayerState> = emptyList(), ) : ProtoBuf { @Serializable internal class PlayerState( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val state: Int = 0, ) : ProtoBuf @Serializable internal class SeatsInfo( @ProtoNumber(1) @JvmField val seatFlag: Int = 0, @ProtoNumber(2) @JvmField val guestUin: Long = 0L, @ProtoNumber(3) @JvmField val seatId: Int = 0, @ProtoNumber(4) @JvmField val seatSeq: Int = 0, ) : ProtoBuf @Serializable internal class WifiPOIInfo( @ProtoNumber(1) @JvmField val uid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val faceId: Int = 0, @ProtoNumber(4) @JvmField val sig: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val groupCode: Int = 0, @ProtoNumber(6) @JvmField val groupUin: Int = 0, @ProtoNumber(7) @JvmField val visitorNum: Int = 0, @ProtoNumber(8) @JvmField val wifiPoiType: Int = 0, @ProtoNumber(9) @JvmField val isMember: Int = 0, @ProtoNumber(10) @JvmField val distance: Int = 0, @ProtoNumber(11) @JvmField val msgTabSwitchOff: Int = 0, @ProtoNumber(12) @JvmField val faceUrl: String = "", @ProtoNumber(13) @JvmField val hotThemeGroupFlag: Int = 0, @ProtoNumber(14) @JvmField val bannerUrl: String = "", @ProtoNumber(15) @JvmField val specialFlag: Int = 0, @ProtoNumber(16) @JvmField val totalNumLimit: Int = 0, @ProtoNumber(17) @JvmField val isAdmin: Int = 0, @ProtoNumber(18) @JvmField val joinGroupUrl: String = "", @ProtoNumber(19) @JvmField val groupTypeFlag: Int = 0, @ProtoNumber(20) @JvmField val createrCityId: Int = 0, @ProtoNumber(21) @JvmField val isUserCreate: Int = 0, @ProtoNumber(22) @JvmField val ownerUin: Long = 0L, @ProtoNumber(23) @JvmField val auditFlag: Int = 0, @ProtoNumber(24) @JvmField val tvPkFlag: Int = 0, @ProtoNumber(25) @JvmField val subType: Int = 0, @ProtoNumber(26) @JvmField val lastMsgSeq: Long = 0L, @ProtoNumber(27) @JvmField val msgSeatsInfo: List<SeatsInfo> = emptyList(), @ProtoNumber(28) @JvmField val flowerNum: Long = 0L, @ProtoNumber(29) @JvmField val flowerPoint: Long = 0L, @ProtoNumber(31) @JvmField val favoritesTime: Long = 0L, @ProtoNumber(32) @JvmField val favoritesExpired: Int = 0, @ProtoNumber(33) @JvmField val groupId: Int = 0, @ProtoNumber(34) @JvmField val praiseNums: Long = 0L, @ProtoNumber(35) @JvmField val reportPraiseGapTime: Long = 0L, @ProtoNumber(36) @JvmField val reportPraiseGapFrequency: Long = 0L, @ProtoNumber(37) @JvmField val getPraiseGapTime: Long = 0L, @ProtoNumber(38) @JvmField val vistorJoinGroupTime: Long = 0L, @ProtoNumber(39) @JvmField val groupIsNotExist: Int = 0, @ProtoNumber(40) @JvmField val guestNum: Int = 0, @ProtoNumber(41) @JvmField val highQualityFlag: Int = 0, @ProtoNumber(42) @JvmField val exitGroupCode: Long = 0L, @ProtoNumber(43) @JvmField val int32Latitude: Int = 0, @ProtoNumber(44) @JvmField val int32Longitude: Int = 0, @ProtoNumber(45) @JvmField val smemo: String = "", @ProtoNumber(46) @JvmField val isAllCountry: Int = 0, ) : ProtoBuf } } } internal class Submsgtype0xde { internal class Submsgtype0xde : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgType: Int = 0, @ProtoNumber(2) @JvmField val unionId: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val uid: Long = 0L, @ProtoNumber(4) @JvmField val vid: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val videoCover: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xdf { internal class Submsgtype0xdf : ProtoBuf { @Serializable internal class MsgBody( // @ProtoNumber(1) @JvmField val msgGameState: ApolloGameStatus.STCMGameMessage? = null, @ProtoNumber(2) @JvmField val uint32UinList: List<Int> = emptyList(), ) : ProtoBuf } } internal class Submsgtype0xe0 { internal class Submsgtype0xe0 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val pushExt: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xe1 { internal class Submsgtype0xe1 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val pushExt: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xe4 { internal class Submsgtype0xe4 : ProtoBuf { @Serializable internal class GeoInfo( @ProtoNumber(1) @JvmField val latitude: Long = 0L, @ProtoNumber(2) @JvmField val longitude: Long = 0L, ) : ProtoBuf @Serializable internal class GiftMsg( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, @ProtoNumber(3) @JvmField val productId: Int = 0, @ProtoNumber(4) @JvmField val giftId: Int = 0, @ProtoNumber(5) @JvmField val giftNum: Long = 0L, @ProtoNumber(6) @JvmField val roomid: String = "", @ProtoNumber(7) @JvmField val giftWording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(8) @JvmField val packageurl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(50) @JvmField val curAddDuration: Int = 0, @ProtoNumber(51) @JvmField val allAddDuration: Int = 0, ) : ProtoBuf @Serializable internal class LikeMsg( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val toUin: Long = 0L, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgMatchPlayer: Player? = null, @ProtoNumber(2) @JvmField val distance: Int = 0, @ProtoNumber(3) @JvmField val hint: String = "", @ProtoNumber(4) @JvmField val countdown: Int = 0, @ProtoNumber(5) @JvmField val key: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val type: Int = 0, @ProtoNumber(7) @JvmField val callType: Int = 0, @ProtoNumber(8) @JvmField val displayDistance: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(9) @JvmField val msgLike: LikeMsg? = null, @ProtoNumber(10) @JvmField val msgGift: GiftMsg? = null, @ProtoNumber(11) @JvmField val msgRoom: Room? = null, ) : ProtoBuf @Serializable internal class Player( @ProtoNumber(1) @JvmField val uin: Long = 0L, @ProtoNumber(2) @JvmField val nickname: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val logoUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(4) @JvmField val gender: Int = 0, @ProtoNumber(5) @JvmField val level: Int = 0, @ProtoNumber(6) @JvmField val age: Int = 0, ) : ProtoBuf @Serializable internal class Room( @ProtoNumber(1) @JvmField val roomId: String = "", ) : ProtoBuf } } internal class Submsgtype0xe5 { internal class Submsgtype0xe5 : ProtoBuf { @Serializable internal class CrmS2CMsgHead( @ProtoNumber(1) @JvmField val crmSubCmd: Int = 0, @ProtoNumber(2) @JvmField val headLen: Int = 0, @ProtoNumber(3) @JvmField val verNo: Int = 0, @ProtoNumber(4) @JvmField val kfUin: Long = 0L, @ProtoNumber(5) @JvmField val seq: Int = 0, @ProtoNumber(6) @JvmField val packNum: Int = 0, @ProtoNumber(7) @JvmField val curPack: Int = 0, @ProtoNumber(8) @JvmField val bufSig: String = "", ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val subCmd: Int = 0, @ProtoNumber(2) @JvmField val msgCrmCommonHead: CrmS2CMsgHead? = null, @ProtoNumber(3) @JvmField val msgS2cCcAgentStatusChangePush: S2CCcAgentStatusChangePush? = null, @ProtoNumber(4) @JvmField val msgS2cCcConfigChangePush: S2CCcConfigChangePush? = null, @ProtoNumber(5) @JvmField val msgS2cCcExceptionOccurPush: S2CCcExceptionOccurPush? = null, @ProtoNumber(6) @JvmField val msgS2cCcTalkingStatusChangePush: S2CCcTalkingStatusChangePush? = null, @ProtoNumber(7) @JvmField val msgS2cCcAgentActionResultPush: S2CCcAgentActionResultPush? = null, @ProtoNumber(8) @JvmField val msgS2cCallRecordChangePush: S2CCallRecordChangePush? = null, @ProtoNumber(9) @JvmField val msgS2cSmsEventPush: S2CSMSEventPush? = null, @ProtoNumber(10) @JvmField val msgS2cAgentCallStatusEventPush: S2CAgentCallStatusEventPush? = null, @ProtoNumber(11) @JvmField val msgS2cUserGetCouponForKfextEventPush: S2CUserGetCouponForKFExtEventPush? = null, @ProtoNumber(12) @JvmField val msgS2cUserGetCouponForCEventPush: S2CUserGetCouponForCEventPush? = null, ) : ProtoBuf { @Serializable internal class S2CAgentCallStatusEventPush( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val callStatus: Int = 0, @ProtoNumber(3) @JvmField val ringAsr: Int = 0, @ProtoNumber(4) @JvmField val callid: String = "", @ProtoNumber(5) @JvmField val fromKfext: Long = 0L, @ProtoNumber(6) @JvmField val timestamp: Int = 0, ) : ProtoBuf @Serializable internal class S2CCallRecordChangePush( @ProtoNumber(1) @JvmField val kfext: Long = 0L, @ProtoType(ProtoIntegerType.FIXED) @ProtoNumber(2) @JvmField val fixed64Timestamp: Long = 0L, ) : ProtoBuf @Serializable internal class S2CCcAgentActionResultPush( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val callid: String = "", @ProtoNumber(3) @JvmField val result: Int = 0, @ProtoNumber(4) @JvmField val timestamp: Int = 0, @ProtoNumber(5) @JvmField val status: Int = 0, @ProtoNumber(6) @JvmField val targetName: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val targetKfext: Long = 0L, ) : ProtoBuf @Serializable internal class S2CCcAgentStatusChangePush( @ProtoNumber(1) @JvmField val readyDevice: Int = 0, @ProtoNumber(2) @JvmField val updateTime: Long = 0L, @ProtoNumber(3) @JvmField val deviceSubState: Int = 0, ) : ProtoBuf @Serializable internal class S2CCcConfigChangePush( @ProtoNumber(1) @JvmField val optype: Int = 0, ) : ProtoBuf @Serializable internal class S2CCcExceptionOccurPush( @ProtoNumber(1) @JvmField val optype: Int = 0, ) : ProtoBuf @Serializable internal class S2CCcTalkingStatusChangePush( @ProtoNumber(1) @JvmField val talkingStatus: Int = 0, @ProtoNumber(2) @JvmField val callid: String = "", ) : ProtoBuf @Serializable internal class S2CSMSEventPush( @ProtoNumber(1) @JvmField val type: Int = 0, @ProtoNumber(2) @JvmField val phoneNum: String = "", @ProtoNumber(3) @JvmField val timestamp: Long = 0L, @ProtoNumber(4) @JvmField val smsId: String = "", @ProtoNumber(5) @JvmField val eventMsg: String = "", ) : ProtoBuf @Serializable internal class S2CUserGetCouponForCEventPush( @ProtoNumber(1) @JvmField val qquin: Long = 0L, @ProtoNumber(2) @JvmField val kfuin: Long = 0L, @ProtoNumber(3) @JvmField val couponId: Long = 0L, @ProtoNumber(4) @JvmField val timestamp: Int = 0, @ProtoNumber(5) @JvmField val kfext: Long = 0L, @ProtoNumber(6) @JvmField val tipsContent: String = "", ) : ProtoBuf @Serializable internal class S2CUserGetCouponForKFExtEventPush( @ProtoNumber(1) @JvmField val channelType: Int = 0, @ProtoNumber(2) @JvmField val fakeuin: Long = 0L, @ProtoNumber(3) @JvmField val qquin: Long = 0L, @ProtoNumber(4) @JvmField val openid: String = "", @ProtoNumber(5) @JvmField val visitorid: String = "", @ProtoNumber(6) @JvmField val appid: String = "", @ProtoNumber(7) @JvmField val qqPubUin: Long = 0L, @ProtoNumber(8) @JvmField val kfuin: Long = 0L, @ProtoNumber(9) @JvmField val couponId: Long = 0L, @ProtoNumber(10) @JvmField val notifyTips: String = "", @ProtoNumber(11) @JvmField val timestamp: Int = 0, @ProtoNumber(12) @JvmField val kfext: Long = 0L, ) : ProtoBuf } } } internal class Submsgtype0xe8 { internal class Submsgtype0xe8 : ProtoBuf { @Serializable internal class MsgBody/*( @ProtoNumber(1) @JvmField val msgItem: ApolloPushMsgInfo.STPushMsgElem? = null )*/ : ProtoBuf } } internal class Submsgtype0xe9 { internal class SubMsgType0xe9 : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val businessType: Int = 0, @ProtoNumber(2) @JvmField val business: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xea { internal class Submsgtype0xea : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val content: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xee { internal class Submsgtype0xee : ProtoBuf { @Serializable internal class AccountInfo( @ProtoNumber(1) @JvmField val id: Long = 0L, @ProtoNumber(2) @JvmField val name: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val iconUrl: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ContextInfo( @ProtoNumber(1) @JvmField val id: Long = 0L, @ProtoNumber(2) @JvmField val title: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val msgPicList: List<PictureInfo> = emptyList(), @ProtoNumber(4) @JvmField val jumpUrl: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(5) @JvmField val orangeWord: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(6) @JvmField val brief: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(7) @JvmField val enumContextType: Int /* enum */ = 0, @ProtoNumber(8) @JvmField val videoBrief: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ControlInfo( @ProtoNumber(1) @JvmField val commentLength: Int = 0, @ProtoNumber(2) @JvmField val showLine: Int = 0, @ProtoNumber(3) @JvmField val fontSize: Int = 0, ) : ProtoBuf @Serializable internal class ExtraInfo( @ProtoNumber(1) @JvmField val ext: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val cookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val id: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val seq: Long = 0L, @ProtoNumber(3) @JvmField val bid: Int = 0, @ProtoNumber(11) @JvmField val msgNotifyList: List<NotifyInfo> = emptyList(), ) : ProtoBuf @Serializable internal class NotifyInfo( @ProtoNumber(1) @JvmField val msgStyleSheet: StyleSheet? = null, @ProtoNumber(2) @JvmField val enumApppushType: Int /* enum */ = 0, @ProtoNumber(3) @JvmField val msgOrdinaryPushInfo: OrdinaryPushInfo? = null, @ProtoNumber(4) @JvmField val msgSocialPushInfo: SocialPushInfo? = null, @ProtoNumber(5) @JvmField val msgUgcPushInfo: UGCPushInfo? = null, @ProtoNumber(11) @JvmField val msgContextInfo: ContextInfo? = null, @ProtoNumber(12) @JvmField val msgAccountInfo: AccountInfo? = null, @ProtoNumber(13) @JvmField val msgStatisticsInfo: StatisticsInfo? = null, @ProtoNumber(14) @JvmField val msgControlInfo: ControlInfo? = null, @ProtoNumber(21) @JvmField val msgExtraInfo: ExtraInfo? = null, ) : ProtoBuf @Serializable internal class OrangeControlInfo( @ProtoNumber(1) @JvmField val color: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val fontSize: Int = 0, ) : ProtoBuf @Serializable internal class OrdinaryPushInfo( @ProtoNumber(1) @JvmField val msgLabelControlInfo: OrangeControlInfo? = null, ) : ProtoBuf @Serializable internal class PictureInfo( @ProtoNumber(1) @JvmField val url: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class SocialPushInfo( @ProtoNumber(1) @JvmField val feedsId: Long = 0L, @ProtoNumber(2) @JvmField val biuReason: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(3) @JvmField val biuComment: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class StatisticsInfo( @ProtoNumber(1) @JvmField val algorithmId: Long = 0L, @ProtoNumber(2) @JvmField val strategyId: Long = 0L, @ProtoNumber(3) @JvmField val folderStatus: Long = 0L, ) : ProtoBuf @Serializable internal class StyleSheet( @ProtoNumber(1) @JvmField val enumStyleType: Int /* enum */ = 0, @ProtoNumber(2) @JvmField val arkEnable: Int = 0, @ProtoNumber(3) @JvmField val scene: Long = 0L, @ProtoNumber(11) @JvmField val duration: Int = 0, @ProtoNumber(12) @JvmField val endTime: Long = 0L, ) : ProtoBuf @Serializable internal class UGCPushInfo( @ProtoNumber(1) @JvmField val feedsId: Long = 0L, @ProtoNumber(2) @JvmField val ugcReason: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf } } internal class Submsgtype0xf9 { internal class Submsgtype0xf9 : ProtoBuf { @Serializable internal class AdInfo( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val nick: String = "", @ProtoNumber(3) @JvmField val headUrl: String = "", @ProtoNumber(4) @JvmField val brief: String = "", @ProtoNumber(5) @JvmField val action: String = "", @ProtoNumber(6) @JvmField val flag: Int = 0, @ProtoNumber(7) @JvmField val serviceID: Int = 0, @ProtoNumber(8) @JvmField val templateID: Int = 0, @ProtoNumber(9) @JvmField val url: String = "", @ProtoNumber(10) @JvmField val msgMsgCommonData: MsgCommonData? = null, @ProtoNumber(11) @JvmField val msgVideo: List<Video> = emptyList(), @ProtoNumber(12) @JvmField val pushTime: Int = 0, @ProtoNumber(13) @JvmField val invalidTime: Int = 0, @ProtoNumber(14) @JvmField val maxExposureTime: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val zipAdInfo: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgCommonData( @ProtoNumber(1) @JvmField val adId: String = "", @ProtoNumber(2) @JvmField val adPosId: String = "", @ProtoNumber(3) @JvmField val boolBannerShow: Boolean = false, @ProtoNumber(4) @JvmField val bannertype: Int = 0, @ProtoNumber(5) @JvmField val jumpType: Int = 0, @ProtoNumber(6) @JvmField val jumpUrl: String = "", @ProtoNumber(7) @JvmField val appId: String = "", @ProtoNumber(8) @JvmField val appName: String = "", @ProtoNumber(9) @JvmField val packagename: String = "", @ProtoNumber(10) @JvmField val androidDownloadUrl: String = "", @ProtoNumber(11) @JvmField val scheme: String = "", @ProtoNumber(12) @JvmField val iosDownloadUrl: String = "", @ProtoNumber(13) @JvmField val bannerImgUrl: String = "", @ProtoNumber(14) @JvmField val bannerText: String = "", @ProtoNumber(15) @JvmField val bannerButtonText: String = "", @ProtoNumber(16) @JvmField val boolSilentDownload: Boolean = false, @ProtoNumber(17) @JvmField val audioSwitchType: Int = 0, @ProtoNumber(18) @JvmField val preDownloadType: Int = 0, @ProtoNumber(19) @JvmField val reportLink: String = "", @ProtoNumber(20) @JvmField val boolHorizontalVideo: Boolean = false, @ProtoNumber(21) @JvmField val audioFadeinDuration: Int = 0, @ProtoNumber(22) @JvmField val openJumpUrlGuide: String = "", @ProtoNumber(23) @JvmField val myappDownloadUrl: String = "", @ProtoNumber(24) @JvmField val jumpTypeParams: String = "", @ProtoNumber(25) @JvmField val scrollUpToJump: Int = 0, @ProtoNumber(26) @JvmField val controlVariable: Int = 0, @ProtoNumber(27) @JvmField val autoJump: Int = 0, @ProtoNumber(28) @JvmField val clickLink: String = "", @ProtoNumber(29) @JvmField val monitorType: Int = 0, @ProtoNumber(30) @JvmField val shareNick: String = "", @ProtoNumber(31) @JvmField val shareAdHeadUrl: String = "", @ProtoNumber(32) @JvmField val shareAdBrief: String = "", @ProtoNumber(33) @JvmField val shareAdTxt: String = "", @ProtoNumber(34) @JvmField val shareAdIconUrl: String = "", @ProtoNumber(35) @JvmField val shareJumpUrl: String = "", @ProtoNumber(36) @JvmField val controlPluginTime: Int = 0, ) : ProtoBuf @Serializable internal class Video( @ProtoNumber(1) @JvmField val layout: Int = 0, @ProtoNumber(2) @JvmField val cover: String = "", @ProtoNumber(3) @JvmField val src: String = "", ) : ProtoBuf } } internal class Submsgtype0xfd { internal class Submsgtype0xfd : ProtoBuf { @Serializable internal class AdInfo( @ProtoNumber(1) @JvmField val fromUin: Long = 0L, @ProtoNumber(2) @JvmField val adId: String = "", @ProtoNumber(3) @JvmField val adSeq: Int = 0, ) : ProtoBuf @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val msgAdInfo: AdInfo? = null, ) : ProtoBuf } } internal class Submsgtype0xfe { internal class Submsgtype0xfe : ProtoBuf { @Serializable internal class MsgBody( @ProtoNumber(1) @JvmField val wording: ByteArray = EMPTY_BYTE_ARRAY, @ProtoNumber(2) @JvmField val innerUnreadNum: Int = 0, @ProtoNumber(3) @JvmField val boxUnreadNum: Int = 0, @ProtoNumber(4) @JvmField val updateTime: Int = 0, ) : ProtoBuf } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/proto/msgType0x211.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "SpellCheckingInspection") package net.mamoe.mirai.internal.network.protocol.data.proto import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoIntegerType import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoType import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.jvm.JvmField @Serializable internal class SubMsgType0x3 : ProtoBuf { @Serializable internal class FailNotify( @JvmField @ProtoNumber(1) val sessionid: Int = 0, @JvmField @ProtoNumber(2) val retCode: Int = 0, @JvmField @ProtoNumber(3) val reason: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgBody( @JvmField @ProtoNumber(1) val msgProgressNotify: ProgressNotify? = null, @JvmField @ProtoNumber(2) val msgFailNotify: FailNotify? = null, ) : ProtoBuf @Serializable internal class ProgressNotify( @JvmField @ProtoNumber(1) val sessionid: Int = 0, @JvmField @ProtoNumber(2) val uuid: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(3) val progress: Int = 0, @JvmField @ProtoNumber(4) val averageSpeed: Int = 0, ) : ProtoBuf } @Serializable internal class SubMsgType0x4 : ProtoBuf { @Serializable internal class MsgBody( @JvmField @ProtoNumber(1) val msgNotOnlineFile: ImMsgBody.NotOnlineFile? = null, @JvmField @ProtoNumber(2) val msgTime: Int = 0, @JvmField @ProtoNumber(3) val onlineFileForPolyToOffline: Int = 0, @JvmField @ProtoNumber(4) val fileImageInfo: HummerResv21.FileImgInfo? = null, @JvmField @ProtoNumber(5) val msgXtfSenderInfo: HummerResv21.XtfSenderInfo? = null, @JvmField @ProtoNumber(6) val resvAttr: HummerResv21.ResvAttr? = null, ) : ProtoBuf } @Serializable internal class SubMsgType0x5 : ProtoBuf { @Serializable internal class MsgBody( @JvmField @ProtoNumber(1) val sessionid: Int = 0, ) : ProtoBuf } @Serializable internal class SubMsgType0x7 : ProtoBuf { @Serializable internal class MsgBody( @JvmField @ProtoNumber(1) val subCmd: Int = 0, @JvmField @ProtoNumber(2) val msgHeader: MsgHeader? = null, @JvmField @ProtoNumber(3) val msgSubcmd0x1FtnNotify: List<FTNNotify> = emptyList(), @JvmField @ProtoNumber(4) val msgSubcmd0x2NfcNotify: List<NFCNotify> = emptyList(), @JvmField @ProtoNumber(5) val msgSubcmd0x3Filecontrol: List<FileControl> = emptyList(), @JvmField @ProtoNumber(6) val msgSubcmd0x4Generic: GenericSubCmd? = null, @JvmField @ProtoNumber(7) val msgSubcmd0x5MoloNotify: List<MoloNotify> = emptyList(), @JvmField @ProtoNumber(8) val msgSubcmd0x8RnfcNotify: List<RNFCNotify> = emptyList(), @JvmField @ProtoNumber(9) val msgSubcmd0x9FtnThumbNotify: List<FTNNotify> = emptyList(), @JvmField @ProtoNumber(10) val msgSubcmd0xaNfcThumbNotify: List<NFCNotify> = emptyList(), @JvmField @ProtoNumber(11) val msgSubcmd0xbMpfileNotify: List<MpFileNotify> = emptyList(), @JvmField @ProtoNumber(12) val msgSubcmd0xcProgressReq: ProgressReq? = null, @JvmField @ProtoNumber(13) val msgSubcmd0xdProgressRsp: ProgressRsp? = null, ) : ProtoBuf { @Serializable internal class ActionInfo( @JvmField @ProtoNumber(1) val serviceName: String = "", @JvmField @ProtoNumber(2) val buf: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class FileControl( @JvmField @ProtoNumber(1) val sessionid: Long = 0L, @JvmField @ProtoNumber(2) val operate: Int = 0, @JvmField @ProtoNumber(3) val seq: Int = 0, @JvmField @ProtoNumber(4) val code: Int = 0, @JvmField @ProtoNumber(5) val msg: String = "", @JvmField @ProtoNumber(6) val groupId: Int = 0, @JvmField @ProtoNumber(7) val groupCurindex: Int = 0, @JvmField @ProtoNumber(8) val batchID: Int = 0, ) : ProtoBuf @Serializable internal class FTNNotify( @JvmField @ProtoNumber(1) val sessionid: Long = 0L, @JvmField @ProtoNumber(2) val fileName: String = "", @JvmField @ProtoNumber(3) val fileIndex: String = "", @JvmField @ProtoNumber(4) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(5) val fileKey: String = "", @JvmField @ProtoNumber(6) val fileLen: Long = 0L, @JvmField @ProtoNumber(7) val originfileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val originfiletype: Int = 0, @JvmField @ProtoNumber(9) val groupId: Int = 0, @JvmField @ProtoNumber(10) val groupSize: Int = 0, @JvmField @ProtoNumber(11) val groupCurindex: Int = 0, @JvmField @ProtoNumber(20) val msgActionInfo: ActionInfo? = null, @JvmField @ProtoNumber(21) val batchID: Int = 0, @JvmField @ProtoNumber(22) val groupflag: Int = 0, ) : ProtoBuf @Serializable internal class MsgItem( @JvmField @ProtoNumber(1) val type: Int = 0, @JvmField @ProtoNumber(2) val text: String = "", ) : ProtoBuf @Serializable internal class QQDataTextMsg( @JvmField @ProtoNumber(1) val msgItems: List<MsgItem> = emptyList(), ) : ProtoBuf @Serializable internal class WifiPhotoNoPush( @JvmField @ProtoNumber(1) val json: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class WifiPhotoWithPush( @JvmField @ProtoNumber(1) val json: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class GenericSubCmd( @JvmField @ProtoNumber(1) val sessionid: Long = 0L, @JvmField @ProtoNumber(2) val size: Int = 0, @JvmField @ProtoNumber(3) val index: Int = 0, @JvmField @ProtoNumber(4) val type: Int = 0, @JvmField @ProtoNumber(5) val buf: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val supportAuth: Int = 0, ) : ProtoBuf @Serializable internal class MoloNotify( @JvmField @ProtoNumber(1) val buf: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(2) val groupId: Int = 0, @JvmField @ProtoNumber(3) val groupSize: Int = 0, @JvmField @ProtoNumber(4) val groupCurindex: Int = 0, ) : ProtoBuf @Serializable internal class MpFileNotify( @JvmField @ProtoNumber(1) val sessionid: Long = 0L, @JvmField @ProtoNumber(2) val operate: Int = 0, @ProtoType(ProtoIntegerType.FIXED) @JvmField @ProtoNumber(3) val fixed32Ip: Int = 0, @JvmField @ProtoNumber(4) val port: Int = 0, @JvmField @ProtoNumber(5) val type: Int = 0, @JvmField @ProtoNumber(6) val power: Int = 0, @JvmField @ProtoNumber(7) val json: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class MsgHeader( @JvmField @ProtoNumber(1) val srcAppId: Int = 0, @JvmField @ProtoNumber(2) val srcInstId: Int = 0, @JvmField @ProtoNumber(3) val dstAppId: Int = 0, @JvmField @ProtoNumber(4) val dstInstId: Int = 0, @JvmField @ProtoNumber(5) val dstUin: Long = 0L, @JvmField @ProtoNumber(6) val srcUin: Long = 0L, @JvmField @ProtoNumber(7) val srcUinType: Int = 0, @JvmField @ProtoNumber(8) val dstUinType: Int = 0, @JvmField @ProtoNumber(9) val srcTerType: Int = 0, @JvmField @ProtoNumber(10) val dstTerType: Int = 0, ) : ProtoBuf @Serializable internal class NFCNotify( @JvmField @ProtoNumber(1) val sessionid: Long = 0L, @JvmField @ProtoNumber(2) val fileName: String = "", @JvmField @ProtoNumber(3) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY, @ProtoType(ProtoIntegerType.FIXED) @JvmField @ProtoNumber(4) val fixed32Ip: Int = 0, @JvmField @ProtoNumber(5) val port: Int = 0, @JvmField @ProtoNumber(6) val urlNotify: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val tokenkey: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(8) val fileLen: Long = 0L, @JvmField @ProtoNumber(9) val originfileMd5: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(10) val originfiletype: Int = 0, @JvmField @ProtoNumber(11) val groupId: Int = 0, @JvmField @ProtoNumber(12) val groupSize: Int = 0, @JvmField @ProtoNumber(13) val groupCurindex: Int = 0, @JvmField @ProtoNumber(20) val msgActionInfo: ActionInfo? = null, @JvmField @ProtoNumber(21) val batchID: Int = 0, @JvmField @ProtoNumber(22) val groupflag: Int = 0, ) : ProtoBuf @Serializable internal class ProgressReq( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val cookie: Long = 0L, @JvmField @ProtoNumber(3) val infoflag: Int = 0, @JvmField @ProtoNumber(4) val uint64Sessionid: List<Long> = emptyList(), ) : ProtoBuf @Serializable internal class ProgressInfo( @JvmField @ProtoNumber(1) val sessionid: Long = 0L, @JvmField @ProtoNumber(2) val progress: Long = 0L, @JvmField @ProtoNumber(3) val status: Int = 0, @JvmField @ProtoNumber(4) val filesize: Long = 0L, @JvmField @ProtoNumber(5) val filename: String = "", @JvmField @ProtoNumber(6) val time: Long = 0L, ) : ProtoBuf @Serializable internal class ProgressRsp( @JvmField @ProtoNumber(1) val cmd: Int = 0, @JvmField @ProtoNumber(2) val cookie: Long = 0L, @JvmField @ProtoNumber(3) val packageCount: Int = 0, @JvmField @ProtoNumber(4) val packageIndex: Int = 0, @JvmField @ProtoNumber(5) val msgProgressinfo: List<ProgressInfo> = emptyList(), ) : ProtoBuf @Serializable internal class RNFCNotify( @JvmField @ProtoNumber(1) val sessionid: Long = 0L, @JvmField @ProtoNumber(2) val token: ByteArray = EMPTY_BYTE_ARRAY, @ProtoType(ProtoIntegerType.FIXED) @JvmField @ProtoNumber(3) val fixed32Ip: Int = 0, @JvmField @ProtoNumber(4) val port: Int = 0, @JvmField @ProtoNumber(5) val svrTaskId: Long = 0L, ) : ProtoBuf } } @Serializable internal class C2CType0x211SubC2CType0x8 : ProtoBuf { @Serializable internal class BusiReqHead( @JvmField @ProtoNumber(1) val int32Version: Int = 0, @JvmField @ProtoNumber(2) val int32Seq: Int = 0, ) : ProtoBuf @Serializable internal class BusiRespHead( @JvmField @ProtoNumber(1) val int32Version: Int = 0, @JvmField @ProtoNumber(2) val int32Seq: Int = 0, @JvmField @ProtoNumber(3) val int32ReplyCode: Int = 0, @JvmField @ProtoNumber(4) val result: String = "", ) : ProtoBuf @Serializable internal class Cell( @JvmField @ProtoNumber(1) val int32Mcc: Int = -1, @JvmField @ProtoNumber(2) val int32Mnc: Int = -1, @JvmField @ProtoNumber(3) val int32Lac: Int = -1, @JvmField @ProtoNumber(4) val int32Cellid: Int = -1, @JvmField @ProtoNumber(5) val int32Rssi: Int = 0, ) : ProtoBuf @Serializable internal class ConnType( @JvmField @ProtoNumber(1) val type: Int /* enum */ = 1, @JvmField @ProtoNumber(2) val desription: String = "", ) : ProtoBuf @Serializable internal class GPS( @JvmField @ProtoNumber(1) val int32Lat: Int = 900000000, @JvmField @ProtoNumber(2) val int32Lon: Int = 900000000, @JvmField @ProtoNumber(3) val int32Alt: Int = -10000000, @JvmField @ProtoNumber(4) val eType: Int /* enum */ = 0, ) : ProtoBuf @Serializable internal class IPAddrInfo( @JvmField @ProtoNumber(1) val int32Ip: Int = 0, @JvmField @ProtoNumber(2) val int32Mask: Int = 0, @JvmField @ProtoNumber(3) val int32Gateway: Int = 0, @JvmField @ProtoNumber(4) val int32Port: Int = 0, ) : ProtoBuf @Serializable internal class JudgeResult( @JvmField @ProtoNumber(1) val type: Int /* enum */ = 0, @JvmField @ProtoNumber(2) val ssid: String = "", @JvmField @ProtoNumber(3) val tips: String = "", @JvmField @ProtoNumber(4) val int32IdleTimeout: Int = 0, @JvmField @ProtoNumber(5) val idleWaiting: Int = 0, @JvmField @ProtoNumber(6) val forceWifi: Int = 0, @JvmField @ProtoNumber(7) val flagsWifipsw: Int = 0, @JvmField @ProtoNumber(8) val flagsNetcheck: Int = 0, ) : ProtoBuf @Serializable internal class LBSInfo( @JvmField @ProtoNumber(1) val msgGps: GPS? = null, @JvmField @ProtoNumber(2) val msgWifis: List<Wifi> = emptyList(), @JvmField @ProtoNumber(3) val msgCells: List<Cell> = emptyList(), ) : ProtoBuf @Serializable internal class MsgBody( @JvmField @ProtoNumber(1) val msgType: Int /* enum */ = 1, @JvmField @ProtoNumber(2) val msgCcNotifylist: NotifyList? = null, @JvmField @ProtoNumber(3) val msgCcnfAbiQuery: NearFieldAbiQuery? = null, @JvmField @ProtoNumber(4) val msgCcPushJudgeResult: PushJudgeResult? = null, @JvmField @ProtoNumber(5) val msgCcnfFilesendReq: NearFieldFileSendReq? = null, @JvmField @ProtoNumber(6) val msgCcnfFilestateSync: NearFieldFileStateSync? = null, ) : ProtoBuf @Serializable internal class NearFieldAbiQuery( @JvmField @ProtoNumber(1) val toUin: Long = 0L, @JvmField @ProtoNumber(2) val fromUin: Long = 0L, @JvmField @ProtoNumber(3) val boolNeedTips: Boolean = false, @JvmField @ProtoNumber(4) val int32Timeout: Int = 0, @JvmField @ProtoNumber(5) val cookie: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(6) val int32PeerIp: Int = 0, @JvmField @ProtoNumber(7) val int32PeerPort: Int = 0, @JvmField @ProtoNumber(8) val peerExtra: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NearFieldFileInfo( @JvmField @ProtoNumber(1) val fileName: String = "", @JvmField @ProtoNumber(2) val fileSize: Long = 0L, @JvmField @ProtoNumber(3) val fileMd5: String = "", @JvmField @ProtoNumber(4) val fileUrl: String = "", @JvmField @ProtoNumber(5) val fileThumbMd5: String = "", @JvmField @ProtoNumber(6) val fileThumbUrl: String = "", @JvmField @ProtoNumber(7) val sessionId: Long = 0L, @JvmField @ProtoNumber(8) val int32Timeout: Int = 0, @JvmField @ProtoNumber(9) val groupId: Long = 0L, ) : ProtoBuf @Serializable internal class NearFieldFileSendReq( @JvmField @ProtoNumber(1) val toUin: Long = 0L, @JvmField @ProtoNumber(2) val msgFileList: List<NearFieldFileInfo> = emptyList(), @JvmField @ProtoNumber(3) val int32Ip: Int = 0, @JvmField @ProtoNumber(4) val int32UdpPort: Int = 0, @JvmField @ProtoNumber(5) val ssid: String = "", @JvmField @ProtoNumber(6) val int32ConnWifiapTimeout: Int = 0, @JvmField @ProtoNumber(7) val forceWifi: Int = 0, @JvmField @ProtoNumber(8) val wifipsw: String = "", ) : ProtoBuf @Serializable internal class NearFieldFileStateSync( @JvmField @ProtoNumber(1) val eType: Int /* enum */ = 1, @JvmField @ProtoNumber(2) val sessionId: Long = 0L, @JvmField @ProtoNumber(3) val fromUin: Long = 0L, @JvmField @ProtoNumber(4) val int32ErrorCode: Int = 0, ) : ProtoBuf @Serializable internal class NearfieldInfo( @JvmField @ProtoNumber(1) val msgLbsInfo: LBSInfo? = null, @JvmField @ProtoNumber(2) val msgConnType: ConnType? = null, @JvmField @ProtoNumber(3) val msgIpInfo: IPAddrInfo? = null, @JvmField @ProtoNumber(4) val msgWifiDetail: WifiDetailInfo? = null, @JvmField @ProtoNumber(5) val msgWifiAbi: WifiAbility? = null, @JvmField @ProtoNumber(6) val extra: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class NotifyList( @JvmField @ProtoNumber(1) val notifyType: Int /* enum */ = 0, @JvmField @ProtoNumber(2) val msgUpdateList: List<UpdateInfo> = emptyList(), @JvmField @ProtoNumber(3) val sessionId: Int = 0, ) : ProtoBuf @Serializable internal class PushJudgeResult( @JvmField @ProtoNumber(1) val msgHead: BusiRespHead? = null, @JvmField @ProtoNumber(2) val toUin: Long = 0L, @JvmField @ProtoNumber(3) val msgResult: JudgeResult? = null, @JvmField @ProtoNumber(4) val int32PeerIp: Int = 0, @JvmField @ProtoNumber(5) val int32PeerPort: Int = 0, @JvmField @ProtoNumber(6) val peerExtra: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ReqAIOJudge( @JvmField @ProtoNumber(1) val msgHead: BusiReqHead? = null, @JvmField @ProtoNumber(2) val toUin: Long = 0L, @JvmField @ProtoNumber(3) val msgNearfieldInfo: NearfieldInfo? = null, ) : ProtoBuf @Serializable internal class ReqExit( @JvmField @ProtoNumber(1) val msgHead: BusiReqHead? = null, @JvmField @ProtoNumber(2) val sessionId: Int = 0, @JvmField @ProtoNumber(3) val msgNearfieldInfo: NearfieldInfo? = null, ) : ProtoBuf @Serializable internal class ReqGetList( @JvmField @ProtoNumber(1) val msgHead: BusiReqHead? = null, @JvmField @ProtoNumber(2) val msgNearfieldInfo: NearfieldInfo? = null, @JvmField @ProtoNumber(3) val sessionId: Int = 0, @JvmField @ProtoNumber(4) val cookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class ReqReportNearFieldAbi( @JvmField @ProtoNumber(1) val msgHead: BusiReqHead? = null, @JvmField @ProtoNumber(2) val fromUin: Long = 0L, @JvmField @ProtoNumber(3) val msgNearfieldInfo: NearfieldInfo? = null, @JvmField @ProtoNumber(4) val cookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RespAIOJudge( @JvmField @ProtoNumber(1) val msgHead: BusiRespHead? = null, @JvmField @ProtoNumber(2) val msgResult: JudgeResult? = null, @JvmField @ProtoNumber(3) val timeout: Int = 0, @JvmField @ProtoNumber(4) val toUin: Long = 0L, @JvmField @ProtoNumber(5) val int32PeerIp: Int = 0, @JvmField @ProtoNumber(6) val int32PeerPort: Int = 0, @JvmField @ProtoNumber(7) val peerExtra: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class RespExit( @JvmField @ProtoNumber(1) val msgHead: BusiRespHead? = null, ) : ProtoBuf @Serializable internal class RespGetList( @JvmField @ProtoNumber(1) val msgHead: BusiRespHead? = null, @JvmField @ProtoNumber(2) val msgUserList: List<UserProfile> = emptyList(), @JvmField @ProtoNumber(3) val sessionId: Int = 0, @JvmField @ProtoNumber(4) val int32UpdateInterval: Int = 0, @JvmField @ProtoNumber(5) val cookie: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class UpdateInfo( @JvmField @ProtoNumber(1) val type: Int /* enum */ = 1, @JvmField @ProtoNumber(2) val msgUser: UserProfile? = null, ) : ProtoBuf @Serializable internal class UserAbility( @JvmField @ProtoNumber(1) val int32SysQlver: Int = 0, @JvmField @ProtoNumber(2) val int32SysTerm: Int = 0, @JvmField @ProtoNumber(3) val int32SysApp: Int = 0, @JvmField @ProtoNumber(10) val int32AbsWifiHost: Int = 0, @JvmField @ProtoNumber(11) val int32AbsWifiClient: Int = 0, @JvmField @ProtoNumber(12) val int32AbsWifiForcedcreate: Int = 0, @JvmField @ProtoNumber(13) val int32AbsWifiForcedconnect: Int = 0, @JvmField @ProtoNumber(14) val int32AbsWifiPassword: Int = 0, @JvmField @ProtoNumber(20) val int32AbsNetReachablecheck: Int = 0, @JvmField @ProtoNumber(21) val int32AbsNetSpeedTest: Int = 0, @JvmField @ProtoNumber(30) val int32AbsUiPromptOnclick: Int = 0, @JvmField @ProtoNumber(31) val int32AbsUiPromptPassive: Int = 0, ) : ProtoBuf @Serializable internal class UserExtraInfo( @JvmField @ProtoNumber(1) val ability: UserAbility? = null, ) : ProtoBuf @Serializable internal class UserProfile( @JvmField @ProtoNumber(1) val uin: Long = 0L, @JvmField @ProtoNumber(2) val int32FaceId: Int = 0, @JvmField @ProtoNumber(3) val int32Sex: Int = 0, @JvmField @ProtoNumber(4) val int32Age: Int = 0, @JvmField @ProtoNumber(5) val nick: String = "", @JvmField @ProtoNumber(6) val tmpTalkSig: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(7) val msgResult: JudgeResult? = null, @JvmField @ProtoNumber(8) val int32Ip: Int = 0, @JvmField @ProtoNumber(9) val int32Port: Int = 0, @JvmField @ProtoNumber(10) val tip: ByteArray = EMPTY_BYTE_ARRAY, @JvmField @ProtoNumber(11) val extra: ByteArray = EMPTY_BYTE_ARRAY, ) : ProtoBuf @Serializable internal class Wifi( @JvmField @ProtoNumber(1) val mac: Long = 0L, @JvmField @ProtoNumber(2) val int32Rssi: Int = 0, ) : ProtoBuf @Serializable internal class WifiAbility( @JvmField @ProtoNumber(1) val boolEstablishAbi: Boolean = false, @JvmField @ProtoNumber(2) val boolAutoConnectAbi: Boolean = false, ) : ProtoBuf @Serializable internal class WifiDetailInfo( @JvmField @ProtoNumber(1) val boolSelfEstablish: Boolean = false, @JvmField @ProtoNumber(2) val ssid: String = "", @JvmField @ProtoNumber(3) val mac: String = "", ) : ProtoBuf } @Serializable internal class C2CType0x211SubC2CType0x9 : ProtoBuf { @Serializable internal class MsgBody( @JvmField @ProtoNumber(1) val service: String = "", @JvmField @ProtoNumber(2) val cMD: Int = 0, @JvmField @ProtoNumber(3) val msgPrinter: MsgPrinter? = null, ) : ProtoBuf { @Serializable internal class HPPrinterStateInfo( @JvmField @ProtoNumber(1) val printerDin: Long = 0L, @JvmField @ProtoNumber(2) val printerQrPicUrl: String = "", @JvmField @ProtoNumber(3) val printerQrOpenUrl: String = "", @JvmField @ProtoNumber(4) val printerTipUrl: String = "", ) : ProtoBuf @Serializable internal class MsgPrinter( @JvmField @ProtoNumber(1) val stringPrinter: List<String> = emptyList(), @JvmField @ProtoNumber(2) val printSessionId: Long = 0L, @JvmField @ProtoNumber(3) val printResult: Int = 0, @JvmField @ProtoNumber(4) val resultMsg: String = "", @JvmField @ProtoNumber(5) val uint64SessionId: List<Long> = emptyList(), @JvmField @ProtoNumber(6) val msgSupportFileInfo: List<SupportFileInfo> = emptyList(), @JvmField @ProtoNumber(7) val hpPrinterStateInfo: HPPrinterStateInfo? = null, ) : ProtoBuf @Serializable internal class SupportFileInfo( @JvmField @ProtoNumber(1) val fileSuffix: String = "", @JvmField @ProtoNumber(2) val copies: Int = 0, @JvmField @ProtoNumber(3) val duplex: Int = 0, ) : ProtoBuf } } @Serializable internal class C2CType0x211SubC2CType0xb : ProtoBuf { @Serializable internal class MsgBody( @JvmField @ProtoNumber(1) val msgMsgHeader: MsgHeader? = null, @JvmField @ProtoNumber(2) val msgRejectMotify: RejectNotify? = null, ) : ProtoBuf { @Serializable internal class MsgHeader( @JvmField @ProtoNumber(1) val bodyType: Int /* enum */ = 101, @JvmField @ProtoNumber(2) val sessionType: Int = 0, @JvmField @ProtoNumber(3) val toUin: Long = 0L, @JvmField @ProtoNumber(4) val toMobile: String = "", @JvmField @ProtoNumber(5) val roomId: Long = 0L, ) : ProtoBuf @Serializable internal class RejectNotify( @JvmField @ProtoNumber(1) val enumRejectReason: Int /* enum */ = 201, @JvmField @ProtoNumber(2) val msg: String = "", @JvmField @ProtoNumber(3) val ringFilename: String = "", ) : ProtoBuf } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/data/richstatus/RichStatus.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.richstatus import kotlin.jvm.JvmField internal class RichStatus( @JvmField var actId: Int = 0, @JvmField var actionId: Int = 0, @JvmField var actionText: String? = null, @JvmField var dataId: Int = 0, @JvmField var dataText: String? = null, @JvmField var feedsId: String? = null, @JvmField var fontId: Int = 0, @JvmField var fontType: Int = 0, @JvmField var key: ByteArray? = null, @JvmField var latitude: Int = 0, @JvmField var locationPosition: Int = 0, @JvmField var locationText: String? = null, @JvmField var lontitude: Int = 0, // @JvmField var mStickerInfos: MutableList<StickerInfo>? = null, // @JvmField var mUins: MutableList<String>? = null, @JvmField var plainText: MutableList<String>? = null, // public HashMap<Integer, /**/> sigZanInfo, @JvmField var signType: Int = 0, @JvmField var time: Long = 0, // var topics: MutableList<Pair<Integer, String>> = mutableListOf(), // var topicsPos: MutableList<Pair<Integer, Integer>> = mutableListOf(), @JvmField var tplId: Int = 0, @JvmField var tplType: Int = 0, ) { fun addPlainText(var1: String) { var pts = this.plainText if (pts == null) { pts = mutableListOf() this.plainText = pts } pts.add(var1) } companion object { fun parseStatus(rawData: ByteArray?): RichStatus { return parseRichStatusImpl(rawData) } } } internal expect fun parseRichStatusImpl(rawData: ByteArray?): RichStatus ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/EncryptMethod.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.utils.crypto.QQEcdh import net.mamoe.mirai.internal.utils.io.encryptAndWrite import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray internal interface EncryptMethod { val id: Int fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket } internal interface EncryptMethodSessionKey : EncryptMethod { override val id: Int get() = 69 val currentLoginState: Int val sessionKey: ByteArray override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket { require(currentLoginState == 2 || currentLoginState == 3) { "currentLoginState must be either 2 or 3" } writeByte(1) // const writeByte(if (currentLoginState == 2) 3 else 2) writeFully(sessionKey) writeShort(258) // const writeShort(0) // const, length of publicKey encryptAndWrite(sessionKey, body) } } internal class EncryptMethodSessionKeyNew( val wtSessionTicket: ByteArray, // t133 val wtSessionTicketKey: ByteArray, // t134 ) : EncryptMethod { override val id: Int get() = 69 override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket { writeShortLVByteArray(wtSessionTicket) encryptAndWrite(wtSessionTicketKey, body) } } internal class EncryptMethodSessionKeyLoginState2(override val sessionKey: ByteArray) : EncryptMethodSessionKey { override val currentLoginState: Int get() = 2 } internal class EncryptMethodSessionKeyLoginState3(override val sessionKey: ByteArray) : EncryptMethodSessionKey { override val currentLoginState: Int get() = 3 } internal class EncryptMethodEcdh135(override val ecdh: QQEcdh) : EncryptMethodEcdh { override val id: Int get() = 135 } internal class EncryptMethodEcdh7(override val ecdh: QQEcdh) : EncryptMethodEcdh { override val id: Int get() = 7 // 135 } internal interface EncryptMethodEcdh : EncryptMethod { companion object { operator fun invoke(ecdh: QQEcdh): EncryptMethodEcdh { return if (ecdh.fallbackMode) { EncryptMethodEcdh135(ecdh) } else EncryptMethodEcdh7(ecdh) } } val ecdh: QQEcdh override fun makeBody(client: QQAndroidClient, body: BytePacketBuilder.() -> Unit): ByteReadPacket = buildPacket { /* //new curve p-256 writeByte(2) // const writeByte(1) // const writeFully(client.randomKey) writeShort(0x0131) // const writeShort(0x0001) */ writeByte(2) // version writeByte(1) // const writeFully(client.randomKey) writeShort(0x0131) writeShort(ecdh.version.toShort())// public key version // for p-256, drop(26). // but not really sure. writeShortLVByteArray(ecdh.publicKey) encryptAndWrite(ecdh.initialQQShareKey, body) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/OutgoingPacket.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet import io.ktor.utils.io.core.* import kotlinx.serialization.encodeToByteArray import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.components.EcdhInitialPublicKeyUpdater import net.mamoe.mirai.internal.network.components.encryptServiceOrNull import net.mamoe.mirai.internal.network.protocol.data.proto.SSOReserveField import net.mamoe.mirai.internal.network.protocol.packet.sso.TRpcRawPacket import net.mamoe.mirai.internal.spi.EncryptService import net.mamoe.mirai.internal.spi.EncryptServiceContext import net.mamoe.mirai.internal.utils.io.encryptAndWrite import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.internal.utils.io.writeHex import net.mamoe.mirai.internal.utils.io.writeIntLVPacket import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.Either.Companion.fold import kotlin.random.Random @Suppress("unused") internal class OutgoingPacketWithRespType<R : Packet?> constructor( remark: String?, commandName: String, sequenceId: Int, delegate: ByteReadPacket ) : OutgoingPacket(remark, commandName, sequenceId, delegate) internal open class OutgoingPacket constructor( remark: String?, val commandName: String, val sequenceId: Int, delegate: ByteReadPacket ) { val delegate = delegate.readBytes() val displayName: String = if (remark == null) commandName else "$commandName($remark)" } internal class IncomingPacket private constructor( val commandName: String, val sequenceId: Int, val result: Either<Throwable, Packet?> // exception will be the same as caught from PacketFactory.decode. So they can be ISE, NPE, etc. ) { companion object { operator fun invoke(commandName: String, sequenceId: Int, data: Packet?) = IncomingPacket(commandName, sequenceId, Either(data)) operator fun invoke(commandName: String, sequenceId: Int, throwable: Throwable) = IncomingPacket(commandName, sequenceId, Either(throwable)) @TestOnly operator fun invoke(commandName: String, data: Packet?) = IncomingPacket(commandName, Random.nextInt(), data) @TestOnly operator fun invoke(commandName: String, throwable: Throwable) = IncomingPacket(commandName, Random.nextInt(), throwable) } override fun toString(): String { return result.fold( onLeft = { "IncomingPacket(cmd=$commandName, seq=$sequenceId, FAILURE, e=$it)" }, onRight = { "IncomingPacket(cmd=$commandName, seq=$sequenceId, SUCCESS, r=$it)" } ) } } internal enum class PacketEncryptType(val value: Int) { NoEncrypt(0x00) { // 0x00 override fun defaultKey(client: QQAndroidClient): ByteArray = NO_ENCRYPT }, D2(0x01) { //0x01 override fun defaultKey(client: QQAndroidClient): ByteArray { return client.wLoginSigInfo.d2Key } }, Empty(0x02) { // 16 zeros,// 0x02 override fun defaultKey(client: QQAndroidClient): ByteArray { return KEY_16_ZEROS } }, Unknown(-1) { override fun defaultKey(client: QQAndroidClient): ByteArray { error("unreachable") } } ; inline val codec: Byte get() = ordinal.toByte() abstract fun defaultKey(client: QQAndroidClient): ByteArray companion object { internal fun of(value: Int) = enumValues<PacketEncryptType>().find { it.value == value } ?: Unknown } } @Suppress("DuplicatedCode") internal fun <R : Packet?> buildRawUniPacket( client: QQAndroidClient, encryptMethod: PacketEncryptType = PacketEncryptType.D2, remark: String?, commandName: String, key: ByteArray = encryptMethod.defaultKey(client), extraData: ByteReadPacket = BRP_STUB, uin: String = client.uin.toString(), sequenceId: Int = client.nextSsoSequenceId(), body: BytePacketBuilder.(sequenceId: Int) -> Unit ): OutgoingPacketWithRespType<R> { return OutgoingPacketWithRespType(remark, commandName, sequenceId, buildPacket { writeIntLVPacket(lengthOffset = { it + 4 }) { writeInt(0x0B) // req type simple writeByte(encryptMethod.codec) writeInt(sequenceId) writeByte(0) uin.let { writeInt(it.length + 4) writeText(it) } val encryptWorker = client.bot.encryptServiceOrNull val bodyBytes = buildPacket { body(sequenceId) }.readBytes() val signDataPacket = if (encryptWorker != null) { val signResult = encryptWorker.qSecurityGetSign( EncryptServiceContext(client.uin), sequenceId, commandName, bodyBytes ) if (signResult != null) buildPacket { writeProtoBuf( SSOReserveField.ReserveFields.serializer(), SSOReserveField.ReserveFields( flag = 0, qimei = client.qimei16?.toByteArray() ?: EMPTY_BYTE_ARRAY, newconnFlag = 0, uid = client.uin.toString(), imsi = 0, networkType = 1, ipStackType = 1, messageType = 0, secInfo = SSOReserveField.SsoSecureInfo( secSig = signResult.sign, secDeviceToken = signResult.token, secExtra = signResult.extra ), ssoIpOrigin = 0, ) ) } else BRP_STUB } else BRP_STUB if (signDataPacket != BRP_STUB && (extraData != BRP_STUB && extraData.remaining != 0L)) { throw IllegalStateException("$commandName cmd needs sign but has extraData!") } if (encryptMethod === PacketEncryptType.NoEncrypt) { writeUniPacket( commandName, client.outgoingPacketSessionId, if (signDataPacket == BRP_STUB) { extraData } else { signDataPacket }, (client.qimei16?.encodeToByteArray() ?: EMPTY_BYTE_ARRAY) ) { writeFully(bodyBytes) } } else { encryptAndWrite(key) { writeUniPacket( commandName, client.outgoingPacketSessionId, if (signDataPacket == BRP_STUB) { extraData } else { signDataPacket }, (client.qimei16?.encodeToByteArray() ?: EMPTY_BYTE_ARRAY) ) { writeFully(bodyBytes) } } } } }) } @Suppress("DuplicatedCode", "NOTHING_TO_INLINE") internal inline fun <R : Packet?> OutgoingPacketFactory<R>.buildOutgoingUniPacket( client: QQAndroidClient, encryptMethod: PacketEncryptType = PacketEncryptType.D2, remark: String? = this.commandName, commandName: String = this.commandName, key: ByteArray = encryptMethod.defaultKey(client), extraData: ByteReadPacket = BRP_STUB, uin: String = client.uin.toString(), sequenceId: Int = client.nextSsoSequenceId(), noinline body: BytePacketBuilder.(sequenceId: Int) -> Unit ): OutgoingPacketWithRespType<R> = buildRawUniPacket(client, encryptMethod, remark, commandName, key, extraData, uin, sequenceId, body) @Suppress("NOTHING_TO_INLINE") internal inline fun <R : Packet?> IncomingPacketFactory<R>.buildResponseUniPacket( client: QQAndroidClient, encryptMethod: PacketEncryptType = PacketEncryptType.D2, // 1: PB? remark: String? = this.responseCommandName, commandName: String = this.responseCommandName, key: ByteArray = encryptMethod.defaultKey(client), extraData: ByteReadPacket = BRP_STUB, sequenceId: Int = client.nextSsoSequenceId(), noinline body: BytePacketBuilder.(sequenceId: Int) -> Unit = {} ): OutgoingPacketWithRespType<R> = buildRawUniPacket( client = client, encryptMethod = encryptMethod, remark = remark, commandName = commandName, key = key, extraData = extraData, sequenceId = sequenceId, body = body ) private inline fun BytePacketBuilder.writeUniPacket( commandName: String, outgoingPacketSessionId: ByteArray, extraData: ByteReadPacket = BRP_STUB, qimei16: ByteArray = EMPTY_BYTE_ARRAY, crossinline body: BytePacketBuilder.() -> Unit ) { writeIntLVPacket(lengthOffset = { it + 4 }) { commandName.let { writeInt(it.length + 4) writeText(it) } writeInt(outgoingPacketSessionId.size + 4) writeFully(outgoingPacketSessionId) // 02 B0 5B 8B if (extraData === BRP_STUB) { writeInt(0x04) } else { writeInt((extraData.remaining + 4).toInt()) writePacket(extraData) } writeInt(qimei16.size + 4) writeFully(qimei16) } // body writeIntLVPacket(lengthOffset = { it + 4 }, builder = body) } internal val NO_ENCRYPT: ByteArray = ByteArray(0) /** * com.tencent.qphone.base.util.CodecWarpper#encodeRequest(int, java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String, byte[], int, int, java.lang.String, byte, byte, byte, byte[], byte[], boolean) */ internal fun <R : Packet?> OutgoingPacketFactory<R>.buildLoginOutgoingPacket( client: QQAndroidClient, encryptMethod: PacketEncryptType, uin: String = client.uin.toString(), extraData: ByteArray = if (encryptMethod == PacketEncryptType.D2) client.wLoginSigInfo.d2.data else EMPTY_BYTE_ARRAY, remark: String? = null, commandName: String = this.commandName, key: ByteArray = encryptMethod.defaultKey(client), body: BytePacketBuilder.(sequenceId: Int) -> Unit ): OutgoingPacketWithRespType<R> { val sequenceId: Int = client.nextSsoSequenceId() return OutgoingPacketWithRespType(remark, commandName, sequenceId, buildPacket { writeIntLVPacket(lengthOffset = { it + 4 }) { writeInt(0x00_00_00_0A) // packet login writeByte(encryptMethod.codec) // encrypt type extraData.let { // actually d2 key if encryptMethod = d2 writeInt(it.size + 4) writeFully(it) } writeByte(0x00) uin.let { writeInt(it.length + 4) writeText(it) } if (encryptMethod == PacketEncryptType.NoEncrypt) { body(sequenceId) } else { encryptAndWrite(key) { body(sequenceId) } } } }) } private inline val BRP_STUB get() = ByteReadPacket.Empty internal fun createChannelProxy(bot: QQAndroidBot): EncryptService.ChannelProxy { return object : EncryptService.ChannelProxy { override suspend fun sendMessage( remark: String, commandName: String, uin: Long, data: ByteArray ): EncryptService.ChannelResult? { if (commandName.startsWith(TRpcRawPacket.COMMAND_PREFIX)) { val client = bot.client val packet = client.bot.network.sendAndExpect( TRpcRawPacket.buildLoginOutgoingPacket( client = client, encryptMethod = PacketEncryptType.Empty, uin = uin.toString(), remark = remark, commandName = commandName, ) { writeSsoPacket( client, client.subAppId, sequenceId = it, commandName = commandName, body = { writeFully(data) } ) } ) return EncryptService.ChannelResult(commandName, packet.data) } return null } } } internal fun BytePacketBuilder.writeSsoPacket( client: QQAndroidClient, subAppId: Long, commandName: String, extraData: ByteReadPacket = BRP_STUB, unknownHex: String = "01 00 00 00 00 00 00 00 00 00 01 00", sequenceId: Int, body: BytePacketBuilder.() -> Unit ) { /* send * 00 00 00 78 * 00 00 94 90 * 20 02 ED BD * 20 02 ED BD * 01 00 00 00 00 00 00 00 00 00 01 00 * 00 00 00 04 * 00 00 00 13 48 65 61 72 74 62 65 61 74 2E 41 6C 69 76 65 * 00 00 00 08 59 E7 DF 4F * 00 00 00 13 38 36 35 31 36 36 30 32 36 34 34 36 39 32 35 * 00 00 00 04 * 00 22 7C 34 36 30 30 30 31 39 31 39 38 37 36 30 32 36 7C 41 38 2E 32 2E 30 2E 32 37 66 36 65 61 39 36 * 00 00 00 04 * * 00 00 00 04 */ val encryptWorker = client.bot.encryptServiceOrNull val bodyBytes = buildPacket(body).readBytes() val reserveField = if (encryptWorker != null) { val signResult = encryptWorker.qSecurityGetSign( EncryptServiceContext(client.uin), sequenceId, commandName, bodyBytes ) if (signResult != null) ProtoBuf.encodeToByteArray( SSOReserveField.ReserveFields( flag = 0, qimei = client.qimei16?.toByteArray() ?: EMPTY_BYTE_ARRAY, newconnFlag = 0, uid = client.uin.toString(), imsi = 0, networkType = 1, ipStackType = 1, messageType = 0, secInfo = SSOReserveField.SsoSecureInfo( secSig = signResult.sign, secDeviceToken = signResult.token, secExtra = signResult.extra ), ssoIpOrigin = 0, ) ) else EMPTY_BYTE_ARRAY } else EMPTY_BYTE_ARRAY writeIntLVPacket(lengthOffset = { it + 4 }) { writeInt(sequenceId) writeInt(subAppId.toInt()) writeInt(subAppId.toInt()) writeHex(unknownHex) if (extraData === BRP_STUB || extraData.remaining == 0L) { // fast-path writeInt(0x04) } else { writeInt((extraData.remaining + 4).toInt()) writePacket(extraData) } writeInt(commandName.length + 4) writeText(commandName) writeInt(client.outgoingPacketSessionId.size + 4) writeFully(client.outgoingPacketSessionId) // 02 B0 5B 8B writeInt(client.device.imei.length + 4) writeText(client.device.imei) writeInt(0x4) writeShort((client.ksid.size + 2).toShort()) writeFully(client.ksid) writeInt(reserveField.size + 4) writeFully(reserveField) val qimei16Bytes = client.qimei16?.toByteArray() ?: EMPTY_BYTE_ARRAY writeInt(qimei16Bytes.size + 4) writeFully(qimei16Bytes) } // body writeIntLVPacket(lengthOffset = { it + 4 }, builder = { writeFully(bodyBytes) }) } internal fun BytePacketBuilder.writeOicqRequestPacket( client: QQAndroidClient, uin: Long = client.uin, encryptMethod: EncryptMethod = EncryptMethodEcdh(client.bot.components[EcdhInitialPublicKeyUpdater].getQQEcdh()), commandId: Int, bodyBlock: BytePacketBuilder.() -> Unit ) { val body = encryptMethod.makeBody(client, bodyBlock) writeByte(0x02) // head writeShort((27 + 2 + body.remaining).toShort()) // orthodox algorithm writeShort(8001) writeShort(commandId.toShort()) writeShort(1) // const?? writeInt(uin.toInt()) writeByte(3) // originally const writeByte(encryptMethod.id.toByte()) writeByte(0) // const8_always_0 writeInt(2) // originally const writeInt(client.appClientVersion) writeInt(0) // constp_always_0 writePacket(body) writeByte(0x03) // tail } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/PacketFactory.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet import io.ktor.utils.io.core.* import net.mamoe.mirai.event.Event import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.PacketCodec import net.mamoe.mirai.internal.network.protocol.packet.chat.* 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.chat.receive.* import net.mamoe.mirai.internal.network.protocol.packet.chat.video.PttCenterSvr 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.list.ProfileService import net.mamoe.mirai.internal.network.protocol.packet.list.StrangerList import net.mamoe.mirai.internal.network.protocol.packet.login.ConfigPushSvc import net.mamoe.mirai.internal.network.protocol.packet.login.Heartbeat import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.summarycard.ChangeFriendRemark import net.mamoe.mirai.internal.network.protocol.packet.summarycard.SummaryCard import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiLoggerWithSwitch internal sealed class PacketFactory<TPacket : Packet?> { /** * 筛选从服务器接收到的包时的 commandName */ abstract val receivingCommandName: String open val canBeCached: Boolean get() = true } /** * 一种客户端主动发送的数据包的处理工厂. * 它必须是由客户端主动发送, 产生一个 sequenceId, 然后服务器以相同的 sequenceId 返回. * 必须在 [KnownPacketFactories] 中注册工厂, 否则将不能收到回复. * 应由一个 `object` 实现, 且实现 `operator fun invoke` 或按 subCommand 或其意义命名的函数来构造 [OutgoingPacket] * * @param TPacket 服务器回复包解析结果 */ internal abstract class OutgoingPacketFactory<TPacket : Packet?>( /** * 命令名. 如 `wtlogin.login`, `ConfigPushSvc.PushDomain` */ val commandName: String ) : PacketFactory<TPacket>() { final override val receivingCommandName: String get() = commandName /** * **解码**服务器的回复数据包. 返回的包若是 [Event], 则会 broadcast. */ abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot): TPacket /** * 可选的处理这个包. 可以在这里面发新的包. */ open suspend fun QQAndroidBot.handle(packet: TPacket) {} } /** * 处理服务器发来的包的工厂. * 这个工厂可以在 [handle] 时回复一个 commandId 为 [responseCommandName] 的包, 也可以不回复. * 必须先到 [KnownPacketFactories] 中注册工厂, 否则不能处理. */ internal abstract class IncomingPacketFactory<TPacket : Packet?>( /** * 接收自服务器的包的 commandName */ override val receivingCommandName: String, /** * 要返回给服务器的包的 commandName */ val responseCommandName: String = "" ) : PacketFactory<TPacket>() { /** * **解码**服务器的回复数据包. 返回的包若是 [Event], 则会 broadcast. */ abstract suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): TPacket /** * 处理解码后的包, 返回一个 [OutgoingPacket] 以发送给服务器, 返回 null 则不作处理. */ open suspend fun QQAndroidBot.handle(packet: TPacket, sequenceId: Int): OutgoingPacket? { return null } } @JvmName("decode0") internal suspend inline fun <P : Packet?> OutgoingPacketFactory<P>.decode( bot: QQAndroidBot, packet: ByteReadPacket ): P = packet.decode(bot) @JvmName("decode1") internal suspend inline fun <P : Packet?> IncomingPacketFactory<P>.decode( bot: QQAndroidBot, packet: ByteReadPacket, sequenceId: Int ): P = packet.decode(bot, sequenceId) /** * 数据包相关的调试输出. * 它默认是关闭的. */ @Deprecated( "Kept for binary compatibility.", ReplaceWith("PacketCodec.PacketLogger", "net.mamoe.mirai.internal.network.components.PacketCodec"), level = DeprecationLevel.HIDDEN, ) @PublishedApi @DeprecatedSinceMirai(hiddenSince = "2.7") internal val PacketLogger: MiraiLoggerWithSwitch get() = PacketCodec.PacketLogger /** * Registered factories. */ internal object KnownPacketFactories { object OutgoingFactories : List<OutgoingPacketFactory<*>> by mutableListOf( WtLogin.Login, WtLogin.ExchangeEmp, WtLogin.TransEmp, StatSvc.Register, StatSvc.GetOnlineStatus, StatSvc.SimpleGet, StatSvc.GetDevLoginInfo, MessageSvcPbGetMsg, MessageSvcPushForceOffline, MessageSvcPbSendMsg, MessageSvcPbDeleteMsg, MessageSvcPbGetRoamMsgReq, MessageSvcPbGetGroupMsg, FriendList.GetFriendGroupList, FriendList.DelFriend, FriendList.GetTroopListSimplify, FriendList.GetTroopMemberList, FriendList.SetGroupReqPack, FriendList.MoveGroupMemReqPack, ImgStore.GroupPicUp, PttStore.GroupPttUp, PttStore.GroupPttDown, PttStore.C2CPttDown, PttCenterSvr.GroupShortVideoUpReq, PttCenterSvr.ShortVideoDownReq, LongConn.OffPicUp, // LongConn.OffPicDown, TroopManagement.EditSpecialTitle, TroopManagement.Mute, TroopManagement.GroupOperation, TroopManagement.GetTroopConfig, TroopManagement.ModifyAdmin, TroopManagement.GetGroupLastMsgSeq, // TroopManagement.GetGroupInfo, TroopManagement.EditGroupNametag, TroopManagement.Kick, TroopManagement.SwitchAnonymousChat, TroopEssenceMsgManager.SetEssence, TroopEssenceMsgManager.RemoveEssence, NudgePacket, Heartbeat.Alive, PbMessageSvc.PbMsgWithDraw, MultiMsg.ApplyUp, MultiMsg.ApplyDown, NewContact.SystemMsgNewFriend, NewContact.SystemMsgNewGroup, ProfileService.GroupMngReq, StrangerList.GetStrangerList, StrangerList.DelStranger, SummaryCard.ReqSummaryCard, ChangeFriendRemark, MusicSharePacket, *FileManagement.factories ) object IncomingFactories : List<IncomingPacketFactory<*>> by mutableListOf( OnlinePushPbPushGroupMsg, OnlinePushReqPush, OnlinePushPbPushTransMsg, OnlinePushSidExpired, MessageSvcPushNotify, MessageSvcPushReaded, MessageSvcRequestPushStatus, ConfigPushSvc.PushReq, PbC2CMsgSync, StatSvc.ReqMSFOffline, StatSvc.SvcReqMSFLoginNotify ) // SvcReqMSFLoginNotify 自己的其他设备上限 // MessageSvcPushReaded 电脑阅读了别人的消息, 告知手机 // OnlinePush.PbC2CMsgSync 电脑发消息给别人, 同步给手机 fun findPacketFactory(commandName: String): PacketFactory<*>? { return OutgoingFactories.firstOrNull { it.receivingCommandName == commandName } ?: IncomingFactories.firstOrNull { it.receivingCommandName == commandName } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/Tlv.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.internal.network.protocol.packet import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.components.encryptServiceOrNull import net.mamoe.mirai.internal.network.protocol.LoginType import net.mamoe.mirai.internal.spi.EncryptServiceContext import net.mamoe.mirai.internal.utils.GuidSource import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag import net.mamoe.mirai.internal.utils.NetworkType import net.mamoe.mirai.internal.utils.guidFlag import net.mamoe.mirai.internal.utils.io.encryptAndWrite import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray import net.mamoe.mirai.internal.utils.io.writeShortLVByteArrayLimitedLength import net.mamoe.mirai.internal.utils.io.writeShortLVString import net.mamoe.mirai.utils.* import kotlin.random.Random private val Char.isHumanReadable get() = this in '0'..'9' || this in 'a'..'z' || this in 'A'..'Z' || this in """ <>?,.";':/\][{}~!@#$%^&*()_+-=`""" || this in "\n\r" internal fun TlvMap.smartToString(leadingLineBreak: Boolean = true, sorted: Boolean = true): String { fun ByteArray.valueToString(): String { val str = this.decodeToString() return if (str.all { it.isHumanReadable }) str else this.toUHexString() } val map = if (sorted) entries.sortedBy { it.key } else this.entries return buildString { if (leadingLineBreak) appendLine() appendLine("count=${map.size}") appendLine(map.joinToString("\n") { (key, value) -> "0x" + key.toShort().toUHexString("") + " = " + value.valueToString() }) } } /** * 显式表示一个 [ByteArray] 是一个 tlv 的 body */ @JvmInline internal value class Tlv(val value: ByteArray) internal fun TlvMapWriter.t1(uin: Long, timeSeconds: Int, ipv4: ByteArray) { require(ipv4.size == 4) { "ip.size must == 4" } tlv(0x01) { writeShort(1) // _ip_ver writeInt(Random.nextInt()) writeInt(uin.toInt()) writeInt(timeSeconds) writeFully(ipv4) writeShort(0) } } internal fun TlvMapWriter.t2(captchaCode: String, captchaToken: ByteArray, sigVer: Short = 0) { tlv(0x02) { writeShort(sigVer) writeShortLVString(captchaCode) writeShortLVByteArray(captchaToken) } } internal fun TlvMapWriter.t8( localId: Int = 2052 ) { tlv(0x08) { writeShort(0) writeInt(localId) // localId writeShort(0) } } internal fun TlvMapWriter.t16( ssoVersion: Int, subAppId: Long, guid: ByteArray, apkId: ByteArray, apkVersionName: ByteArray, apkSignatureMd5: ByteArray ) { tlv(0x16) { writeInt(ssoVersion) writeInt(16) writeInt(subAppId.toInt()) writeFully(guid) writeShortLVByteArray(apkId) writeShortLVByteArray(apkVersionName) writeShortLVByteArray(apkSignatureMd5) } } internal fun TlvMapWriter.t18( appId: Long, appClientVersion: Int = 0, uin: Long, constant1_always_0: Int = 0 ) { tlv(0x18) { writeShort(1) //_ping_version writeInt(0x00_00_06_00) //_sso_version=1536 writeInt(appId.toInt()) writeInt(appClientVersion) writeInt(uin.toInt()) writeShort(constant1_always_0.toShort()) writeShort(0) } } internal fun TlvMapWriter.t1b( micro: Int = 0, version: Int = 0, size: Int = 3, margin: Int = 4, dpi: Int = 72, ecLevel: Int = 2, hint: Int = 2 ) { tlv(0x1b) { writeInt(micro) writeInt(version) writeInt(size) writeInt(margin) writeInt(dpi) writeInt(ecLevel) writeInt(hint) writeShort(0) } } internal fun TlvMapWriter.t1d( miscBitmap: Int, ) { tlv(0x1d) { writeByte(1) writeInt(miscBitmap) writeInt(0) writeByte(0) writeInt(0) } } internal fun TlvMapWriter.t1f( isRoot: Boolean = false, osName: ByteArray, osVersion: ByteArray, simVendor: ByteArray, apn: ByteArray, networkType: Short = 2, ) { tlv(0x1f) { writeByte(if (isRoot) 1 else 0) writeShortLVByteArray(osName) writeShortLVByteArray(osVersion) writeShort(networkType) writeShortLVByteArray(simVendor) writeShortLVByteArray(EMPTY_BYTE_ARRAY) writeShortLVByteArray(apn) } } internal fun TlvMapWriter.t33( guid: ByteArray, ) { tlv(0x33, guid) } internal fun TlvMapWriter.t35( productType: Int ) { tlv(0x35) { writeInt(productType) } } internal fun TlvMapWriter.t106( client: QQAndroidClient, appId: Long = 16L, passwordMd5: ByteArray, ) { return t106( appId, client.subAppId /* maybe 1*/, client.appClientVersion, client.uin, client.device.ipAddress, true, passwordMd5, 0, client.uin.toByteArray(), client.tgtgtKey, true, client.device.guid, LoginType.PASSWORD, client.ssoVersion ) } internal fun TlvMapWriter.t106( encryptA1: ByteArray ) { tlv(0x106) { writeFully(encryptA1) } } /** * A1 */ internal fun TlvMapWriter.t106( appId: Long = 16L, subAppId: Long, appClientVersion: Int = 0, uin: Long, ipv4: ByteArray, isSavePassword: Boolean = true, passwordMd5: ByteArray, salt: Long, uinAccountString: ByteArray, tgtgtKey: ByteArray, isGuidAvailable: Boolean = true, guid: ByteArray?, loginType: LoginType, ssoVersion: Int, ) { passwordMd5.requireSize(16) tgtgtKey.requireSize(16) guid?.requireSize(16) ipv4.requireSize(4) tlv(0x106) { encryptAndWrite( (passwordMd5 + ByteArray(4) + (salt.takeIf { it != 0L } ?: uin).toInt() .toByteArray()).md5() ) { writeShort(4)//TGTGTVer writeInt(Random.nextInt()) writeInt(ssoVersion)//ssoVer writeInt(appId.toInt()) writeInt(appClientVersion) if (uin == 0L) { writeLong(salt) } else { writeLong(uin) } writeInt(currentTimeSeconds().toInt()) writeFully(ipv4) // writeByte(isSavePassword.toByte()) writeFully(passwordMd5) writeFully(tgtgtKey) writeInt(0) // wtf writeByte(isGuidAvailable.toByte()) if (isGuidAvailable) { require(guid != null) { "Guid must not be null when isGuidAvailable==true" } } if (guid == null) { repeat(4) { writeInt(Random.nextInt()) } } else { writeFully(guid) } writeInt(subAppId.toInt()) writeInt(loginType.value) writeShortLVByteArray(uinAccountString) writeShort(0) } } } internal fun TlvMapWriter.t116( miscBitmap: Int, subSigMap: Int, appIdList: LongArray = longArrayOf(1600000226L) ) { tlv(0x116) { writeByte(0) // _ver writeInt(miscBitmap) // 184024956 writeInt(subSigMap) // 66560 writeByte(appIdList.size.toByte()) appIdList.forEach { writeInt(it.toInt()) } } } internal fun TlvMapWriter.t100( appId: Long = 16, subAppId: Long, appClientVersion: Int, ssoVersion: Int, mainSigMap: Int ) { tlv(0x100) { writeShort(1)//db_buf_ver writeInt(ssoVersion)//sso_ver writeInt(appId.toInt()) writeInt(subAppId.toInt()) writeInt(appClientVersion) writeInt(mainSigMap) // sigMap, 34869472? } } internal fun TlvMapWriter.t10a( tgt: ByteArray, ) { tlv(0x10a) { writeFully(tgt) } } internal fun TlvMapWriter.t107( picType: Int, capType: Int = 0, picSize: Int = 0, retType: Int = 1 ) { tlv(0x107) { writeShort(picType.toShort()) writeByte(capType.toByte()) writeShort(picSize.toShort()) writeByte(retType.toByte()) } } internal fun TlvMapWriter.t108( ksid: ByteArray ) { // require(ksid.size == 16) { "ksid should length 16" } tlv(0x108) { writeFully(ksid) } } internal fun TlvMapWriter.t104( t104Data: ByteArray ) { tlv(0x104) { writeFully(t104Data) } } internal fun TlvMapWriter.t547( t547Data: ByteArray ) { tlv(0x547) { writeFully(t547Data) } } internal fun TlvMapWriter.t174( t174Data: ByteArray ) { tlv(0x174) { writeFully(t174Data) } } internal fun TlvMapWriter.t17a( smsAppId: Int = 0 ) { tlv(0x17a) { writeInt(smsAppId) } } internal fun TlvMapWriter.t197( devLockMobileType: Byte = 0 ) { tlv(0x197) { writeByte(devLockMobileType) } } internal fun TlvMapWriter.t198() { tlv(0x198) { writeByte(0) } } internal fun TlvMapWriter.t19e( value: Int = 0 ) { tlv(0x19e) { writeShort(1) writeByte(value.toByte()) } } internal fun TlvMapWriter.t17c( t17cData: ByteArray ) { tlv(0x17c) { writeShort(t17cData.size.toShort()) writeFully(t17cData) } } internal fun TlvMapWriter.t401( t401Data: ByteArray ) { tlv(0x401) { writeFully(t401Data) } } /** * @param apkId application.getPackageName().getBytes() */ internal fun TlvMapWriter.t142( apkId: ByteArray ) { tlv(0x142) { writeShort(0) //_version writeShortLVByteArrayLimitedLength(apkId, 32) } } internal fun TlvMapWriter.t143( d2: ByteArray ) { tlv(0x143) { writeFully(d2) } } internal fun TlvMapWriter.t112( nonNumberUin: ByteArray ) { tlv(0x112) { writeFully(nonNumberUin) } } internal fun TlvMapWriter.t144( client: QQAndroidClient ) { return t144( androidId = client.device.androidId, androidDevInfo = client.device.generateDeviceInfoData(), osType = client.device.osType, osVersion = client.device.version.release, networkType = client.networkType, simInfo = client.device.simInfo, unknown = byteArrayOf(), apn = client.device.apn, isGuidFromFileNull = false, isGuidAvailable = true, isGuidChanged = false, guidFlag = guidFlag(GuidSource.FROM_STORAGE, MacOrAndroidIdChangeFlag(0)), buildModel = client.device.model, guid = client.device.guid, buildBrand = client.device.brand, tgtgtKey = client.tgtgtKey ) } internal fun TlvMapWriter.t144( // t109 androidId: ByteArray, // t52d androidDevInfo: ByteArray, // t124 osType: ByteArray = "android".toByteArray(), osVersion: ByteArray, networkType: NetworkType, simInfo: ByteArray, unknown: ByteArray, apn: ByteArray = "wifi".toByteArray(), // t128 isGuidFromFileNull: Boolean = false, isGuidAvailable: Boolean = true, isGuidChanged: Boolean = false, guidFlag: Long, buildModel: ByteArray, guid: ByteArray, buildBrand: ByteArray, // encrypt tgtgtKey: ByteArray ) { tlv(0x144) { encryptAndWrite(tgtgtKey) { _writeTlvMap { t109(androidId) t52d(androidDevInfo) t124(osType, osVersion, networkType, simInfo, unknown, apn) t128(isGuidFromFileNull, isGuidAvailable, isGuidChanged, guidFlag, buildModel, guid, buildBrand) t16e(buildModel) } } } } internal fun TlvMapWriter.t109( androidId: ByteArray ) { tlv(0x109) { writeFully(androidId.md5()) } } internal fun TlvMapWriter.t52d( androidDevInfo: ByteArray // oicq.wlogin_sdk.tools.util#get_android_dev_info ) { tlv(0x52d) { writeFully(androidDevInfo) // 0A 07 75 6E 6B 6E 6F 77 6E 12 7E 4C 69 6E 75 78 20 76 65 72 73 69 6F 6E 20 34 2E 39 2E 33 31 20 28 62 75 69 6C 64 40 42 75 69 6C 64 32 29 20 28 67 63 63 20 76 65 72 73 69 6F 6E 20 34 2E 39 20 32 30 31 35 30 31 32 33 20 28 70 72 65 72 65 6C 65 61 73 65 29 20 28 47 43 43 29 20 29 20 23 31 20 53 4D 50 20 50 52 45 45 4D 50 54 20 54 68 75 20 44 65 63 20 31 32 20 31 35 3A 33 30 3A 35 35 20 49 53 54 20 32 30 31 39 1A 03 52 45 4C 22 03 33 32 37 2A 41 4F 6E 65 50 6C 75 73 2F 4F 6E 65 50 6C 75 73 35 2F 4F 6E 65 50 6C 75 73 35 3A 37 2E 31 2E 31 2F 4E 4D 46 32 36 58 2F 31 30 31 37 31 36 31 37 3A 75 73 65 72 2F 72 65 6C 65 61 73 65 2D 6B 65 79 73 32 24 36 63 39 39 37 36 33 66 2D 66 62 34 32 2D 34 38 38 31 2D 62 37 32 65 2D 63 37 61 61 38 61 36 63 31 63 61 34 3A 10 65 38 63 37 30 35 34 64 30 32 66 33 36 33 64 30 42 0A 6E 6F 20 6D 65 73 73 61 67 65 4A 03 33 32 37 } } internal fun TlvMapWriter.t124( osType: ByteArray = "android".toByteArray(), osVersion: ByteArray, // Build.VERSION.RELEASE.toByteArray() networkType: NetworkType, //oicq.wlogin_sdk.tools.util#get_network_type simInfo: ByteArray, // oicq.wlogin_sdk.tools.util#get_sim_operator_name address: ByteArray, // always new byte[0] apn: ByteArray = "wifi".toByteArray() // oicq.wlogin_sdk.tools.util#get_apn_string ) { tlv(0x124) { writeShortLVByteArrayLimitedLength(osType, 16) writeShortLVByteArrayLimitedLength(osVersion, 16) writeShort(networkType.value.toShort()) writeShortLVByteArrayLimitedLength(simInfo, 16) writeShortLVByteArrayLimitedLength(address, 32) writeShortLVByteArrayLimitedLength(apn, 16) } } internal fun TlvMapWriter.t128( isGuidFromFileNull: Boolean = false, // 保存到文件的 GUID 是否为 null isGuidAvailable: Boolean = true, // GUID 是否可用(计算/读取成功) isGuidChanged: Boolean = false, // GUID 是否有变动 /** * guidFlag: * ```java * GUID_FLAG = 0; * GUID_FLAG |= GUID_SRC << 24 & 0xFF000000; * GUID_FLAG |= FLAG_MAC_ANDROIDID_GUID_CHANGE << 8 & 0xFF00; * ``` * * * GUID_SRC: * 0: 初始值; * 1: 以前保存的文件; * 20: 以前没保存且现在生成失败; * 17: 以前没保存但现在生成成功; * * * FLAG_MAC_ANDROIDID_GUID_CHANGE: * ```java * if (!Arrays.equals(currentMac, get_last_mac)) { * oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x1; * } * if (!Arrays.equals(currentAndroidId, get_last_android_id)) { * oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x2; * } * if (!Arrays.equals(currentGuid, get_last_guid)) { * oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x4; * } * ``` */ guidFlag: Long, buildModel: ByteArray, // android.os.Build.MODEL /** * defaults `"%4;7t>;28<fc.5*6".toByteArray()` */ guid: ByteArray, buildBrand: ByteArray // android.os.Build.BRAND ) { tlv(0x128) { writeShort(0) writeByte(isGuidFromFileNull.toByte()) writeByte(isGuidAvailable.toByte()) writeByte(isGuidChanged.toByte()) writeInt(guidFlag.toInt()) // 11 00 00 00 writeShortLVByteArrayLimitedLength(buildModel, 32) writeShortLVByteArrayLimitedLength(guid, 16) writeShortLVByteArrayLimitedLength(buildBrand, 16) } } internal fun TlvMapWriter.t16e( buildModel: ByteArray ) { tlv(0x16e) { writeFully(buildModel) } } internal fun TlvMapWriter.t145( guid: ByteArray ) { tlv(0x145) { writeFully(guid) } } internal fun TlvMapWriter.t147( appId: Long, apkVersionName: ByteArray, apkSignatureMd5: ByteArray ) { tlv(0x147) { writeInt(appId.toInt()) writeShortLVByteArrayLimitedLength(apkVersionName, 32) writeShortLVByteArrayLimitedLength(apkSignatureMd5, 32) } } internal fun TlvMapWriter.t166( imageType: Int ) { tlv(0x166) { writeByte(imageType.toByte()) } } internal fun TlvMapWriter.t16a( noPicSig: ByteArray // unknown source ) { tlv(0x16a) { writeFully(noPicSig) } } internal fun TlvMapWriter.t154( ssoSequenceId: Int // starts from 0 ) { tlv(0x154) { writeInt(ssoSequenceId) } } internal fun TlvMapWriter.t141( simInfo: ByteArray, networkType: NetworkType, apn: ByteArray ) { tlv(0x141) { writeShort(1) // version writeShortLVByteArray(simInfo) writeShort(networkType.value.toShort()) writeShortLVByteArray(apn) } } internal fun TlvMapWriter.t511( domains: List<String> = listOf( "tenpay.com", "openmobile.qq.com", "docs.qq.com", "connect.qq.com", "qzone.qq.com", "vip.qq.com", "gamecenter.qq.com", "qun.qq.com", "game.qq.com", "qqweb.qq.com", "office.qq.com", "ti.qq.com", "mail.qq.com", "mma.qq.com", ) ) { tlv(0x511) { val list = domains.filter { it.isNotEmpty() } writeShort(list.size.toShort()) list.forEach { element -> if (element.startsWith('(')) { val split = element.drop(1).split(')') val flag = split[0].toInt() var n = (flag and 0x100000 > 0).toInt() if (flag and 0x8000000 > 0) { n = n or 0x2 } writeByte(n.toByte()) writeShortLVString(split[1]) } else { writeByte(1) writeShortLVString(element) } } } } internal fun TlvMapWriter.t172( rollbackSig: ByteArray // 由服务器发来的 tlv_t172 获得 ) { tlv(0x172) { writeFully(rollbackSig) } } internal fun TlvMapWriter.t185() { tlv(0x185) { writeByte(1) writeByte(1) } } internal fun TlvMapWriter.t400( /** * if (var1[2] != null && var1[2].length > 0) { this._G = (byte[])var1[2].clone(); } */ g: ByteArray, // 用于加密这个 tlv uin: Long, guid: ByteArray, dpwd: ByteArray, appId: Long, subAppId: Long, randomSeed: ByteArray ) { tlv(0x400) { encryptAndWrite(g) { writeByte(1) // version writeLong(uin) writeFully(guid) writeFully(dpwd) writeInt(appId.toInt()) writeInt(subAppId.toInt()) writeInt(currentTimeSeconds().toInt()) writeFully(randomSeed) } } } internal fun TlvMapWriter.t187( macAddress: ByteArray ) { tlv(0x187) { writeFully(macAddress.md5()) // may be md5 } } internal fun TlvMapWriter.t188( androidId: ByteArray ) { tlv(0x188) { writeFully(androidId.md5()) } } internal fun TlvMapWriter.t193( ticket: String ) { tlv(0x193) { writeFully(ticket.toByteArray()) } } internal fun TlvMapWriter.t194( imsiMd5: ByteArray ) { imsiMd5 requireSize 16 tlv(0x194) { writeFully(imsiMd5) } } internal fun TlvMapWriter.t191( K: Int = 0x82 ) { tlv(0x191) { writeByte(K.toByte()) } } internal fun TlvMapWriter.t201( L: ByteArray = byteArrayOf(), // unknown channelId: ByteArray = byteArrayOf(), clientType: ByteArray = "qq".toByteArray(), N: ByteArray ) { tlv(0x201) { writeShortLVByteArray(L) writeShortLVByteArray(channelId) writeShortLVByteArray(clientType) writeShortLVByteArray(N) } } internal fun TlvMapWriter.t202( wifiBSSID: ByteArray, wifiSSID: ByteArray ) { tlv(0x202) { writeShortLVByteArrayLimitedLength(wifiBSSID, 16) writeShortLVByteArrayLimitedLength(wifiSSID, 32) } } internal fun TlvMapWriter.t177( buildTime: Long = 1571193922L, // wtLogin BuildTime buildVersion: String = "6.0.0.2413" // wtLogin SDK Version ) { tlv(0x177) { writeByte(1) writeInt(buildTime.toInt()) writeShortLVString(buildVersion) } // shouldEqualsTo 0x11 } internal fun TlvMapWriter.t516( // 1302 sourceType: Int = 0 // always 0 ) { tlv(0x516) { writeInt(sourceType) } } internal fun TlvMapWriter.t521( // 1313 productType: Int = 0, // coz setProductType is never used unknown: Short = 0 // const ) { tlv(0x521) { writeInt(productType) writeShort(unknown) } } internal fun TlvMapWriter.t52c( // ? ) { tlv(0x52c) { writeByte(1) writeLong(-1) } } internal fun TlvMapWriter.t536( // 1334 loginExtraData: ByteArray ) { tlv(0x536) { writeFully(loginExtraData) } } internal fun TlvMapWriter.t536( // 1334 loginExtraData: Collection<LoginExtraData> ) { tlv(0x536) { //com.tencent.loginsecsdk.ProtocolDet#packExtraData writeByte(1) // const writeByte(loginExtraData.size.toByte()) // data count for (extraData in loginExtraData) { writeLoginExtraData(extraData) } } } internal fun TlvMapWriter.t525( loginExtraData: Collection<LoginExtraData>, ) { tlv(0x525) { _writeTlvMap { t536(loginExtraData) } } } internal fun TlvMapWriter.t525( t536: ByteReadPacket = buildPacket { _writeTlvMap(includeCount = false) { t536(buildPacket { //com.tencent.loginsecsdk.ProtocolDet#packExtraData writeByte(1) // const writeByte(0) // data count }.readBytes()) } } ) { tlv(0x525) { writeShort(1) writePacket(t536) } } internal fun TlvMapWriter.t542( value: ByteArray ) { tlv(0x542) { writeFully(value) } } internal fun TlvMapWriter.t545( qimei: String ) { tlv(0x545) { writeFully(qimei.toByteArray()) } } internal fun TlvMapWriter.t548( nativeGetTestData: ByteArray = ( "01 02 01 01 00 0A 00 00 00 80 5E C1 1A B0 39 A0 " + "E0 5C 67 DF 44 F8 E5 86 91 A2 A4 5D 92 2B 25 3A " + "B6 6E 2F F1 A1 E3 60 B8 36 1E 2F 6B 6F F7 2D F7 " + "F8 21 F1 0B 75 7D 2A 4F 63 B8 83 9C 41 0B AA C7 " + "C9 69 0D 70 AB F3 0F 46 28 C2 CD DB 81 CC 74 18 " + "ED 97 CD 31 3E 1A 17 F1 94 96 AB 6C 6B 25 4F 83 " + "5B 15 82 B0 8F 53 82 3F 59 FE 6E B5 EA B5 EA 7A " + "0C E7 2B 31 CA 4C FD 43 9A DB 40 7A CA 51 D7 9A " + "3C AD 6D 8F 3C C6 84 A5 4A 5F 00 20 BE FB 91 06 " + "F0 67 42 8B CC 59 27 4E BC 91 78 55 4E E4 5C 98 " + "4B 8B 0F C9 A3 83 56 06 E8 AE 5A 0D 00 AC 01 02 " + "01 02 00 0A 00 00 00 80 5E C1 1A B0 39 A0 E0 5C " + "67 DF 44 F8 E5 86 91 A2 A4 5D 92 2B 25 3A B6 6E " + "2F F1 A1 E3 60 B8 36 1E 2F 6B 6F F7 2D F7 F8 21 " + "F1 0B 75 7D 2A 4F 63 B8 83 9C 41 0B AA C7 C9 69 " + "0D 70 AB F3 0F 46 28 C2 CD DB 81 CC 74 18 ED 97 " + "CD 31 3E 1A 17 F1 94 96 AB 6C 6B 25 4F 83 5B 15 " + "82 B0 8F 53 82 3F 59 FE 6E B5 EA B5 EA 7A 0C E7 " + "2B 31 CA 4C FD 43 9A DB 40 7A CA 51 D7 9A 3C AD " + "6D 8F 3C C6 84 A5 4A 5F 00 20 BE FB 91 06 F0 67 " + "42 8B CC 59 27 4E BC 91 78 55 4E E4 5C 98 4B 8B " + "0F C9 A3 83 56 06 E8 AE 5A 0D 00 80 5E C1 1A B0 " + "39 A0 E0 5C 67 DF 44 F8 E5 86 91 A2 A4 5D 92 2B " + "25 3A B6 6E 2F F1 A1 E3 60 B8 36 1E 2F 6B 6F F7 " + "2D F7 F8 21 F1 0B 75 7D 2A 4F 63 B8 83 9C 41 0B " + "AA C7 C9 69 0D 70 AB F3 0F 46 28 C2 CD DB 81 CC " + "74 18 ED 97 CD 31 3E 1A 17 F1 94 96 AB 6C 6B 25 " + "4F 83 5B 15 82 B0 8F 53 82 3F 59 FE 6E B5 EA B5 " + "EA 7A 0C E7 2B 31 CA 4C FD 43 9A DB 40 7A CA 51 " + "D7 9A 3C AD 6D 8F 3C C6 84 A5 71 6F 00 00 00 1F " + "00 00 27 10").hexToBytes() ) { tlv(0x548) { writeFully(nativeGetTestData) } } internal fun TlvMapWriter.t544ForToken( // 1348 client: QQAndroidClient, uin: Long, protocol: BotConfiguration.MiraiProtocol, guid: ByteArray, sdkVersion: String, subCommandId: Int, commandStr: String ) { val service = client.bot.encryptServiceOrNull ?: return tlv(0x544) { buildPacket { writeFully(buildPacket { writeLong(uin) }.readBytes(4)) writeShortLVByteArray(guid) writeShortLVString(sdkVersion) writeInt(subCommandId) writeInt(0) }.use { dataIn -> service.encryptTlv(EncryptServiceContext(uin, buildTypeSafeMap { set(EncryptServiceContext.KEY_COMMAND_STR, commandStr) set(EncryptServiceContext.KEY_BOT_PROTOCOL, protocol) }), 0x544, dataIn.readBytes()) }.let { result -> writeFully(result ?: "".toByteArray()) // Empty str means native throws exception } } } internal fun TlvMapWriter.t544ForVerify( // 1348 client: QQAndroidClient, uin: Long, protocol: BotConfiguration.MiraiProtocol, guid: ByteArray, sdkVersion: String, subCommandId: Int, commandStr: String ) { val service = client.bot.encryptServiceOrNull ?: return tlv(0x544) { buildPacket { writeLong(uin) writeShortLVByteArray(guid) writeShortLVString(sdkVersion) writeInt(subCommandId) }.use { dataIn -> service.encryptTlv(EncryptServiceContext(uin, buildTypeSafeMap { set(EncryptServiceContext.KEY_COMMAND_STR, commandStr) set(EncryptServiceContext.KEY_BOT_PROTOCOL, protocol) }), 0x544, dataIn.readBytes()) }.let { result -> writeFully(result ?: "".toByteArray()) // Empty str means native throws exception } } } internal fun TlvMapWriter.t318( tgtQR: ByteArray // unknown ) { tlv(0x318) { writeFully(tgtQR) } } private inline fun Boolean.toByte(): Byte = if (this) 1 else 0 private inline fun Boolean.toInt(): Int = if (this) 1 else 0 // noinline: wrong exception stacktrace reported private infix fun Int.shouldEqualsTo(int: Int) = check(this == int) { "Required $int, but found $this" } private infix fun ByteArray.requireSize(exactSize: Int) = check(this.size == exactSize) { "Required size $exactSize, but found ${this.size}" } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/ChatType.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat internal enum class ChatType(val internalID: Int) { FRIEND(2),//可以为任何数字 CONTACT(1006), //推测为"群" TROOP(1), TROOP_HCTOPIC(1026), //坦白说 CONFESS_A(1033), CONFESS_B(1034), CM_GAME_TEMP(1036), DISCUSSION(3000), DEVICE_MSG(9501), } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/GroupFile.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* import kotlinx.serialization.DeserializationStrategy import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.* import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.readOidbSsoPkg import net.mamoe.mirai.internal.utils.io.serialization.writeOidb import net.mamoe.mirai.utils.ExternalResource import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.math.absoluteValue import kotlin.random.Random internal sealed class CommonOidbResponse<T> : Packet { data class Failure<T>( val result: Int, val msg: String, val e: Throwable?, ) : CommonOidbResponse<T>() { inline fun createException(actionName: String): IllegalStateException { return IllegalStateException("Failed $actionName, result=$result, msg=$msg", e) } override fun toString(): String { return "CommonOidbResponse.Failure(result=$result, msg=$msg, e=$e)" } } class Success<T>( val resp: T ) : CommonOidbResponse<T>() { override fun toString(): String { return "CommonOidbResponse.Success" } } } internal interface CheckableStruct { val int32RetCode: Int val retMsg: String } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly internal inline fun <T> CommonOidbResponse<T>.toResult(actionName: String, checkResp: Boolean = true): Result<T> { return if (this is CommonOidbResponse.Failure) { Result.failure(this.createException(actionName)) } else { this as CommonOidbResponse.Success<T> if (!checkResp) return Result.success(this.resp) val result = this.resp if (result is CheckableStruct) { if (result.int32RetCode != 0) return Result.failure(IllegalStateException("Failed $actionName, result=${result.int32RetCode}, msg=${result.retMsg}")) } Result.success(this.resp) } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly internal inline fun <T> CommonOidbResponse<T>.toResult( actionName: String, checkResp: CheckableStruct.(Int) -> Boolean ): Result<T> { return if (this is CommonOidbResponse.Failure) { Result.failure(this.createException(actionName)) } else { this as CommonOidbResponse.Success<T> val result = this.resp if (result is CheckableStruct) { if (!checkResp( result, result.int32RetCode ) ) return Result.failure(IllegalStateException("Failed $actionName, result=${result.int32RetCode}, msg=${result.retMsg}")) } Result.success(this.resp) } } /** * @param respMapper may throw any exception, which will be wrapped to CommonOidbResponse.Failure */ internal inline fun <T : ProtoBuf, R> ByteReadPacket.readOidbRespCommon( bodyBufferDeserializer: DeserializationStrategy<T>, respMapper: (T) -> R ): CommonOidbResponse<R> { contract { callsInPlace(respMapper, InvocationKind.AT_MOST_ONCE) } val oidb = readOidbSsoPkg(bodyBufferDeserializer) return oidb.fold( onSuccess = { CommonOidbResponse.Success(kotlin.runCatching { respMapper(this) }.getOrElse { return CommonOidbResponse.Failure(0, it.message ?: "", it) }) }, onFailure = { CommonOidbResponse.Failure(result, errorMsg, null) } ) } internal object FileManagement { val factories = arrayOf( GetFileList, GetFileInfo, RequestDownload, RequestUpload, DeleteFile, MoveFile, RenameFile, TransferFile, Feed, RenameFolder, // MoveFolder, DeleteFolder, CreateFolder, ) object GetFileList : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d8.GetFileListRspBody>>("OidbSvc.0x6d8_1") { operator fun invoke( client: QQAndroidClient, groupCode: Long, folderId: String, startIndex: Int, ) = buildOutgoingUniPacket(client) { writeOidb( 1752, 1, Oidb0x6d8.ReqBody.serializer(), Oidb0x6d8.ReqBody( fileListInfoReq = Oidb0x6d8.GetFileListReqBody( groupCode = groupCode, appId = 3, folderId = folderId, fileCount = 20, reqFrom = 3, sortBy = 1, startIndex = startIndex ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d8.GetFileListRspBody> { return readOidbRespCommon(Oidb0x6d8.RspBody.serializer()) { it.fileListInfoRsp!! } } } object GetFileInfo : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d8.GetFileInfoRspBody>>("OidbSvc.0x6d8_0") { operator fun invoke( client: QQAndroidClient, groupCode: Long, fileId: String, busId: Int, ) = buildOutgoingUniPacket(client) { writeOidb( 1752, 0, Oidb0x6d8.ReqBody.serializer(), Oidb0x6d8.ReqBody( fileInfoReq = Oidb0x6d8.GetFileInfoReqBody( groupCode = groupCode, appId = 3, fileId = fileId, busId = busId ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d8.GetFileInfoRspBody> { return readOidbRespCommon(Oidb0x6d8.RspBody.serializer()) { it.fileInfoRsp!! } } } object RequestUpload : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d6.UploadFileRspBody>>("OidbSvc.0x6d6_0") { operator fun invoke( client: QQAndroidClient, groupCode: Long, folderId: String, resource: ExternalResource, filename: String, ) = buildOutgoingUniPacket(client) { resource.sha1 // check supported writeOidb( command = 1750, serviceType = 0, Oidb0x6d6.ReqBody.serializer(), Oidb0x6d6.ReqBody( uploadFileReq = Oidb0x6d6.UploadFileReqBody( groupCode = groupCode, appId = 3, busId = 102, entrance = 5, parentFolderId = folderId, fileName = filename, localPath = "/storage/emulated/0/Pictures/files/s/$filename", fileSize = resource.size, sha = resource.sha1, md5 = resource.md5, boolSupportMultiUpload = true, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d6.UploadFileRspBody> { return readOidbRespCommon(Oidb0x6d6.RspBody.serializer()) { it.uploadFileRsp!! } } } object RequestDownload : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d6.DownloadFileRspBody>>("OidbSvc.0x6d6_2") { operator fun invoke( client: QQAndroidClient, groupCode: Long, busId: Int, fileId: String, ) = buildOutgoingUniPacket(client) { writeOidb( command = 1750, serviceType = 2, Oidb0x6d6.ReqBody.serializer(), Oidb0x6d6.ReqBody( downloadFileReq = Oidb0x6d6.DownloadFileReqBody( groupCode = groupCode, appId = 3, busId = busId, fileId = fileId, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d6.DownloadFileRspBody> { return readOidbRespCommon(Oidb0x6d6.RspBody.serializer()) { it.downloadFileRsp!! } } } object MoveFile : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d6.MoveFileRspBody>>("OidbSvc.0x6d6_5") { operator fun invoke( client: QQAndroidClient, groupCode: Long, busId: Int, fileId: String, parentFolderId: String, destFolderId: String, ) = buildOutgoingUniPacket(client) { writeOidb( command = 1750, serviceType = 5, Oidb0x6d6.ReqBody.serializer(), Oidb0x6d6.ReqBody( moveFileReq = Oidb0x6d6.MoveFileReqBody( groupCode = groupCode, appId = 3, busId = busId, fileId = fileId, parentFolderId = parentFolderId, destFolderId = destFolderId ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d6.MoveFileRspBody> { return readOidbRespCommon(Oidb0x6d6.RspBody.serializer()) { it.moveFileRsp!! } } } // 转发 object TransferFile : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d9.TransFileRspBody>>("OidbSvc.0x6d9_0") { operator fun invoke( client: QQAndroidClient, groupCode: Long, busId: Int, fileId: String, ) = buildOutgoingUniPacket(client) { writeOidb( command = 1753, serviceType = 0, Oidb0x6d9.ReqBody.serializer(), Oidb0x6d9.ReqBody( transFileReq = Oidb0x6d9.TransFileReqBody( groupCode = groupCode, appId = 3, busId = busId, fileId = fileId, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d9.TransFileRspBody> { return readOidbRespCommon(Oidb0x6d9.RspBody.serializer()) { it.transFileRsp!! } } } object RenameFile : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d6.RenameFileRspBody>>("OidbSvc.0x6d6_4") { operator fun invoke( client: QQAndroidClient, groupCode: Long, busId: Int, fileId: String, parentFolderId: String, newName: String, ) = buildOutgoingUniPacket(client) { writeOidb( command = 1750, serviceType = 4, Oidb0x6d6.ReqBody.serializer(), Oidb0x6d6.ReqBody( renameFileReq = Oidb0x6d6.RenameFileReqBody( groupCode = groupCode, appId = 3, busId = busId, fileId = fileId, parentFolderId = parentFolderId, newFileName = newName, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d6.RenameFileRspBody> { return readOidbRespCommon(Oidb0x6d6.RspBody.serializer()) { it.renameFileRsp!! } } } object DeleteFile : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d6.DeleteFileRspBody>>("OidbSvc.0x6d6_3") { operator fun invoke( client: QQAndroidClient, groupCode: Long, busId: Int, fileId: String, parentFolderId: String, ) = buildOutgoingUniPacket(client) { writeOidb( command = 1750, serviceType = 3, Oidb0x6d6.ReqBody.serializer(), Oidb0x6d6.ReqBody( deleteFileReq = Oidb0x6d6.DeleteFileReqBody( groupCode = groupCode, appId = 3, busId = busId, fileId = fileId, parentFolderId = parentFolderId, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d6.DeleteFileRspBody> { return readOidbRespCommon(Oidb0x6d6.RspBody.serializer()) { it.deleteFileRsp!! } } } object Feed : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d9.FeedsRspBody>>("OidbSvc.0x6d9_4") { operator fun invoke( client: QQAndroidClient, groupCode: Long, busId: Int, fileId: String, random: Int = Random.nextInt().absoluteValue, ) = buildOutgoingUniPacket(client) { writeOidb( command = 1753, serviceType = 4, Oidb0x6d9.ReqBody.serializer(), Oidb0x6d9.ReqBody( feedsInfoReq = Oidb0x6d9.FeedsReqBody( groupCode = groupCode, appId = 3, feedsInfoList = listOf( GroupFileCommon.FeedsInfo( busId = busId, fileId = fileId, feedFlag = 1, msgRandom = random, ) ) ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d9.FeedsRspBody> { return readOidbRespCommon(Oidb0x6d9.RspBody.serializer()) { it.feedsInfoRsp!! } } } object RenameFolder : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d7.RenameFolderRspBody>>("OidbSvc.0x6d7_2") { operator fun invoke( client: QQAndroidClient, groupCode: Long, folderId: String, newName: String ) = buildOutgoingUniPacket(client) { writeOidb( command = 1751, serviceType = 2, Oidb0x6d7.ReqBody.serializer(), Oidb0x6d7.ReqBody( renameFolderReq = Oidb0x6d7.RenameFolderReqBody( groupCode = groupCode, appId = 3, folderId = folderId, newFolderName = newName, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d7.RenameFolderRspBody> { return readOidbRespCommon(Oidb0x6d7.RspBody.serializer()) { it.renameFolderRsp!! } } } // qq doesn't support // object MoveFolder : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d7.MoveFolderRspBody>>("OidbSvc.0x6d7_3") { // operator fun invoke( // client: QQAndroidClient, // groupCode: Long, // folderId: String, // parentFolderId: String, // newParentFolderId: String, // ) = buildOutgoingUniPacket(client) { // writeOidb( // command = 1751, // serviceType = 3, // Oidb0x6d7.ReqBody.serializer(), // Oidb0x6d7.ReqBody( // moveFolderReq = Oidb0x6d7.MoveFolderReqBody( // groupCode = groupCode, // appId = 3, // folderId = folderId, // parentFolderId = parentFolderId, // destFolderId = newParentFolderId, // ) // ) // ) // } // // override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d7.MoveFolderRspBody> { // return readOidbRespCommon(Oidb0x6d7.RspBody.serializer()) { it.moveFolderRsp!! } // } // } object DeleteFolder : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d7.DeleteFolderRspBody>>("OidbSvc.0x6d7_1") { operator fun invoke( client: QQAndroidClient, groupCode: Long, folderId: String, ) = buildOutgoingUniPacket(client) { writeOidb( command = 1751, serviceType = 1, Oidb0x6d7.ReqBody.serializer(), Oidb0x6d7.ReqBody( deleteFolderReq = Oidb0x6d7.DeleteFolderReqBody( groupCode = groupCode, appId = 3, folderId = folderId, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d7.DeleteFolderRspBody> { return readOidbRespCommon(Oidb0x6d7.RspBody.serializer()) { it.deleteFolderRsp!! } } } object CreateFolder : OutgoingPacketFactory<CommonOidbResponse<Oidb0x6d7.CreateFolderRspBody>>("OidbSvc.0x6d7_0") { operator fun invoke( client: QQAndroidClient, groupCode: Long, parentFolderId: String, name: String ) = buildOutgoingUniPacket(client) { writeOidb( command = 1751, serviceType = 0, Oidb0x6d7.ReqBody.serializer(), Oidb0x6d7.ReqBody( createFolderReq = Oidb0x6d7.CreateFolderReqBody( groupCode = groupCode, appId = 3, parentFolderId = parentFolderId, folderName = name, ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CommonOidbResponse<Oidb0x6d7.CreateFolderRspBody> { return readOidbRespCommon(Oidb0x6d7.RspBody.serializer()) { it.createFolderRsp!! } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MultiMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.PacketCodec import net.mamoe.mirai.internal.network.protocol.data.proto.MultiMsg import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.structureToString internal class MessageValidationData( val data: ByteArray, val md5: ByteArray = data.md5() ) { override fun toString(): String { return "MessageValidationData(data=<size=${data.size}>, md5=${md5.contentToString()})" } } internal class MultiMsg { object ApplyUp : OutgoingPacketFactory<ApplyUp.Response>("MultiMsg.ApplyUp") { sealed class Response : Packet { data class RequireUpload( val proto: MultiMsg.MultiMsgApplyUpRsp ) : Response() { override fun toString(): String { if (PacketCodec.PacketLogger.isEnabled) { return structureToString() } return "MultiMsg.ApplyUp.Response.RequireUpload" } } object MessageTooLarge : Response() } // captured from group fun createForGroup( buType: Int, client: QQAndroidClient, messageData: MessageValidationData, dstUin: Long // group uin ) = buildOutgoingUniPacket(client) { writeProtoBuf( MultiMsg.ReqBody.serializer(), MultiMsg.ReqBody( buType = buType, // 1: long, 2: 合并转发 buildVer = "8.2.0.1296", multimsgApplyupReq = listOf( MultiMsg.MultiMsgApplyUpReq( applyId = 0, dstUin = dstUin, msgMd5 = messageData.md5, msgSize = messageData.data.size.toLong(), msgType = 3 // TODO 3 for group? ) ), netType = 3, // wifi=3, wap=5 platformType = 9, subcmd = 1, termType = 5, reqChannelType = 0 ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val body = readProtoBuf(MultiMsg.RspBody.serializer()) val response = body.multimsgApplyupRsp.first() return when (response.result) { 0 -> Response.RequireUpload(response) 193 -> Response.MessageTooLarge //1 -> Response.OK(resId = response.msgResid) else -> { error(kotlin.run { println(response.structureToString()) }.let { "Protocol error: MultiMsg.ApplyUp failed with result ${response.result}" }) } } } } object ApplyDown : OutgoingPacketFactory<ApplyDown.Response>("MultiMsg.ApplyDown") { sealed class Response : Packet { class RequireDownload( val origin: MultiMsg.MultiMsgApplyDownRsp ) : Response() { override fun toString(): String = "MultiMsg.ApplyDown.Response" } object MessageTooLarge : Response() } operator fun invoke( client: QQAndroidClient, buType: Int, resId: String, msgType: Int, ) = buildOutgoingUniPacket(client) { writeProtoBuf( MultiMsg.ReqBody.serializer(), MultiMsg.ReqBody( buType = buType, // 1: long, 2: 合并转发 buildVer = "8.2.0.1296", multimsgApplydownReq = listOf( MultiMsg.MultiMsgApplyDownReq( msgResid = resId, msgType = msgType, ) ), netType = 3, // wifi=3, wap=5 platformType = 9, subcmd = 2, termType = 5, reqChannelType = 2 ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val body = readProtoBuf(MultiMsg.RspBody.serializer()) val response = body.multimsgApplydownRsp.first() return when (response.result) { 0 -> Response.RequireDownload(response) 193 -> Response.MessageTooLarge //1 -> Response.OK(resId = response.msgResid) else -> throw contextualBugReportException( "MultiMsg.ApplyDown", response.structureToString(), additional = "Decode failure result=${response.result}" ) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/MusicSharePacket.kt ================================================ /* * 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.protocol.packet.chat import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.clientVersion import net.mamoe.mirai.internal.network.protocol.data.proto.OidbCmd0xb77 import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.MusicShare internal object MusicSharePacket : OutgoingPacketFactory<MusicSharePacket.Response>("OidbSvc.0xb77_9") { class Response( val pkg: OidbSso.OIDBSSOPkg, ) : Packet { val response by lazy { pkg.bodybuffer.loadAs(OidbCmd0xb77.RspBody.serializer()) } override fun toString(): String = "MusicSharePacket.Response(success=${pkg.result == 0}, error=${pkg.errorMsg})" } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { return Response(readProtoBuf(OidbSso.OIDBSSOPkg.serializer())) } operator fun invoke( client: QQAndroidClient, musicShare: MusicShare, targetUin: Long, targetKind: MessageSourceKind ) = buildOutgoingUniPacket(client) { with(musicShare) { val musicType = musicShare.kind writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 2935, serviceType = 9, clientVersion = client.clientVersion, bodybuffer = OidbCmd0xb77.ReqBody( appid = musicType.appId, appType = 1, msgStyle = if (jumpUrl.isNotBlank()) 4 else 0, // 有播放连接为4, 无播放连接为0 clientInfo = OidbCmd0xb77.ClientInfo( platform = musicType.platform, sdkVersion = musicType.sdkVersion, androidPackageName = musicType.packageName, androidSignature = musicType.signature ), extInfo = OidbCmd0xb77.ExtInfo( msgSeq = 0 ), sendType = when (targetKind) { MessageSourceKind.FRIEND -> 0 MessageSourceKind.GROUP -> 1 else -> error("Internal error: Unsupported targetKind $targetKind") }, recvUin = targetUin, richMsgBody = OidbCmd0xb77.RichMsgBody( title = title, summary = summary, brief = brief, url = jumpUrl, pictureUrl = pictureUrl, musicUrl = musicUrl ) ).toByteArray(OidbCmd0xb77.ReqBody.serializer()) ) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NewContact.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER") package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.network.toPacket import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import kotlin.math.max internal class NewContact { internal object SystemMsgNewFriend : OutgoingPacketFactory<Packet>("ProfileService.Pb.ReqSystemMsgNew.Friend") { operator fun invoke(client: QQAndroidClient) = buildOutgoingUniPacket(client) { writeProtoBuf( Structmsg.ReqSystemMsgNew.serializer(), Structmsg.ReqSystemMsgNew( checktype = 2, flag = Structmsg.FlagInfo( frdMsgDiscuss2ManyChat = 1, frdMsgGetBusiCard = 1, frdMsgNeedWaitingMsg = 1, frdMsgUint32NeedAllUnreadMsg = 1, grpMsgMaskInviteAutoJoin = 1, ), friendMsgTypeFlag = 1, isGetFrdRibbon = false, isGetGrpRibbon = false, msgNum = 20, version = 1000, ), ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet { readProtoBuf(Structmsg.RspSystemMsgNew.serializer()).run { return friendmsgs.filter { it.msgTime >= bot.syncController.latestMsgNewFriendTime }.mapNotNull { struct -> if (!bot.syncController.syncNewFriend(struct.msgSeq, struct.msgTime)) { // duplicate return@mapNotNull null } struct.msg?.run { NewFriendRequestEvent( bot, struct.msgSeq, msgAdditional, struct.reqUin, groupCode, reqUinNick, ) } }.toPacket().also { bot.syncController.run { latestMsgNewFriendTime = max(latestMsgNewFriendTime, friendmsgs.maxOfOrNull { it.msgTime } ?: 0) } } } } internal object Action : OutgoingPacketFactory<Nothing?>("ProfileService.Pb.ReqSystemMsgAction.Friend") { operator fun invoke( client: QQAndroidClient, eventId: Long, fromId: Long, accept: Boolean, blackList: Boolean = false, ) = buildOutgoingUniPacket(client) { writeProtoBuf( Structmsg.ReqSystemMsgAction.serializer(), Structmsg.ReqSystemMsgAction( actionInfo = Structmsg.SystemMsgActionInfo( type = if (accept) 2 else 3, addFrdSNInfo = Structmsg.AddFrdSNInfo(), msg = "", remark = "", blacklist = !accept && blackList, ), msgSeq = eventId, reqUin = fromId, srcId = 6, subSrcId = 7, subType = 1, ), ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null } } internal object SystemMsgNewGroup : OutgoingPacketFactory<Packet?>("ProfileService.Pb.ReqSystemMsgNew.Group") { operator fun invoke(client: QQAndroidClient) = buildOutgoingUniPacket(client) { writeProtoBuf( Structmsg.ReqSystemMsgNew.serializer(), Structmsg.ReqSystemMsgNew( checktype = 3, flag = Structmsg.FlagInfo( frdMsgDiscuss2ManyChat = 1, frdMsgGetBusiCard = 0, frdMsgNeedWaitingMsg = 1, frdMsgUint32NeedAllUnreadMsg = 1, grpMsgGetC2cInviteJoinGroup = 1, grpMsgMaskInviteAutoJoin = 1, grpMsgGetDisbandedByAdmin = 1, grpMsgGetOfficialAccount = 1, grpMsgGetPayInGroup = 1, grpMsgGetQuitPayGroupMsgFlag = 1, grpMsgGetTransferGroupMsgFlag = 1, grpMsgHiddenGrp = 1, grpMsgKickAdmin = 1, grpMsgNeedAutoAdminWording = 1, grpMsgNotAllowJoinGrpInviteNotFrd = 1, grpMsgSupportInviteAutoJoin = 1, grpMsgWordingDown = 1, ), friendMsgTypeFlag = 1, isGetFrdRibbon = false, isGetGrpRibbon = false, msgNum = 5, version = 1000, ), ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Packet { return readBytes().loadAs(Structmsg.RspSystemMsgNew.serializer()).run { groupmsgs.filter { it.msgTime >= bot.syncController.latestMsgNewGroupTime }.mapNotNull { struct -> if (!bot.syncController.syncNewGroup(struct.msgSeq, struct.msgTime)) { // duplicate return@mapNotNull null } bot.processPacketThroughPipeline(struct) }.toPacket().also { bot.syncController.run { latestMsgNewGroupTime = max(latestMsgNewGroupTime, groupmsgs.maxOfOrNull { it.msgTime } ?: 0) } } } } internal object Action : OutgoingPacketFactory<Nothing?>("ProfileService.Pb.ReqSystemMsgAction.Group") { operator fun invoke( client: QQAndroidClient, eventId: Long, fromId: Long, groupId: Long, isInvited: Boolean, accept: Boolean?, blackList: Boolean = false, message: String = "", ) = buildOutgoingUniPacket(client) { writeProtoBuf( Structmsg.ReqSystemMsgAction.serializer(), Structmsg.ReqSystemMsgAction( actionInfo = Structmsg.SystemMsgActionInfo( type = when (accept) { null -> 14 // ignore true -> 11 // accept false -> 12 // reject }, groupCode = groupId, msg = message, remark = "", blacklist = blackList, ), groupMsgType = if (isInvited) 2 else 1, language = 1000, msgSeq = eventId, reqUin = fromId, srcId = 3, subSrcId = if (isInvited) 10016 else 31, subType = 1, ), ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/NudgePacket.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0xed3 import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf internal object NudgePacket : OutgoingPacketFactory<NudgePacket.Response>("OidbSvc.0xed3") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { with(readBytes().loadAs(OidbSso.OIDBSSOPkg.serializer())) { return Response(result == 0, result) } } class Response(val success: Boolean, val code: Int) : Packet { override fun toString(): String = "NudgeResponse(success=$success,code=$code)" } fun friendInvoke( client: QQAndroidClient, nudgeTargetId: Long, messageReceiverUin: Long, ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 3795, serviceType = 1, result = 0, bodybuffer = Cmd0xed3.ReqBody( toUin = nudgeTargetId, aioUin = messageReceiverUin ).toByteArray(Cmd0xed3.ReqBody.serializer()) ) ) } fun troopInvoke( client: QQAndroidClient, messageReceiverGroupCode: Long, nudgeTargetId: Long, ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 3795, serviceType = 1, result = 0, bodybuffer = Cmd0xed3.ReqBody( toUin = nudgeTargetId, groupCode = messageReceiverGroupCode ).toByteArray(Cmd0xed3.ReqBody.serializer()) ) ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/PbMessageSvc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.MsgRevokeUserDef import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.toLongUnsigned internal class PbMessageSvc { object PbMsgWithDraw : OutgoingPacketFactory<PbMsgWithDraw.Response>( "PbMessageSvc.PbMsgWithDraw" ) { sealed class Response : Packet { object Success : Response() { override fun toString(): String { return "PbMsgWithDraw.Success" } } data class Failed( val result: Int, val errorMessage: String ) : Response() } // 12 1A 08 01 10 00 18 E7 C1 AD B8 02 22 0A 08 BF BA 03 10 BF 81 CB B7 03 2A 02 08 00 fun createForGroupMessage( client: QQAndroidClient, groupCode: Long, messageSequenceId: IntArray, // 56639 messageRandom: IntArray, // 921878719 messageType: Int = 0 ): OutgoingPacketWithRespType<Response> { require(messageSequenceId.size == messageRandom.size) return buildOutgoingUniPacket( client, remark = "PbMsgWithDraw(" + "group=$groupCode, " + "seq=${messageSequenceId.joinToString(separator = ",")}, " + "rand=${messageRandom.joinToString(separator = ",")}" + ")" ) { writeProtoBuf( MsgSvc.PbMsgWithDrawReq.serializer(), MsgSvc.PbMsgWithDrawReq( groupWithDraw = listOf( MsgSvc.PbGroupMsgWithDrawReq( subCmd = 1, groupType = 0, // 普通群 groupCode = groupCode, msgList = messageSequenceId.zip(messageRandom).map { (seq, random) -> MsgSvc.PbGroupMsgWithDrawReq.MessageInfo( msgSeq = seq, msgRandom = random, msgType = messageType ) }, userdef = MsgRevokeUserDef.MsgInfoUserDef( longMessageFlag = 0 ).toByteArray(MsgRevokeUserDef.MsgInfoUserDef.serializer()) ) ) ) ) } } fun createForGroupTempMessage( client: QQAndroidClient, groupUin: Long, toUin: Long, messageSequenceId: IntArray, // 56639 messageRandom: IntArray, // 921878719 time: Int ): OutgoingPacketWithRespType<Response> { require(messageSequenceId.size == messageRandom.size) return buildOutgoingUniPacket( client, remark = "PbMsgWithDraw(" + "groupTemp=$toUin, " + "seq=${messageSequenceId.joinToString(separator = ",")}, " + "rand=${messageRandom.joinToString(separator = ",")}, " + "time=${time}" + ")" ) { writeProtoBuf( MsgSvc.PbMsgWithDrawReq.serializer(), MsgSvc.PbMsgWithDrawReq( c2cWithDraw = listOf( MsgSvc.PbC2CMsgWithDrawReq( subCmd = 1, longMessageFlag = 0, msgInfo = messageSequenceId.zip(messageRandom).map { (seq, random) -> MsgSvc.PbC2CMsgWithDrawReq.MsgInfo( msgType = 0, fromUin = client.bot.id, toUin = toUin, msgSeq = seq, msgRandom = random, msgUid = 0x0100000000000000 or random.toLongUnsigned(), msgTime = time.toLongUnsigned(), routingHead = MsgSvc.RoutingHead( grpTmp = MsgSvc.GrpTmp(groupUin, toUin) ), ) }, reserved = RESERVED_TEMP ) ) ) ) } } private val RESERVED_TEMP = "08 01 10 E3 E9 D6 80 02".hexToBytes() fun createForFriendMessage( client: QQAndroidClient, toUin: Long, messageSequenceId: IntArray, // 56639 messageRandom: IntArray, // 921878719 time: Int ): OutgoingPacketWithRespType<Response> { require(messageSequenceId.size == messageRandom.size) return buildOutgoingUniPacket( client, remark = "PbMsgWithDraw(" + "friend=$toUin, " + "seq=${messageSequenceId.joinToString(separator = ",")}, " + "rand=${messageRandom.joinToString(separator = ",")}, " + "time=${time}" + ")" ) { writeProtoBuf( MsgSvc.PbMsgWithDrawReq.serializer(), MsgSvc.PbMsgWithDrawReq( c2cWithDraw = listOf( MsgSvc.PbC2CMsgWithDrawReq( subCmd = 1, longMessageFlag = 0, msgInfo = messageSequenceId.zip(messageRandom).map { (seq, random) -> MsgSvc.PbC2CMsgWithDrawReq.MsgInfo( msgType = 0, fromUin = client.bot.id, toUin = toUin, msgSeq = seq, msgRandom = random, msgUid = 0x0100000000000000 or random.toLongUnsigned(), msgTime = time.toLongUnsigned(), routingHead = MsgSvc.RoutingHead( c2c = MsgSvc.C2C( toUin = toUin ) ) ) }, reserved = MsgRevokeUserDef.UinTypeUserDef( 0, ).toByteArray(MsgRevokeUserDef.UinTypeUserDef.serializer()) ) ) ) ) } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readProtoBuf(MsgSvc.PbMsgWithDrawResp.serializer()) resp.groupWithDraw.firstOrNull()?.let { if (it.result != 0) { return Response.Failed(it.result, it.errmsg) } return Response.Success } resp.c2cWithDraw.firstOrNull()?.let { if (it.result != 2 && it.result != 3) { return Response.Failed(it.result, it.errmsg) } return Response.Success } return Response.Failed(-1, "No response") } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/SendMessageMultiProtocol.kt ================================================ /* * 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.protocol.packet.chat ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopEssenceMsgManager.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0xeac import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf /** * 群精华消息管理 * * */ internal class TroopEssenceMsgManager { internal data class Response(val success: Boolean, val msg: String?) : Packet internal object SetEssence : OutgoingPacketFactory<Response>("OidbSvc.0xeac_1") { operator fun invoke( client: QQAndroidClient, troopUin: Long, msgRandom: Int, msgSeq: Int ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 3756, result = 0, serviceType = 1, bodybuffer = Oidb0xeac.ReqBody( groupCode = troopUin, msgSeq = msgSeq.and(-1), msgRandom = msgRandom ).toByteArray(Oidb0xeac.ReqBody.serializer()), ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> pkg.bodybuffer.loadAs(Oidb0xeac.RspBody.serializer()).let { data -> return Response(data.errorCode == 0, data.wording) } } } } internal object RemoveEssence : OutgoingPacketFactory<Response>("OidbSvc.0xeac_2") { operator fun invoke( client: QQAndroidClient, troopUin: Long, msgRandom: Int, msgSeq: Int ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 3756, result = 0, serviceType = 1, bodybuffer = Oidb0xeac.ReqBody( groupCode = troopUin, msgSeq = msgSeq.and(-1), msgRandom = msgRandom ).toByteArray(Oidb0xeac.ReqBody.serializer()), ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> pkg.bodybuffer.loadAs(Oidb0xeac.RspBody.serializer()).let { data -> return Response(data.errorCode == 0, data.wording) } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/TroopManagement.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.Member import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.info.GroupInfoImpl import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.jce.ModifyGroupCardReq import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.internal.network.protocol.data.jce.stUinInfo import net.mamoe.mirai.internal.network.protocol.data.proto.* import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.network.subAppId import net.mamoe.mirai.internal.utils.io.serialization.* import net.mamoe.mirai.utils.daysToSeconds internal class TroopManagement { internal object Mute : OutgoingPacketFactory<Mute.Response>("OidbSvc.0x570_8") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { //屁用没有 return Response } operator fun invoke( client: QQAndroidClient, groupCode: Long, memberUin: Long, timeInSecond: Int ): OutgoingPacketWithRespType<Response> { require(timeInSecond in 0..30.daysToSeconds) return buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 1392, serviceType = 8, result = 0, bodybuffer = buildPacket { writeInt(groupCode.toInt())//id or UIN? writeByte(32) writeShort(1) writeInt(memberUin.toInt()) writeInt(timeInSecond) }.readBytes() ) ) } } object Response : Packet { override fun toString(): String = "Response(Mute)" } } internal object GetGroupInfo : OutgoingPacketFactory<GroupInfoImpl>("OidbSvc.0x88d_7") { @Deprecated("") operator fun invoke( client: QQAndroidClient, groupCode: Long ): OutgoingPacketWithRespType<GroupInfoImpl> { return buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 2189, serviceType = 7, result = 0, bodybuffer = Oidb0x88d.ReqBody( appid = client.subAppId.toInt(), stzreqgroupinfo = listOf( Oidb0x88d.ReqGroupInfo( stgroupinfo = Oidb0x88d.GroupInfo( groupFlagExt = 0, groupFlagext4 = 0, groupFlag = 0, groupFlagext3 = 1,//获取confess noFingerOpenFlag = 1, cmduinFlagEx2 = 0, groupTypeFlag = 0, appPrivilegeFlag = 0, cmduinFlagEx = 0, cmduinNewMobileFlag = 0, cmduinUinFlag = 0, createSourceFlag = 0, noCodeFingerOpenFlag = 0, ingGroupQuestion = "", ingGroupAnswer = "", groupName = "", longGroupName = "", groupMemo = "", groupUin = 0, groupOwner = 0 ), groupCode = groupCode ) ) ).toByteArray(Oidb0x88d.ReqBody.serializer()) ) ) } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupInfoImpl { error("deprecated") /* with( this.readBytes() .loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x88d.RspBody.serializer()).stzrspgroupinfo!![0].stgroupinfo!! ) { return GroupInfoImpl() }*/ } } internal object GetTroopConfig : OutgoingPacketFactory<GetTroopConfig.Response>("OidbSvc.0x496") { class Response( val success: Boolean ) : Packet { override fun toString(): String = "TroopManagement.GetTroopConfig.Response($success)" } operator fun invoke( client: QQAndroidClient ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 1174, result = 0, serviceType = 0, clientVersion = "android 8.4.18", bodybuffer = Oidb0x496.ReqBody( updateTime = 0, firstUnreadManagerMsgSeq = 1, version = client.groupConfig.robotConfigVersion, aioKeywordVersion = client.groupConfig.aioKeyWordVersion, type = 3 ).toByteArray(Oidb0x496.ReqBody.serializer()) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> pkg.bodybuffer.loadAs(Oidb0x496.RspBody.serializer()).let { data -> bot.client.groupConfig.let { config -> config.aioKeyWordVersion = data.aioKeywordConfig!!.version config.robotConfigVersion = data.robotConfig!!.version config.robotUinRangeList = data.robotConfig.uinRange.asSequence().map { range -> LongRange(range.startUin, range.endUin) }.toList() } } return Response(pkg.result == 0) } } } internal object Kick : OutgoingPacketFactory<Kick.Response>("OidbSvc.0x8a0_0") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val ret = this.readBytes() .loadAs(OidbSso.OIDBSSOPkg.serializer()).bodybuffer.loadAs(Oidb0x8a0.RspBody.serializer()).msgKickResult.first().optUint32Result return Response( ret == 0, ret ) } class Response( val success: Boolean, val ret: Int ) : Packet { override fun toString(): String = "TroopManagement.Kick.Response($success)" } operator fun invoke( client: QQAndroidClient, groupCode: Long, memberId: Long, message: String, ban: Boolean ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 2208, serviceType = 0,//或者1 result = 0, bodybuffer = Oidb0x8a0.ReqBody( optUint64GroupCode = groupCode, msgKickList = listOf( Oidb0x8a0.KickMemberInfo( optUint32Operate = 5, optUint64MemberUin = memberId, optUint32Flag = if (ban) 1 else 0 //1为拉黑 ) ), kickMsg = message.toByteArray() ).toByteArray(Oidb0x8a0.ReqBody.serializer()) ) ) } } internal object SwitchAnonymousChat : OutgoingPacketFactory<SwitchAnonymousChat.Response>("OidbSvc.0x568_22") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val ret = this.readBytes() .loadAs(OidbSso.OIDBSSOPkg.serializer()).result return Response( ret == 0 ) } class Response( val success: Boolean ) : Packet { override fun toString(): String = "TroopManagement.SwitchAnonymousChat.Response($success)" } operator fun invoke( client: QQAndroidClient, groupCode: Long, switch: Boolean ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 1384, serviceType = 22, result = 0, bodybuffer = buildPacket { writeInt(groupCode.toInt()) if (switch) { writeByte(1) } else { writeByte(0) } }.readBytes() ) ) } } internal object GroupOperation : OutgoingPacketFactory<GroupOperation.Response>("OidbSvc.0x89a_0") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response = Response fun muteAll( client: QQAndroidClient, groupCode: Long, switch: Boolean ) = impl(client, groupCode) { shutupTime = if (switch) 0x0FFFFFFF else 0 } private inline fun impl( client: QQAndroidClient, groupCode: Long, crossinline info: Oidb0x89a.Groupinfo.() -> Unit ): OutgoingPacket { return buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 2202, serviceType = 0, bodybuffer = Oidb0x89a.ReqBody( groupCode = groupCode, stGroupInfo = Oidb0x89a.Groupinfo().apply(info) ).toByteArray(Oidb0x89a.ReqBody.serializer()), ) ) } } fun autoApprove( client: QQAndroidClient, groupCode: Long, switch: Boolean ) = impl(client, groupCode) { groupFlagext3 = if (switch) 0x00100000 else 0x00000000//暂时无效 } fun name( client: QQAndroidClient, groupCode: Long, newName: String ) = impl(client, groupCode) { ingGroupName = newName.toByteArray() } fun memo( client: QQAndroidClient, groupCode: Long, newMemo: String ) = impl(client, groupCode) { ingGroupMemo = newMemo.toByteArray() } fun allowMemberInvite( client: QQAndroidClient, groupCode: Long, switch: Boolean ) = impl(client, groupCode) { allowMemberInvite = if (switch) 1 else 0 } object Response : Packet { override fun toString(): String { return "TroopManagement.GroupOperation.Response" } } } internal object EditSpecialTitle : OutgoingPacketFactory<EditSpecialTitle.Response>("OidbSvc.0x8fc_2") { object Response : Packet override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { return Response } operator fun invoke( client: QQAndroidClient, member: Member, newName: String ): OutgoingPacket { return buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 2300, serviceType = 2, bodybuffer = Oidb0x8fc.ReqBody( groupCode = member.group.id, memLevelInfo = listOf( Oidb0x8fc.MemberInfo( uin = member.id, uinName = newName.toByteArray(), specialTitle = newName.toByteArray(), specialTitleExpireTime = -1 ) ) ).toByteArray(Oidb0x8fc.ReqBody.serializer()) ) ) } } } internal object EditGroupNametag : OutgoingPacketFactory<EditGroupNametag.Response>("friendlist.ModifyGroupCardReq") { object Response : Packet { override fun toString(): String { return "TroopManagement.EditGroupNametag.Response" } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): EditGroupNametag.Response { this.close() return Response } operator fun invoke( client: QQAndroidClient, member: Member, newName: String ): OutgoingPacketWithRespType<Response> { return buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( funcName = "ModifyGroupCardReq", servantName = "mqq.IMService.FriendListServiceServantObj", version = 3, cPacketType = 0x00, requestId = client.nextRequestPacketRequestId(), sBuffer = jceRequestSBuffer( "MGCREQ", ModifyGroupCardReq.serializer(), ModifyGroupCardReq( dwZero = 0L, dwGroupCode = member.group.id, dwNewSeq = 0L, vecUinInfo = listOf( stUinInfo( gender = 0, dwuin = member.id, dwFlag = 31, sName = newName, sPhone = "", sEmail = "", sRemark = "" ) ) ) ) ) ) } } } internal object ModifyAdmin : OutgoingPacketFactory<ModifyAdmin.Response>("OidbSvc.0x55c_1") { data class Response( val code: Int, val success: Boolean, val msg: String, ) : Packet { override fun toString(): String { return "TroopManagement.ModifyAdmin.Response(code=${code}, success=${success}, msg=${msg})" } } /** * @param operation: true is add */ operator fun invoke( client: QQAndroidClient, member: Member, operation: Boolean ): OutgoingPacket { return buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 1372, serviceType = 1, bodybuffer = buildPacket { writeInt(member.group.id.toInt()) writeInt(member.id.toInt()) writeByte(if (operation) 1 else 0) }.readBytes() ) ) } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ModifyAdmin.Response { val stupidPacket = readProtoBuf(OidbSso.OIDBSSOPkg.serializer()) return stupidPacket.run { ModifyAdmin.Response( this.result, this.result == 0, this.errorMsg ) } } } internal object GetGroupLastMsgSeq : OutgoingPacketFactory<GetGroupLastMsgSeq.Response>("OidbSvc.0x88d_0") { sealed class Response(val groupUin: Long, val seq: Int) : Packet { object Failed : Response(-1, -1) { override fun toString(): String { return "TroopManagement.GetGroupLastMsgSeq.Failed" } } class Success(groupUin: Long, seq: Int) : Response(groupUin, seq) { override fun toString(): String { return "TroopManagement.GetGroupLastMsgSeq.Response(groupUin=${groupUin}, seq=${seq})" } } } operator fun invoke( client: QQAndroidClient, groupUin: Long, ) = buildOutgoingUniPacket(client) { writeOidb( 2189, 0, Oidb0x88d.ReqBody.serializer(), Oidb0x88d.ReqBody( appid = client.subAppId.toInt(), stzreqgroupinfo = listOf( Oidb0x88d.ReqGroupInfo( groupCode = groupUin, stgroupinfo = Oidb0x88d.GroupInfo(groupCurMsgSeq = 0) ) ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readOidbRespCommon(Oidb0x88d.RspBody.serializer()) { it.stzrspgroupinfo } .toResult("OidbSvc.0x88d_0") { it == 0 } .getOrNull() ?: return Response.Failed check(resp.isNotEmpty()) { return Response.Failed } val group = resp.first() val info = group.stgroupinfo ?: return Response.Failed val seq = info.groupCurMsgSeq ?: return Response.Failed return Response.Success(group.groupCode, seq) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/image/ImgStore.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.image import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import kotlin.random.Random import kotlin.random.nextInt internal fun getRandomString(length: Int): String = getRandomString(length, charRanges = defaultRanges) private val defaultRanges: Array<CharRange> = arrayOf('a'..'z', 'A'..'Z', '0'..'9') internal fun getRandomString(length: Int, vararg charRanges: CharRange): String = CharArray(length) { charRanges[Random.Default.nextInt(0..charRanges.lastIndex)].random() }.concatToString() internal class ImgStore { object GroupPicUp : OutgoingPacketFactory<GroupPicUp.Response>("ImgStore.GroupPicUp") { operator fun invoke( client: QQAndroidClient, uin: Long, groupCode: Long, md5: ByteArray, size: Long, picWidth: Int = 0, // not orthodox picHeight: Int = 0, // not orthodox picType: Int = 2001, fileId: Long = 0, filename: String = getRandomString(16) + ".gif", // make server happier srcTerm: Int = 5, platformType: Int = 9, buType: Int = 1, // group 1, other 2 appPicType: Int = 1006, ) = buildOutgoingUniPacket(client) { writeProtoBuf( Cmd0x388.ReqBody.serializer(), Cmd0x388.ReqBody( netType = 3, // wifi subcmd = 1, msgTryupImgReq = listOf( Cmd0x388.TryUpImgReq( groupCode = groupCode, srcUin = uin, fileMd5 = md5, fileSize = size, fileId = fileId, fileName = filename, picWidth = picWidth, picHeight = picHeight, picType = picType, appPicType = appPicType, buildVer = client.buildVer, srcTerm = srcTerm, platformType = platformType, //For gif, not original there originalPic = if (picType == 2000) { 0 } else { 1 }, buType = buType ) ) ) ) } sealed class Response : Packet { class FileExists( val fileId: Long, val fileInfo: Cmd0x388.ImgInfo ) : Response() { override fun toString(): String { return "FileExists(fileId=$fileId, fileInfo=$fileInfo)" } } class RequireUpload( val fileId: Long, val uKey: ByteArray, val uploadIpList: List<Int>, val uploadPortList: List<Int> ) : Response() { override fun toString(): String { return "RequireUpload(fileId=$fileId, uKey=${uKey.contentToString()})" } } data class Failed( val resultCode: Int, val message: String ) : Response() } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp0 = readProtoBuf(Cmd0x388.RspBody.serializer()) val resp = resp0.msgTryupImgRsp.firstOrNull() ?: error("cannot find `msgTryupImgRsp` from `Cmd0x388.RspBody`") return when { resp.result != 0 -> Response.Failed(resultCode = resp.result, message = resp.failMsg) resp.boolFileExit -> Response.FileExists(fileId = resp.fileid, fileInfo = resp.msgImgInfo!!) else -> Response.RequireUpload( fileId = resp.fileid, uKey = resp.upUkey, uploadIpList = resp.uint32UpIp, uploadPortList = resp.uint32UpPort ) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/image/LongConn.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.image import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x352 import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf internal class LongConn { internal object OffPicUp : OutgoingPacketFactory<OffPicUp.Response>("LongConn.OffPicUp") { operator fun invoke(client: QQAndroidClient, req: Cmd0x352.TryUpImgReq) = buildOutgoingUniPacket(client) { writeProtoBuf( Cmd0x352.ReqBody.serializer(), Cmd0x352.ReqBody( subcmd = 1, netType = 3, msgTryupImgReq = listOf(req), msgDelImgReq = listOf(), msgGetimgUrlReq = listOf(), ) ) } //08 01 12 7D 08 00 10 AB E1 9D DF 07 18 00 28 01 32 1C 0A 10 8E C4 9D 72 26 AE 20 C0 5D A2 B6 78 4D 12 B7 3A 10 E9 07 18 86 1F 20 30 28 30 52 25 2F 61 30 30 39 32 64 61 39 2D 64 39 31 38 2D 34 38 31 62 2D 38 34 30 63 2D 33 32 33 64 64 33 39 33 34 35 37 63 5A 25 2F 61 30 30 39 32 64 61 39 2D 64 39 31 38 2D 34 38 31 62 2D 38 34 30 63 2D 33 32 33 64 64 33 39 33 34 35 37 63 60 00 68 80 40 20 01 override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readProtoBuf(Cmd0x352.RspBody.serializer()) if (resp.failMsg?.isNotEmpty() == true) { return Response.Failed(resp.failMsg) } check(resp.subcmd == 1) val imgRsp = resp.msgTryupImgRsp.first() if (imgRsp.result != 0) { return Response.Failed(imgRsp.failMsg) } return if (imgRsp.boolFileExit) { Response.FileExists(imgRsp.upResid, imgRsp.msgImgInfo!!) } else { Response.RequireUpload(imgRsp.upResid, imgRsp.uint32UpIp, imgRsp.uint32UpPort, imgRsp.upUkey) } } sealed class Response : Packet { data class FileExists(val resourceId: String, val imageInfo: Cmd0x352.ImgInfo) : Response() @Suppress("ArrayInDataClass") data class RequireUpload( val resourceId: String, val serverIp: List<Int>, val serverPort: List<Int>, val uKey: ByteArray ) : Response() data class Failed(val message: String) : Response() } } // object OffPicDown : OutgoingPacketFactory<OffPicDown.ImageDownPacketResponse>("LongConn.OffPicDown") { // operator fun invoke(client: QQAndroidClient, @Suppress("UNUSED_PARAMETER") req: GetImgUrlReq): OutgoingPacket { // return buildOutgoingUniPacket(client) { // } // } // // override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): ImageDownPacketResponse { // } // // // sealed class ImageDownPacketResponse : Packet { // object Success : ImageDownPacketResponse() // } // } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbDeleteMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf internal object MessageSvcPbDeleteMsg : OutgoingPacketFactory<Nothing?>("MessageSvc.PbDeleteMsg") { internal operator fun invoke(client: QQAndroidClient, items: List<MsgSvc.PbDeleteMsgReq.MsgItem>) = buildOutgoingUniPacket(client) { writeProtoBuf( MsgSvc.PbDeleteMsgReq.serializer(), MsgSvc.PbDeleteMsgReq( msgItems = items ) ) } internal suspend fun delete(bot: QQAndroidBot, messages: List<MsgComm.Msg>) { val map = messages.map { MsgSvc.PbDeleteMsgReq.MsgItem( fromUin = it.msgHead.fromUin, toUin = it.msgHead.toUin, // 群为84、好友为187。群通过其他方法删除,但测试结果显示通过187也能删除群消息。 msgType = 187, msgSeq = it.msgHead.msgSeq, msgUid = it.msgHead.msgUid, ) } bot.network.sendWithoutExpect(MessageSvcPbDeleteMsg(bot.client, map)) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot) = null } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetGroupMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf /* * 获取群历史消息 */ internal object MessageSvcPbGetGroupMsg : OutgoingPacketFactory<MessageSvcPbGetGroupMsg.Response>("MessageSvc.PbGetGroupMsg") { sealed class Response : Packet class Failed( val result: Int, val errorMsg: String ) : Response() class Success( val msgElem: List<MsgComm.Msg>, val beginSequence: Long, val endSequence: Long, ) : Response() operator fun invoke( client: QQAndroidClient, groupUin: Long, messageSequence: Long, count: Int, ) = buildOutgoingUniPacket(client) { writeProtoBuf( MsgSvc.PbGetGroupMsgReq.serializer(), MsgSvc.PbGetGroupMsgReq( groupCode = groupUin, beginSeq = messageSequence - count + 1, endSeq = messageSequence, ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readProtoBuf(MsgSvc.PbGetGroupMsgResp.serializer()) return if (resp.result != 0) { Failed(resp.result, resp.errmsg) } else { Success( msgElem = resp.msg, beginSequence = resp.returnBeginSeq, endSequence = resp.returnEndSeq ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.delay import kotlinx.coroutines.launch import net.mamoe.mirai.Bot import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.MultiPacket import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.components.syncGetMessage import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.utils.toLongUnsigned import kotlin.random.Random /** * 获取好友消息和消息记录 */ internal object MessageSvcPbGetMsg : OutgoingPacketFactory<MessageSvcPbGetMsg.Response>("MessageSvc.PbGetMsg") { @Suppress("SpellCheckingInspection") operator fun invoke( client: QQAndroidClient, syncFlag: MsgSvc.SyncFlag = MsgSvc.SyncFlag.START, syncCookie: ByteArray?, //PbPushMsg.msg.msgHead.msgTime ) = buildOutgoingUniPacket(client) { //println("syncCookie=${client.c2cMessageSync.syncCookie?.toUHexString()}") writeProtoBuf( MsgSvc.PbGetMsgReq.serializer(), MsgSvc.PbGetMsgReq( msgReqType = 1, // from.ctype.toInt() contextFlag = 1, rambleFlag = 0, latestRambleNumber = 20, otherRambleNumber = 3, onlineSyncFlag = 1, whisperSessionId = 0, syncFlag = syncFlag, // serverBuf = from.serverBuf ?: EMPTY_BYTE_ARRAY, syncCookie = syncCookie ?: client.bot.syncController.syncCookie ?: byteArrayOf(), //.also { client.c2cMessageSync.syncCookie = it }, // syncFlag = client.c2cMessageSync.syncFlag, //msgCtrlBuf = client.c2cMessageSync.msgCtrlBuf, //pubaccountCookie = client.c2cMessageSync.pubAccountCookie ), ) } open class GetMsgSuccess(delegate: List<Packet>, syncCookie: ByteArray?, bot: QQAndroidBot) : Response(MsgSvc.SyncFlag.STOP, delegate, syncCookie, bot) { override fun toString(): String = "MessageSvcPbGetMsg.GetMsgSuccess" } /** * 不要直接 expect 这个 class. 它可能还没同步完成 */ open class Response( internal val syncFlagFromServer: MsgSvc.SyncFlag, private val delegate: List<Packet>, val syncCookie: ByteArray?, override val bot: Bot, ) : AbstractEvent(), MultiPacket, Packet.NoEventLog, BotEvent { override val isMeaningful: Boolean get() = true override fun children(): Iterator<Packet> { return delegate.iterator() } override fun toString(): String = "MessageSvcPbGetMsg.Response(flag=$syncFlagFromServer)" } class EmptyResponse( bot: QQAndroidBot, ) : GetMsgSuccess(emptyList(), null, bot) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { // 00 00 01 0F 08 00 12 00 1A 34 08 FF C1 C4 F1 05 10 FF C1 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 8A CA 91 D1 0C 48 9B A5 BD 9B 0A 58 DE 9D 99 F8 08 60 1D 68 FF C1 C4 F1 05 70 00 20 02 2A 9D 01 08 F3 C1 C4 F1 05 10 A2 FF 8C F0 03 18 01 22 8A 01 0A 2A 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 A6 01 20 0B 28 AE F9 01 30 F4 C1 C4 F1 05 38 A7 E3 D8 D4 84 80 80 80 01 B8 01 CD B5 01 12 08 08 01 10 00 18 00 20 00 1A 52 0A 50 0A 27 08 00 10 F4 C1 C4 F1 05 18 A7 E3 D8 D4 04 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 08 0A 06 0A 04 4E 4D 53 4C 12 15 AA 02 12 9A 01 0F 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 12 04 4A 02 08 00 30 01 2A 15 08 97 A2 C1 F1 05 10 95 A6 F5 E5 0C 18 01 30 01 40 01 48 81 01 2A 10 08 D3 F7 B5 F1 05 10 DD F1 92 B7 07 18 01 30 01 38 00 42 00 48 00 val resp = readProtoBuf(MsgSvc.PbGetMsgResp.serializer()) if (resp.result != 0) { // this is normally recoverable, no need to log // bot.network.logger // .warning { "MessageSvcPushNotify: result != 0, result = ${resp.result}, errorMsg=${resp.errmsg}" } bot.network.launch(CoroutineName("MessageSvcPushNotify.retry")) { delay(500 + Random.nextLong(0, 1000)) bot.network.sendWithoutExpect(MessageSvcPbGetMsg(bot.client, syncCookie = null)) } return EmptyResponse(bot) } when (resp.msgRspType) { 0 -> { bot.syncController.syncCookie = resp.syncCookie bot.syncController.pubAccountCookie = resp.pubAccountCookie } 1 -> { bot.syncController.syncCookie = resp.syncCookie } 2 -> { bot.syncController.pubAccountCookie = resp.pubAccountCookie } } // bot.logger.debug(resp.msgRspType._miraiContentToString()) // bot.logger.debug(resp.syncCookie._miraiContentToString()) bot.syncController.msgCtrlBuf = resp.msgCtrlBuf if (resp.uinPairMsgs.isEmpty()) return EmptyResponse(bot) val messages = resp.uinPairMsgs.asSequence() .filterNot { it.msg.isEmpty() } .flatMap { pair -> pair.msg.asSequence() .filter { msg: MsgComm.Msg -> msg.msgHead.msgTime > pair.lastReadTime.toLongUnsigned() } } .toList() .also { MessageSvcPbDeleteMsg.delete(bot, it) } // 删除消息 .filter { msg -> bot.syncController.syncGetMessage(msg.msgHead) } .map { msg -> bot.processPacketThroughPipeline(msg, KEY_FROM_SYNC to false) } val list: List<Packet> = messages if (resp.syncFlag == MsgSvc.SyncFlag.STOP) { return GetMsgSuccess(list, resp.syncCookie, bot) } return Response(resp.syncFlag, list, resp.syncCookie, bot) } override suspend fun QQAndroidBot.handle(packet: Response) { when (packet.syncFlagFromServer) { MsgSvc.SyncFlag.STOP -> { } MsgSvc.SyncFlag.START -> { network.sendAndExpect( MessageSvcPbGetMsg( client, MsgSvc.SyncFlag.CONTINUE, bot.syncController.syncCookie, ), 5000, 2 ) return } MsgSvc.SyncFlag.CONTINUE -> { network.sendAndExpect( MessageSvcPbGetMsg( client, MsgSvc.SyncFlag.CONTINUE, bot.syncController.syncCookie, ), 5000, 2 ) return } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbGetRoamMsgReq.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.Either import net.mamoe.mirai.utils.Either.Companion.mapRight import net.mamoe.mirai.utils.FailureResponse import net.mamoe.mirai.utils.checked internal class CheckedResponse<T>( val value: Either<FailureResponse, T> ) : Packet internal object MessageSvcPbGetRoamMsgReq : OutgoingPacketFactory<CheckedResponse<MessageSvcPbGetRoamMsgReq.Response>>("MessageSvc.PbGetRoamMsg") { class Response( val messages: List<MsgComm.Msg>?, val lastMessageTime: Long, val random: Long, val sig: ByteArray, // 似乎没被用到, 服务器每次返回不同 ) { } fun createForFriend( client: QQAndroidClient, uin: Long, timeStart: Long, lastMsgTime: Long, // 上次 resp 中的, 否则为期待的 time end random: Long = 0, maxCount: Int = 1000, sig: ByteArray = EMPTY_BYTE_ARRAY, // 客户端每次请求相同 pwd: ByteArray = EMPTY_BYTE_ARRAY, ) = buildOutgoingUniPacket(client) { writeProtoBuf( MsgSvc.PbGetRoamMsgReq.serializer(), MsgSvc.PbGetRoamMsgReq( peerUin = uin, beginMsgtime = timeStart, lastMsgtime = lastMsgTime, checkPwd = 1, // always readCnt = maxCount, subcmd = 1, reqType = 1, sig = sig, pwd = pwd, random = random, ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): CheckedResponse<Response> { val resp = readProtoBuf(MsgSvc.PbGetRoamMsgResp.serializer()) if (resp.result == 1) return CheckedResponse( Either.right( Response( null, resp.lastMsgtime, resp.random, resp.sig, ) ) ) // finished return CheckedResponse(resp.checked().mapRight { Response( messages = resp.msg.asReversed(), lastMessageTime = resp.lastMsgtime, random = resp.random, sig = resp.sig, ) }) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PbSendMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.groupCode import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.message.data.ForwardMessageInternal import net.mamoe.mirai.internal.message.data.toPtt import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToFriendImpl import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToStrangerImpl import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToTempImpl import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.ClockHolder.Companion.clock import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncCookie 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.network.protocol.data.proto.MsgCtrl import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.getRandomUnsignedInt import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.math.absoluteValue import kotlin.random.Random internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.Response>("MessageSvc.PbSendMsg") { sealed class Response : Packet { class SUCCESS( val sendTime: Int, ) : Response() { override fun toString(): String = "MessageSvcPbSendMsg.Response.SUCCESS(time=$sendTime)" } object MessageTooLarge : Response() { override fun toString(): String = "MessageSvcPbSendMsg.Response.MessageTooLarge" } object ServiceUnavailable : Response() { override fun toString(): String = "MessageSvcPbSendMsg.Response.ServiceUnavailable" } /** * 121: 被限制? 个别号才不能发 */ data class Failed(val resultType: Int, val errorCode: Int, val errorMessage: String) : Response() { override fun toString(): String = "MessageSvcPbSendMsg.Response.Failed(resultType=$resultType, errorCode=$errorCode, errorMessage=$errorMessage)" } } internal fun MessageChain.fragmented(): List<MessageChain> { val results = mutableListOf<MessageChain>() var txtAdd = false val last = mutableListOf<Message>() fun flush() { txtAdd = false if (last.isNotEmpty()) { results.add(ArrayList(last).toMessageChain()) last.clear() } } forEach { element -> if (last.size >= 4) { flush() } if (element is PlainText) { if (txtAdd) { flush() } if (element.content.length < 80) { txtAdd = true last.add(element) } else { val split = element.content.chunked(80) flush() split.forEach { results.add(PlainText(it).toMessageChain()) } } } else { last.add(element) } } flush() return results } class LateinitBox<T : Any> { lateinit var value: T } private inline fun buildOutgoingMessageCommon( client: QQAndroidClient, message: MessageChain, crossinline fragmentTranslator: (MessageChain) -> ImMsgBody.MsgBody, crossinline pbSendMsgReq: ( msgBody: ImMsgBody.MsgBody, msgSeq: Int, msgRand: Int, contentHead: MsgComm.ContentHead, ) -> MsgSvc.PbSendMsgReq, sequenceIds: LateinitBox<IntArray>, sequenceIdsInitializer: (Int) -> IntArray, randIds: LateinitBox<IntArray>, doFragmented: Boolean = true, postInit: () -> Unit, ): List<OutgoingPacket> { val fragmented = if (doFragmented) message.fragmented() else listOf(message) val response = mutableListOf<OutgoingPacket>() val div = if (fragmented.size == 1) 0 else getRandomUnsignedInt() val pkgNum = fragmented.size val seqIds = sequenceIdsInitializer(pkgNum) val randIds0 = IntArray(pkgNum) { getRandomUnsignedInt() } sequenceIds.value = seqIds randIds.value = randIds0 postInit() fragmented.forEachIndexed { pkgIndex, fMsg -> response.add( buildOutgoingUniPacket(client) { writeProtoBuf( MsgSvc.PbSendMsgReq.serializer(), pbSendMsgReq( fragmentTranslator(fMsg), seqIds[pkgIndex], randIds0[pkgIndex], MsgComm.ContentHead( pkgNum = pkgNum, divSeq = div, pkgIndex = pkgIndex, ), ), ) }, ) } return response } // old Voice private fun PttMessage.toPtt() = run { (this.pttInternalInstance as? ImMsgBody.Ptt)?.let { return it } ImMsgBody.Ptt( fileName = fileName.toByteArray(), fileMd5 = md5, boolValid = true, fileSize = fileSize.toInt(), fileType = 4, pbReserve = byteArrayOf(0), format = let { @Suppress("DEPRECATION_ERROR") if (it is net.mamoe.mirai.message.data.Voice) { it._codec } else { 0 } }, ) } /** * 发送陌生人消息 */ internal fun createToStrangerImpl( client: QQAndroidClient, target: Stranger, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, source: (OnlineMessageSourceToStrangerImpl) -> Unit, ): List<OutgoingPacket> { val sequenceIds = LateinitBox<IntArray>() val randIds = LateinitBox<IntArray>() return buildOutgoingMessageCommon( client = client, message = message, fragmentTranslator = { ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = MessageProtocolFacade.encode(it, messageTarget = target, withGeneralFlags = true), ), ) }, pbSendMsgReq = { msgBody, msgSeq, msgRand, contentHead -> MsgSvc.PbSendMsgReq( routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = target.uin)), contentHead = contentHead, msgBody = msgBody, msgSeq = msgSeq, msgRand = msgRand, syncCookie = client.syncCookie ?: byteArrayOf(), // msgVia = 1 ) }, sequenceIds = sequenceIds, randIds = randIds, sequenceIdsInitializer = { size -> IntArray(size) { client.atomicNextMessageSequenceId() } }, postInit = { source( OnlineMessageSourceToStrangerImpl( internalIds = randIds.value, sender = client.bot, target = target, time = client.bot.clock.server.currentTimeSeconds().toInt(), sequenceIds = sequenceIds.value, originalMessage = originalMessage, ), ) }, doFragmented = fragmented, ) } /** * 发送好友消息 */ internal fun createToFriendImpl( client: QQAndroidClient, targetFriend: Friend, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSourceToFriendImpl) -> Unit, ): List<OutgoingPacket> { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } val sequenceIds = LateinitBox<IntArray>() val randIds = LateinitBox<IntArray>() return buildOutgoingMessageCommon( client = client, message = message, fragmentTranslator = { subChain -> ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = MessageProtocolFacade.encode( subChain, messageTarget = targetFriend, withGeneralFlags = true ), ptt = subChain.findPtt(), ), ) }, pbSendMsgReq = { msgBody, msgSeq, msgRand, contentHead -> MsgSvc.PbSendMsgReq( routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = targetFriend.uin)), contentHead = contentHead, msgBody = msgBody, msgSeq = msgSeq, msgRand = msgRand, syncCookie = client.syncCookie ?: byteArrayOf(), // msgVia = 1 ) }, sequenceIds = sequenceIds, randIds = randIds, sequenceIdsInitializer = { size -> IntArray(size) { client.sendFriendMessageSeq.next() } }, postInit = { sourceCallback( OnlineMessageSourceToFriendImpl( internalIds = randIds.value, sender = client.bot, target = targetFriend, time = client.bot.clock.server.currentTimeSeconds().toInt(), sequenceIds = sequenceIds.value, originalMessage = originalMessage, ), ) }, doFragmented = fragmented, ) } /*= buildOutgoingUniPacket(client) { ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) val rand = Random.nextInt().absoluteValue val source = MessageSourceToFriendImpl( internalIds = intArrayOf(rand), sender = client.bot, target = qq, time = currentTimeSeconds().toInt(), sequenceIds = intArrayOf(client.nextFriendSeq()), originalMessage = message ) sourceCallback(source) ///return@buildOutgoingUniPacket writeProtoBuf( MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( routingHead = MsgSvc.RoutingHead(c2c = MsgSvc.C2C(toUin = targetFriend.uin)), contentHead = MsgComm.ContentHead(pkgNum = 1), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = message.toRichTextElems(messageTarget = targetFriend, withGeneralFlags = true) ) ), msgSeq = source.sequenceIds.single(), msgRand = source.internalIds.single(), syncCookie = client.syncCookie ?: byteArrayOf() // msgVia = 1 ) ) } */ /** * 发送临时消息 */ internal fun createToTempImpl( client: QQAndroidClient, targetMember: Member, message: MessageChain, source: OnlineMessageSourceToTempImpl, ) = buildOutgoingUniPacket(client) { writeProtoBuf( MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( routingHead = MsgSvc.RoutingHead( grpTmp = MsgSvc.GrpTmp(targetMember.group.uin, targetMember.id), ), contentHead = MsgComm.ContentHead(pkgNum = 1), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = MessageProtocolFacade.encode( message, messageTarget = targetMember, withGeneralFlags = true ), ), ), msgSeq = source.sequenceIds.single(), msgRand = source.internalIds.single(), syncCookie = client.syncCookie ?: byteArrayOf(), ), ) } /** * 发送群消息 */ internal fun createToGroupImpl( client: QQAndroidClient, targetGroup: Group, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (OnlineMessageSourceToGroupImpl) -> Unit, ): List<OutgoingPacket> { val sequenceIds = LateinitBox<IntArray>() val randIds = LateinitBox<IntArray>() return buildOutgoingMessageCommon( client = client, message = message, fragmentTranslator = { subChain -> ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = MessageProtocolFacade.encode( subChain, messageTarget = targetGroup, withGeneralFlags = true ), ptt = subChain.findPtt(), ), ) }, pbSendMsgReq = { msgBody, msgSeq, msgRand, contentHead -> MsgSvc.PbSendMsgReq( routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = targetGroup.groupCode)), contentHead = contentHead, msgBody = msgBody, msgSeq = msgSeq, msgRand = msgRand, syncCookie = client.syncCookie ?: byteArrayOf(), msgVia = 1, msgCtrl = if (message[ForwardMessageInternal] != null) MsgCtrl.MsgCtrl(msgFlag = 4) else null, ) }, sequenceIds = sequenceIds, randIds = randIds, sequenceIdsInitializer = { size -> IntArray(size) { client.atomicNextMessageSequenceId() } }, postInit = { randIds.value.forEach { id -> client.syncController.syncGroupMessageReceipt(id) } sourceCallback( OnlineMessageSourceToGroupImpl( targetGroup, internalIds = randIds.value, sender = client.bot, target = targetGroup, time = client.bot.clock.server.currentTimeSeconds().toInt(), originalMessage = originalMessage, //, // sourceMessage = message ), ) }, doFragmented = fragmented, ) } private fun MessageChain.findPtt() = findIsInstance<Audio>()?.toPtt() ?: this[PttMessage]?.toPtt() /* = buildOutgoingUniPacket(client) { ///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes()) // DebugLogger.debug("sending group message: " + message.toRichTextElems().contentToString()) ///return@buildOutgoingUniPacket writeProtoBuf( MsgSvc.PbSendMsgReq.serializer(), MsgSvc.PbSendMsgReq( routingHead = MsgSvc.RoutingHead(grp = MsgSvc.Grp(groupCode = targetGroup.groupCode)), contentHead = MsgComm.ContentHead(pkgNum = 1), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( elems = message.toRichTextElems(messageTarget = targetGroup, withGeneralFlags = true), ptt = message[PttMessage]?.run { ImMsgBody.Ptt( fileName = fileName.toByteArray(), fileMd5 = md5, boolValid = true, fileSize = fileSize.toInt(), fileType = 4, pbReserve = byteArrayOf(0), format = let { if (it is Voice) { it.codec } else { 0 } } ) } ) ), msgSeq = client.atomicNextMessageSequenceId(), msgRand = source.internalIds.single(), syncCookie = EMPTY_BYTE_ARRAY, msgVia = 1, msgCtrl = if (message[ForwardMessageInternal] != null) MsgCtrl.MsgCtrl(msgFlag = 4) else null ) ) } */ override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val response = readProtoBuf(MsgSvc.PbSendMsgResp.serializer()) return when (response.result) { 0 -> Response.SUCCESS(response.sendTime) 10 -> Response.MessageTooLarge 32 -> Response.ServiceUnavailable else -> Response.Failed( response.result, response.errtype, response.errmsg, ) } } } @Suppress("UNUSED_PARAMETER") internal fun MessageSvcPbSendMsg.createToTemp( client: QQAndroidClient, member: Member, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSourceToTempImpl>) -> Unit, ): List<OutgoingPacket> { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } val source = OnlineMessageSourceToTempImpl( internalIds = intArrayOf(Random.nextInt().absoluteValue), sender = client.bot, target = member, time = client.bot.clock.server.currentTimeSeconds().toInt(), sequenceIds = intArrayOf(client.atomicNextMessageSequenceId()), originalMessage = originalMessage, ) sourceCallback(CompletableDeferred(source)) return createToTempImpl( client, member, message, source, ).let { listOf(it) } } internal fun MessageSvcPbSendMsg.createToStranger( client: QQAndroidClient, stranger: Stranger, message: MessageChain, // to send originalMessage: MessageChain, // for Receipt fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSourceToStrangerImpl>) -> Unit, ): List<OutgoingPacket> { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } return createToStrangerImpl( client, stranger, message, originalMessage, fragmented, ) { sourceCallback(CompletableDeferred(it)) } } internal fun MessageSvcPbSendMsg.createToFriend( client: QQAndroidClient, qq: Friend, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSourceToFriendImpl>) -> Unit, ): List<OutgoingPacket> { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } return createToFriendImpl( client, qq, message, originalMessage, fragmented, ) { sourceCallback(CompletableDeferred(it)) } } internal fun MessageSvcPbSendMsg.createToGroup( client: QQAndroidClient, group: Group, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSourceToGroupImpl>) -> Unit, ): List<OutgoingPacket> { contract { callsInPlace(sourceCallback, InvocationKind.EXACTLY_ONCE) } return createToGroupImpl( client, group, message, originalMessage, fragmented, ) { sourceCallback(CompletableDeferred(it)) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PushForceOffline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.components.AccountSecretsManager import net.mamoe.mirai.internal.network.components.BotInitProcessor import net.mamoe.mirai.internal.network.impl.ForceOfflineException import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket /** * 被挤下线 */ internal object MessageSvcPushForceOffline : OutgoingPacketFactory<RequestPushForceOffline>("MessageSvc.PushForceOffline") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RequestPushForceOffline { return readUniPacket(RequestPushForceOffline.serializer()) } override suspend fun QQAndroidBot.handle(packet: RequestPushForceOffline) { components[AccountSecretsManager].invalidate() // otherwise you receive `MessageSvc.PushForceOffline` again just after logging in. components[BotInitProcessor].setLoginHalted() // so that BotInitProcessor will be run on successful reconnection. network.close(ForceOfflineException(packet.title, packet.tips)) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PushNotify.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushNotify import net.mamoe.mirai.internal.network.protocol.data.proto.MsgSvc import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket /** * 告知要刷新好友消息 */ internal object MessageSvcPushNotify : IncomingPacketFactory<RequestPushNotify>("MessageSvc.PushNotify") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): RequestPushNotify { discardExact(4) // don't remove return readUniPacket(RequestPushNotify.serializer()) } override suspend fun QQAndroidBot.handle(packet: RequestPushNotify, sequenceId: Int): OutgoingPacket { while (true) { val firstNotify = syncController.firstNotify val cookie = if (firstNotify) { if (!syncController.casFirstNotify(firstNotify, false)) { continue } null } else { packet.vNotifyCookie } return MessageSvcPbGetMsg( client, MsgSvc.SyncFlag.START, cookie, ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.PushReaded.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory internal object MessageSvcPushReaded : IncomingPacketFactory<Packet?>( "MessageSvc.PushReaded", "" ) { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { // val notify = readUniPacket(SvcRequestPushReadedNotify.serializer()) // just ignore. return null } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/MessageSvc.RequestPushStatus.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushStatus import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket internal object MessageSvcRequestPushStatus : IncomingPacketFactory<Packet>( "MessageSvc.RequestPushStatus", "", ) { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet { val packet = readUniPacket(RequestPushStatus.serializer()) return bot.processPacketThroughPipeline(packet) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbC2CMsgSync.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf internal object PbC2CMsgSync : IncomingPacketFactory<Packet>( "OnlinePush.PbC2CMsgSync", "", ) { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet { return bot.processPacketThroughPipeline( readProtoBuf(MsgOnlinePush.PbPushMsg.serializer()).msg, KEY_FROM_SYNC to true, ) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushGroupMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf /** * 接受群消息 */ internal object OnlinePushPbPushGroupMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushGroupMsg") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { // 00 00 02 E4 0A D5 05 0A 4F 08 A2 FF 8C F0 03 10 DD F1 92 B7 07 18 52 20 00 28 BC 3D 30 8C 82 AB F1 05 38 D2 80 E0 8C 80 80 80 80 02 4A 21 08 E7 C1 AD B8 02 10 01 18 BA 05 22 09 48 69 6D 31 38 38 6D 6F 65 30 06 38 02 42 05 4D 69 72 61 69 50 01 58 01 60 00 88 01 08 12 06 08 01 10 00 18 00 1A F9 04 0A F6 04 0A 26 08 00 10 87 82 AB F1 05 18 B7 B4 BF 30 20 00 28 0C 30 00 38 86 01 40 22 4A 0C E5 BE AE E8 BD AF E9 9B 85 E9 BB 91 12 E6 03 42 E3 03 12 2A 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 22 00 2A 04 03 00 00 00 32 60 15 36 20 39 36 6B 45 31 41 38 35 32 32 39 64 63 36 39 38 34 37 39 37 37 62 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 34 45 31 38 35 38 32 32 2D 30 45 37 42 2D 46 38 30 46 2D 43 35 42 31 2D 33 34 34 38 38 33 37 34 44 33 39 43 7D 2E 6A 70 67 31 32 31 32 41 38 C6 BB 8A A9 08 40 FB AE 9E C2 09 48 50 50 41 5A 00 60 01 6A 10 4E 18 58 22 0E 7B F8 0F C5 B1 34 48 83 74 D3 9C 72 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 31 39 38 3F 74 65 72 6D 3D 32 82 01 57 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 30 3F 74 65 72 6D 3D 32 B0 01 4D B8 01 2E C8 01 FF 05 D8 01 4D E0 01 2E FA 01 59 2F 67 63 68 61 74 70 69 63 5F 6E 65 77 2F 31 30 34 30 34 30 30 32 39 30 2F 36 35 35 30 35 37 31 32 37 2D 32 32 33 33 36 33 38 33 34 32 2D 34 45 31 38 35 38 32 32 30 45 37 42 46 38 30 46 43 35 42 31 33 34 34 38 38 33 37 34 44 33 39 43 2F 34 30 30 3F 74 65 72 6D 3D 32 80 02 4D 88 02 2E 12 45 AA 02 42 50 03 60 00 68 00 9A 01 39 08 09 20 BF 50 80 01 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 04 08 02 08 01 90 04 80 80 80 10 B8 04 00 C0 04 00 12 06 4A 04 08 00 40 01 12 14 82 01 11 0A 09 48 69 6D 31 38 38 6D 6F 65 18 06 20 08 28 03 10 8A CA 9D A1 07 1A 00 if (!bot.components[SsoProcessor].firstLoginSucceed) return null return bot.processPacketThroughPipeline(readProtoBuf(MsgOnlinePush.PbPushMsg.serializer())) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.PbPushTransMsg.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline import net.mamoe.mirai.internal.network.components.SyncController.Companion.syncController import net.mamoe.mirai.internal.network.components.syncPushTrans import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf internal object OnlinePushPbPushTransMsg : IncomingPacketFactory<Packet?>("OnlinePush.PbPushTransMsg", "OnlinePush.RespPush") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { val content = this.readProtoBuf(OnlinePushTrans.PbMsgInfo.serializer()) if (!bot.syncController.syncPushTrans(content)) return null return bot.processPacketThroughPipeline(content) } override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket { return buildResponseUniPacket(client, sequenceId = sequenceId) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.ReqPush.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.MultiPacket import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.processPacketThroughPipeline import net.mamoe.mirai.internal.network.protocol.data.jce.OnlinePushPack import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket import net.mamoe.mirai.internal.utils.io.serialization.writeJceRequestPacket internal object OnlinePushReqPush : IncomingPacketFactory<OnlinePushReqPush.ReqPushDecoded>( "OnlinePush.ReqPush", "OnlinePush.RespPush", ) { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): ReqPushDecoded { val reqPushMsg = readUniPacket(OnlinePushPack.SvcReqPushMsg.serializer(), "req") return ReqPushDecoded(reqPushMsg, bot.processPacketThroughPipeline(reqPushMsg)) } internal class ReqPushDecoded(val request: OnlinePushPack.SvcReqPushMsg, packet: Packet) : MultiPacket by MultiPacket(packet), Packet.NoLog { override fun toString(): String = "OnlinePush.ReqPush.ReqPushDecoded" } override suspend fun QQAndroidBot.handle(packet: ReqPushDecoded, sequenceId: Int): OutgoingPacket { return buildResponseUniPacket(client) { writeJceRequestPacket( servantName = "OnlinePush", funcName = "SvcRespPushMsg", name = "resp", serializer = OnlinePushPack.SvcRespPushMsg.serializer(), body = OnlinePushPack.SvcRespPushMsg( packet.request.uin, packet.request.vMsgInfos.map { msg -> OnlinePushPack.DelMsgInfo( fromUin = msg.lFromUin, shMsgSeq = msg.shMsgSeq, vMsgCookies = msg.vMsgCookies, uMsgTime = msg.uMsgTime, // captured 0 ) }, ), ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/receive/OnlinePush.SidExpired.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.receive import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.AccountSecretsImpl import net.mamoe.mirai.internal.network.components.AccountSecretsManager import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin10 internal object OnlinePushSidExpired : IncomingPacketFactory<Packet?>("OnlinePush.SidTicketExpired", "OnlinePush.SidTicketExpired") { override suspend fun QQAndroidBot.handle(packet: Packet?, sequenceId: Int): OutgoingPacket { return buildResponseUniPacket(client, sequenceId = sequenceId).also { bot.network.sendAndExpect(WtLogin10(client, mainSigMap = 1052896, remark = "10:refresh-token")) bot.components[AccountSecretsManager].saveSecrets(bot.account, AccountSecretsImpl(client)) } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? { return null } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/shortvideo/PttCenterSvr.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.video import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.User import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.PttShortVideo import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf internal class PttCenterSvr { object GroupShortVideoUpReq : OutgoingPacketFactory<GroupShortVideoUpReq.Response>("PttCenterSvr.GroupShortVideoUpReq") { sealed class Response : Packet { class FileExists(val fileId: String) : Response() { override fun toString(): String { return "PttCenterSvr.GroupShortVideoUpReq.Response.FileExists(fileId=${fileId})" } } object RequireUpload : Response() { override fun toString(): String { return "PttCenterSvr.GroupShortVideoUpReq.Response.RequireUpload" } } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readProtoBuf(PttShortVideo.RspBody.serializer()) val upResp = resp.msgPttShortVideoUploadResp ?: return Response.RequireUpload return if (upResp.fileExist == 1) { Response.FileExists(upResp.fileid) } else { Response.RequireUpload } } operator fun invoke( client: QQAndroidClient, contact: Contact, thumbnailFileMd5: ByteArray, thumbnailFileSize: Long, videoFileName: String, videoFileMd5: ByteArray, videoFileSize: Long, videoFileFormat: String ) = buildOutgoingUniPacket(client) { sequenceId -> writeProtoBuf( PttShortVideo.ReqBody.serializer(), PttShortVideo.ReqBody( cmd = 300, seq = sequenceId, msgPttShortVideoUploadReq = buildShortVideoFileInfo( client, contact, thumbnailFileMd5, thumbnailFileSize, videoFileName, videoFileMd5, videoFileSize, videoFileFormat ), msgExtensionReq = listOf( PttShortVideo.ExtensionReq( subBusiType = 0, userCnt = 1 ) ) ) ) } internal fun buildShortVideoFileInfo( client: QQAndroidClient, contact: Contact, thumbnailFileMd5: ByteArray, thumbnailFileSize: Long, videoFileName: String, videoFileMd5: ByteArray, videoFileSize: Long, videoFileFormat: String ) = PttShortVideo.PttShortVideoUploadReq( fromuin = client.uin, touin = contact.uin, chatType = 1, // guild channel = 4, others = 1 clientType = 2, msgPttShortVideoFileInfo = PttShortVideo.PttShortVideoFileInfo( fileName = videoFileName + videoFileFormat, fileMd5 = videoFileMd5, fileSize = videoFileSize, fileResLength = 1280, fileResWidth = 720, // Lcom/tencent/mobileqq/transfile/ShortVideoUploadProcessor;getFormat(Ljava/lang/String;)I fileFormat = 3, fileTime = 120, thumbFileMd5 = thumbnailFileMd5, thumbFileSize = thumbnailFileSize ), groupCode = if (contact is Group) contact.uin else 0, flagSupportLargeSize = 1 ) } object ShortVideoDownReq : OutgoingPacketFactory<ShortVideoDownReq.Response>("PttCenterSvr.ShortVideoDownReq") { sealed class Response : Packet { class Success(val fileMd5: ByteArray, val urlV4: String, val urlV6: String?) : Response() { override fun toString(): String { return "PttCenterSvr.ShortVideoDownReq.Response.Success(" + "urlV4=$urlV4, urlV6=$urlV6)" } } object Failed : Response() { override fun toString(): String { return "PttCenterSvr.ShortVideoDownReq.Response.Failed" } } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readProtoBuf(PttShortVideo.RspBody.serializer()) val shortVideoDownloadResp = resp.msgPttShortVideoDownloadResp ?: return Response.Failed val attr = shortVideoDownloadResp.msgDownloadAddr ?: return Response.Failed val fileMd5 = shortVideoDownloadResp.fileMd5 val urlV4 = attr.strHost.first() + attr.urlArgs val urlV6 = attr.strHostIpv6.firstOrNull()?.plus(attr.urlArgs) return Response.Success(fileMd5, urlV4, urlV6) } // Lcom/tencent/mobileqq/transfile/protohandler/ShortVideoDownHandler;constructReqBody(Ljava/util/List;)[B operator fun invoke( client: QQAndroidClient, contact: Contact, sender: User, videoFIleId: String, videoFileMd5: ByteArray, ) = buildOutgoingUniPacket(client) { sequenceId -> writeProtoBuf( PttShortVideo.ReqBody.serializer(), PttShortVideo.ReqBody( cmd = 400, seq = sequenceId, msgPttShortVideoDownloadReq = PttShortVideo.PttShortVideoDownloadReq( fromuin = sender.uin, touin = client.uin, chatType = if (sender is Friend) 0 else 1, clientType = 7, fileid = videoFIleId, groupCode = if (contact is Group) contact.uin else 0L, fileMd5 = videoFileMd5, businessType = 1, flagSupportLargeSize = 1, flagClientQuicProtoEnable = 1, fileType = 2, // maybe 1 = newly uploaded video, unverified downType = 2, sceneType = 2, // hooked 0 and 1, but unknown reqTransferType = 1, reqHostType = 11, ), msgExtensionReq = listOf( PttShortVideo.ExtensionReq(subBusiType = 0) ) ) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/chat/voice/PttStore.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.chat.voice import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x346 import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388 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.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.internal.utils.toIpV4AddressString import net.mamoe.mirai.message.data.AudioCodec import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.toUHexString internal inline val ExternalResource.voiceCodec: Int get() = audioCodec.id internal val ExternalResource.audioCodec: AudioCodec get() { return when (formatName) { // 实际上 amr 是 0, 但用 1 也可以发. 为了避免 silk 错被以 amr 发送导致降音质就都用 1 "amr" -> AudioCodec.SILK "silk" -> AudioCodec.SILK else -> AudioCodec.AMR // use amr by default } } internal class PttStore { object GroupPttUp : OutgoingPacketFactory<GroupPttUp.Response>("PttStore.GroupPttUp") { sealed class Response : Packet { class RequireUpload( val fileId: Long, val uKey: ByteArray, val uploadIpList: List<Int>, val uploadPortList: List<Int>, val fileKey: ByteArray ) : GroupPttUp.Response() { override fun toString(): String { return "RequireUpload(" + "fileId=$fileId, " + "uploadServers=${ uploadIpList.zip(uploadPortList) .joinToString( prefix = "[", postfix = "]" ) { "${it.first.toIpV4AddressString()}:${it.second}1" } }, " + "uKey=${uKey.toUHexString("")}, " + "fileKey=${fileKey.toUHexString("")}" + ")" } } } fun createTryUpPttPack( uin: Long, groupCode: Long, resource: ExternalResource ): Cmd0x388.ReqBody { return Cmd0x388.ReqBody( netType = 3, // wifi subcmd = 3, msgTryupPttReq = listOf( Cmd0x388.TryUpPttReq( srcUin = uin, groupCode = groupCode, fileId = 0, fileSize = resource.size, fileMd5 = resource.md5, fileName = resource.md5, srcTerm = 5, platformType = 9, buType = 4, innerIp = 0, buildVer = "6.5.5.663".encodeToByteArray(), voiceLength = 1, codec = resource.voiceCodec, // HTTP 时只支持 0 // 2021/1/26 因为 #577 修改为 resource.voiceCodec voiceType = 1, boolNewUpChan = true ) ) ) } operator fun invoke( client: QQAndroidClient, uin: Long, groupCode: Long, resource: ExternalResource ): OutgoingPacketWithRespType<Response> { val pack = createTryUpPttPack(uin, groupCode, resource) return buildOutgoingUniPacket(client) { writeProtoBuf(Cmd0x388.ReqBody.serializer(), pack) } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp0 = readProtoBuf(Cmd0x388.RspBody.serializer()) val resp = resp0.msgTryupPttRsp.firstOrNull() ?: error("cannot find `msgTryupPttRsp` from `Cmd0x388.RspBody`") if (resp.failMsg != null) { throw IllegalStateException(resp.failMsg.decodeToString()) } return Response.RequireUpload( fileId = resp.fileid, uKey = resp.upUkey, uploadIpList = resp.uint32UpIp, uploadPortList = resp.uint32UpPort, fileKey = resp.fileKey ) } } object GroupPttDown : OutgoingPacketFactory<GroupPttDown.Response.DownLoadInfo>("PttStore.GroupPttDown") { sealed class Response : Packet { class DownLoadInfo( val downDomain: ByteArray, val downPara: ByteArray, val strDomain: String, val uint32DownIp: List<Int>, val uint32DownPort: List<Int> ) : GroupPttDown.Response() { override fun toString(): String { return "GroupPttDown(downPara=${downPara.decodeToString()},strDomain=$strDomain})" } } } operator fun invoke( client: QQAndroidClient, groupCode: Long, ptt: ImMsgBody.Ptt, msg: MsgComm.Msg, ) = buildOutgoingUniPacket(client) { writeProtoBuf( Cmd0x388.ReqBody.serializer(), Cmd0x388.ReqBody( netType = 3, // wifi subcmd = 4, msgGetpttUrlReq = listOf( Cmd0x388.GetPttUrlReq( groupCode = groupCode, fileid = 0, fileId = ptt.fileId.toLong(), fileMd5 = ptt.fileMd5, dstUin = msg.msgHead.toUin, fileKey = ptt.groupFileKey, buType = 3, innerIp = 0, buildVer = "8.5.5".encodeToByteArray(), codec = ptt.format, reqTerm = 5, reqPlatformType = 9, ) ) ) ) } @OptIn(ExperimentalStdlibApi::class) operator fun invoke( client: QQAndroidClient, groupCode: Long, dstUin: Long, md5: ByteArray ) = buildOutgoingUniPacket(client) { writeProtoBuf( Cmd0x388.ReqBody.serializer(), Cmd0x388.ReqBody( netType = 3, // wifi subcmd = 4, msgGetpttUrlReq = listOf( Cmd0x388.GetPttUrlReq( groupCode = groupCode, fileMd5 = md5, dstUin = dstUin, buType = 4, innerIp = 0, buildVer = "8.5.5".encodeToByteArray(), codec = 0, reqTerm = 5, reqPlatformType = 9 ) ) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response.DownLoadInfo { val resp0 = readProtoBuf(Cmd0x388.RspBody.serializer()) val resp = resp0.msgGetpttUrlRsp.firstOrNull() ?: error("cannot find `msgGetpttUrlRsp` from `Cmd0x388.RspBody`") if (!resp.failMsg.contentEquals(EMPTY_BYTE_ARRAY)) { throw IllegalStateException(resp.failMsg.decodeToString()) } return Response.DownLoadInfo( downDomain = resp.downDomain, downPara = resp.downPara, uint32DownIp = resp.uint32DownIp, uint32DownPort = resp.uint32DownPort, strDomain = resp.strDomain ) } } object C2CPttDown : OutgoingPacketFactory<C2CPttDown.Response>( "PttCenterSvr.pb_pttCenter_CMD_REQ_APPLY_DOWNLOAD-1200" ) { operator fun invoke(client: QQAndroidClient, uin: Long, uuid: ByteArray) = buildOutgoingUniPacket(client) { writeProtoBuf( Cmd0x346.ReqBody.serializer(), Cmd0x346.ReqBody( msgApplyDownloadReq = Cmd0x346.ApplyDownloadReq( uin = uin, uuid = uuid, needHttpsUrl = 1, ), clientType = 104, cmd = 1200, businessId = 17, // or 3? ) ) } sealed class Response : Packet { class Failed(val retMsg: String) : Response() { override fun toString(): String { return "PttCenterSvr.pb_pttCenter#download.Failed(retMsg=$retMsg)" } } class Success(val downloadUrl: String) : Response() { override fun toString(): String { return "PttCenterSvr.pb_pttCenter#download.Success" } } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): C2CPttDown.Response { val data = readProtoBuf(Cmd0x346.RspBody.serializer()) val rsp = data.msgApplyDownloadRsp ?: return Response.Failed("Response not found") if (rsp.retMsg != "success") { return Response.Failed(rsp.retMsg) } val downloadInfo = rsp.msgDownloadInfo ?: return Response.Failed("Download info not found") return Response.Success(downloadInfo.downloadUrl) } } object C2C { fun createC2CPttStoreBDHExt( bot: QQAndroidBot, uin: Long, resource: ExternalResource ): Cmd0x346.ReqBody { return Cmd0x346.ReqBody( cmd = 500, businessId = 17, clientType = 104, msgExtensionReq = Cmd0x346.ExtensionReq( id = 3, pttFormat = 1, netType = 3, voiceType = 1, pttTime = 1, ), msgApplyUploadReq = Cmd0x346.ApplyUploadReq( senderUin = bot.uin, recverUin = uin, fileType = 2, fileSize = resource.size, fileName = resource.md5, _10mMd5 = resource.md5, ), ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/list/FriendList.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.list import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.jce.* import net.mamoe.mirai.internal.network.protocol.data.proto.Vec0xd50 import net.mamoe.mirai.internal.network.protocol.data.proto.Vec0xd6b import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.* import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.toByteArray internal class FriendList { internal object GetTroopMemberList : OutgoingPacketFactory<GetTroopMemberList.Response>("friendlist.GetTroopMemberListReq") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val res = this.readUniPacket(GetTroopMemberListResp.serializer()) return Response( res.vecTroopMember, res.nextUin ) } operator fun invoke( client: QQAndroidClient, targetGroupUin: Long, targetGroupCode: Long, nextUin: Long = 0 ) = buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( funcName = "GetTroopMemberListReq", servantName = "mqq.IMService.FriendListServiceServantObj", version = 3, requestId = client.nextRequestPacketRequestId(), sBuffer = jceRequestSBuffer( "GTML", GetTroopMemberListReq.serializer(), GetTroopMemberListReq( uin = client.uin, groupCode = targetGroupCode, groupUin = targetGroupUin, nextUin = nextUin, reqType = 0, version = 2 ) ) ) ) } class Response( val members: List<StTroopMemberInfo>, val nextUin: Long ) : Packet { override fun toString(): String = "FriendList.GetTroopMemberList.Response" } } internal object GetTroopListSimplify : OutgoingPacketFactory<GetTroopListSimplify.Response>("friendlist.GetTroopListReqV2") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val res = this.readUniPacket(GetTroopListRespV2.serializer()) return Response(res.vecTroopList.orEmpty(), res.vecTroopRank.orEmpty()) } class Response( val groups: List<StTroopNum>, val ranks: List<StGroupRankInfo> ) : Packet { override fun toString(): String = "FriendList.GetFriendGroupList.Response" } operator fun invoke( client: QQAndroidClient ) = buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( funcName = "GetTroopListReqV2Simplify", servantName = "mqq.IMService.FriendListServiceServantObj", version = 3, cPacketType = 0x00, requestId = client.nextRequestPacketRequestId(), sBuffer = jceRequestSBuffer( "GetTroopListReqV2Simplify", GetTroopListReqV2Simplify.serializer(), GetTroopListReqV2Simplify( uin = client.uin, getMSFMsgFlag = 0, groupFlagExt = 1, shVersion = 7, dwCompanyId = 0, versionNum = 1, vecGroupInfo = listOf(), getLongGroupName = 1 ) ) ) ) } } internal object DelFriend : OutgoingPacketFactory<DelFriend.Response>("friendlist.delFriend") { class Response(val isSuccess: Boolean, val resultCode: Int) : Packet { override fun toString(): String = "FriendList.DelFriend.Response(isSuccess=$isSuccess)" } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val res = this.readUniPacket(DelFriendResp.serializer()) return Response(res.result == 0, res.result) } operator fun invoke( client: QQAndroidClient, friend: Friend ) = buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( funcName = "DelFriendReq", servantName = "mqq.IMService.FriendListServiceServantObj", version = 3, sBuffer = jceRequestSBuffer( "DF", DelFriendReq.serializer(), DelFriendReq( uin = client.uin, delType = 2, delUin = friend.uin, version = 1 ) ) ) ) } } internal object GetFriendGroupList : OutgoingPacketFactory<GetFriendGroupList.Response>("friendlist.getFriendGroupList") { class Response( val selfInfo: FriendInfo?, val totalFriendCount: Short, val friendList: List<FriendInfo>, // for FriendGroup val groupList: List<GroupInfo>, val totoalGroupCount: Short ) : Packet { override fun toString(): String = "FriendList.GetFriendGroupList.Response" } override suspend fun QQAndroidBot.handle(packet: Response) { packet.selfInfo?.let { this.nick = it.nick } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val res = this.readUniPacket(GetFriendListResp.serializer()) return Response( res.stSelfInfo, res.totoalFriendCount, res.vecFriendInfo.orEmpty(), res.vecGroupInfo.orEmpty(), res.totoalGroupCount ?: -1 ) } fun forSingleFriend( client: QQAndroidClient, uin: Long ) = buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( funcName = "GetFriendListReq", servantName = "mqq.IMService.FriendListServiceServantObj", version = 3, cPacketType = 0x003, requestId = client.nextRequestPacketRequestId(), sBuffer = jceRequestSBuffer( "FL", GetFriendListReq.serializer(), GetFriendListReq( reqtype = 3, ifGetGroupInfo = 0, ifReflush = 1, uin = client.uin, startIndex = 0, groupstartIndex = 0, getgroupCount = 0, ifShowTermType = 1, version = 27L, getfriendCount = 0, ifGetMSFGroup = 0, eAppType = 0, groupid = 0, ifGetBothFlag = 0, ifGetDOVId = 0, uinList = listOf(uin), vec0xd6bReq = Vec0xd6b.ReqBody().toByteArray(Vec0xd6b.ReqBody.serializer()), vec0xd50Req = Vec0xd50.ReqBody( appid = 10002L, reqKsingSwitch = 1, reqMusicSwitch = 1, reqMutualmarkLbsshare = 1, reqMutualmarkAlienation = 1 ).toByteArray(Vec0xd50.ReqBody.serializer()) ) ) ) ) } operator fun invoke( client: QQAndroidClient, friendListStartIndex: Int, friendListCount: Int, groupListStartIndex: Int, groupListCount: Int ) = buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( funcName = "GetFriendListReq", servantName = "mqq.IMService.FriendListServiceServantObj", version = 3, cPacketType = 0x003, requestId = client.nextRequestPacketRequestId(), sBuffer = jceRequestSBuffer( "FL", GetFriendListReq.serializer(), GetFriendListReq( reqtype = 3, ifReflush = if (friendListStartIndex + groupListStartIndex <= 0) { 0 } else { 1 }, uin = client.uin, startIndex = friendListStartIndex.toShort(), getfriendCount = friendListCount.toShort(), groupid = 0, ifGetGroupInfo = if (groupListCount <= 0) { 0 } else { 1 }, groupstartIndex = groupListStartIndex.toByte(), getgroupCount = groupListCount.toByte(), ifGetMSFGroup = 0, ifShowTermType = 1, version = 27L, uinList = null, eAppType = 0, ifGetBothFlag = 0, ifGetDOVId = 0, vec0xd6bReq = EMPTY_BYTE_ARRAY, vec0xd50Req = Vec0xd50.ReqBody( appid = 10002L, reqKsingSwitch = 1, reqMusicSwitch = 1, reqMutualmarkLbsshare = 1, reqMutualmarkAlienation = 1 ).toByteArray(Vec0xd50.ReqBody.serializer()), vecSnsTypelist = listOf(13580L, 13581L, 13582L) ) ) ) ) } } // for FriendGroup internal object SetGroupReqPack : OutgoingPacketFactory<SetGroupReqPack.Response>("friendlist.SetGroupReq") { class Response( // Success: result == 0x00 val result: Byte, val errStr: String, // groupId for delete val groupId: Int, val isSuccess: Boolean = result.toInt() == 0 ) : Packet { override fun toString(): String { return "SetGroupResp(isSuccess=$isSuccess,resultCode=$result, errString=$errStr, groupId=$groupId)" } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val pack = this.readUniPacket(SetGroupResp.serializer()) return if (pack.result.toInt() == 0) { Response(pack.result, pack.errorString, pack.vecBody?.get(8)?.toInt() ?: -1) } else { Response(pack.result, pack.errorString, -1) } } object New { operator fun invoke( client: QQAndroidClient, groupName: String ) = buildOutgoingUniPacket(client) { val arr = groupName.toByteArray() // maybe is constant val constant: Byte = 0x01 writeJceRequestPacket( servantName = "mqq.IMService.FriendListServiceServantObj", funcName = "SetGroupReq", serializer = SetGroupReq.serializer(), body = SetGroupReq(0, client.uin, byteArrayOf(constant, arr.size.toByte()) + arr) ) } } object Rename { operator fun invoke(client: QQAndroidClient, newName: String, id: Int) = buildOutgoingUniPacket(client) { val arr = newName.toByteArray() writeJceRequestPacket( servantName = "mqq.IMService.FriendListServiceServantObj", funcName = "SetGroupReq", serializer = SetGroupReq.serializer(), body = SetGroupReq(1, client.uin, byteArrayOf(id.toByte(), arr.size.toByte()) + arr) ) } } object Delete { operator fun invoke(client: QQAndroidClient, id: Int) = buildOutgoingUniPacket(client) { writeJceRequestPacket( servantName = "mqq.IMService.FriendListServiceServantObj", funcName = "SetGroupReq", serializer = SetGroupReq.serializer(), body = SetGroupReq(2, client.uin, byteArrayOf(id.toByte())) ) } } } // for FriendGroup internal object MoveGroupMemReqPack : OutgoingPacketFactory<MoveGroupMemReqPack.Response>("friendlist.MovGroupMemReq") { private fun Long.toByteArray2(): ByteArray { val arr = this.toByteArray() val index = arr.indexOfFirst { it.toInt() != 0 } return arr.sliceArray(index until arr.size) } // 如果不成功会自动移动到id = 0的默认好友分组, result 还是会返回0 class Response( // Success: result == 0x00 val result: Byte, val errStr: String, val isSuccess: Boolean = result.toInt() == 0 ) : Packet { override fun toString(): String { return "MoveGroupMemReq(isSuccess=$isSuccess,resultCode=$result, errString=$errStr)" } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val pack = this.readUniPacket(MovGroupMemResp.serializer()) return Response(pack.result, pack.errorString) } operator fun invoke( client: QQAndroidClient, // friend id id: Long, // friend group id groupId: Int ) = buildOutgoingUniPacket(client) { writeJceRequestPacket( servantName = "mqq.IMService.FriendListServiceServantObj", funcName = "MovGroupMemReq", serializer = MovGroupMemReq.serializer(), body = MovGroupMemReq( client.uin, 0, byteArrayOf( 0x01, 0x00, (id.toByteArray2().size + 1).toByte() ) + id.toByteArray2() + byteArrayOf(groupId.toByte(), 0x00, 0x00) ) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/list/ProfileService.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.list import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.jce.GroupMngReqJce import net.mamoe.mirai.internal.network.protocol.data.jce.GroupMngRes import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.jceRequestSBuffer import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket import net.mamoe.mirai.internal.utils.io.serialization.writeJceStruct import net.mamoe.mirai.utils.toByteArray internal class ProfileService { object GroupMngReq : OutgoingPacketFactory<GroupMngReq.GroupMngReqResponse>("ProfileService.GroupMngReq") { data class GroupMngReqResponse(val errorCode: Int, val errorMessage: String) : Packet override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): GroupMngReqResponse { val resp = readUniPacket(GroupMngRes.serializer()) return GroupMngReqResponse(resp.errorCode.toInt(), resp.errorString) } operator fun invoke( client: QQAndroidClient, groupCode: Long ) = buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( servantName = "KQQ.ProfileService.ProfileServantObj", funcName = "GroupMngReq", requestId = client.nextRequestPacketRequestId(), sBuffer = jceRequestSBuffer( "GroupMngReq", GroupMngReqJce.serializer(), GroupMngReqJce( reqtype = 2, uin = client.uin, vecBody = client.uin.shl(32).or(groupCode).toByteArray() ) ) ) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/list/StrangerList.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.list import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.StrangerRelationChangeEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x5d2 import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x5d4 import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf internal class StrangerList { object GetStrangerList : OutgoingPacketFactory<GetStrangerList.Response>("OidbSvc.0x5d2_0") { class Response( val result: Int, val strangerList: List<Oidb0x5d2.FriendEntry>, val origin: Oidb0x5d2.RspGetList? ) : Packet { override fun toString(): String { return "StrangerList.GetStrangerList.Response(result=$result)" } } operator fun invoke( client: QQAndroidClient, ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 1490, serviceType = 0, bodybuffer = Oidb0x5d2.ReqBody( subCmd = 1, reqGetList = Oidb0x5d2.ReqGetList( seq = client.strangerSeq ) ).toByteArray(Oidb0x5d2.ReqBody.serializer()) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> if (pkg.result == 0) { pkg.bodybuffer.loadAs(Oidb0x5d2.RspBody.serializer()).rspGetList!!.let { bot.client.strangerSeq = it.seq return Response(pkg.result, it.list, it) } } return Response(pkg.result, emptyList(), null) } } } object DelStranger : OutgoingPacketFactory<DelStranger.Response>("OidbSvc.0x5d4_0") { class Response(val result: Int) : Packet { val isSuccess = result == 0 override fun toString(): String = "DelStranger.Response(success=${result == 0})" } operator fun invoke( client: QQAndroidClient, stranger: Stranger ) = buildOutgoingUniPacket(client) { writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = 1492, serviceType = 0, result = 0, bodybuffer = Oidb0x5d4.ReqBody( uinList = listOf(stranger.id) ).toByteArray(Oidb0x5d4.ReqBody.serializer()) ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { readProtoBuf(OidbSso.OIDBSSOPkg.serializer()).let { pkg -> if (pkg.result == 0) { pkg.bodybuffer.loadAs(Oidb0x5d4.RspBody.serializer()).result.forEach { delResult -> bot.getStranger(delResult.uin)?.let { bot.strangers.remove(delResult.uin) StrangerRelationChangeEvent.Deleted(it).broadcast() } } } return Response(pkg.result) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/ConfigPushSvc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login import io.ktor.utils.io.core.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.networkType import net.mamoe.mirai.internal.network.protocol.data.jce.FileStoragePushFSSvcList import net.mamoe.mirai.internal.network.protocol.data.jce.PushResp import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.internal.network.protocol.data.jce.ServerListPush import net.mamoe.mirai.internal.network.protocol.data.proto.Subcmd0x501 import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.buildResponseUniPacket import net.mamoe.mirai.internal.utils.NetworkType import net.mamoe.mirai.internal.utils.io.serialization.jceRequestSBuffer import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket import net.mamoe.mirai.internal.utils.io.serialization.writeJceStruct import net.mamoe.mirai.utils.info import net.mamoe.mirai.utils.toUHexString import net.mamoe.mirai.internal.network.protocol.data.jce.PushReq as PushReqJceStruct internal class ConfigPushSvc { object PushReq : IncomingPacketFactory<PushReq.PushReqResponse>( receivingCommandName = "ConfigPushSvc.PushReq", responseCommandName = "ConfigPushSvc.PushResp" ) { override val canBeCached: Boolean get() = false sealed class PushReqResponse( val struct: PushReqJceStruct, val bot: QQAndroidBot, ) : Packet, Event, AbstractEvent(), Packet.NoEventLog { class Unknown(struct: PushReqJceStruct, bot: QQAndroidBot) : PushReqResponse(struct, bot) { override fun toString(): String { return "ConfigPushSvc.PushReq.PushReqResponse.Unknown" } } class LogAction(struct: PushReqJceStruct, bot: QQAndroidBot) : PushReqResponse(struct, bot) { override fun toString(): String { return "ConfigPushSvc.PushReq.PushReqResponse.LogAction" } } class ServerListPush(struct: PushReqJceStruct, bot: QQAndroidBot) : PushReqResponse(struct, bot) { override fun toString(): String { return "ConfigPushSvc.PushReq.PushReqResponse.ServerListPush" } } class ConfigPush(struct: PushReqJceStruct, bot: QQAndroidBot) : PushReqResponse(struct, bot) { override fun toString(): String { return "ConfigPushSvc.PushReq.PushReqResponse.ConfigPush" } } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): PushReqResponse { val pushReq = readUniPacket(PushReqJceStruct.serializer(), "PushReq") return when (pushReq.type) { 1 -> PushReqResponse.ServerListPush(pushReq, bot) 2 -> PushReqResponse.ConfigPush(pushReq, bot) 3 -> PushReqResponse.LogAction(pushReq, bot) else -> PushReqResponse.Unknown(pushReq, bot) } } override suspend fun QQAndroidBot.handle(packet: PushReqResponse, sequenceId: Int): OutgoingPacket? { val bdhSyncer = bot.components[BdhSessionSyncer] fun handleConfigPush(packet: PushReqResponse.ConfigPush) { val pushReq = packet.struct // FS server val fileStoragePushFSSvcList = pushReq.jcebuf.loadAs(FileStoragePushFSSvcList.serializer()) bot.client.fileStoragePushFSSvcList = fileStoragePushFSSvcList val bigDataChannel = fileStoragePushFSSvcList.bigDataChannel if (bigDataChannel?.vBigdataPbBuf == null) { bdhSyncer.bdhSession.completeExceptionally(IllegalStateException("BdhSession not received.")) return } kotlin.runCatching { val resp = bigDataChannel.vBigdataPbBuf.loadAs(Subcmd0x501.RspBody.serializer()).msgSubcmd0x501RspBody ?: error("msgSubcmd0x501RspBody not found") val session = BdhSession( sigSession = resp.httpconnSigSession, sessionKey = resp.sessionKey ) for ((type, addresses) in resp.msgHttpconnAddrs) { when (type) { 10 -> session.ssoAddresses.addAll(addresses.map { it.decode() }) 21 -> session.otherAddresses.addAll(addresses.map { it.decode() }) } } session }.fold( onSuccess = { bdhSyncer.overrideSession(it) }, onFailure = { cause -> val e = IllegalStateException("Failed to decode BdhSession", cause) bdhSyncer.bdhSession.completeExceptionally(e) logger.error(e) } ) } fun handleServerListPush(resp: PushReqResponse.ServerListPush) { val serverListPush = kotlin.runCatching { resp.struct.jcebuf.loadAs(ServerListPush.serializer()) }.getOrElse { throw contextualBugReportException( "ConfigPush.ReqPush type=1", forDebug = resp.struct.jcebuf.toUHexString(), ) } val pushServerList = if (client.networkType == NetworkType.WIFI) { serverListPush.wifiSSOServerList } else { serverListPush.mobileSSOServerList } if (pushServerList.isNotEmpty()) { bot.components[ServerList].setPreferred( pushServerList.shuffled().map { ServerAddress(it.host, it.port) }) } bdhSyncer.saveToCache() bdhSyncer.saveServerListToCache() if (serverListPush.reconnectNeeded == 1) { bot.logger.info { "Server request to change server." } bot.components[EventDispatcher].broadcastAsync( BotOfflineEvent.RequireReconnect(bot, IllegalStateException("Server request to change server.")) ) } } when (packet) { is PushReqResponse.ConfigPush -> { handleConfigPush(packet) } is PushReqResponse.ServerListPush -> { handleServerListPush(packet) } is PushReqResponse.LogAction, is PushReqResponse.Unknown -> { //ignore } } //Always send resp if (!client.wLoginSigInfoInitialized) return null // concurrently doing reconnection return buildResponseUniPacket( client, sequenceId = sequenceId, key = client.wLoginSigInfo.d2Key ) { writeJceStruct( RequestPacket.serializer(), RequestPacket( requestId = client.nextRequestPacketRequestId(), version = 3, servantName = "QQService.ConfigPushSvc.MainServant", funcName = "PushResp", sBuffer = jceRequestSBuffer( "PushResp", PushResp.serializer(), PushResp( type = packet.struct.type, seq = packet.struct.seq, jcebuf = if (packet.struct.type == 3) packet.struct.jcebuf else null ) ) ) ) // writePacket(this.build().debugPrintThis()) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/Heartbeat.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.PacketEncryptType import net.mamoe.mirai.internal.network.protocol.packet.buildLoginOutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.writeSsoPacket import net.mamoe.mirai.internal.network.subAppId internal class Heartbeat { object Alive : OutgoingPacketFactory<Alive.Response>("Heartbeat.Alive") { object Response : Packet { override fun toString(): String = "Heartbeat.Alive.Response" } operator fun invoke( client: QQAndroidClient ) = buildLoginOutgoingPacket(client, encryptMethod = PacketEncryptType.NoEncrypt) { writeSsoPacket(client, client.subAppId, commandName, sequenceId = it) { } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { return Response } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/StatSvc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login import io.ktor.utils.io.core.* import kotlinx.coroutines.CancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.sync.withLock import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.ClientKind import net.mamoe.mirai.contact.OtherClientInfo import net.mamoe.mirai.contact.Platform import net.mamoe.mirai.data.OnlineStatus import net.mamoe.mirai.event.events.OtherClientOfflineEvent import net.mamoe.mirai.event.events.OtherClientOnlineEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.appId import net.mamoe.mirai.internal.contact.createOtherClient import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.internal.network.components.ContactCacheService import net.mamoe.mirai.internal.network.components.ContactUpdater import net.mamoe.mirai.internal.network.components.ServerList import net.mamoe.mirai.internal.network.getRandomByteArray import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.impl.HeartbeatFailedException import net.mamoe.mirai.internal.network.protocol.data.jce.* import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x769 import net.mamoe.mirai.internal.network.protocol.data.proto.StatSvcGetOnline import net.mamoe.mirai.internal.network.protocol.data.proto.StatSvcSimpleGet import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.utils.NetworkType import net.mamoe.mirai.internal.utils.io.serialization.* import net.mamoe.mirai.utils.* @Suppress("EnumEntryName", "unused") internal enum class RegPushReason { appRegister, createDefaultRegInfo, fillRegProxy, msfBoot, msfByNetChange, msfHeartTimeTooLong, serverPush, setOnlineStatus, unknown } internal class StatSvc { internal object GetOnlineStatus : OutgoingPacketFactory<GetOnlineStatus.Response>("StatSvc.GetOnlineStatus") { internal sealed class Response : Packet { override fun toString(): String = "StatSvc.GetOnlineStatus.Response" object Success : Response() { override fun toString(): String { return "StatSvc.GetOnlineStatus.Response.Success" } } class Failed(val errno: Int, val message: String) : Response() { override fun toString(): String { return "StatSvc.GetOnlineStatus.Response.Failed(errno=$errno, message=$message)" } } } operator fun invoke( client: QQAndroidClient ) = buildLoginOutgoingPacket(client, PacketEncryptType.D2) { writeProtoBuf( StatSvcGetOnline.ReqBody.serializer(), StatSvcGetOnline.ReqBody( uin = client.uin, appid = 0 ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readProtoBuf(StatSvcGetOnline.RspBody.serializer()) return if (resp.errorCode != 0) { Response.Failed(resp.errorCode, resp.errorMsg) } else { Response.Success } } } internal object SimpleGet : OutgoingPacketFactory<SimpleGet.Response>("StatSvc.SimpleGet") { internal sealed interface Response : Packet { object Success : Response { override fun toString(): String = "SimpleGet.Response.Success" } class Error(val code: Int, val msg: String) : Response { override fun toString(): String = "SimpleGet.Response.Error(code=$code,msg=$msg)" } } operator fun invoke( client: QQAndroidClient ) = buildLoginOutgoingPacket( client, encryptMethod = PacketEncryptType.D2 ) { writeSsoPacket(client, client.subAppId, commandName, sequenceId = it) { } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { readProtoBuf(StatSvcSimpleGet.RspBody.serializer()).let { return if (it.errorCode == 0) { Response.Success } else { Response.Error(it.errorCode, it.errmsg) } } } override suspend fun QQAndroidBot.handle(packet: Response) { if (packet is Response.Error) { network.close(HeartbeatFailedException("StatSvc.SimpleGet", IllegalStateException(packet.toString()))) } } } internal object Register : OutgoingPacketFactory<Register.Response>("StatSvc.register") { internal class Response( val origin: SvcRespRegister ) : Packet { override fun toString(): String = "Response(StatSvc.register)" } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val packet = readUniPacket(SvcRespRegister.serializer()) return Response(packet) } override suspend fun QQAndroidBot.handle(packet: Response) { packet.origin.iHelloInterval.let { bot.configuration.statHeartbeatPeriodMillis = it.times(1000).toLong() } val serverTime = packet.origin.serverTime val diffMillis = if (serverTime == 0L) 0 else serverTime - currentTimeSeconds() bot.components[ClockHolder].server = Clock.SystemDefault.adjusted(diffMillis) bot.network.logger.info { "Server time updated, serverTime: $serverTime, diff: ${diffMillis}ms=${diffMillis.millisToHumanReadableString()}" } } fun online( client: QQAndroidClient, regPushReason: RegPushReason = RegPushReason.appRegister ) = impl("online", client, 1L or 2 or 4, client.onlineStatus, regPushReason) { if (client.bot.configuration.protocol == BotConfiguration.MiraiProtocol.ANDROID_PHONE) { client.bot.components[ServerList].run { kotlin.runCatching { uOldSSOIp = lastDisconnectedIP.toIpV4Long() uNewSSOIp = lastConnectedIP.toIpV4Long() }.onFailure { err -> client.bot.network.logger.warning({ "Exception when converting ipaddress to long: ld=${lastDisconnectedIP}, lc=${lastConnectedIP}" }, err) uNewSSOIp = 0 uOldSSOIp = 0 } } } else { uOldSSOIp = 0 uNewSSOIp = 0 } client.bot.components[ContactCacheService].friendListCache?.let { friendListCache -> iLargeSeq = friendListCache.friendListSeq } // timeStamp = friendListCache.timeStamp strVendorName = "MIUI" strVendorOSName = "?ONEPLUS A5000_23_17" } fun offline( client: QQAndroidClient, regPushReason: RegPushReason = RegPushReason.appRegister ) = impl("offline", client, 1L or 2 or 4, OnlineStatus.OFFLINE, regPushReason) private fun impl( name: String, client: QQAndroidClient, bid: Long, status: OnlineStatus, regPushReason: RegPushReason = RegPushReason.appRegister, applyAction: SvcReqRegister.() -> Unit = {} ) = buildLoginOutgoingPacket( client, encryptMethod = PacketEncryptType.D2, extraData = client.wLoginSigInfo.d2.data, key = client.wLoginSigInfo.d2Key, remark = name, ) { sequenceId -> writeSsoPacket( client, subAppId = client.subAppId, commandName = commandName, extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId ) { writeJceStruct( RequestPacket.serializer(), RequestPacket( servantName = "PushService", funcName = "SvcReqRegister", requestId = 0, sBuffer = jceRequestSBuffer( "SvcReqRegister", SvcReqRegister.serializer(), SvcReqRegister( cConnType = 0, lBid = bid, lUin = client.uin, iStatus = status.id, bKikPC = 0, // 是否把 PC 踢下线 bKikWeak = 0, timeStamp = 0, // timeStamp = currentTimeSeconds // millis or seconds?? iLargeSeq = 0, // ? bOpenPush = 1, iLocaleID = 2052, bRegType = (if (regPushReason == RegPushReason.appRegister || regPushReason == RegPushReason.fillRegProxy || regPushReason == RegPushReason.createDefaultRegInfo || regPushReason == RegPushReason.setOnlineStatus ) 0 else 1).toByte(), bIsSetStatus = if (regPushReason == RegPushReason.setOnlineStatus) 1 else 0, iOSVersion = client.device.version.sdk.toLong(), cNetType = if (client.networkType == NetworkType.WIFI) 1 else 0, vecGuid = client.device.guid, strDevName = client.device.model.decodeToString(), strDevType = client.device.model.decodeToString(), strOSVer = client.device.version.release.decodeToString(), // register 时还需要 /* var44.uNewSSOIp = field_127445; var44.uOldSSOIp = field_127444; var44.strVendorName = ROMUtil.getRomName(); var44.strVendorOSName = ROMUtil.getRomVersion(20); */ bytes_0x769_reqbody = ProtoBuf.encodeToByteArray( Oidb0x769.ReqBody.serializer(), Oidb0x769.ReqBody( configList = listOf( Oidb0x769.ConfigSeq( type = 46, version = 1610538309 ), Oidb0x769.ConfigSeq( type = 283, version = 0 ) ) ) ), bSetMute = 0 ).apply(applyAction) ) ) ) } } } internal object ReqMSFOffline : IncomingPacketFactory<ReqMSFOffline.MsfOfflinePacket>("StatSvc.ReqMSFOffline", "StatSvc.RspMSFForceOffline") { internal class MsfOfflinePacket( val token: MsfOfflineToken, ) : Packet { override fun toString(): String = "StatSvc.ReqMSFOffline" } internal data class MsfOfflineToken( val uin: Long, val seq: Long, val const: Byte ) : Packet, NetworkException("dropped by StatSvc.ReqMSFOffline", true) override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): MsfOfflinePacket { val decodeUniPacket = readUniPacket(RequestMSFForceOffline.serializer()) @Suppress("INVISIBLE_MEMBER") return MsfOfflinePacket(MsfOfflineToken(decodeUniPacket.uin, decodeUniPacket.iSeqno, 0)) } override suspend fun QQAndroidBot.handle(packet: MsfOfflinePacket, sequenceId: Int): OutgoingPacket? { val cause = packet.token val resp = buildResponseUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( servantName = "StatSvc", funcName = "RspMSFForceOffline", requestId = 0, sBuffer = jceRequestSBuffer( "RspMSFForceOffline", RspMSFForceOffline.serializer(), RspMSFForceOffline( cause.uin, cause.seq, cause.const ) ) ) ) } kotlin.runCatching { bot.network.sendWithoutExpect(resp) } bot.network.close(cause) return null } } internal object SvcReqMSFLoginNotify : IncomingPacketFactory<Packet?>("StatSvc.SvcReqMSFLoginNotify", "StatSvc.SvcReqMSFLoginNotify") { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot, sequenceId: Int): Packet? = bot.components[ContactUpdater].otherClientsLock.withLock { val notify = readUniPacket(SvcReqMSFLoginNotifyData.serializer()) val appId = notify.iAppId.toInt() when (notify.status.toInt()) { 1 -> { // online if (bot.otherClients.any { it.appId == appId }) return null val info = Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } ?: kotlin.run { delay(2000) // sometimes server sync slow Mirai.getOnlineOtherClientsList(bot).find { it.appId == appId } } ?: kotlin.run { // 你的帐号在平板电脑上登录了 val kind = notify.info?.substringAfter("在")?.substringBefore("上").orEmpty() OtherClientInfo( appId, Platform.MOBILE, deviceName = kind, deviceKind = kind ) } val client = bot.createOtherClient(info) bot.otherClients.delegate.add(client) OtherClientOnlineEvent( client, ClientKind[notify.iClientType?.toInt() ?: 0] ) } 2 -> { // off val client = bot.otherClients.find { it.appId == appId } ?: return null client.cancel(CancellationException("Offline")) bot.otherClients.delegate.remove(client) OtherClientOfflineEvent(client) } else -> throw contextualBugReportException( "decode SvcReqMSFLoginNotify (OtherClient status change)", notify.structureToString(), additional = "unknown notify.status=${notify.status}" ) } } } internal object GetDevLoginInfo : OutgoingPacketFactory<GetDevLoginInfo.Response>("StatSvc.GetDevLoginInfo") { @Suppress("unused") // false positive data class Response( val deviceList: List<SvcDevLoginInfo>, ) : Packet { override fun toString(): String { return "StatSvc.GetDevLoginInfo.Response(deviceList.size=${deviceList.size})" } } operator fun invoke( client: QQAndroidClient, ) = buildOutgoingUniPacket(client) { writeJceRequestPacket( servantName = "StatSvc", funcName = "SvcReqGetDevLoginInfo", serializer = SvcReqGetDevLoginInfo.serializer(), body = SvcReqGetDevLoginInfo( iLoginType = 2, iRequireMax = 20, iTimeStamp = currentTimeMillis(), iGetDevListType = 1, vecGuid = getRandomByteArray(16), // 服务器防止频繁查询 iNextItemIndex = 0, appName = client.protocol.apkId //"com.tencent.mobileqq" ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val resp = readUniPacket(SvcRspGetDevLoginInfo.serializer()) // result 62 maybe too frequent return Response( resp.vecCurrentLoginDevInfo?.takeIf { it.isNotEmpty() } ?: resp.vecAuthLoginDevInfo?.takeIf { it.isNotEmpty() } ?: resp.vecHistoryLoginDevInfo.orEmpty() ) } } } internal fun String.toIpV4Long(): Long { if (isEmpty()) return 0 val split = split('.') if (split.size != 4) return 0 return split.mapToByteArray { it.toUByte().toByte() }.toInt().toLongUnsigned() } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/WtLogin.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login import io.ktor.utils.io.core.* import net.mamoe.mirai.Bot import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.DebuggingProperties.SHOW_TLV_MAP_ON_LOGIN_SUCCESS import net.mamoe.mirai.internal.network.components.KeyRefreshProcessor import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLogin8 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.WtLoginExt import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.analysisTlv0x531 import net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin.orEmpty import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray import net.mamoe.mirai.internal.utils.io.writeShortLVPacket import net.mamoe.mirai.internal.utils.printStructure import net.mamoe.mirai.network.InconsistentBotIdException import net.mamoe.mirai.network.RetryLaterException import net.mamoe.mirai.network.WrongPasswordException import net.mamoe.mirai.utils.* internal class SmsVerifyInfo( override val countryCode: String?, // 86 override val phoneNumber: String?, // 123*******1 private val token: ByteArray, // t174 private val bot: AbstractBot, ) : DeviceVerificationRequests.SmsRequest { override suspend fun requestSms() { val result = try { bot.network.sendAndExpect(WtLogin8(bot.client, token)) } catch (e: Exception) { bot.logger.warning("Exception while requesting SMS.", e) throw e } if (result !is WtLogin.Login.LoginPacketResponse.SmsRequestSuccess) { bot.logger.warning("Failed requestSms, result = $result") if (result is WtLogin.Login.LoginPacketResponse.Error) { when (result.code) { 161, 162 -> { // 今日操作次数过多,请等待一天后再试。 throw RetryLaterException( result.message, null, killBot = true ) } } } throw WrongPasswordException("Failed requestSms, result = $result") } } override fun solved(code: String): DeviceVerificationResult { return SmsDeviceVerificationResult(code, token) } override fun toString(): String { return "SmsVerifyInfo(+$countryCode $phoneNumber)" } } internal class FallbackRequestImpl(override val url: String) : DeviceVerificationRequests.FallbackRequest { override fun solved(): DeviceVerificationResult { return UrlDeviceVerificationResult } override fun toString(): String { return "FallbackRequestImpl($url)" } } internal sealed interface DeviceVerificationResultImpl : DeviceVerificationResult internal object UrlDeviceVerificationResult : DeviceVerificationResultImpl { override fun toString(): String { return "UrlVerificationResult" } } internal class SmsDeviceVerificationResult( val code: String, val token: ByteArray, // t174 ) : DeviceVerificationResultImpl { override fun toString(): String { return "SmsVerificationResult(code=$code, token=${token.toUHexString("")})" } } internal class WtLogin { /** * OicqRequest */ internal object Login : OutgoingPacketFactory<Login.LoginPacketResponse>("wtlogin.login"), WtLoginExt { /** * Check SMS Login */ object SubCommand17 { operator fun invoke( client: QQAndroidClient ) = buildLoginOutgoingPacket(client, encryptMethod = PacketEncryptType.Empty) { sequenceId -> writeSsoPacket( client, client.subAppId, commandName, sequenceId = sequenceId, unknownHex = "01 00 00 00 00 00 00 00 00 00 01 00" ) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(17) // subCommand _writeTlvMap { t100(16, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap) t108(client.ksid) t109(client.device.androidId) t8(2052) t142(client.apkId) t145(client.device.guid) t154(0) // 需要 t112, 但在实现 QR 时删除了 phoneNumber //t112(client.account.phoneNumber.encodeToByteArray()) t116(client.miscBitMap, client.subSigMap) t521() t52c() t52d(client.device.generateDeviceInfoData()) } } } } } sealed class LoginPacketResponse : Packet, BotEvent, Packet.NoEventLog, AbstractEvent() { class Success(override val bot: Bot) : LoginPacketResponse() { override fun toString(): String = "LoginPacketResponse.Success" } class SmsRequestSuccess(override val bot: Bot) : LoginPacketResponse() { override fun toString(): String = "LoginPacketResponse.SmsRequestSuccess" } data class Error( override val bot: Bot, val code: Int, val title: String, val message: String, val errorInfo: String, ) : LoginPacketResponse() sealed class Captcha : LoginPacketResponse() { class Slider( override val bot: Bot, val url: String, ) : Captcha() { override fun toString(): String = "LoginPacketResponse.Captcha.Slider" } class Picture( override val bot: Bot, val data: ByteArray, val sign: ByteArray, ) : Captcha() { override fun toString(): String = "LoginPacketResponse.Captcha.Picture" } } class VerificationNeeded( override val bot: Bot, val message: String?, val requests: DeviceVerificationRequests, ) : LoginPacketResponse() { override fun toString(): String = "LoginPacketResponse.VerificationNeeded(requests=$requests)" } class DeviceLockLogin( override val bot: Bot, ) : LoginPacketResponse() { override fun toString(): String = "LoginPacketResponse.DeviceLockLogin" } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): LoginPacketResponse { val subCommand = readShort().toUShort() // subCommand // println("subCommand=$subCommand") val type = readByte().toUByte() // println("type=$type") discardExact(2) val tlvMap: TlvMap = this._readTLVMap() if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) { tlvMap.smartToString().printStructure("tlvMap outer") } // tlvMap.printTLVMap() tlvMap[0x161]?.let { bot.client.analysisTlv161(it) } tlvMap[0x403]?.let { bot.client.randSeed = it } tlvMap[0x402]?.let { bot.client.t402 = it } tlvMap[0x546]?.let { bot.client.analysisTlv546(it) } tlvMap[0x543]?.let { bot.client.t543 = it } // tlvMap[0x402]?.let { t402 -> // bot.client.G = buildPacket { // writeFully(bot.client.device.guid) // writeFully(bot.client.dpwd) // writeFully(tlvMap[0x402] ?: EMPTY_BYTE_ARRAY) // }.readBytes().md5() // } tlvMap[0x402]?.let { t402 -> bot.client.dpwd = getRandomByteArray(16) bot.client.t402 = t402 bot.run { // client.dpwd = getRandomString(16).toByteArray() client.G = (client.device.guid + client.dpwd + t402).md5() } } return when (type.toInt()) { 0 -> onLoginSuccess(subCommand.toInt(), tlvMap, bot) 2 -> onSolveLoginCaptcha(tlvMap, bot) 160, 239 /*-96*/ -> onVerificationNeeded(subCommand.toShort(), tlvMap, bot) // 40: blocked // 161: 今日操作次数过多,请等待一天后再试。 (SMS) // 162: 可能也是 SMS 太频繁 // 180:可能是需要换服务器或者 refresh key 180 -> onRequestRefreshKeys(tlvMap, bot) 204 /*-52*/ -> onDevLockLogin(tlvMap, bot) // 1, 15 -> onErrorMessage(tlvMap) ?: error("Cannot find error message") else -> { onErrorMessage(type.toInt(), tlvMap, bot) ?: error( "Cannot find error message, unknown login result type: $type, TLVMap = ${dumpTlvMap(tlvMap)}" ) } } } private fun dumpTlvMap(tlvMap: TlvMap): String { return tlvMap.entries.joinToString { "${it.key}=${it.value.toUHexString()}" } } // request refresh keys or change server ip? private suspend fun onRequestRefreshKeys( tlvMap: TlvMap, bot: QQAndroidBot ): LoginPacketResponse { val errorMessage = onErrorMessage(0x146, tlvMap, bot) if (errorMessage != null) { return errorMessage } // analysis tlv t161 val t161 = (tlvMap[0x161] ?: return LoginPacketResponse.Error(bot, 0x146, "login failed", "login result type 180 without and t161 error message", "")) .toReadPacket() .apply { discardExact(2) } .withUse { _readTLVMap() } .also { tm -> tm[0x173]?.let { bot.client.analysisTlv173(it) } tm[0x17f]?.let { bot.client.analysisTlv17f(it) } } val t172 = t161[0x172] if (t172 != null) { if (t172.contentEquals(bot.client.rollbackSig)) { return LoginPacketResponse.Error(bot, 0x146, "login failed", "login result type 180 with same t172 as the client's", "") } runCatching { bot.client.rollbackSig = t172 bot.components[KeyRefreshProcessor].refreshKeysNow(bot.network) }.fold( onSuccess = { return LoginPacketResponse.Success(bot) }, onFailure = { return LoginPacketResponse.Error(bot, 0x146, "login failed", "failed to refresh keys on receiving login result type 180", "") } ) } else { return LoginPacketResponse.Error(bot, 0x146, "login failed", "login result type 180 without t172", "") } } private fun onDevLockLogin( tlvMap: TlvMap, bot: QQAndroidBot ): LoginPacketResponse.DeviceLockLogin { bot.client.t104 = tlvMap.getOrFail(0x104) // println("403: " + tlvMap[0x403]?.toUHexString()) return LoginPacketResponse.DeviceLockLogin(bot) } private fun onVerificationNeeded(subCommand: Short, tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse { val t174 = tlvMap[0x174] val t17b = tlvMap[0x17b] val message = tlvMap[0x17e]?.decodeToString() t174?.let { bot.client.t174 = it } if (t174 != null || t17b != null) { tlvMap[0x104]?.let { // verify token bot.client.t104 = it } } tlvMap[0x403]?.let { bot.client.randSeed = it } if (subCommand == WtLogin8.subCommand) { // response of submit sms // tlvMap 只有 0x17b 和 0x174 return LoginPacketResponse.SmsRequestSuccess(bot) } var countryCode: String? = null var phoneNumber: String? = null tlvMap[0x178]?.read { // phone number countryCode = readUShortLVString() phoneNumber = readUShortLVString() } val url = tlvMap[0x204]?.decodeToString() check(url != null || t174 != null) { "Verification is needed but no method available." } return LoginPacketResponse.VerificationNeeded( bot, message = message, requests = object : DeviceVerificationRequests { override val sms: DeviceVerificationRequests.SmsRequest? = t174?.let { SmsVerifyInfo(countryCode, phoneNumber, t174, bot) } override val fallback: DeviceVerificationRequests.FallbackRequest? = url?.let { FallbackRequestImpl(it) } override val preferSms: Boolean = tlvMap[0x17b] != null override fun toString(): String { return "DeviceVerificationRequests(sms=$sms, preferSms=$preferSms, fallback=$fallback)" } }, ) } private fun onSolveLoginCaptcha(tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Captcha { /* java.lang.IllegalStateException: UNKNOWN CAPTCHA QUESTION: 00 00 00 01 0A 70 69 63 5F 72 65 61 73 6F 6E 00 00 00 7F 51 51 E5 AE 89 E5 85 A8 E4 B8 AD E5 BF 83 E6 B8 A9 E9 A6 A8 E6 8F 90 E7 A4 BA EF BC 9A E5 BD 93 E5 89 8D E7 BD 91 E7 BB 9C E7 8E AF E5 A2 83 E6 9C 89 E5 8D B1 E5 8F 8A 51 51 E5 AE 89 E5 85 A8 E7 9A 84 E8 A1 8C E4 B8 BA EF BC 8C E4 B8 BA E4 BA 86 E4 BD A0 E7 9A 84 E5 B8 90 E5 8F B7 E5 AE 89 E5 85 A8 EF BC 8C E8 AF B7 E4 BD A0 E5 A1 AB E5 86 99 E9 AA 8C E8 AF 81 E7 A0 81 E3 80 82, tlvMap={ 0x00000104(260)=41 74 78 43 52 56 6A 46 61 79 33 76 56 5A 66 56 47 50 4B 63 4F 59 54 6D 76 32 4A 67 35 35 76 6A 51 67 3D 3D, 0x00000105(261)=00 04 41 7A 55 47 08 88 FF D8 FF E0 00 10 4A 46 49 46 00 01 01 00 00 01 00 01 00 00 FF FE 00 22 39 35 30 35 34 31 36 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 88 5C DF 07 FF 7F 00 00 FF DB 00 43 00 0A 07 07 08 07 06 0A 08 08 08 0B 0A 0A 0B 0E 18 10 0E 0D 0D 0E 1D 15 16 11 18 23 1F 25 24 22 1F 22 21 26 2B 37 2F 26 29 34 29 21 22 30 41 31 34 39 3B 3E 3E 3E 25 2E 44 49 43 3C 48 37 3D 3E 3B FF DB 00 43 01 0A 0B 0B 0E 0D 0E 1C 10 10 1C 3B 28 22 28 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B 3B FF C0 00 11 08 00 35 00 82 03 01 22 00 02 11 01 03 11 01 FF C4 00 1F 00 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B FF C4 00 B5 10 00 02 01 03 03 02 04 03 05 05 04 04 00 00 01 7D 01 02 03 00 04 11 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 A1 08 23 42 B1 C1 15 52 D1 F0 24 33 62 72 82 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FF C4 00 1F 01 00 03 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B FF C4 00 B5 11 00 02 01 02 04 04 03 04 07 05 04 04 00 01 02 77 00 01 02 03 11 04 05 21 31 06 12 41 51 07 61 71 13 22 32 81 08 14 42 91 A1 B1 C1 09 23 33 52 F0 15 62 72 D1 0A 16 24 34 E1 25 F1 17 18 19 1A 26 27 28 29 2A 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 82 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 F4 F5 F6 F7 F8 F9 FA FF DA 00 0C 03 01 00 02 11 03 11 00 3F 00 F5 EA 42 40 EA 40 AC CF 10 E9 D7 BA A6 96 6D AC 6F 05 AC 85 C3 12 77 00 EA 3F 87 2A 41 19 E3 91 5C BE 9D A7 E8 D2 5C B6 99 AB E9 CF 6B AA 85 3B 16 7B A9 5E 29 FD D1 8B 72 3D BA D7 4D 2A 31 9C 1C 9B F9 25 7F 9E E8 CA 53 69 DA C7 72 F2 C7 1A EE 92 45 45 F5 66 00 54 0B A9 D8 3C CB 0A DF 5B 34 8C 70 A8 25 5D C7 F0 CD 72 36 3A 22 18 2D 3F B5 EC 64 B4 8B 4A DC B2 24 8C B3 41 3E EE EA 0E 4F 24 8F A7 4A DE B6 F0 FD 9B 5D 45 76 F6 16 D6 C2 16 DF 0C 11 42 80 A9 EC 58 81 C9 F6 1C 0F 7A 73 A3 4A 1B CA FF 00 D7 A8 94 A4 FA 0F 6D 7C 16 B9 36 FA 75 D5 CC 56 AE D1 CB 32 49 0A AA B2 FD E0 77 C8 A4 63 DC 0E 39 1C 10 6A 7B 9D 5E 3B 5D 32 1B E9 2D AE 07 9D B0 2C 25 42 B8 66 E8 AD B8 85 53 DB 92 06 78 EA 40 34 0D F5 AE 16 39 60 86 6B 7D 55 F3 70 F1 96 C0 49 41 48 49 04 74 65 40 87 91 83 8F 5A 9B 4F 7B B8 ED 9A C6 ED 66 91 AC 64 09 E7 1D DF E9 0A 30 63 3B 8E 32 C7 20 37 6C AB 67 82 28 74 E2 B5 E5 FE BF E1 FD 37 40 A4 FB 96 ED 35 19 6E 2E 8D BC BA 65 DD A1 D8 5F 74 CD 11 5E A0 63 E4 76 20 F3 DF D0 D3 2F 7C 41 A4 69 C4 AD D6 A1 02 3A F5 8C 36 E7 FF 00 BE 46 4F E9 58 D7 A8 FA D4 36 33 C5 21 B3 1A D5 98 86 42 06 FD AE BF BD 55 23 8E 02 F9 EA 7A 75 FA 54 96 96 EB 1F 85 2F E2 B4 B4 B7 86 F6 DC 4B 14 AB 6B 17 97 BD D7 38 C6 39 F9 97 07 FE 05 55 EC 61 A3 97 A5 97 DD 7B BB F5 0E 77 B2 34 35 8D 7A 0D 2F 4D 82 F5 7C B9 56 E5 D5 62 67 93 CB 8F 90 58 16 62 0E 06 07 A1 ED 55 2E 24 F1 16 EB 77 7B DB 0B 61 2C EA B1 C3 0C 46 52 E3 A9 05 D8 8F E1 0C 78 03 A5 3B FE 12 7D 1A EE D0 43 69 1C D7 EB 24 7B 7C 8B 7B 76 7F 94 8E 87 8C 0F C4 D1 73 6D 35 A7 83 E0 DE 18 CD A7 C5 14 D8 E0 B7 EE C8 62 BC 77 21 4A FE 34 E3 1E 4B 27 1B 36 FA FF 00 97 90 9B BE CC DF A2 A8 DF 6B 5A 76 98 B1 35 E5 D2 45 E7 0C C6 30 58 B8 F6 03 93 D4 55 55 F1 56 8E CB 21 FB 44 8A 23 88 CA 7C CB 79 13 2A 08 1C 6E 51 9E 58 0C 0F 51 5C CA 8D 49 2B A8 BF B8 D1 CA 2B 76 6C 51 58 47 C5 0A 0A 96 D1 75 65 8C BA AF 98 D6 E1 46 58 80 38 2D 9E A4 76 A8 A4 F1 45 CB FD AD EC 34 77 B9 82 D2 46 8E 49 9E E5 23 1B 97 A8 DB CB 7D 38 E7 8A A5 87 A8 FA 7E 28 5E D2 27 45 45 47 0B BC 90 46 F2 46 62 76 50 59 09 CE D3 8E 46 7D AA 4A C4 B0 A2 8A 29 01 15 C5 C4 36 90 34 F7 12 AC 51 20 CB 3B 9C 01 59 1A 91 B3 D4 AD 3E D9 7B 6D 8B 1B 43 E7 87 96 32 1D 8A F3 95 1D 40 FE 7F 4A D2 BF D3 AD 75 3B 71 6F 77 19 92 30 C1 C0 0E CB C8 E8 72 08 35 53 FE 11 8D 14 FD EB 14 93 FE BA 33 3F F3 35 D1 4A 54 E3 AB 6E FF 00 D7 99 12 52 7A 74 32 8D FF 00 F6 CD C1 B1 D4 52 C6 EB 49 BD 83 CE 8E 58 A4 2A 62 00 F0 1B 3D F3 D0 8C 74 3E 95 9D 71 AC DC 78 6A 17 B3 6D 45 75 3D 3D C1 54 9A 2B 84 FB 5D B8 3C 63 04 FC E7 D0 FF 00 FA AB AD 7D 13 4A 94 C6 64 D3 AD 5C C6 81 13 74 4A 76 A8 E8 07 1D 2A 58 B4 FB 18 08 30 D9 C1 1E 3F B9 12 8F E9 5B AC 45 25 A7 2D D7 6F D7 D4 87 09 3E BA 99 D6 B6 F3 EB 16 BA 94 3A 95 9C 90 59 DD 95 F2 12 57 53 22 A1 8D 41 E0 64 2E 18 12 39 CE 49 E0 63 9B 3A 58 BF 96 18 BF B4 E2 D9 35 B6 50 B6 50 89 98 71 E6 80 07 CA 08 E8 38 FB CC 08 E0 13 A3 45 72 CA AD D5 AD FD 6D F8 F5 34 51 B1 CB B4 7A 89 D2 3E CF 16 8D 7B 1D CA DD 35 DC 6E CD 03 2A 39 94 C9 B7 89 41 23 E6 2A 4F 19 04 FA E2 B4 2C 1A 58 35 B9 A3 9D 04 6F 7B 6D 1D C3 22 9D C0 48 BF 24 98 3D F8 F2 C5 6C 56 36 A5 71 24 3A E5 A3 26 9D 77 72 63 85 F6 B4 0A 30 4B 10 36 96 24 28 1F 2E 79 3E 95 B2 A8 EA 5E 36 5A DF FC FA BE E8 97 1E 5D 48 61 D3 20 D6 A6 BB 7B F9 EE 9F CA B8 78 8D A8 9D 92 34 00 E5 78 5C 67 2A 55 B9 CF 5A 97 4A B0 87 4D D4 6F B4 DB 78 F6 D9 B4 71 CC 91 F5 08 5B 72 B0 FA 1D 80 FE 26 A3 B3 D3 EF E5 D7 46 AA F1 36 9F 1C 8B 99 AD C5 CF 99 E7 1D BB 46 E5 03 68 20 63 90 C7 A0 15 6E 6D 0E 2B 8B D9 AE 24 BC BC 0B 36 37 43 1C C6 34 E0 01 D5 70 DD BA 67 1D 69 CE 69 5E 2E 5A 5B EE 7F 90 92 EB 63 9F 8E DA 57 41 69 6D 1D CD BE B7 A4 44 56 DE 46 C8 8E EA 14 6C 2A 93 9C 30 61 B7 39 E4 13 53 DD DF 0B A7 B5 D4 D2 DD 6E EC 35 5B 6F B2 98 64 5D DE 54 B9 25 41 F4 05 B2 AD E8 42 D7 43 65 A5 D8 69 BB BE C7 69 14 25 F1 BD 95 7E 67 C7 4C 9E A7 F1 AC C4 B7 D4 74 7D 4E EB EC 36 02 EE CA ED 84 C1 44 CA 9E 4C A7 87 EB D8 F0 78 EF 9A D1 56 8C DB B7 E3 A7 AF A6 BA AD 7B F7 27 91 A4 43 7A E2 6B 69 B4 8D 33 CC 5B DD 21 22 9A DC 13 81 36 D1 C2 8E 79 07 05 4F A1 35 7E C4 34 EF 1C AD 0B 42 F7 04 DD 4C 8D C1 1C 05 45 61 EB 80 3F 14 A8 6F 34 8B C5 6B 2B DB 03 08 BF B6 67 DE 25 76 58 E5 59 39 75 24 02 7E F6 08 E3 B5 68 D8 C5 72 A8 D2 DE F9 5F 68 93 1B 84 44 95 50 3A 00 48 04 F7 3C FA 9A CA 73 8F 27 BB FF 00 07 FE 1B A9 49 3B EA 5A A2 8A 2B 90 D4 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 28 A2 8A 00 FF D9, 0x00000165(357)=00 00 00 01 0A 70 69 63 5F 72 65 61 73 6F 6E 00 00 00 7F 51 51 E5 AE 89 E5 85 A8 E4 B8 AD E5 BF 83 E6 B8 A9 E9 A6 A8 E6 8F 90 E7 A4 BA EF BC 9A E5 BD 93 E5 89 8D E7 BD 91 E7 BB 9C E7 8E AF E5 A2 83 E6 9C 89 E5 8D B1 E5 8F 8A 51 51 E5 AE 89 E5 85 A8 E7 9A 84 E8 A1 8C E4 B8 BA EF BC 8C E4 B8 BA E4 BA 86 E4 BD A0 E7 9A 84 E5 B8 90 E5 8F B7 E5 AE 89 E5 85 A8 EF BC 8C E8 AF B7 E4 BD A0 E5 A1 AB E5 86 99 E9 AA 8C E8 AF 81 E7 A0 81 E3 80 82 } */ // val ret = tlvMap[0x104]?.let { println(it.toUHexString()) } bot.client.t104 = tlvMap.getOrFail(0x104) tlvMap[0x192]?.let { return LoginPacketResponse.Captcha.Slider(bot, it.decodeToString()) } tlvMap[0x165]?.let { // if (question[18].toInt() == 0x36) { // 图片验证 // DebugLogger.debug("是一个图片验证码") return tlvMap.getOrFail(0x105).toReadPacket().withUse { val imageData = this val signInfoLength = imageData.readShort() imageData.discardExact(2)//image Length val sign = imageData.readBytes(signInfoLength.toInt()) LoginPacketResponse.Captcha.Picture( bot, data = imageData.readBytes(), sign = sign ) } // } else error("UNKNOWN CAPTCHA QUESTION: ${question.toUHexString()}, tlvMap=" + tlvMap.contentToString()) } error("UNKNOWN CAPTCHA, tlvMap=" + tlvMap.structureToString()) } fun onLoginSuccess(subCommand: Int, tlvMap: TlvMap, bot: QQAndroidBot): LoginPacketResponse.Success { val client = bot.client //println("TLV KEYS: " + tlvMap.keys.joinToString { it.contentToString() }) tlvMap[0x150]?.let { client.analysisTlv150(it) } // tlvMap[0x305]?.let { println("TLV 0x305=${it.toUHexString()}") } tlvMap[0x161]?.let { client.analysisTlv161(it) } tlvMap[0x119]?.let { t119Data -> TEA.decrypt( t119Data, if (subCommand == 11) { client.wLoginSigInfo.d2Key.md5() } else { client.tgtgtKey } ).read { discardExact(2) // always discarded. 00 1C // 00 1C // 01 08 00 10 A1 73 76 98 64 E0 38 C6 C8 18 73 FA D3 85 DA D6 01 6A 00 30 1D 99 4A 28 7E B3 B8 AC 74 B9 C4 BB 6D BB 41 72 F7 5C 9F 0F 79 8A 82 4F 1F 69 34 6D 10 D6 BB E8 A3 4A 2B 5D F1 C7 05 3C F8 72 EF CF 67 E4 3C 94 01 06 00 78 B4 ED 9F 44 ED 10 18 A8 85 0A 8A 85 79 45 47 7F 25 AA EE 2C 53 83 80 0A B3 B0 47 3E 95 51 A4 AE 3E CA A0 1D B4 91 F7 BB 2E 94 76 A8 C8 97 02 C4 5B 15 02 B7 03 9A FC C2 58 6D 17 92 46 AE EB 2F 6F 65 B8 69 6C D6 9D AC 18 6F 07 53 AC FE FA BC BD CE 57 13 10 2D 5A C6 50 AA C2 AE 18 D4 FD CD F2 E0 D1 25 29 56 21 35 8F 01 9D D6 69 44 8F 06 D0 23 26 D3 0E E6 E6 B7 01 0C 00 10 73 32 61 4E 2C 72 35 58 68 28 47 3E 2B 6E 52 62 01 0A 00 48 A4 DA 48 FB B4 8D DA 7B 86 D7 A7 FE 01 1B 70 6F 54 F8 55 38 B0 AD 1B 0C 0B B9 F6 94 24 F8 9E 30 32 22 99 0C 22 CD 44 B8 B0 8A A8 65 E1 B8 F0 49 EF E1 23 D7 0D A3 F1 BB 52 B7 4B AF BD 50 EA BF 15 02 78 2B 8B 10 FB 15 01 0D 00 10 29 75 38 72 21 5D 3F 24 37 46 67 79 2B 65 6D 34 01 14 00 60 00 01 5E 19 65 8C 00 58 93 DD 4D 2C 2D 01 44 99 62 B8 7A EF 04 C5 71 0B F1 BE 4C F4 21 F2 97 B0 14 67 0E 14 9F D8 A2 0B 93 40 90 80 F3 59 7A 69 45 D7 D4 53 4C 08 3A 56 1D C9 95 36 2C 7C 5E EE 36 47 5F AE 26 72 76 FD FD 69 E6 0C 2D 3A E8 CF D4 8D 76 C9 17 C3 E3 CD 21 AB 04 6B 70 C5 EC EC 01 0E 00 10 56 48 3E 29 3A 5A 21 74 55 6A 2C 72 58 73 79 71 01 03 00 30 9B A6 5D 85 5C 40 7C 28 E7 05 A9 25 CA F5 FC C0 51 40 85 F3 2F D2 37 F9 09 A6 E6 56 7F 7A 2E 7D 9F B9 1C 00 65 55 D2 A9 60 03 77 AB 6A F5 3F CE 01 33 00 30 F4 3A A7 08 E2 04 FA C8 9D 54 49 DE 63 EA F0 A5 1C C4 03 57 51 B6 AE 0B 55 41 F8 AB 22 F1 DC A3 B0 73 08 55 14 02 BF FF 55 87 42 4C 23 70 91 6A 01 34 00 10 61 C7 02 3F 1D BE A6 27 2F 24 D4 92 95 68 71 EF 05 28 00 1A 7B 22 51 49 4D 5F 69 6E 76 69 74 61 74 69 6F 6E 5F 62 69 74 22 3A 22 31 22 7D 03 22 00 10 CE 1E 2E DC 69 24 4F 9B FF 2F 52 D8 8F 69 DD 40 01 1D 00 76 5F 5E 10 E2 34 36 79 27 23 53 4D 65 6B 6A 33 6D 7D 4E 3C 5F 00 60 00 01 5E 19 65 8C 00 58 67 00 9C 02 E4 BC DB A3 93 98 A1 ED 4C 91 08 6F 0C 06 E0 12 6A DC 14 5B 4D 20 7C 82 83 AE 94 53 A2 4A A0 35 FF 59 9D F3 EF 82 42 61 67 2A 31 E7 87 7E 74 E7 A3 E7 5C A8 3C 87 CF 40 6A 9F E5 F7 20 4E 56 C6 4F 1C 98 3A 8B A9 4F 1D 10 35 C2 3B A1 08 7A 89 0B 25 0C 63 01 1F 00 0A 00 01 51 80 00 00 03 84 00 00 01 38 00 0E 00 00 00 01 01 0A 00 27 8D 00 00 00 00 00 01 1A 00 13 02 5B 06 01 0E 73 74 65 61 6D 63 68 69 6E 61 2E 66 75 6E 05 22 00 14 00 00 00 00 76 E4 B8 DD AB 53 02 9F 5E 19 65 8C 20 02 ED BD 05 37 00 17 01 01 00 00 00 00 76 E4 B8 DD 04 AB 53 02 9F 5E 19 65 8C 20 02 ED BD 01 20 00 0A 4D 39 50 57 50 6E 4C 31 65 4F 01 6D 00 2C 31 7A 50 7A 63 72 70 4D 30 43 6E 31 37 4C 32 32 6E 77 2D 36 7A 4E 71 48 48 59 41 35 48 71 77 41 37 6D 76 4F 63 2D 4A 56 77 47 51 5F 05 12 03 5D 00 0E 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 00 2C 6E 4A 72 55 55 74 63 2A 34 7A 32 76 31 66 6A 75 77 6F 6A 65 73 72 76 4F 68 70 66 45 76 4A 75 55 4B 6D 34 43 2D 76 74 38 4D 77 38 5F 00 00 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 00 2C 78 59 35 65 62 4D 74 48 44 6D 30 53 6F 68 56 71 68 33 43 79 79 34 6F 63 65 4A 46 6A 51 58 65 68 30 44 61 75 55 30 6C 78 65 52 6B 5F 00 00 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 00 2C 64 6A 62 79 47 57 45 4F 34 58 34 6A 36 4A 73 48 45 65 6B 73 69 74 72 78 79 62 57 69 77 49 68 46 45 70 72 4A 59 4F 2D 6B 36 47 6F 5F 00 00 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 00 2C 64 4C 31 41 79 32 41 31 74 33 58 36 58 58 2A 74 33 64 4E 70 2A 31 61 2D 50 7A 65 57 67 48 70 2D 65 47 78 6B 59 74 71 62 69 6C 55 5F 00 00 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 00 2C 75 6A 55 5A 4F 6A 4F 48 52 61 75 6B 32 55 50 38 77 33 34 68 36 69 46 38 2A 77 4E 50 35 2D 66 54 75 37 67 39 56 67 44 57 2A 6B 6F 5F 00 00 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 00 2C 37 47 31 44 6F 54 2D 4D 57 50 63 2D 62 43 46 68 63 62 32 56 38 6E 77 4A 75 41 51 63 54 39 77 45 49 62 57 43 4A 4B 44 4D 6C 6D 34 5F 00 00 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 00 2C 7A 73 70 5A 56 43 59 45 7A 35 2A 4F 6B 4E 68 6E 74 79 61 69 6E 6F 68 4D 32 6B 41 6C 2A 74 31 63 7A 48 57 77 30 41 6A 4B 50 4B 6B 5F 00 00 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 00 2C 32 6F 2D 51 53 36 65 43 70 37 6A 43 4E 34 6A 74 6E 47 4F 4B 33 67 73 32 63 4A 6F 56 71 58 65 44 48 61 55 39 65 34 2D 32 34 64 30 5F 00 00 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 00 2C 63 54 4D 79 64 51 43 35 50 74 43 45 51 72 6F 33 53 54 41 66 7A 56 2D 44 76 46 56 35 58 6D 56 6B 49 31 68 4C 55 48 4E 65 76 56 38 5F 00 00 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 00 2C 6F 73 72 54 36 32 69 37 66 76 6D 49 50 64 6F 58 4B 48 74 38 58 52 59 56 77 72 7A 6E 69 31 58 7A 57 4C 77 2A 71 36 33 44 74 73 6F 5F 00 00 00 09 74 69 2E 71 71 2E 63 6F 6D 00 2C 41 61 77 4D 78 4D 32 79 58 51 47 75 72 75 55 6C 66 53 58 79 5A 57 48 53 78 52 57 58 50 74 6B 6B 4F 78 6F 66 4A 59 47 6C 71 68 34 5F 00 00 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 00 2C 67 72 57 68 58 77 34 4C 6E 4B 49 4F 67 63 78 45 71 70 33 61 45 67 37 38 46 7A 77 4E 6D 4B 48 56 6E 6F 50 4C 4F 32 6D 57 6D 6E 38 5F 00 00 00 09 71 7A 6F 6E 65 2E 63 6F 6D 00 2C 72 61 47 79 51 35 54 72 4D 55 7A 6E 74 31 4E 52 44 2D 50 72 74 72 41 55 43 35 6A 61 2D 49 47 2D 73 77 4C 6D 49 51 51 41 44 4C 41 5F 00 00 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 00 2C 39 73 2D 4F 51 30 67 76 39 42 6A 37 58 71 52 49 4E 30 35 46 32 64 4D 47 67 47 43 58 57 4A 62 68 63 30 38 63 7A 4B 52 76 6B 78 6B 5F 00 00 03 05 00 10 77 75 6E 54 5F 7E 66 7A 72 40 3C 6E 35 50 53 46 01 43 00 40 3A AE 30 87 81 3D EE BA 31 9C EA 9D 0D D4 73 B1 81 12 E0 94 71 73 7A B0 47 3D 09 47 E5 1B E1 E2 06 1A CB A4 E3 71 9E A6 EA 2A 73 5C C8 D3 B1 2A B1 C7 DA 04 A6 6D 12 26 DF 6B 8B EC C7 12 F8 E1 01 18 00 05 00 00 00 01 00 01 63 00 10 67 6B 60 23 24 6A 55 39 4E 58 24 5E 39 2B 7A 69 01 38 00 5E 00 00 00 09 01 06 00 27 8D 00 00 00 00 00 01 0A 00 24 EA 00 00 00 00 00 01 1C 00 1A 5E 00 00 00 00 00 01 02 00 01 51 80 00 00 00 00 01 03 00 00 1C 20 00 00 00 00 01 20 00 01 51 80 00 00 00 00 01 36 00 1B AF 80 00 00 00 00 01 43 00 1B AF 80 00 00 00 00 01 64 00 1B AF 80 00 00 00 00 01 30 00 0E 00 00 5E 19 65 8C 9F 02 53 AB 00 00 00 00 val tlvMap119 = this._readTLVMap() if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) { tlvMap119.smartToString().printStructure("TlvMap119") } tlvMap119[0x10c]?.read { client.tgtgtKey = readBytes(16) } // ??? tlvMap119[0x1c]?.read { val bytes = readBytes() bot.network.logger.debug("onLoginSuccess, tlvMap119[0x1c]: " + bytes.toUHexString()) bot.network.logger.debug("onLoginSuccess, tlvMap119[0x1c]: " + bytes.decodeToString()) } tlvMap119[0x130]?.let { client.analysisTlv130(it) } tlvMap119[0x113]?.let { client.analysisTlv113(it) } // t528, t530 QQ 中最终保存到 oicq.wlogin_sdk.request.WUserSigInfo#loginResultTLVMap tlvMap119[0x528]?.let { client.t528 = it } tlvMap119[0x530]?.let { client.t530 = it } // tlvMap119[0x118]?.let { client.mainDisplayName = it } tlvMap119[0x108]?.let { client.ksid = it } tlvMap119[0x11a]?.read { readShort().toInt() // faceId readByte().toInt() // age readByte().toInt() // gender val nickLength = readByte().toInt() bot.nick = readString(nickLength) } var openId: ByteArray? = null var openKey: ByteArray? = null tlvMap119[0x125]?.read { openId = readUShortLVByteArray() openKey = readUShortLVByteArray() } /* util.LOGI("tgt len:" + util.buf_len(t10a.get_body_data()) + " tgt_key len:" + util.buf_len(t10d.get_body_data()) + " st len:" + util.buf_len(t114.get_body_data()) + " st_key len:" + util.buf_len(t10e.get_body_data()) + " stwx_web len:" + util.buf_len(t103Data) + " lskey len:" + util.buf_len(t11cData) + " skey len:" + util.buf_len(t120Data) + " sig64 len:" + util.buf_len(t121Data) + " openid len:" + util.buf_len(openId) + " openkey len:" + util.buf_len(openKey) + " pwdflag: " + t186.get_data_len() + t186.getPwdflag(), "" + this.field_61436.uin); */ tlvMap119[0x186]?.let { client.analysisTlv186(it) } tlvMap119[0x537]?.let { client.analysisTlv537(it) } tlvMap119[0x169]?.let { t169 -> client.wFastLoginInfo = WFastLoginInfo( outA1 = client.runCatching { parseWFastLoginInfoDataOutA1(t169) }.getOrElse { ByteReadPacket(byteArrayOf()) } ) } tlvMap119[0x167]?.let { val imgType = byteArrayOf(readByte()) val imgFormat = byteArrayOf(readByte()) val imgUrl = readUShortLVByteArray() // dont move into constructor, keep order client.reserveUinInfo = ReserveUinInfo(imgType, imgFormat, imgUrl) } // client.qrPushSig = tlvMap119[0x317] ?: byteArrayOf() var payToken: ByteArray? = null tlvMap119[0x199]?.read { openId = readUShortLVByteArray() payToken = readUShortLVByteArray() } var pf: ByteArray? = null var pfKey: ByteArray? = null tlvMap119[0x200]?.let { pf = readUShortLVByteArray() pfKey = readUShortLVByteArray() } // sigMap??? =0x21410e0 // from qq val creationTime = currentTimeSeconds() val expireTime = creationTime + 21600 val changeTokenTimeMap = tlvMap119[0x138]?.read { val tlvMap138 = mutableMapOf<Int, Long>() val count = readInt() repeat(count) { val key = readShort().toInt() val value = readInt().toLong() tlvMap138[key] = value } tlvMap138 } ?: emptyMap() if (SHOW_TLV_MAP_ON_LOGIN_SUCCESS) { changeTokenTimeMap.structureToString().printStructure("tokenChangeTime") } val outPSKeyMap: PSKeyMap? val outPt4TokenMap: Pt4TokenMap? if (tlvMap119[0x512] != null) { outPSKeyMap = mutableMapOf() outPt4TokenMap = mutableMapOf() parsePSKeyMapAndPt4TokenMap( tlvMap119[0x512]!!, creationTime, expireTime, outPSKeyMap, outPt4TokenMap ) } else { outPSKeyMap = null outPt4TokenMap = null } if (client.wLoginSigInfoInitialized) { client.wLoginSigInfo.apply { superKey = tlvMap119.getOrElse(0x16d) { superKey } d2 = KeyWithExpiry( tlvMap119.getOrElse(0x143) { d2.data }, creationTime, creationTime + changeTokenTimeMap.getOrElse(0x143) { 1728000L } ) d2Key = tlvMap119.getOrElse(0x305) { d2Key } tgt = tlvMap119.getOrElse(0x10a) { tgt } tgtKey = tlvMap119.getOrElse(0x10d) { tgtKey } a2ExpiryTime = creationTime + changeTokenTimeMap.getOrElse(0x10a) { 2160000L } userStWebSig = KeyWithExpiry( tlvMap119.getOrElse(0x103) { userStWebSig.data }, creationTime, creationTime + changeTokenTimeMap.getOrElse(0x103) { 6000L } ) userStKey = tlvMap119.getOrElse(0x10e) { userStKey } userStSig = KeyWithCreationTime((tlvMap119.getOrElse(0x114) { userStSig.data }), creationTime) appPri = tlvMap119[0x11f]?.let { it.read { //change interval (int time) discardExact(4) readInt().toUInt().toLong() } } ?: appPri sKey = KeyWithExpiry( tlvMap119.getOrEmpty(0x120), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x120) { 86400L } ) wtSessionTicket = KeyWithCreationTime( tlvMap119.getOrElse( 0x133 ) { client.wLoginSigInfo.wtSessionTicket.data }, creationTime ) wtSessionTicketKey = tlvMap119.getOrElse(0x134) { client.wLoginSigInfo.wtSessionTicketKey } deviceToken = tlvMap119.getOrElse(0x322) { deviceToken } encryptedDownloadSession = tlvMap119[0x11d]?.let { client.analysisTlv11d(it) } ?: encryptedDownloadSession encryptA1 = tlvMap119.getOrElse(0x106) { encryptA1 } noPicSig = tlvMap119.getOrElse(0x16a) { noPicSig } psKeyMap.putAll(outPSKeyMap.orEmpty().toMutableMap()) pt4TokenMap.putAll(outPt4TokenMap.orEmpty().toMutableMap()) } } else { var a1: ByteArray? = tlvMap119.getOrFail(0x106) var noPicSig: ByteArray? = tlvMap119[0x16a] tlvMap119[0x531]?.let { analysisTlv0x531(it) { arg1, arg2 -> a1 = arg1 noPicSig = arg2 } } client.wLoginSigInfo = WLoginSigInfo( uin = client.uin, encryptA1 = a1, noPicSig = noPicSig, simpleInfo = WLoginSimpleInfo( uin = client.uin, imgType = client.reserveUinInfo?.imgType ?: byteArrayOf(), imgFormat = client.reserveUinInfo?.imgFormat ?: byteArrayOf(), imgUrl = client.reserveUinInfo?.imgUrl ?: byteArrayOf(), mainDisplayName = tlvMap119[0x118] ?: error("Cannot find tlv 0x118") ), // defaults {}, from asyncContext._G appPri = tlvMap119[0x11f]?.let { it.read { //change interval (int time) discardExact(4) readInt().toUInt().toLong() } } ?: 4294967295L, // defaults {}, from asyncContext._G a2ExpiryTime = creationTime + changeTokenTimeMap.getOrElse( 0x10a ) { 2160000L }, // or from asyncContext._t403.get_body_data() loginBitmap = 0, tgt = tlvMap119.getOrFail(0x10a), a2CreationTime = creationTime, tgtKey = tlvMap119.getOrEmpty(0x10d), // from asyncContext._login_bitmap userStSig = KeyWithCreationTime((tlvMap119.getOrEmpty(0x114)), creationTime), userStKey = tlvMap119.getOrEmpty(0x10e), userStWebSig = KeyWithExpiry( tlvMap119.getOrEmpty(0x103), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x103) { 6000L } ), userA5 = KeyWithCreationTime(tlvMap119.getOrEmpty(0x10b), creationTime), userA8 = KeyWithExpiry( tlvMap119.getOrEmpty(0x102), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x102) { 72000L } ), lsKey = KeyWithExpiry( tlvMap119.getOrEmpty(0x11c), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x11c) { 1641600L } ), sKey = KeyWithExpiry( tlvMap119.getOrEmpty(0x120), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x120) { 86400L } ), userSig64 = KeyWithCreationTime(tlvMap119.getOrEmpty(0x121), creationTime), openId = openId.orEmpty(), openKey = KeyWithCreationTime(openKey.orEmpty(), creationTime), vKey = KeyWithExpiry( tlvMap119.getOrEmpty(0x136), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x136) { 1728000L } ), accessToken = KeyWithCreationTime(tlvMap119.getOrEmpty(0x136), creationTime), d2 = KeyWithExpiry( tlvMap119.getOrFail(0x143), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x143) { 1728000L } ), d2Key = tlvMap119.getOrEmpty(0x305), sid = KeyWithExpiry( tlvMap119.getOrEmpty(0x164), creationTime, creationTime + changeTokenTimeMap.getOrElse(0x164) { 1728000L } ), aqSig = KeyWithCreationTime(tlvMap119.getOrEmpty(0x171), creationTime), psKeyMap = outPSKeyMap.orEmpty().toMutableMap(), pt4TokenMap = outPt4TokenMap.orEmpty().toMutableMap(), superKey = tlvMap119.getOrEmpty(0x16d), payToken = payToken.orEmpty(), pf = pf.orEmpty(), pfKey = pfKey.orEmpty(), da2 = tlvMap119.getOrEmpty(0x203), wtSessionTicket = KeyWithCreationTime(tlvMap119.getOrEmpty(0x133), creationTime), wtSessionTicketKey = tlvMap119.getOrEmpty(0x134), deviceToken = tlvMap119.getOrEmpty(0x322), encryptedDownloadSession = tlvMap119[0x11d]?.let { client.analysisTlv11d(it) }, ) } //bot.network.logger.error(client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString()) } } return LoginPacketResponse.Success(bot) } } internal object ExchangeEmp : OutgoingPacketFactory<Login.LoginPacketResponse>("wtlogin.exchange_emp"), WtLoginExt { override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Login.LoginPacketResponse { return Login.run { decode(bot) } } } internal object TransEmp : OutgoingPacketFactory<TransEmp.Response>("wtlogin.trans_emp") { fun FetchQRCode( client: QQAndroidClient, size: Int, margin: Int, ecLevel: Int ) = TransEmp.buildLoginOutgoingPacket(client, encryptMethod = PacketEncryptType.Empty, uin = "") { sequenceId -> writeSsoPacket(client, client.subAppId, TransEmp.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, uin = 0, commandId = 0x812) { val code2dPacket = buildCode2dPacket(0, 0, 0x31) { writeShort(0) writeInt(16) writeLong(0) writeByte(8) writeShortLVPacket { } _writeTlvMap { t16( client.ssoVersion, client.subAppId, client.device.guid, client.apkId, client.apkVersionName, client.apkSignatureMd5 ) t1b( size = size, margin = margin, ecLevel = ecLevel ) t1d(client.miscBitMap) val protocol = client.bot.configuration.protocol when (protocol) { BotConfiguration.MiraiProtocol.MACOS -> t1f( false, "Mac OSX".toByteArray(), "10".toByteArray(), "mac carrier".toByteArray(), client.device.apn, 2 ) BotConfiguration.MiraiProtocol.ANDROID_WATCH -> t1f( false, client.device.osType, "7.1.2".toByteArray(), "China Mobile GSM".toByteArray(), client.device.apn, 2 ) else -> error("protocol $protocol doesn't support qrcode login.") } t33(client.device.guid) t35( when (protocol) { BotConfiguration.MiraiProtocol.MACOS -> 5 BotConfiguration.MiraiProtocol.ANDROID_WATCH -> 8 else -> error("assertion") } ) } } writeByte(0) writeShort(code2dPacket.remaining.toShort()) writeInt(0x10) // appId, const 16 writeInt(0x72) // 0x90 writeFully(ByteArray(3) { 0x00 }) writePacket(code2dPacket) code2dPacket.release() } } } fun QueryQRCodeStatus( client: QQAndroidClient, sig: ByteArray, ) = TransEmp.buildLoginOutgoingPacket(client, encryptMethod = PacketEncryptType.Empty, uin = "") { sequenceId -> writeSsoPacket(client, client.subAppId, TransEmp.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, uin = 0, commandId = 0x812) { val code2dPacket = buildCode2dPacket(1, 0, 0x12) { writeShort(5) writeByte(1) writeInt(8) writeInt(16) writeShortLVByteArray(sig) writeLong(0) writeByte(8) writeShortLVPacket { } writeShort(0) } writeByte(0) writeShort(code2dPacket.remaining.toShort()) writeInt(0x10) // appId, const 16 writeInt(0x72) // 0x90 writeFully(ByteArray(3) { 0x00 }) writePacket(code2dPacket) code2dPacket.release() } } } private fun buildCode2dPacket( sequence: Int, uin: Long, command: Short, body: BytePacketBuilder.() -> Unit ) = buildPacket { writeInt(currentTimeSeconds().toInt()) writeByte(2) val bodyPacket = buildPacket(body) writeShort((43 + bodyPacket.remaining + 1).toUShort().toShort()) writeShort(command) writeFully(ByteArray(21) { 0 }) writeByte(3) writeShort(0) writeShort(50) writeInt(sequence) writeLong(uin) writePacket(bodyPacket) bodyPacket.release() writeByte(3) } sealed class Response() : Packet { class FetchQRCode(val imageData: ByteArray, val sig: ByteArray) : Response() { override fun toString(): String { return "WtLogin.TransEmp.Response.FetchQRCode" + "(imageData=${imageData.toUHexString()}, sig=${sig.toUHexString()})" } } class QRCodeStatus(val state: State) : Response() { override fun toString(): String { return "WtLogin.TransEmp.Response.QRCodeStatus(state=$state)" } enum class State { WAITING_FOR_SCAN, WAITING_FOR_CONFIRM, CANCELLED, TIMEOUT } } class QRCodeConfirmed(val data: QRCodeLoginData) : Response() { override fun toString(): String { return "WtLogin.TransEmp.Response.QRCodeConfirmed(data=$data)" } } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { check(remaining >= 48) { "remaining payload is too short, current is $remaining." } discardExact(5) readByte().toUByte() readShort().toUShort() val command = readShort().toUShort().toInt() discardExact(21) readByte().toUByte() readShort().toUShort() readShort().toUShort() readInt() readLong() return when (command) { 0x31 -> { // qr code data readShort() readInt() val code = readByte().toInt() check(code == 0) { "code is not 0 while parsing wtlogin.trans_emp with command 0x31." } val sig = readUShortLVByteArray() readShort().toUShort() val tlv = _readTLVMap() val data = tlv.getOrFail(0x17) { "missing tlv 0x17 while parsing wtlogin.trans_emp with command 0x31." } Response.FetchQRCode(data, sig) } 0x12 -> { // qr code state var length = readShort().toUShort().toInt() if (length != 0) { length-- if (readByte().toUByte().toInt() == 2) { readLong() length -= 8 } } if (length > 0) { discardExact(length) } readInt() val code = readByte().toUByte().toInt() if (code != 0) { when (code) { // code 0x30 -> Response.QRCodeStatus(Response.QRCodeStatus.State.WAITING_FOR_SCAN) 0x35 -> Response.QRCodeStatus(Response.QRCodeStatus.State.WAITING_FOR_CONFIRM) 0x36 -> Response.QRCodeStatus(Response.QRCodeStatus.State.CANCELLED) 0x11 -> Response.QRCodeStatus(Response.QRCodeStatus.State.TIMEOUT) else -> error("unknown code $code while parsing wtlogin.trans_emp with command 0x12.") } } else { val client = bot.client val uin = readLong() if (client.uin != uin) { throw InconsistentBotIdException(expected = client.uin, actual = uin) } readInt() readShort().toUShort() val tlv = _readTLVMap() val tmpPwd = tlv.getOrFail(0x18) { "missing tlv 0x18 while parsing wtlogin.trans_emp with command 0x12." } val noPicSig = tlv.getOrFail(0x19) { "missing tlv 0x19 while parsing wtlogin.trans_emp with command 0x12." } val tgtQR = tlv.getOrFail(0x65) { "missing tlv 0x65 while parsing wtlogin.trans_emp with command 0x12." } client.tgtgtKey = tlv.getOrFail(0x1e) { "missing tlv 0x1e while parsing wtlogin.trans_emp with command 0x12." } Response.QRCodeConfirmed(QRCodeLoginData(tmpPwd, noPicSig, tgtQR)) } } else -> error("wtlogin.trans_emp received an unknown command: $command") } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin10.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.utils.GuidSource import net.mamoe.mirai.internal.utils.MacOrAndroidIdChangeFlag import net.mamoe.mirai.internal.utils.guidFlag import net.mamoe.mirai.utils._writeTlvMap import net.mamoe.mirai.utils.generateDeviceInfoData import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.toReadPacket internal object WtLogin10 : WtLoginExt { const val appId: Long = 16L operator fun invoke( client: QQAndroidClient, subAppId: Long = 100, mainSigMap: Int = client.mainSigMap, remark: String = "10:fast-login", ) = WtLogin.ExchangeEmp.buildLoginOutgoingPacket( client, encryptMethod = PacketEncryptType.Empty, remark = remark ) { sequenceId -> writeSsoPacket( client, client.subAppId, WtLogin.ExchangeEmp.commandName, extraData = client.wLoginSigInfo.tgt.toReadPacket(), sequenceId = sequenceId ) { writeOicqRequestPacket( client, commandId = 0x0810 ) { writeShort(11) // subCommand _writeTlvMap(Short.SIZE_BYTES) { t100(appId, subAppId, client.appClientVersion, client.ssoVersion, mainSigMap) t10a(client.wLoginSigInfo.tgt) t116(client.miscBitMap, client.subSigMap) t108(client.ksid) t144( androidId = client.device.androidId, androidDevInfo = client.device.generateDeviceInfoData(), osType = client.device.osType, osVersion = client.device.version.release, networkType = client.networkType, simInfo = client.device.simInfo, unknown = byteArrayOf(), apn = client.device.apn, isGuidFromFileNull = false, isGuidAvailable = true, isGuidChanged = false, guidFlag = guidFlag(GuidSource.FROM_STORAGE, MacOrAndroidIdChangeFlag(0)), buildModel = client.device.model, guid = client.device.guid, buildBrand = client.device.brand, tgtgtKey = client.wLoginSigInfo.d2Key.md5() ) //t112(client.account.phoneNumber.encodeToByteArray()) t143(client.wLoginSigInfo.d2.data) t145(client.device.guid) t142(client.apkId) t154(sequenceId) t18(appId, uin = client.uin) t141(client.device.simInfo, client.networkType, client.device.apn) t8(2052) //t511() t147(appId, client.apkVersionName, client.apkSignatureMd5) t177(client.buildTime, client.sdkVersion) t187(client.device.macAddress) t188(client.device.androidId) t194(client.device.imsiMd5) t511() t202(client.device.wifiBSSID, client.device.wifiSSID) if (client.supportedEncrypt) { t544ForToken( client = client, uin = client.uin, protocol = client.bot.configuration.protocol, guid = client.device.guid, sdkVersion = client.sdkVersion, subCommandId = 10, commandStr = "810_a" ) } } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin15.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.utils._writeTlvMap import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.toByteArray import kotlin.math.abs import kotlin.random.Random internal object WtLogin15 : WtLoginExt { private const val subCommand = 15.toShort() private const val appId = 16L operator fun invoke( client: QQAndroidClient, ) = WtLogin.ExchangeEmp.buildOutgoingUniPacket( client, encryptMethod = PacketEncryptType.Empty, remark = "15:refresh-keys" ) { sequenceId -> // writeSsoPacket(client, client.subAppId, WtLogin.ExchangeEmp.commandName, sequenceId = sequenceId) { writeOicqRequestPacket( client, encryptMethod = EncryptMethodSessionKeyNew( client.wLoginSigInfo.wtSessionTicket.data, client.wLoginSigInfo.wtSessionTicketKey ), commandId = 0x0810 ) { writeShort(subCommand) // subCommand _writeTlvMap { // writeFully(("00 18 00 16 00 01 00 00 06 00 00 00 00 10 00 00 00 00 76 E4 B8 DD 00 00 00 00 00 01 00 14 00 01 5A 11 60 11 76 E4 B8 DD 60 0D 44 35 90 E8 11 1B 00 00 " + // //"01 06 " + // //client.wLoginSigInfo.encryptA1!!.size.toShort().toUHexString() + " " + client.wLoginSigInfo.encryptA1!!.toUHexString() + " "+ // "01 06 00 78 CD 52 75 7E B7 CF A0 FC 58 15 1E 79 29 86 F0 28 06 AE 4D E4 EF 39 6B 8B 64 19 90 7E DF 2B 71 48 53 E6 71 19 4A 7F 81 06 EB 19 A2 CA 8B B3 47 D9 07 73 04 71 99 B3 F2 7E 2B F0 93 6B A0 FA 94 D3 15 41 55 6C EF 87 96 47 22 5C 6B 10 0F EC 27 3A 8F 83 0A 75 8B 95 EA 81 02 AA C6 E7 C8 6C 7B C3 93 4E EC 08 2E DB 9B 3E 4B 17 7C 06 1C 66 31 F4 EE 70 55 87 49 A2 5B 25 " + // "01 16 00 0E 00 0A F7 FF 7C 00 01 " + // "04 00 01 5F 5E 10 E2 01 00 00 16 00 01 00 00 00 0F 00 00 00 10 20 02 FD E2 00 00 00 00 02 10 10 E0 01 07 00 06 00 00 00 00 00 01 01 44 01 B8 20 3F 4F ED A0 CD 3B CC 07 47 A3 91 8B B5 C8 18 EA D1 45 8A 0F F3 1F CD 95 85 00 7C AD 7F 18 7C 43 B6 A6 C3 FF 53 D5 F6 F4 E6 75 1D 4F AF A4 DF C5 EB 3D 6F C5 C3 21 E9 C7 66 8A EF 4A BD 45 CC D2 A5 34 E4 1D F5 9B 07 9B 65 6A 35 BD B4 0B D1 94 43 18 1C 48 1D B5 2C B5 62 FA E9 E1 35 14 13 BC EE BF FA FD 98 A4 72 A8 1A 71 9C 77 2D 4D BF 58 3A 0A 72 D4 B0 A2 DA 6C FC 04 49 F5 05 3D EF 60 E5 92 9A 04 96 AA A4 22 4E 13 8F 63 3B D9 54 B8 DC CC 2D A0 0B 8B B9 9A D7 8A D0 E4 A4 EE 4F 2E 8D 52 86 35 74 70 93 A0 21 0D 98 DE A4 5B B9 79 A5 8E 31 8D A5 AC 0B DB A8 65 6C 93 1C EE FB E9 A2 FD 22 90 0B A5 41 3D 1F 2B A9 84 FF D8 64 DF 94 48 B3 D6 20 47 F3 12 D3 63 F0 84 1C 6A 1E 0A 8B 13 02 EA F2 C3 3E 41 87 59 5A 65 80 E4 7C 3E FC 52 70 20 26 ED 27 91 01 C0 4D 50 23 B9 1F 59 77 AE D5 4D C8 57 DB 86 E9 5B 98 0C 95 A2 15 AB 01 3E 10 D5 3B 01 57 FE C8 88 80 4D 1A 8A 4D 64 89 C3 7F E6 73 D3 04 C8 EA 98 E2 F3 82 48 7D FC D7 CF 07 " + // "F4 33 F1 1E D3 1C 0D 48 37 3A 50 0B 39 28 AB F3 4F BF C9 D8 70 6F B9 F2 FB 46 5A 4B 21 DE D8 B8 30 A1 FC C6 09 60 4E 07 21 28 F7 CC 7A A0 07 1C 87 42 90 D3 40 07 1B 35 52 56 31 E2 C0 6D 1F 79 43 6C 63 46 D4 92 EE 30 A2 D2 D6 0F 79 46 2A EF C7 C6 CF 54 1D 03 FE 80 D4 28 87 AF 2D 1A FF 71 99 FC 23 09 79 B0 9D B4 E9 0F 4E E3 D2 79 10 2C C7 6E 30 34 A5 66 2E 33 00 08 D0 58 2B 7F D8 E6 21 2E 7E 30 01 42 00 18 00 00 00 14 63 6F 6D 2E 74 65 6E 63 65 6E 74 2E 6D 6F 62 69 6C 65 71 71 01 45 00 10 E2 9A BF 20 0D 00 CA F0 4D 28 CA 66 BF 31 6E AF 01 6A 00 38 CA 78 6A 94 A7 A4 F3 BC 28 58 3C FF 53 41 4C A3 5B 98 AB 7C 21 FB 34 D9 28 59 91 D5 15 12 04 A0 7E 27 25 DF 7A A0 06 87 EB 13 12 10 CD 80 85 78 17 F9 1C D6 21 A7 AF 8C 01 54 00 04 00 01 38 E4 01 41 00 1C 00 01 00 10 43 68 69 6E 61 20 4D 6F 62 69 6C 65 20 47 53 4D 00 02 00 04 77 69 66 69 00 08 00 08 00 00 00 00 08 04 00 00 05 11 00 D3 00 0E 01 00 0A 74 65 6E 70 61 79 2E 63 6F 6D 01 00 11 6F 70 65 6E 6D 6F 62 69 6C 65 2E 71 71 2E 63 6F 6D 01 00 0B 64 6F 63 73 2E 71 71 2E 63 6F 6D 01 00 0E 63 6F 6E 6E 65 63 74 2E 71 71 2E 63 6F 6D 01 00 0C 71 7A 6F 6E 65 2E 71 71 2E 63 6F 6D 01 00 0A 76 69 70 2E 71 71 2E 63 6F 6D 01 00 11 67 61 6D 65 63 65 6E 74 65 72 2E 71 71 2E 63 6F 6D 01 00 0A 71 75 6E 2E 71 71 2E 63 6F 6D 01 00 0B 67 61 6D 65 2E 71 71 2E 63 6F 6D 01 00 0C 71 71 77 65 62 2E 71 71 2E 63 6F 6D 01 00 09 74 69 2E 71 71 2E 63 6F 6D 01 00 0D 6F 66 66 69 63 65 2E 71 71 2E 63 6F 6D 01 00 0B 6D 61 69 6C 2E 71 71 2E 63 6F 6D 01 00 0A 6D 6D 61 2E 71 71 2E 63 6F 6D 01 47 00 1D 00 00 00 10 00 05 38 2E 35 2E 35 00 10 A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D 01 77 00 11 01 5F EC 50 93 00 0A 36 2E 30 2E 30 2E 32 34 36 33 " + // "04 00 00 48 2F C5 1F 51 62 56 7E A0 39 92 E9 DC 0D 55 00 E5 BA 24 8D 38 47 8F 1C 7F 6A 91 54 62 D9 B3 25 B0 2D 10 A2 15 5E B4 C7 5F 76 CA AC EF 76 88 92 F4 FF 16 55 98 EB 01 BB 4D 34 95 9E 8E 80 59 4E B3 99 1E 80 CD 4E 27 A4 8E" + // " 01 87 00 10 30 86 EC F4 ED 94 5D D2 6F 88 8A 39 46 6C 22 7D 01 88 00 10 9D B6 A8 DF CB C1 79 06 EB 5D 95 FA 20 5A 9E 11 01 94 00 10 73 A1 26 09 B8 99 62 29 04 95 B9 9E 5C DA 22 8C 02 02 00 1B 00 10 36 85 4A A7 02 92 35 CE 9F B5 A2 D0 7A E4 88 F8 00 07 22 38 32 45 46 45 22 05 16 00 04 00 00 00 00 05 21 00 06 00 00 00 00 00 00 05 25 00 47 00 01 05 36 00 41 01 03 00 00 00 00 76 E4 B8 DD 04 1B 11 E8 90 60 0D 3C 28 20 02 FD E2 00 00 00 00 76 E4 B8 DD 04 1B 11 E8 90 60 0D 3C 28 20 02 FD E2 00 00 00 00 76 E4 B8 DD 04 1B 11 E8 90 60 0D 3C 95 20 02 FD E2 " + // "").hexToBytes()) // return@writeOicqRequestPacket t18(appId, client.appClientVersion, uin = client.uin) t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress) t106(client.wLoginSigInfo.encryptA1!!) // kotlin.run { // val key = (client.account.passwordMd5 + ByteArray(4) + client.uin.toInt().toByteArray()).md5() // kotlin.runCatching { // TEA.decrypt(encryptA1, key).toUHexString() // }.soutv("DEC") // success // } // val a1 = kotlin.runCatching { // TEA.decrypt(encryptA1, buildPacket { // writeFully(client.device.guid) // writeFully(client.dpwd) // writeFully(client.randSeed) // }.readBytes().md5()) // }.recoverCatching { // client.tryDecryptOrNull(encryptA1) { it }!! // }.getOrElse { // encryptA1.soutv("ENCRYPT A1") // client.soutv("CLIENT") // // exitProcess(1) // // error("Failed to decrypt A1") // encryptA1 // } t116(client.miscBitMap, client.subSigMap) if (client.miscBitMap and 128 != 0) { t166(1) client.rollbackSig?.let { t172(it) } } //t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap) //t100(appId, 1, client.appClientVersion, client.ssoVersion, mainSigMap = 1048768) t100(appId, 2, client.appClientVersion, client.ssoVersion, client.mainSigMap) t107(0) if (client.ksid.isNotEmpty()) { t108(client.ksid) } t144(client) t142(client.apkId) if (client.uin !in 10000L..4000000000L) { t112(client.uin.toByteArray()) } t145(client.device.guid) val noPicSig = client.wLoginSigInfo.noPicSig ?: error("Internal error: doing exchange emp 15 while noPicSig=null") t16a(noPicSig) t154(sequenceId) t141(client.device.simInfo, client.networkType, client.device.apn) t8(2052) t511() t147(appId, client.apkVersionName, client.apkSignatureMd5) t177(buildTime = client.buildTime, buildVersion = client.sdkVersion) // new t400( g = client.G, uin = client.uin, guid = client.device.guid, dpwd = client.dpwd, appId = appId, subAppId = 1, randomSeed = client.randSeed ) t187(client.device.macAddress) t188(client.device.androidId) t194(client.device.imsiMd5) // ignored t201 cuz SetNeedForPayToken is never called. t202(client.device.wifiBSSID, client.device.wifiSSID) t516() t521() // new t525(client.loginExtraData) // new if (client.supportedEncrypt) { t544ForToken( client = client, uin = client.uin, protocol = client.bot.configuration.protocol, guid = client.device.guid, sdkVersion = client.sdkVersion, subCommandId = 15, commandStr = "810_f" ) } t545(client.qimei16 ?: client.device.imei) } } // } } } @Suppress("FunctionName", "SpellCheckingInspection") internal fun get_mpasswd(random: Random = Random): String { var var5: String run label41@{ val var6 = ByteArray(16) random.nextBytes(var6) var var0 = 0 var var4 = "" while (true) { var5 = var4 if (var0 >= var6.size) { return var5 } val var3: Boolean = random.nextBoolean() val var2: Int = abs(var6[var0] % 26) val var1: Byte = if (var3) { 97 } else { 65 } var4 += ((var1 + var2).toChar()).toString() ++var0 } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin2.kt ================================================ /* * 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.protocol.packet.login.wtlogin import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.utils._writeTlvMap internal object WtLogin2 : WtLoginExt { fun SubmitSliderCaptcha( client: QQAndroidClient, ticket: String ) = WtLogin.Login.buildLoginOutgoingPacket(client, encryptMethod = PacketEncryptType.Empty, remark = "2:submit-slider") { sequenceId -> writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(2) // subCommand _writeTlvMap { t193(ticket) t8(2052) t104(client.t104) t116(client.miscBitMap, client.subSigMap) client.t547?.let { t547(it) } if (client.supportedEncrypt) { t544ForVerify( client = client, uin = client.uin, protocol = client.bot.configuration.protocol, guid = client.device.guid, sdkVersion = client.sdkVersion, subCommandId = 2, commandStr = "810_2" ) } } } } } fun SubmitPictureCaptcha( client: QQAndroidClient, captchaSign: ByteArray, captchaAnswer: String ) = WtLogin.Login.buildLoginOutgoingPacket(client, encryptMethod = PacketEncryptType.Empty, remark = "2:submit-captcha") { sequenceId -> writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(2) // subCommand _writeTlvMap { t2(captchaAnswer, captchaSign, 0) t8(2052) t104(client.t104) t116(client.miscBitMap, client.subSigMap) client.t547?.let { t547(it) } if (client.supportedEncrypt) { t544ForVerify( client = client, uin = client.uin, protocol = client.bot.configuration.protocol, guid = client.device.guid, sdkVersion = client.sdkVersion, subCommandId = 2, commandStr = "810_2" ) } } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin20.kt ================================================ /* * 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.protocol.packet.login.wtlogin import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.miscBitMap import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.subAppId import net.mamoe.mirai.internal.network.subSigMap import net.mamoe.mirai.utils._writeTlvMap internal object WtLogin20 : WtLoginExt { operator fun invoke( client: QQAndroidClient ) = WtLogin.Login.buildLoginOutgoingPacket(client, encryptMethod = PacketEncryptType.Empty, remark = "20:dev-lock") { sequenceId -> writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(20) // subCommand _writeTlvMap { t8(2052) t104(client.t104) t116(client.miscBitMap, client.subSigMap) t401(client.G) // (client.device.guid + "stMNokHgxZUGhsYp".toByteArray() + t402).md5() } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin7.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.utils.DeviceVerificationRequests import net.mamoe.mirai.utils._writeTlvMap /** * Submit SMS. * @see DeviceVerificationRequests.SmsRequest.requestSms */ internal object WtLogin7 : WtLoginExt { operator fun invoke( client: QQAndroidClient, t174: ByteArray, code: String ) = WtLogin.Login.buildLoginOutgoingPacket( client, encryptMethod = PacketEncryptType.Empty, remark = "7:submit-sms" ) { sequenceId -> writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(7) // subCommand _writeTlvMap { t8(2052) t104(client.t104) t116(client.miscBitMap, client.subSigMap) t174(client.t174 ?: t174) t17c(code.encodeToByteArray()) t401(client.G) t198() if (client.supportedEncrypt) { t544ForVerify( client = client, uin = client.uin, protocol = client.bot.configuration.protocol, guid = client.device.guid, sdkVersion = client.sdkVersion, subCommandId = 7, commandStr = "810_7" ) } } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin8.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.miscBitMap import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.subAppId import net.mamoe.mirai.internal.network.subSigMap import net.mamoe.mirai.utils.DeviceVerificationRequests import net.mamoe.mirai.utils._writeTlvMap /** * Request SMS. * @see DeviceVerificationRequests.SmsRequest.requestSms */ internal object WtLogin8 : WtLoginExt { val subCommand: Short = 8 operator fun invoke( client: QQAndroidClient, t174: ByteArray ) = WtLogin.Login.buildLoginOutgoingPacket( client, encryptMethod = PacketEncryptType.Empty, remark = "8:request-sms" ) { sequenceId -> writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(subCommand) // subCommand _writeTlvMap { t8(2052) t104(client.t104) t116(client.miscBitMap, client.subSigMap) t174(t174) t17a(9) t197() // Lcom/tencent/mobileqq/msf/core/auth/l;a(Ljava/lang/String;JLoicq/wlogin_sdk/request/WUserSigInfo;IIILoicq/wlogin_sdk/tools/ErrMsg;)V // a2.addAttribute("smsExtraData", WtloginHelper.getLoginResultData(wUserSigInfo, 1347)); // wUserSigInfo.loginResultTLVMap.get(new Integer(1347)).get_data() // this.mUserSigInfo.loginResultTLVMap.put(new Integer(1347), async_contextVar._t543); // toServiceMsg.getAttribute("smsExtraData")) client.t543?.let { t542(it) } } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLogin9.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.login.wtlogin import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.* import net.mamoe.mirai.internal.network.protocol.packet.* import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.utils._writeTlvMap import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.toByteArray internal object WtLogin9 : WtLoginExt { private const val appId = 16L fun Password( client: QQAndroidClient, passwordMd5: ByteArray, allowSlider: Boolean ) = WtLogin.Login.buildLoginOutgoingPacket( client, encryptMethod = PacketEncryptType.Empty, remark = "9:password-login" ) { sequenceId -> writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(9) // subCommand val useEncryptA1AndNoPicSig = client.wLoginSigInfoInitialized && client.wLoginSigInfo.noPicSig != null && client.wLoginSigInfo.encryptA1 != null //writeShort(LoginType.PASSWORD.value.toShort()) _writeTlvMap { t18(appId, client.appClientVersion, client.uin) t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress) if (useEncryptA1AndNoPicSig) { t106(client.wLoginSigInfo.encryptA1!!) } else { t106(client, appId, passwordMd5) } /* // from GetStWithPasswd int mMiscBitmap = this.mMiscBitmap; if (t.uinDeviceToken) { mMiscBitmap = (this.mMiscBitmap | 0x2000000); } // defaults true if (ConfigManager.get_loginWithPicSt()) appIdList = longArrayOf(1600000226L) */ t116(client.miscBitMap, client.subSigMap) t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap) t107(0) if (client.ksid.isNotEmpty()) { t108(client.ksid) } // t108(byteArrayOf()) if (client.t104Initialized) { t104(client.t104) } t142(client.apkId) // if login with non-number uin if (client.uin !in 10000L..4000000000L) { t112(client.uin.toByteArray()) } t144(client) //this.build().debugPrint("傻逼") t145(client.device.guid) t147(appId, client.apkVersionName, client.apkSignatureMd5) if (client.miscBitMap and 0x80 != 0) { t166(1) // com.tencent.luggage.wxa.me.e.CTRL_INDEX } if (useEncryptA1AndNoPicSig) { t16a(client.wLoginSigInfo.noPicSig!!) } t154(sequenceId) t141(client.device.simInfo, client.networkType, client.device.apn) t8(2052) t511() // ignored t172 because rollbackSig is null // ignored t185 because loginType is not SMS if (useEncryptA1AndNoPicSig) { t400( g = client.G, uin = client.uin, guid = client.device.guid, dpwd = client.dpwd, appId = appId, subAppId = client.subAppId, randomSeed = client.randSeed, ) } t187(client.device.macAddress) t188(client.device.androidId) t194(client.device.imsiMd5) if (allowSlider) { t191() } //t201(N = byteArrayOf()) t202(client.device.wifiBSSID, client.device.wifiSSID) t177( buildTime = client.buildTime, buildVersion = client.sdkVersion, ) t516() t521() t525() t545(client.qimei16 ?: client.device.imei) // t548() // this.build().debugPrint("傻逼") // ignored t318 because not logging in by QR if (client.supportedEncrypt) { t544ForToken( client = client, uin = client.uin, protocol = client.bot.configuration.protocol, guid = client.device.guid, sdkVersion = client.sdkVersion, subCommandId = 9, commandStr = "810_9" ) } } } } } @Suppress("DuplicatedCode") fun QRCode( client: QQAndroidClient, data: QRCodeLoginData, ) = WtLogin.Login.buildLoginOutgoingPacket( client, encryptMethod = PacketEncryptType.Empty, remark = "9:qrcode-login" ) { sequenceId -> writeSsoPacket(client, client.subAppId, WtLogin.Login.commandName, sequenceId = sequenceId) { writeOicqRequestPacket(client, commandId = 0x0810) { writeShort(9) // subCommand // writeShort(0x19) // count of TLVs, probably ignored by server? _writeTlvMap { t18(appId, client.appClientVersion, client.uin) t1(client.uin, (currentTimeSeconds() + client.timeDifference).toInt(), client.device.ipAddress) t106(data.tmpPwd) t116(client.miscBitMap, client.subSigMap) t100(appId, client.subAppId, client.appClientVersion, client.ssoVersion, client.mainSigMap) t107(0) t108(client.device.imei.toByteArray()) t142(client.apkId) t144(client) t145(client.device.guid) t147(appId, client.apkVersionName, client.apkSignatureMd5) t16a(data.noPicSig) t154(sequenceId) t141(client.device.simInfo, client.networkType, client.device.apn) t8(2052) t511() t187(client.device.macAddress) t188(client.device.androidId) t194(client.device.imsiMd5) t191(0x00) t202(client.device.wifiBSSID, client.device.wifiSSID) t177(client.buildTime, client.sdkVersion) t516() t521(8) t318(data.tgtQR) if (client.supportedEncrypt) { t544ForToken( client = client, uin = client.uin, protocol = client.bot.configuration.protocol, guid = client.device.guid, sdkVersion = client.sdkVersion, subCommandId = 9, commandStr = "810_9" ) } } } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/login/wtlogin/WtLoginExt.kt ================================================ /* * 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.protocol.packet.login.wtlogin import com.ionspin.kotlin.bignum.integer.BigInteger import com.ionspin.kotlin.bignum.integer.util.fromTwosComplementByteArray import com.ionspin.kotlin.bignum.integer.util.toTwosComplementByteArray import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.LoginExtraData import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.WLoginSigInfo import net.mamoe.mirai.internal.network.protocol.packet.Tlv import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.internal.network.protocol.packet.t145 import net.mamoe.mirai.internal.utils.io.writeShortLVByteArray import net.mamoe.mirai.utils.* @Suppress("UnusedReceiverParameter") internal inline fun WtLoginExt.analysisTlv0x531( t531: ByteArray, handler: (a1: ByteArray, noPicSig: ByteArray) -> Unit ) { val map = t531.toReadPacket().withUse { _readTLVMap() } val t106 = map[0x106] val t16a = map[0x16a] val t113 = map[0x113] val t10c = map[0x10c] if (t106 != null && t16a != null && t113 != null && t10c != null) { handler(t106 + t10c, t16a) } } /** * @see WtLogin */ internal interface WtLoginExt { // so as not to register to global extension fun onErrorMessage(type: Int, tlvMap: TlvMap, bot: QQAndroidBot): WtLogin.Login.LoginPacketResponse.Error? { return tlvMap[0x149]?.read { discardExact(2) //type val title: String = readUShortLVString() val content: String = readUShortLVString() val otherInfo: String = readUShortLVString() WtLogin.Login.LoginPacketResponse.Error(bot, type, title, content, otherInfo) } ?: tlvMap[0x146]?.read { discardExact(2) // ver discardExact(2) // code val title = readUShortLVString() val message = readUShortLVString() val errorInfo = readUShortLVString() WtLogin.Login.LoginPacketResponse.Error(bot, type, title, message, errorInfo) } } fun TlvMap.getOrEmpty(key: Int): ByteArray { return this[key] ?: byteArrayOf() } /** * @throws error */ fun QQAndroidClient.parseWFastLoginInfoDataOutA1(t169: ByteArray): ByteReadPacket { val map = t169.toReadPacket().withUse { _readTLVMap() } val t106 = map[0x106] val t10c = map[0x10c] val t16a = map[0x16a] check(t106 != null) { "getWFastLoginInfoDataOutA1: Cannot find tlv 0x106!!" } check(t10c != null) { "getWFastLoginInfoDataOutA1: Cannot find tlv 0x10c!!" } check(t16a != null) { "getWFastLoginInfoDataOutA1: Cannot find tlv 0x16a!!" } return buildPacket { writeByte(64) _writeTlvMap { // TLV tlv(0x106, t106) tlv(0x10c, t10c) tlv(0x16a, t16a) t145(device.guid) } } } /** * login extra data * * oicq/wlogin_sdk/request/oicq_request.java:1445 */ fun QQAndroidClient.analysisTlv537(t537: ByteArray) = t537.read { //discardExact(2) discardExact(1) repeat(readByte().toInt()) { loginExtraData.add( LoginExtraData( // args are to correct order uin = readLong(), ip = readBytes(readByte().toInt() and 0xff), time = readInt(), // correct version = readInt() ) ) } } /** * Encrypt sig and key for pic downloading */ fun QQAndroidClient.analysisTlv11d(t11d: ByteArray): WLoginSigInfo.EncryptedDownloadSession = t11d.read { val appid = readInt().toLong().and(4294967295L) val stKey = ByteArray(16) readAvailable(stKey) val stSigLength = readShort().toUShort().toInt() val stSig = ByteArray(stSigLength) readAvailable(stSig) WLoginSigInfo.EncryptedDownloadSession( appid, stKey, stSig ) } /** * pwd flag */ fun QQAndroidClient.analysisTlv186(t186: ByteArray) = t186.read { discardExact(1) pwdFlag = readByte().toInt() == 1 } /** * 设置 [QQAndroidClient.uin] */ fun QQAndroidClient.analysisTlv113(t113: ByteArray) = t113.read { _uin = readInt().toUInt().toLong() /* // nothing to do if (!asyncContext.ifQQLoginInQim(class_1048.productType)) { this.field_61436.method_62330(this.field_61436.field_63973, this.field_61436.uin); } */ } /** * 设置 [QQAndroidClient.timeDifference] 和 [QQAndroidClient.ipFromT149] */ fun QQAndroidClient.analysisTlv130(t130: ByteArray) = t130.read { discardExact(2) timeDifference = readInt().toUInt().toLong() - currentTimeSeconds() ipFromT149 = readBytes(4) } fun QQAndroidClient.analysisTlv150(t150: ByteArray) { this.t150 = Tlv(t150) } fun QQAndroidClient.analysisTlv546(t546: ByteArray) { val version: Byte val algorithmType: Byte val hashType: Byte val maxIndex: Short val reserveBytes: ByteArray val inputBigNumArr: ByteArray val targetHashArr: ByteArray val reserveHashArr: ByteArray var resultArr: ByteArray = EMPTY_BYTE_ARRAY var costTimeMS: Int var recursiveDepth = 0 var failed = false fun getPadRemaining(bigNumArr: ByteArray, bound: Short): Int { if (bound > 32) { return 1 } var maxLoopCount = 255 var index = 0 while (maxLoopCount >= 0 && index < bound) { if (bigNumArr[maxLoopCount / 8].toInt() and (1 shl maxLoopCount) % 8 != 0) { return 2 } maxLoopCount-- index++ } return 0 } fun calcType1(bigNumArrIn: ByteArray, maxLength: Short) { var bigIntArrClone = bigNumArrIn.copyOf() val originLength = bigIntArrClone.size var bigInteger = BigInteger.fromTwosComplementByteArray(bigIntArrClone) while (true) { if (getPadRemaining(bigIntArrClone.sha256().copyOf(32), maxLength) == 0) { resultArr = bigIntArrClone return } recursiveDepth++ bigInteger = bigInteger.add(BigInteger.ONE) bigIntArrClone = bigInteger.toTwosComplementByteArray() if (bigIntArrClone.size > originLength) { failed = true return } } } fun calcType2(bigNumArrIn: ByteArray, hashTarget: ByteArray) { var bigIntArrClone = bigNumArrIn.copyOf() val originLength = bigIntArrClone.size var bigInteger = BigInteger.fromTwosComplementByteArray(bigIntArrClone) while (true) { if (bigIntArrClone.sha256().copyOf(32).contentEquals(hashTarget)) { resultArr = bigIntArrClone return } recursiveDepth++ bigInteger = bigInteger.add(BigInteger.ONE) bigIntArrClone = bigInteger.toTwosComplementByteArray() if (bigIntArrClone.size > originLength) { failed = true return } } } t546.toReadPacket().apply { version = readByte() algorithmType = readByte() hashType = readByte() readByte() // Ignore resultStatus since it's useless maxIndex = readShort() reserveBytes = readBytes(2) inputBigNumArr = readBytes(readShort().toInt()) targetHashArr = readBytes(readShort().toInt()) reserveHashArr = readBytes(readShort().toInt()) } val startTimeMS: Long = currentTimeMillis() costTimeMS = 0 recursiveDepth = 0 if (hashType == 1.toByte()) { bot.logger.info("Calculating type $algorithmType PoW, it can take some time....") when (algorithmType.toInt()) { 1 -> calcType1(inputBigNumArr, maxIndex) 2 -> calcType2(inputBigNumArr, targetHashArr) else -> { failed = true bot.logger.warning("Unsupported tlv546 algorithm type:${algorithmType}") } } } else { failed = true bot.logger.warning("Unsupported tlv546 hash type:${hashType}") } if (!failed) { costTimeMS = (currentTimeMillis() - startTimeMS).toInt() bot.logger.info("Got PoW result, cost: $costTimeMS ms") this.t547 = buildPacket { writeByte(version) writeByte(algorithmType) writeByte(hashType) writeByte(1) //resultStatus writeShort(maxIndex) writeFully(reserveBytes) writeShortLVByteArray(inputBigNumArr) writeShortLVByteArray(targetHashArr) writeShortLVByteArray(reserveHashArr) writeShortLVByteArray(resultArr) writeInt(costTimeMS) writeInt(recursiveDepth) }.readBytes() } else { bot.logger.warning("Failed to get PoW result, login may fail with error 0x6!") } } fun QQAndroidClient.analysisTlv161(t161: ByteArray) { val tlv = t161.toReadPacket().apply { discardExact(2) }.withUse { _readTLVMap() } tlv[0x173]?.let { analysisTlv173(it) } tlv[0x17f]?.let { analysisTlv17f(it) } tlv[0x172]?.let { rollbackSig = it } } /** * server host */ fun QQAndroidClient.analysisTlv173(t173: ByteArray) { t173.read { val type = readByte() val host = readUShortLVString() val port = readShort() bot.logger.warning("服务器: host=$host, port=$port, type=$type") // SEE oicq_request.java at method analysisT173 } } /** * ipv6 address */ fun QQAndroidClient.analysisTlv17f(t17f: ByteArray) { t17f.read { val type = readByte() val host = readUShortLVString() val port = readShort() bot.logger.warning("服务器 ipv6: host=$host, port=$port, type=$type") // SEE oicq_request.java at method analysisT17f } } fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray()) } internal fun ByteArray?.orEmpty(size: Int = 0): ByteArray { return this ?: if (size == 0) EMPTY_BYTE_ARRAY else ByteArray(size) } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/sso/TRpcRawPacket.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.sso import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.components.RawIncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory internal object TRpcRawPacket : OutgoingPacketFactory<TRpcRawPacket.RawData>("trpc.o3.*") { const val COMMAND_PREFIX = "trpc.o3." internal class RawData( val command: String, val data: ByteArray, ) : Packet override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): RawData { error("") } fun decode(raw: RawIncomingPacket, body: ByteReadPacket): RawData = RawData(raw.commandName, body.readBytes()) } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/summarycard/FriendRemark.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.summarycard import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.jce.ChangeFriendNameReq import net.mamoe.mirai.internal.network.protocol.data.jce.ChangeFriendNameRes import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketWithRespType import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.readUniPacket import net.mamoe.mirai.internal.utils.io.serialization.writeJceRequestPacket internal object ChangeFriendRemark : OutgoingPacketFactory<ChangeFriendRemark.Response>("ProfileService.ChangeFriendName") { class Response(val isSuccess: Boolean, val resultCode: Int) : Packet { override fun toString(): String { return "ProfileService.ChangeFriendName.Response(isSuccess=$isSuccess, resultCode=$resultCode)" } } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response { val res = this.readUniPacket(ChangeFriendNameRes.serializer()) return Response(res.result == 0x0.toByte(), res.result.toInt()) } operator fun invoke( client: QQAndroidClient, id: Long, newRemark: String ): OutgoingPacketWithRespType<Response> { return buildOutgoingUniPacket(client) { writeJceRequestPacket( servantName = "KQQ.ProfileService.ProfileServantObj", funcName = "ChangeFriendName", name = "req", serializer = ChangeFriendNameReq.serializer(), body = ChangeFriendNameReq(id, newRemark) ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/protocol/packet/summarycard/SummaryCard.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.packet.summarycard import io.ktor.utils.io.core.* import net.mamoe.mirai.data.UserProfile import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.data.jce.ReqHead import net.mamoe.mirai.internal.network.protocol.data.jce.RequestDataVersion2 import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.internal.network.protocol.data.richstatus.RichStatus import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket import net.mamoe.mirai.internal.utils.io.serialization.jceRequestSBuffer import net.mamoe.mirai.internal.utils.io.serialization.readJceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars import net.mamoe.mirai.internal.utils.io.serialization.writeJceStruct import net.mamoe.mirai.utils.read import net.mamoe.mirai.internal.network.protocol.data.jce.ReqSummaryCard as JceReqSummaryCard import net.mamoe.mirai.internal.network.protocol.data.jce.RespSummaryCard as JceRespSummaryCard internal data class UserProfileImpl( override val nickname: String, override val email: String, override val age: Int, override val qLevel: Int, override val sex: UserProfile.Sex, override val sign: String, override val friendGroupId: Int, ) : Packet, UserProfile { override fun toString(): String { return "UserProfile(nickname=$nickname, email=$email, age=$age, qLevel=$qLevel, sex=$sex, sign=$sign, friendGroupId=$friendGroupId)" } } internal object SummaryCard { internal object ReqSummaryCard : OutgoingPacketFactory<UserProfileImpl>( "SummaryCard.ReqSummaryCard" ) { operator fun invoke( client: QQAndroidClient, uin: Long, ) = buildOutgoingUniPacket(client) { writeJceStruct( RequestPacket.serializer(), RequestPacket( funcName = "ReqSummaryCard", servantName = "SummaryCardServantObj", version = 3, sBuffer = jceRequestSBuffer { "ReqHead"(ReqHead.serializer(), ReqHead(2)) "ReqSummaryCard"( JceReqSummaryCard.serializer(), JceReqSummaryCard( uin = uin, eComeFrom = 31, getControl = 69181, eAddFriendSource = 3001, vSecureSig = byteArrayOf(0x00), reqMedalWallInfo = 0, vReq0x5ebFieldId = listOf( 27225, 27224, 42122, 42121, 27236, 27238, 42167, 42172, 40324, 42284, 42326, 42325, 42356, 42363, 42361, 42367, 42377, 42425, 42505, 42488 ), reqNearbyGodInfo = 1, reqExtendCard = 1, ) ) } ) ) } override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): UserProfileImpl { val fullMap = readJceStruct( RequestPacket.serializer() ).sBuffer.read { readJceStruct(RequestDataVersion2.serializer()) }.map val map = fullMap["RespSummaryCard"] ?: error("Missing RespSummaryCard in response") val pck = map["SummaryCard.RespSummaryCard"] ?: map["SummaryCard_Old.RespSummaryCard"] ?: error("No Response found") val response = pck.read { discardExact(1) Tars.UTF_8.load(JceRespSummaryCard.serializer(), this) } fun parseSignFromRichSign(): String? { val vsign = response.vRichSign ?: return null val richStatus = RichStatus.parseStatus(vsign) val pt = richStatus.plainText if (pt != null && pt.isNotEmpty()) { return pt.first() } return null } val sign = response.sign?.takeIf { it.isNotEmpty() } ?: parseSignFromRichSign() ?: "" return UserProfileImpl( nickname = response.nick ?: "", email = response.email ?: "", age = response.age?.let { it.toInt() and 0xFF } ?: -1, qLevel = response.iLevel ?: -1, sex = when (response.sex?.let { it.toInt() and 0xFF }) { 0 -> UserProfile.Sex.MALE 1 -> UserProfile.Sex.FEMALE else -> UserProfile.Sex.UNKNOWN }, sign = sign, friendGroupId = response.uFriendGroupId?.toInt() ?: 0 ) } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/network/qimei/Qimei.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.qimei import io.ktor.client.plugins.* import io.ktor.client.request.* import io.ktor.client.statement.* import io.ktor.http.* import io.ktor.utils.io.core.* import kotlinx.serialization.Serializable import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.components.BotClientHolder import net.mamoe.mirai.internal.network.components.HttpClientProvider import net.mamoe.mirai.internal.network.components.SsoProcessorContext import net.mamoe.mirai.internal.network.protocol import net.mamoe.mirai.internal.utils.crypto.aesDecrypt import net.mamoe.mirai.internal.utils.crypto.aesEncrypt import net.mamoe.mirai.internal.utils.crypto.rsaEncryptWithX509PubKey import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol import kotlin.random.Random private val secret = "ZdJqM15EeO2zWc08" private val rsaPubKey = """ -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDEIxgwoutfwoJxcGQeedgP7FG9 qaIuS0qzfR8gWkrkTZKM2iWHn2ajQpBRZjMSoSf6+KJGvar2ORhBfpDXyVtZCKpq LQ+FLkpncClKVIrBwv6PHyUvuCb0rIarmgDnzkfQAqVufEtR64iazGDKatvJ9y6B 9NMbHddGSAUmRTCrHQIDAQAB -----END PUBLIC KEY----- """.trimIndent() internal suspend fun QQAndroidBot.requestQimei(logger: MiraiLogger) { val protocol = components[BotClientHolder].client.protocol if (protocol.appKey.isEmpty()) return val deviceInfo = components[SsoProcessorContext].device val httpClient = components[HttpClientProvider].getHttpClient() val seed = deviceInfo.guid.foldRight(0x6f4L) { curr, acc -> acc + curr.toLong() } val random = Random(seed) val reservedData = Json.encodeToString( ReservedData( harmony = "0", clone = "0", containe = "", oz = "UhYmelwouA+V2nPWbOvLTgN2/m8jwGB+yUB5v9tysQg=", oo = "Xecjt+9S1+f8Pz2VLSxgpw==", kelong = "0", uptimes = formatTime(currentTimeMillis() - random.nextLong(14_400_000), null), multiUser = "0", bod = deviceInfo.board.decodeToString(), brd = deviceInfo.brand.decodeToString(), dv = deviceInfo.device.decodeToString(), firstLevel = "", manufact = deviceInfo.brand.decodeToString(), name = deviceInfo.model.decodeToString(), host = "se.infra", kernel = deviceInfo.procVersion.decodeToString(), ) ) val yearMonthFormatted = formatTime(currentTimeMillis(), "yyyy-MM-01") val rand1 = random.nextInt(899999) + 100000 val rand2 = random.nextInt(899999999) + 100000000 val beaconId = buildString { (1..40).forEach { i -> when (i) { 1, 2, 13, 14, 17, 18, 21, 22, 25, 26, 29, 30, 33, 34, 37, 38 -> { append('k') append(i) append(':') append(yearMonthFormatted) append(rand1) append('.') append(rand2) } 3 -> append("k3:0000000000000000") 4 -> { append("k4:") append(getRandomString(16)) } else -> { append('k') append(i) append(':') append(random.nextInt(10000)) } } append(';') } } val payloadParam = Json.encodeToString( DevicePayloadData( androidId = deviceInfo.androidId.decodeToString(), platformId = 1, appKey = protocol.appKey, appVersion = protocol.buildVer, beaconIdSrc = beaconId, brand = deviceInfo.brand.decodeToString(), channelId = "2017", cid = "", imei = deviceInfo.imei, imsi = "", mac = deviceInfo.macAddress.decodeToString(), model = deviceInfo.model.decodeToString(), networkType = "unknown", oaid = "", osVersion = buildString { append("Android ") append(deviceInfo.version.release.decodeToString()) append(", level ") append(deviceInfo.version.sdk.toString()) }, qimei = "", qimei36 = "", sdkVersion = "1.2.13.6", audit = "", userId = "{}", packageId = protocol.apkId, deviceType = if (configuration.protocol == MiraiProtocol.ANDROID_PAD) "Pad" else "Phone", sdkName = "", reserved = reservedData, ) ).toByteArray() val aesKey = getRandomString(16).toByteArray() val nonce = getRandomString(16) val timestamp = currentTimeSeconds() * 1000 val encodedAESKey = rsaEncryptWithX509PubKey(aesKey, rsaPubKey, timestamp).encodeBase64() val encodedPayloadParam = aesEncrypt(payloadParam, aesKey, aesKey).encodeBase64() val payload = Json.encodeToString( PostData( key = encodedAESKey, params = encodedPayloadParam, time = timestamp, nonce = nonce, sign = buildString { append(encodedAESKey) append(encodedPayloadParam) append(timestamp) append(nonce) append(secret) }.md5().toUHexString(""), extra = "" ) ) val resp = Json.decodeFromString( OLAAndroidResp.serializer(), httpClient.post("https://snowflake.qq.com/ola/android") { userAgent(buildString { append("Dalvik/") append(dalvikVersions[deviceInfo.version.sdk] ?: "2.1.0") append(" (Linux; U; Android ") append(deviceInfo.version.release.decodeToString()) append("; ") append(deviceInfo.device.decodeToString()) append(" Build/") append(deviceInfo.display.decodeToString()) append(")") }) contentType(ContentType.Application.Json) header("Cookie", "") setBody(payload.toByteArray()) timeout { connectTimeoutMillis = 5000 requestTimeoutMillis = 5000 socketTimeoutMillis = 5000 } }.bodyAsText() ) if (resp.code != 0) { logger.warning { "Cannot get qimei from server, return code = ${resp.code}" } return } val decryptedData = aesDecrypt(resp.data.decodeBase64(), aesKey, aesKey) val qimeiData = Json.decodeFromString(QimeiData.serializer(), decryptedData.decodeToString()) client.bot.components[SsoProcessorContext].let { context -> context.qimei36 = qimeiData.q36 context.qimei16 = qimeiData.q16 } } private val dalvikVersions = mapOf( 14 to "1.6", 15 to "1.6", 16 to "1.6", 17 to "1.6", 18 to "1.6", 19 to "2.0", 20 to "2.0", 21 to "2.1.0", 22 to "2.1.0", 23 to "2.1.0", 24 to "2.1.0", 25 to "2.1.0", 26 to "2.1.0", 27 to "2.1.0", 28 to "2.1.0", 29 to "2.1.0", 30 to "2.1.0", 31 to "2.1.0", 32 to "2.1.0", 33 to "2.1.0", 34 to "2.1.0", ) @Serializable private class OLAAndroidResp( val code: Int, val data: String, ) @Serializable private class QimeiData( val q16: String, val q36: String, ) @Suppress("unused") @Serializable private class ReservedData( val harmony: String, val clone: String, val containe: String, val oz: String, val oo: String, val kelong: String, val uptimes: String, val multiUser: String, val bod: String, val brd: String, val dv: String, val firstLevel: String, val manufact: String, val name: String, val host: String, val kernel: String ) @Suppress("unused") @Serializable private class DevicePayloadData( val androidId: String, val platformId: Int, val appKey: String, val appVersion: String, val beaconIdSrc: String, val brand: String, val channelId: String, val cid: String, val imei: String, val imsi: String, val mac: String, val model: String, val networkType: String, val oaid: String, val osVersion: String, val qimei: String, val qimei36: String, val sdkVersion: String, val audit: String, val userId: String, val packageId: String, val deviceType: String, val sdkName: String, val reserved: String ) @Suppress("unused") @Serializable private class PostData( val key: String, val params: String, val time: Long, val nonce: String, val sign: String, val extra: String ) ================================================ FILE: mirai-core/src/commonMain/kotlin/pipeline/ProcessorPipeline.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.pipeline import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.network.components.NoticeProcessor import net.mamoe.mirai.utils.* import kotlin.jvm.JvmInline internal interface Processor<C : ProcessorPipelineContext<D, *>, D> : PipelineConsumptionMarker { val origin: Any get() = this suspend fun process(context: C, data: D) } /** * # Processor Pipeline Architecture * * */ internal interface ProcessorPipeline<P : Processor<C, D>, C : ProcessorPipelineContext<D, R>, D, R> { val processors: MutableCollection<ProcessorBox<P>> fun interface DisposableRegistry : Closeable { fun dispose() override fun close() { dispose() } } fun registerProcessor(processor: P): DisposableRegistry // after fun registerBefore(processor: P): DisposableRegistry fun createContext(data: D, attributes: TypeSafeMap): C /** * Process using the [context]. * * **Note:** This function may or may not re-throw exception caught when calling the [processors]. * @see AbstractProcessorPipeline.handleExceptionInProcess */ suspend fun process( data: D, context: C, attributes: TypeSafeMap = TypeSafeMap.EMPTY, ): ProcessResult<C, R> /** * Process with a new [context][C] * * **Note:** This function may or may not re-throw exception caught when calling the [processors]. * @see AbstractProcessorPipeline.handleExceptionInProcess */ suspend fun process( data: D, attributes: TypeSafeMap = TypeSafeMap.EMPTY ): ProcessResult<C, R> } internal inline fun <P : Processor<*, *>, Pip : ProcessorPipeline<P, *, *, *>> Pip.replaceProcessor( predicate: (origin: Any) -> Boolean, processor: P ): Boolean { for (box in processors) { val value = box.value if (predicate(value.origin)) { box.value = processor return true } } return false } /** * Boxes a mutable property. */ // data used for equality internal data class ProcessorBox<P : Processor<*, *>>( var value: P ) /** * Returned from entry point methods in [MessageProtocolFacade]. */ // data used for destruction, don't change parameter ordering internal data class ProcessResult<C : ProcessorPipelineContext<*, R>, R>( val context: C, val collected: Collection<R>, ) // Box the @JvmInline internal value class MutablePipelineResult<R>( val data: MutableCollection<R> ) /** * Marks a class that is allowed to call [ProcessorPipelineContext.markAsConsumed] */ internal interface PipelineConsumptionMarker internal interface ProcessorPipelineContext<D, R> { /** * Child processes ([processAlso]) will inherit [attributes] from its parent, while any other properties from the context will not. */ val attributes: TypeSafeMap val collected: MutablePipelineResult<R> // DSL to simplify some expressions operator fun MutablePipelineResult<R>.plusAssign(result: R?) { if (result != null) collect(result) } /** * Collect a result. */ fun collect(result: R) /** * Collect results. */ fun collect(results: Iterable<R>) val isConsumed: Boolean /** * Marks the input as consumed so that there will not be warnings like 'Unknown type xxx'. * * **This may or may not stop the pipeline — it's implementation-specific. Check pipeline's [AbstractProcessorPipeline.configuration] ([PipelineConfiguration.stopWhenConsumed]).** * * You need to invoke [markAsConsumed] if your implementation includes some `else` branch which covers all situations, * and throws a [contextualBugReportException] or logs something. */ @ConsumptionMarker fun PipelineConsumptionMarker.markAsConsumed(marker: Any = this) /** * Marks the input as not consumed, if it was marked by this [NoticeProcessor]. */ @ConsumptionMarker fun PipelineConsumptionMarker.markNotConsumed(marker: Any = this) @DslMarker annotation class ConsumptionMarker // to give an explicit color. /** * Fire the [data] into this processor pipeline, starting from the beginning, and collect the results to current [collected]. * * **Please note different [ProcessorPipeline]s' [processAlso] may have different behavior. ALWAYS check implementation!** * * @param extraAttributes extra attributes * @return result collected from processors. This would also have been collected to this context (where you call [processAlso]). */ suspend fun processAlso( data: D, extraAttributes: TypeSafeMap = TypeSafeMap.EMPTY ): ProcessResult<out ProcessorPipelineContext<D, R>, R> } internal abstract class AbstractProcessorPipelineContext<D, R>( override val attributes: TypeSafeMap, private val traceLogging: MiraiLogger, ) : ProcessorPipelineContext<D, R> { private val consumers: ArrayDeque<Any> = ArrayDeque() override val isConsumed: Boolean get() = consumers.isNotEmpty() override fun PipelineConsumptionMarker.markAsConsumed(marker: Any) { traceLogging.info { "markAsConsumed: marker=$marker" } consumers.addFirst(marker) } override fun PipelineConsumptionMarker.markNotConsumed(marker: Any) { if (consumers.firstOrNull() === marker) { consumers.removeFirst() traceLogging.info { "markNotConsumed: Y, marker=$marker" } } else { traceLogging.info { "markNotConsumed: N, marker=$marker" } } } override val collected: MutablePipelineResult<R> = MutablePipelineResult(ConcurrentLinkedDeque()) override fun collect(result: R) { collected.data.add(result) traceLogging.info { "collect: $result" } } override fun collect(results: Iterable<R>) { this.collected.data.addAll(results) traceLogging.info { "collect: $results" } } } internal class PipelineConfiguration( var stopWhenConsumed: Boolean ) /** * Basic implementation of [ProcessorPipeline]. * [configuration] used to control behaviors. * [traceLogging] will always be called frequently, but you can pass a [SilentLogger] or disable the logging levels. */ internal abstract class AbstractProcessorPipeline<P : Processor<C, D>, C : ProcessorPipelineContext<D, R>, D, R> protected constructor( val configuration: PipelineConfiguration, val traceLogging: MiraiLogger, ) : ProcessorPipeline<P, C, D, R> { constructor(configuration: PipelineConfiguration) : this(configuration, SilentLogger) /** * Must be ordered */ override val processors: MutableDeque<ProcessorBox<P>> = ConcurrentLinkedDeque() override fun registerProcessor(processor: P): ProcessorPipeline.DisposableRegistry { val box = ProcessorBox(processor) processors.add(box) return ProcessorPipeline.DisposableRegistry { processors.remove(box) } } override fun registerBefore(processor: P): ProcessorPipeline.DisposableRegistry { val box = ProcessorBox(processor) processors.addFirst(box) return ProcessorPipeline.DisposableRegistry { processors.remove(box) } } abstract inner class BaseContextImpl( attributes: TypeSafeMap, ) : AbstractProcessorPipelineContext<D, R>(attributes, traceLogging) { override suspend fun processAlso( data: D, extraAttributes: TypeSafeMap ): ProcessResult<out ProcessorPipelineContext<D, R>, R> { traceLogging.info { "processAlso: data=${data.structureToStringAndDesensitizeIfAvailable()}" } traceLogging.info { "extraAttributes = $extraAttributes" } val newAttributes = this.attributes + extraAttributes traceLogging.info { "newAttributes = $newAttributes" } return process(data, newAttributes).also { this.collected.data += it.collected traceLogging.info { "processAlso: result=$it" } } } } protected open fun handleExceptionInProcess( data: D, context: C, attributes: TypeSafeMap, processor: P, e: Throwable ): Unit = throw e override suspend fun process(data: D, attributes: TypeSafeMap): ProcessResult<C, R> { return process(data, createContext(data, attributes), attributes) } override suspend fun process(data: D, context: C, attributes: TypeSafeMap): ProcessResult<C, R> { traceLogging.info { "process: data=${data.structureToStringAndDesensitizeIfAvailable()}" } val diff = if (traceLogging.isEnabled) CollectionDiff<R>() else null diff?.save(context.collected.data) for ((processor) in processors) { val result = kotlin.runCatching { processor.process(context, data) }.onFailure { e -> handleExceptionInProcess(data, context, attributes, processor, e) } diff?.run { val diffPackets = subtractAndSave(context.collected.data) traceLogging.info { "Finished ${ processor.toString().replace("net.mamoe.mirai.internal.network.notice.", "") }, success=${result.isSuccess}, consumed=${context.isConsumed}, diff=$diffPackets" } } if (context.isConsumed && configuration.stopWhenConsumed) { traceLogging.info { "stopWhenConsumed=true, stopped." } break } } return ProcessResult(context, context.collected.data) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/spi/EncryptService.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.internal.spi import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.Bot import net.mamoe.mirai.spi.BaseService import net.mamoe.mirai.spi.SpiServiceLoader import net.mamoe.mirai.utils.* /** * @since 2.15.0 */ public class EncryptServiceContext @MiraiInternalApi constructor( /** * [Bot.id] */ public val id: Long, public val extraArgs: TypeSafeMap = TypeSafeMap.EMPTY ) { public companion object { public val KEY_COMMAND_STR: TypeKey<String> = TypeKey("KEY_COMMAND_STR") public val KEY_BOT_PROTOCOL: TypeKey<BotConfiguration.MiraiProtocol> = TypeKey("BOT_PROTOCOL") public val KEY_CHANNEL_PROXY: TypeKey<EncryptService.ChannelProxy> = TypeKey("KEY_CHANNEL_PROXY") public val KEY_DEVICE_INFO: TypeKey<DeviceInfo> = TypeKey("KEY_DEVICE_INFO") public val KEY_QIMEI36: TypeKey<String> = TypeKey("KEY_QIMEI36") public val KEY_BOT_WORKING_DIR: TypeKey<String> = TypeKey("KEY_BOT_WORKING_DIR") public val KEY_BOT_CACHING_DIR: TypeKey<String> = TypeKey("KEY_BOT_CACHING_DIR") } } /** * @since 2.15.0 */ public interface EncryptService { public fun initialize(context: EncryptServiceContext) /** * Returns `false` if not supported. */ public fun supports(protocol: BotConfiguration.MiraiProtocol): Boolean { return protocol != BotConfiguration.MiraiProtocol.ANDROID_WATCH } /** * Returns `null` if encrypt fail. */ public fun encryptTlv( context: EncryptServiceContext, tlvType: Int, payload: ByteArray, // Do not write to payload ): ByteArray? public fun qSecurityGetSign( context: EncryptServiceContext, sequenceId: Int, commandName: String, payload: ByteArray ): SignResult? public class SignResult( public val sign: ByteArray = EMPTY_BYTE_ARRAY, public val token: ByteArray = EMPTY_BYTE_ARRAY, public val extra: ByteArray = EMPTY_BYTE_ARRAY, ) public class ChannelResult( public val cmd: String, public val data: ByteArray, ) public interface ChannelProxy { public suspend fun sendMessage(remark: String, commandName: String, uin: Long, data: ByteArray): ChannelResult? } // net.mamoe.mirai.internal.spi.EncryptService$Factory public interface Factory : BaseService { /* * cleanup: * serviceSubScope.coroutineContext.job.invokeOnCompletion { } */ public fun createForBot(context: EncryptServiceContext, serviceSubScope: CoroutineScope): EncryptService } public companion object { private val loader = SpiServiceLoader(Factory::class) private val warningAlert: Unit by lazy { val log = MiraiLogger.Factory.create(EncryptService::class, "EncryptService.alert") val serviceUsed = loader.service if (serviceUsed != null) { val serviceClass = serviceUsed.javaClass log.warning { "Encrypt service was loaded: $serviceUsed" } log.warning { "All outgoing message may be leaked by this service." } log.warning { "Use this service if and only if you trusted this service and the service provider." } log.warning { "Service details:" } log.warning { " `- Jvm Class: $serviceClass" } log.warning { " `- ClassLoader: " + serviceClass.classLoader } log.warning { " `- Source: " + serviceClass.protectionDomain?.codeSource?.location } log.warning { " `- Protected Domain: " + serviceClass.protectionDomain } } } internal val factory: Factory? get() { warningAlert return loader.service } } // special error: no service used public object SignalServiceNotAvailable : RuntimeException() } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/AtomicIntSeq.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ // IDE doesn't show warnings but compiler do. @file:Suppress("NOTHING_TO_INLINE", "KotlinRedundantDiagnosticSuppress") package net.mamoe.mirai.internal.utils import kotlinx.atomicfu.AtomicInt import kotlinx.atomicfu.atomic import kotlinx.atomicfu.update import net.mamoe.mirai.utils.getRandomUnsignedInt import kotlin.jvm.JvmStatic internal object AtomicIntSeq { @JvmStatic fun forMessageSeq(): AtomicIntMaxSeq = AtomicIntMaxSeq(0) @JvmStatic fun forPrivateSync(): AtomicInt65535Seq = AtomicInt65535Seq(getRandomUnsignedInt()) } // value classes to optimize space internal class AtomicIntMaxSeq( value: Int ) { private val value: AtomicInt = atomic(value) /** * Increment [value] within the range from `0` (inclusive) to [Int.MAX_VALUE] (exclusive). */ inline fun next(): Int = value.incrementAndGet().mod(Int.MAX_VALUE) /** * Atomically update [value] if it is smaller than [new]. * * @param new should be positive */ inline fun updateIfSmallerThan(new: Int): Boolean { value.update { instant -> if (instant < new) new else return false } return true } /** * Atomically update [value] if it different with [new]. * * @param new should be positive */ inline fun updateIfDifferentWith(new: Int): Boolean { value.update { instant -> if (instant == new) return false new } return true } } internal class AtomicInt65535Seq( value: Int ) { private val value: AtomicInt = atomic(value) /** * Increment [value] within the range from `0` (inclusive) to `65535` (exclusive). */ inline fun next(): Int = value.incrementAndGet().mod(65535) /** * Atomically update [value] if it is smaller than [new]. * * @param new should be positive */ inline fun updateIfSmallerThan(new: Int): Boolean { value.update { instant -> if (instant < new) new else return false } return true } /** * Atomically update [value] if it different with [new]. * * @param new should be positive */ inline fun updateIfDifferentWith(new: Int): Boolean { value.update { instant -> if (instant == new) return false new } return true } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/BotConfigurationExt.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("BotConfigurationExt_common") package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.MiraiFile import net.mamoe.mirai.utils.resolveCreateFile import net.mamoe.mirai.utils.resolveMkdir import kotlin.jvm.JvmName internal expect val BotConfiguration.workingDirPath: String /* Note: Required the written path, NOT the resolved (absolute) path. See: #2160 */ internal expect val BotConfiguration.cacheDirPath: String internal fun BotConfiguration.actualCacheDir(): MiraiFile = MiraiFile.create(workingDirPath).resolveMkdir(cacheDirPath) internal fun BotConfiguration.contactCacheDir(): MiraiFile = actualCacheDir().resolveMkdir("contacts") internal fun BotConfiguration.friendCacheFile(): MiraiFile = contactCacheDir().resolveCreateFile("friends.json") internal fun BotConfiguration.groupCacheDir(): MiraiFile = contactCacheDir().resolveMkdir("groups") internal fun BotConfiguration.groupCacheFile(groupId: Long): MiraiFile = groupCacheDir().resolveCreateFile("$groupId.json") internal fun BotConfiguration.accountSecretsFile(): MiraiFile = actualCacheDir().resolve("account.secrets") ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/ExternalResourceImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.ExternalResource @Suppress("FunctionName") internal expect fun CombinedExternalResource(vararg resources: ExternalResource): ExternalResource ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/FileSystem.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils // internal for tests internal object FileSystem { fun checkLegitimacy(path: String) { val char = path.firstOrNull { it in """:*?"<>|""" } if (char != null) { throw IllegalArgumentException("""Chars ':*?"<>|' are not allowed in path. RemoteFile path contains illegal char: '$char'. path='$path'""") } } fun isLegal(path: String): Boolean { return path.firstOrNull { it in """:*?"<>|""" } == null } fun normalize(path: String): String { checkLegitimacy(path) return path.replace('\\', '/') } // net.mamoe.mirai.internal.utils.internal.utils.FileSystemTest fun normalize(parent: String, name: String): String { var nName = normalize(name) if (nName.startsWith('/')) return nName // absolute path then ignore parent nName = nName.removeSuffix("/") var nParent = normalize(parent) if (nParent == "/") return "/$nName" if (!nParent.startsWith('/')) nParent = "/$nParent" val slash = nName.indexOf('/') if (slash != -1) { nParent += '/' + nName.substring(0, slash) nName = nName.substring(slash + 1) } return "$nParent/$nName" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/FragmentedMsgParsingCache.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import kotlinx.atomicfu.locks.reentrantLock import kotlinx.atomicfu.locks.withLock import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm import net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.utils.currentTimeMillis /** * fragmented message */ internal abstract class FragmentedMsgParsingCache<T> { class PkgMsg<T>( val size: Int, val divSeq: Int, val data: MutableMap<Int, T>, ) { val createTime = currentTimeMillis() } private val deque = ArrayList<PkgMsg<T>>(16) private val accessLock = reentrantLock() private fun clearInvalid() { deque.removeAll { currentTimeMillis() - it.createTime > 10000L } } internal abstract val T.contentHead: MsgComm.ContentHead? fun tryMerge(msg: T): List<T> { val head = msg.contentHead ?: return listOf(msg) val size = head.pkgNum if (size < 2) return listOf(msg) accessLock.withLock { clearInvalid() val seq = head.divSeq val index = head.pkgIndex val pkgMsg = deque.find { it.divSeq == seq } ?: PkgMsg<T>(size, seq, mutableMapOf()).also { deque.add(it) } pkgMsg.data[index] = msg if (pkgMsg.data.size == pkgMsg.size) { deque.removeAll { it.divSeq == seq } return pkgMsg.data.entries.asSequence() .sortedBy { it.key } .map { it.value } .toList() } return emptyList() } } } /** * fragmented message */ internal class GroupPkgMsgParsingCache : FragmentedMsgParsingCache<MsgOnlinePush.PbPushMsg>() { override val MsgOnlinePush.PbPushMsg.contentHead: MsgComm.ContentHead? get() = this.msg.contentHead } /** * fragmented message */ internal class C2CPkgMsgParsingCache : FragmentedMsgParsingCache<MsgComm.Msg>() { @Suppress("EXTENSION_SHADOWED_BY_MEMBER") override val MsgComm.Msg.contentHead: MsgComm.ContentHead? get() = this.contentHead } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/GuidSource.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import kotlin.jvm.JvmInline import kotlin.jvm.JvmStatic /** * GUID 来源 * * 0: 初始值; * 1: 以前保存的文件; * 20: 以前没保存且现在生成失败; * 17: 以前没保存但现在生成成功; */ @Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") @JvmInline internal value class GuidSource private constructor(val id: Long) { // uint actually companion object { /** * 初始值 */ @JvmStatic val STUB = GuidSource(0) /** * */ @JvmStatic val NEWLY_GENERATED = GuidSource(17) /** * 以前没保存但现在生成成功 */ @JvmStatic val FROM_STORAGE = GuidSource(1) /** * 以前没保存且现在生成失败 */ @JvmStatic val UNAVAILABLE = GuidSource(20) } } /** * ```java * GUID_FLAG = 0; * GUID_FLAG |= GUID_SRC << 24 & 0xFF000000; * GUID_FLAG |= FLAG_MAC_ANDROIDID_GUID_CHANGE << 8 & 0xFF00; * ``` * * FLAG_MAC_ANDROIDID_GUID_CHANGE: * ```java * if (!Arrays.equals(currentMac, get_last_mac)) { * oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x1; * } * if (!Arrays.equals(currentAndroidId, get_last_android_id)) { * oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x2; * } * if (!Arrays.equals(currentGuid, get_last_guid)) { * oicq.wlogin_sdk.request.t.FLAG_MAC_ANDROIDID_GUID_CHANGEMENT |= 0x4; * } * ``` */ internal fun guidFlag( guidSource: GuidSource, macOrAndroidIdChangeFlag: MacOrAndroidIdChangeFlag, ): Long { var flag = 0L flag = flag or (guidSource.id shl 24 and 0xFF000000) flag = flag or (macOrAndroidIdChangeFlag.value shl 8 and 0xFF00) return flag } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/ImagePatcher.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.message.image.FriendImage import net.mamoe.mirai.internal.message.image.OfflineGroupImage import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore import net.mamoe.mirai.utils.ResourceAccessLock import net.mamoe.mirai.utils.UnsafeMutableNonNullProperty import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.unsafeMutableNonNullPropertyOf internal interface ImagePatcher { fun findCacheByImageId(id: String): ImageCache? fun putCache(image: OfflineGroupImage) suspend fun patchOfflineGroupImage( group: GroupImpl, image: OfflineGroupImage, ) suspend fun patchFriendImageToGroupImage( group: GroupImpl, image: FriendImage, ): OfflineGroupImage companion object : ComponentKey<ImagePatcher> } internal data class ImageCache( var updateTime: Long = 0, val id: UnsafeMutableNonNullProperty<String> = unsafeMutableNonNullPropertyOf(), // OGI: OfflineGroupImage val cacheOGI: UnsafeMutableNonNullProperty<OfflineGroupImage> = unsafeMutableNonNullPropertyOf(), val accessLock: ResourceAccessLock = ResourceAccessLock(), ) internal inline fun <T> ImageCache.withCache(action: (ImageCache) -> T): T { return try { action(this) } finally { this.accessLock.release() } } internal open class ImagePatcherImpl : ImagePatcher { val caches: Array<ImageCache> = Array(20) { ImageCache() } fun findCache(id: String): ImageCache? { return caches.firstOrNull { it.id.value0 == id && it.accessLock.tryUse() } } override fun findCacheByImageId(id: String): ImageCache? = findCache(calcInternalIdByImageId(id)) override fun putCache(image: OfflineGroupImage) { putCache(calcInternalIdByImageId(image.imageId)).cacheOGI.value0 = image } fun putCache(id: String): ImageCache { fun ImageCache.postReturn(): ImageCache = also { cache -> cache.updateTime = currentTimeMillis() cache.id.value0 = id } caches.forEach { exists -> if (exists.id.value0 == id && exists.accessLock.tryInitialize()) { return exists.postReturn() } } // Try to use existing slot caches.forEach { exists -> if (exists.accessLock.tryInitialize()) { return exists.postReturn() } } val availableCaches = caches.filter { it.accessLock.lockIfNotUsing() } if (availableCaches.isNotEmpty()) { val target = availableCaches.minByOrNull { it.updateTime }!! availableCaches.forEach { if (it !== target) it.accessLock.unlock() } target.accessLock.setInitialized() return target.postReturn() } // No available sort. Force to override the last one val newCache = ImageCache() newCache.accessLock.setInitialized() newCache.postReturn() var idx = 0 var lupd = Long.MAX_VALUE caches.forEachIndexed { index, imageCache -> val upd0 = imageCache.updateTime if (upd0 < lupd) { lupd = upd0 idx = index } } caches[idx] = newCache return newCache } private fun calcInternalIdByImageId(imageId: String): String { return imageId.substring(1, imageId.indexOf('}')) } override suspend fun patchOfflineGroupImage( group: GroupImpl, image: OfflineGroupImage, ) { if (image.fileId != null) return val iid = calcInternalIdByImageId(image.imageId) findCache(iid)?.withCache { cache -> cache.cacheOGI.value0?.let { cachedOGI -> image.fileId = cachedOGI.fileId return } } val bot = group.bot val response: ImgStore.GroupPicUp.Response = bot.network.sendAndExpect( ImgStore.GroupPicUp( bot.client, uin = bot.id, groupCode = group.id, md5 = image.md5, size = 1, ) ) when (response) { is ImgStore.GroupPicUp.Response.Failed -> { image.fileId = 0 // Failed } is ImgStore.GroupPicUp.Response.FileExists -> { image.fileId = response.fileId.toInt() } is ImgStore.GroupPicUp.Response.RequireUpload -> { image.fileId = response.fileId.toInt() } } putCache(iid).cacheOGI.value0 = image } /** * Ensures server holds the cache */ override suspend fun patchFriendImageToGroupImage( group: GroupImpl, image: FriendImage, ): OfflineGroupImage { val iid = calcInternalIdByImageId(image.imageId) findCache(iid)?.withCache { cache -> cache.cacheOGI.value0?.let { return it } } val bot = group.bot val response = bot.network.sendAndExpect( ImgStore.GroupPicUp( bot.client, uin = bot.id, groupCode = group.id, md5 = image.md5, size = image.size ) ) return OfflineGroupImage( imageId = image.imageId, width = image.width, height = image.height, size = if (response is ImgStore.GroupPicUp.Response.FileExists) { response.fileInfo.fileSize } else { image.size }, imageType = image.imageType ).also { img -> when (response) { is ImgStore.GroupPicUp.Response.FileExists -> { img.fileId = response.fileId.toInt() } is ImgStore.GroupPicUp.Response.RequireUpload -> { img.fileId = response.fileId.toInt() } is ImgStore.GroupPicUp.Response.Failed -> { img.fileId = 0 } } }.also { img -> putCache(iid).cacheOGI.value0 = img } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/MiraiCoreServices.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.internal.event.InternalEventMechanism import net.mamoe.mirai.utils.Services internal object MiraiCoreServices { @Suppress("RemoveRedundantQualifierName") @OptIn(InternalEventMechanism::class) fun registerAll() { Services.register( "net.mamoe.mirai.event.InternalGlobalEventChannelProvider", "net.mamoe.mirai.internal.event.GlobalEventChannelProviderImpl" ) { net.mamoe.mirai.internal.event.GlobalEventChannelProviderImpl() } Services.register( "net.mamoe.mirai.IMirai", "net.mamoe.mirai.IMirai" ) { net.mamoe.mirai.internal.MiraiImpl() } val msgProtocol = "net.mamoe.mirai.internal.message.protocol.MessageProtocol" Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.AudioProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.AudioProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.CustomMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.CustomMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.FaceProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.FaceProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.FileMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.FileMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.FlashImageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.FlashImageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.IgnoredMessagesProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.IgnoredMessagesProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.ImageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.ImageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.MarketFaceProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.MarketFaceProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.SuperFaceProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.SuperFaceProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.MusicShareProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.MusicShareProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.PttMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.PttMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.TextProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.TextProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.ForwardMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.ForwardMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.LongMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.LongMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.UnsupportedMessageProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.UnsupportedMessageProtocol() } Services.register( msgProtocol, "net.mamoe.mirai.internal.message.protocol.impl.GeneralMessageSenderProtocol" ) { net.mamoe.mirai.internal.message.protocol.impl.GeneralMessageSenderProtocol() } Services.register( "net.mamoe.mirai.message.data.InternalImageProtocol", "net.mamoe.mirai.internal.message.image.InternalImageProtocolImpl" ) { net.mamoe.mirai.internal.message.image.InternalImageProtocolImpl() } Services.register( "net.mamoe.mirai.message.data.OfflineAudio.Factory", "net.mamoe.mirai.internal.message.data.OfflineAudioFactoryImpl" ) { net.mamoe.mirai.internal.message.data.OfflineAudioFactoryImpl() } Services.register( "net.mamoe.mirai.auth.DefaultBotAuthorizationFactory", "net.mamoe.mirai.internal.network.auth.DefaultBotAuthorizationFactoryImpl" ) { net.mamoe.mirai.internal.network.auth.DefaultBotAuthorizationFactoryImpl() } Services.register( "net.mamoe.mirai.utils.InternalProtocolDataExchange", "net.mamoe.mirai.internal.utils.MiraiProtocolInternal\$Exchange" ) { net.mamoe.mirai.internal.utils.MiraiProtocolInternal.Exchange() } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/MiraiProtocolInternal.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol import net.mamoe.mirai.utils.EnumMap import net.mamoe.mirai.utils.InternalProtocolDataExchange import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.toUHexString internal class MiraiProtocolInternal( var apkId: String, var id: Long, var ver: String, var buildVer: String, var sdkVer: String, var miscBitMap: Int, var subSigMap: Int, var mainSigMap: Int, var sign: String, var buildTime: Long, var ssoVersion: Int, var appKey: String, var supportsQRLogin: Boolean, var supportsNudge: Boolean // don't change property signatures, used externally. ) : InternalProtocolDataExchange.InternalProtocolData { internal companion object { // don't change signature internal val protocols = EnumMap<MiraiProtocol, MiraiProtocolInternal>(MiraiProtocol::class) // don't change signature operator fun get(protocol: MiraiProtocol): MiraiProtocolInternal = protocols[protocol] ?: error("Internal Error: Missing protocol $protocol") init { //Updated from MiraiGo (2023/6/18) protocols[MiraiProtocol.ANDROID_PHONE] = MiraiProtocolInternal( apkId = "com.tencent.mobileqq", id = 537163098, ver = "8.9.58", buildVer = "8.9.58.11170", sdkVer = "6.0.0.2545", miscBitMap = 150470524, subSigMap = 0x10400, mainSigMap = 34869344 or 192, sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D", buildTime = 1684467300L, ssoVersion = 20, appKey = "0S200MNJT807V3GE", supportsQRLogin = false, supportsNudge = true ) //Updated from MiraiGo (2023/6/18) protocols[MiraiProtocol.ANDROID_PAD] = MiraiProtocolInternal( apkId = "com.tencent.mobileqq", id = 537161402, ver = "8.9.58", buildVer = "8.9.58.11170", sdkVer = "6.0.0.2545", miscBitMap = 150470524, subSigMap = 0x10400, mainSigMap = 34869344 or 192, sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D", buildTime = 1684467300L, ssoVersion = 20, appKey = "0S200MNJT807V3GE", supportsQRLogin = false, supportsNudge = true ) //Updated from MiraiGo (2023/3/24) protocols[MiraiProtocol.ANDROID_WATCH] = MiraiProtocolInternal( apkId = "com.tencent.qqlite", id = 537065138, ver = "2.0.8", buildVer = "2.0.8", sdkVer = "6.0.0.2365", miscBitMap = 16252796, subSigMap = 0x10400, mainSigMap = 16724722, sign = "A6 B7 45 BF 24 A2 C2 77 52 77 16 F6 F3 6E B6 8D", buildTime = 1559564731L, ssoVersion = 5, appKey = "", supportsQRLogin = true, supportsNudge = false ) protocols[MiraiProtocol.IPAD] = MiraiProtocolInternal( apkId = "com.tencent.minihd.qq", id = 537151363, ver = "8.9.33", buildVer = "8.9.33.614", sdkVer = "6.0.0.2433", miscBitMap = 150470524, subSigMap = 66560, mainSigMap = 1970400, sign = "AA 39 78 F4 1F D9 6F F9 91 4A 66 9E 18 64 74 C7", buildTime = 1640921786L, ssoVersion = 12, appKey = "", supportsQRLogin = false, supportsNudge = true ) protocols[MiraiProtocol.MACOS] = MiraiProtocolInternal( apkId = "com.tencent.qq", id = 537128930, ver = "6.8.2", buildVer = "6.8.2.21241", sdkVer = "6.2.0.1023", miscBitMap = 0x7ffc, subSigMap = 66560, mainSigMap = 1970400, sign = "com.tencent.qq".encodeToByteArray().toUHexString(" "), buildTime = 1647227495L, ssoVersion = 7, appKey = "", supportsQRLogin = true, supportsNudge = false ) } inline val MiraiProtocol.asInternal: MiraiProtocolInternal get() = get(this) } @PublishedApi internal class Exchange : InternalProtocolDataExchange { @MiraiInternalApi override fun of(protocol: MiraiProtocol): InternalProtocolDataExchange.InternalProtocolData { return get(protocol) } } override val isQRLoginSupported: Boolean get() = supportsQRLogin override val isNudgeSupported: Boolean get() = supportsNudge override val mainVersion: String get() = ver override val buildVersion: String get() = buildVer override val sdkVersion: String get() = sdkVer } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/MiraiUtilsLogger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.UtilsLogger import net.mamoe.mirai.utils.asUtilsLogger as asUtilsLogger1 internal fun MiraiLogger.asUtilsLogger(): UtilsLogger = asUtilsLogger1() ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/NetworkType.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import kotlin.jvm.JvmInline /** * 连接类型 */ @JvmInline internal value class NetworkType(val value: Int) { companion object { /** * 移动网络 */ val MOBILE = NetworkType(1) /** * Wifi */ val WIFI = NetworkType(2) /** * 其他任何类型 */ val OTHER = NetworkType(0) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/PlatformDatagramChannel.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import io.ktor.utils.io.core.* // ///** // * 多平台适配的 DatagramChannel. // */ //internal class PlatformDatagramChannel(serverHost: String, serverPort: Short) : Closeable { // @PublishedApi // internal val channel: DatagramChannel = // DatagramChannel.open().connect(InetSocketAddress(serverHost, serverPort.toInt())) // val isOpen: Boolean get() = channel.isOpen // override fun close() = channel.close() // // suspend inline fun send(packet: ByteReadPacket): Boolean = withContext(Dispatchers.IO) { // try { // (channel as WritableByteChannel).writePacket(packet) // } catch (e: Throwable) { // throw SendPacketInternalException(e) // } // } // // suspend inline fun read(): ByteReadPacket = withContext(Dispatchers.IO) { // try { // (channel as ReadableByteChannel).readPacketAtMost(Long.MAX_VALUE) // } catch (e: Throwable) { // throw ReadPacketInternalException(e) // } // } //} /* actual class PlatformDatagramChannel actual constructor(serverHost: String, serverPort: Short) : Closeable { private val serverAddress: InetSocketAddress = InetSocketAddress(serverHost, serverPort.toInt()) @KtorExperimentalAPI val socket = runBlocking { aSocket(ActorSelectorManager(Dispatchers.IO)).tcp() .connect(remoteAddress = serverAddress) } @KtorExperimentalAPI val readChannel = socket.openReadChannel() @KtorExperimentalAPI val writeChannel = socket.openWriteChannel(true) @KtorExperimentalAPI @Throws(ReadPacketInternalException::class) actual suspend fun read(buffer: IoBuffer) = try { readChannel.readAvailable(buffer) } catch (e: ClosedChannelException) { throw e } catch (e: Throwable) { throw ReadPacketInternalException(e) } @KtorExperimentalAPI @Throws(SendPacketInternalException::class) actual suspend fun send(buffer: IoBuffer) = buffer.readDirect { try { writeChannel.writeFully(it) } catch (e: ClosedChannelException) { throw e } catch (e: Throwable) { throw SendPacketInternalException(e) } } @KtorExperimentalAPI @Throws(IOException::class) override fun close() { socket.close() } @KtorExperimentalAPI actual val isOpen: Boolean get() = socket.isClosed.not() } */ /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ internal class SendPacketInternalException(cause: Throwable?) : Exception(cause) /** * 在 [PlatformDatagramChannel.send] 或 [PlatformDatagramChannel.read] 时出现的错误. */ internal class ReadPacketInternalException(cause: Throwable?) : Exception(cause) ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/PlatformSocket.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("PlatformSocketKt_common") package net.mamoe.mirai.internal.utils import io.ktor.utils.io.core.* import io.ktor.utils.io.errors.* import net.mamoe.mirai.internal.network.handler.SocketAddress import net.mamoe.mirai.internal.network.handler.getHost import net.mamoe.mirai.internal.network.handler.getPort import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel import kotlin.jvm.JvmName /** * TCP Socket. */ internal expect class PlatformSocket : Closeable, HighwayProtocolChannel { val isOpen: Boolean override fun close() suspend fun send(packet: ByteArray, offset: Int, length: Int) /** * @throws SendPacketInternalException */ override suspend fun send(packet: ByteReadPacket) /** * @throws ReadPacketInternalException */ override suspend fun read(): ByteReadPacket companion object { suspend fun connect( serverIp: String, serverPort: Int, ): PlatformSocket suspend inline fun <R> withConnection( serverIp: String, serverPort: Int, block: PlatformSocket.() -> R, ): R } } internal suspend inline fun PlatformSocket.Companion.connect(address: SocketAddress): PlatformSocket { return connect(address.getHost(), address.getPort()) } internal expect class SocketException : IOException { constructor() constructor(message: String) } internal expect class NoRouteToHostException : IOException { constructor() constructor(message: String) } internal expect class UnknownHostException : IOException { constructor() constructor(message: String) } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/ScheduledJob.kt ================================================ /* * 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.utils import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.sample import kotlin.coroutines.CoroutineContext @OptIn(FlowPreview::class) internal class ScheduledJob( coroutineContext: CoroutineContext, private val intervalMillis: Long, private val task: suspend () -> Unit, ) : CoroutineScope by CoroutineScope(coroutineContext + SupervisorJob(coroutineContext[Job])) { private val coroutineExceptionHandler = coroutineContext[CoroutineExceptionHandler].also { requireNotNull(it) { "Could not init ScheduledJob, coroutineExceptionHandler == null" } } private val channel = Channel<Unit>(Channel.CONFLATED) fun notice() { if (intervalMillis == 0L) { launch { task() } } else channel.trySend(Unit) } private suspend fun doTask() { runCatching { task() }.onFailure { coroutineExceptionHandler!!.handleException(currentCoroutineContext(), it) } } init { if (intervalMillis != 0L) { launch { channel.receiveAsFlow() .runCatching { sample(intervalMillis) } .fold( onSuccess = { flow -> flow.collect { doTask() } }, onFailure = { // binary change while (isActive) { delay(intervalMillis) task() } } ) } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/SingleEntrantLock.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized internal class SingleEntrantLock : SynchronizedObject() { private val locker: AtomicRef<Any?> = atomic(null) inline fun <R> withLock(locker: Any, crossinline block: () -> R): R? { return synchronized(this) { if (this.locker.value === locker) return@synchronized null this.locker.value = locker block().also { this.locker.value = null } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/SubLogger.kt ================================================ /* * 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.utils import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineExceptionHandler import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.coroutineName internal fun CoroutineExceptionHandler.Key.fromMiraiLogger( logger: MiraiLogger, ignoreCancellationException: Boolean = true, // kotlinx.coroutines default ): CoroutineExceptionHandler { return CoroutineExceptionHandler { coroutineContext, throwable -> if (!ignoreCancellationException || throwable !is CancellationException) { logger.error("Exception in coroutine '${coroutineContext.coroutineName}'", throwable) } } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") internal fun MiraiLogger.subLogger(name: String): MiraiLogger = subLoggerImpl(this, name) ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/collection.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils internal fun <T, M : Comparable<M>> List<T>.indexFirstBE( value: M, mapping: (T) -> M ): Int { var (left, right) = 0 to size - 1 var index = -1 while (left <= right) { val middle = left + (right - left) / 2 if (mapping(get(middle)) >= value) { index = middle right = middle - 1 } else { left = middle + 1 } } return index } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/crypto/AES.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto internal expect fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray internal expect fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/crypto/Ecdh.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import kotlin.jvm.JvmStatic internal data class EcdhKeyPair<TPublicKey, TPrivate>(val public: TPublicKey, val private: TPrivate) internal interface Ecdh<TPublicKey, TPrivate> { fun generateKeyPair(): EcdhKeyPair<TPublicKey, TPrivate> fun calculateShareKey(peerKey: TPublicKey, privateKey: TPrivate): ByteArray /** * @param encoded The encoding should conform with * Sec. 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography") standard, * with compression is off. * @see <a href="https://www.secg.org/sec1-v2.pdf">SECG SEC 1: Elliptic Curve Cryptography</a> */ fun importPublicKey(encoded: ByteArray): TPublicKey /** * @return The encoding conforms with * Sec. 2.3.3 of the SECG SEC 1 ("Elliptic Curve Cryptography") standard, * with compression is off. * @see <a href="https://www.secg.org/sec1-v2.pdf">SECG SEC 1: Elliptic Curve Cryptography</a> */ fun exportPublicKey(key: TPublicKey): ByteArray companion object { @JvmStatic val Instance by lazy { @Suppress("UNCHECKED_CAST") Ecdh.create() as Ecdh<Any, Any> } } } internal expect fun Ecdh.Companion.create() : Ecdh<*, *> ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/crypto/QQEcdh.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.md5 private val defaultPublicKey = "04edb8906046f5bfbe9abbc5a88b37d70a6006bfbabc1f0cd49dfb33505e63efc5d78ee4e0a4595033b93d02096dcd3190279211f7b4f6785079e19004aa0e03bc".hexToBytes() private val defaultQQShareKey = "c129edba736f4909ecc4ab8e010f46a3".hexToBytes() @Serializable internal data class QQEcdhInitialPublicKey(val version: Int = 1, val keyStr: String, val expireTime: Long = 0) { @Transient internal val key = Ecdh.Instance.importPublicKey(keyStr.hexToBytes()) companion object { internal val default: QQEcdhInitialPublicKey by lazy { QQEcdhInitialPublicKey(keyStr = "04EBCA94D733E399B2DB96EACDD3F69A8BB0F74224E2B44E3357812211D2E62EFBC91BB553098E25E33A799ADC7F76FEB208DA7C6522CDB0719A305180CC54A82E") } } } internal expect fun QQEcdhInitialPublicKey.verify(sign: String): Boolean internal data class QQEcdh(private val initialPublicKey: QQEcdhInitialPublicKey = QQEcdhInitialPublicKey.default) { val version: Int = initialPublicKey.version private val keyPair = try { Ecdh.Instance.generateKeyPair() } catch (e:Throwable){ null } val publicKey: ByteArray = keyPair?.let { Ecdh.Instance.exportPublicKey(it.public) } ?: defaultPublicKey val initialQQShareKey: ByteArray = keyPair?.let { Ecdh.Instance.calculateShareKey(initialPublicKey.key, it.private).copyOf(16).md5() } ?: defaultQQShareKey val fallbackMode : Boolean = keyPair == null /** * 由 [keyPair] 的私匙和 [peerKey] 计算 shareKey */ fun calculateQQShareKey(peerKey: Any): ByteArray { check (keyPair != null) { "cannot calculate QQShareKey in fallback mode" } return Ecdh.Instance.calculateShareKey(peerKey.cast(), keyPair.private).copyOf(16).md5() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/crypto/RSA.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto internal class RSAKeyPair( val plainPubPemKey: String, val plainPrivPemKey: String ) internal expect fun generateRSAKeyPair(keySize: Int): RSAKeyPair internal expect fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray internal expect fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/crypto/TEA.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import io.ktor.utils.io.core.* import net.mamoe.mirai.utils.ByteArrayPool import net.mamoe.mirai.utils.toByteArray import net.mamoe.mirai.utils.toUHexString import kotlin.experimental.and import kotlin.experimental.xor import kotlin.jvm.JvmStatic import kotlin.random.Random /** * 解密错误 */ internal class DecryptionFailedException : Exception { constructor() : super() constructor(message: String?) : super(message) } /** * TEA 算法加密解密工具类. * * **注意**: 此为 Mirai 内部 API. 它可能会在任何时刻被改变. */ internal object TEA { /** * 在 [ByteArrayPool] 缓存 [this], 然后使用 [key] 加密. * * @param key 长度至少为 16 * @consumer 由于缓存需要被回收, 需在方法内执行解密后明文的消耗过程 * @throws DecryptionFailedException 解密错误时 */ inline fun encrypt( receiver: ByteReadPacket, key: ByteArray, offset: Int = 0, length: Int = receiver.remaining.toInt() - offset, consumer: (ByteArray) -> Unit, ) { ByteArrayPool.useInstance(length) { receiver.readFully(it, offset, length) consumer(encrypt(it, key, length = length)) } } @JvmStatic fun decrypt( receiver: ByteReadPacket, key: ByteArray, offset: Int = 0, length: Int = (receiver.remaining - offset).toInt(), ): ByteReadPacket = decryptAsByteArray(receiver, key, offset, length) { data -> ByteReadPacket(data) } inline fun <R> decryptAsByteArray( receiver: ByteReadPacket, key: ByteArray, offset: Int = 0, length: Int = (receiver.remaining - offset).toInt(), consumer: (ByteArray) -> R, ): R { return ByteArrayPool.useInstance(length) { receiver.readFully(it, offset, length) consumer(decrypt(it, key, length)) }.also { receiver.close() } } private const val UINT32_MASK = 0xffffffffL private fun doOption(data: ByteArray, key: ByteArray, length: Int, encrypt: Boolean): ByteArray { lateinit var mOutput: ByteArray lateinit var mInBlock: ByteArray var mIndexPos: Int lateinit var mIV: ByteArray var mOutPos = 0 var mPreOutPos = 0 var isFirstBlock = true val mKey = LongArray(4) for (i in 0..3) { mKey[i] = key.pack(i * 4, 4) } fun rand(): Int = Random.Default.nextInt() fun encode(bytes: ByteArray): ByteArray { var v0 = bytes.pack(0, 4) var v1 = bytes.pack(4, 4) var sum: Long = 0 val delta = 0x9e3779b9L for (i in 0..15) { sum = sum + delta and UINT32_MASK v0 += (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] v0 = v0 and UINT32_MASK v1 += (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] v1 = v1 and UINT32_MASK } return v0.toInt().toByteArray() + v1.toInt().toByteArray() } fun decode(bytes: ByteArray, offset: Int): ByteArray { var v0 = bytes.pack(offset, 4) var v1 = bytes.pack(offset + 4, 4) val delta = 0x9e3779b9L var sum = delta shl 4 and UINT32_MASK for (i in 0..15) { v1 -= (v0 shl 4) + mKey[2] xor v0 + sum xor v0.ushr(5) + mKey[3] v1 = v1 and UINT32_MASK v0 -= (v1 shl 4) + mKey[0] xor v1 + sum xor v1.ushr(5) + mKey[1] v0 = v0 and UINT32_MASK sum = sum - delta and UINT32_MASK } return v0.toInt().toByteArray() + v1.toInt().toByteArray() } fun encodeOneBlock() { mIndexPos = 0 while (mIndexPos < 8) { mInBlock[mIndexPos] = if (isFirstBlock) mInBlock[mIndexPos] else (mInBlock[mIndexPos] xor mOutput[mPreOutPos + mIndexPos]) mIndexPos++ } encode(mInBlock).copyInto(mOutput, mOutPos, 0, 8) mIndexPos = 0 while (mIndexPos < 8) { val outPos = mOutPos + mIndexPos mOutput[outPos] = (mOutput[outPos] xor mIV[mIndexPos]) mIndexPos++ } mInBlock.copyInto(mIV, 0, 0, 8) mPreOutPos = mOutPos mOutPos += 8 mIndexPos = 0 isFirstBlock = false } fun decodeOneBlock(ciphertext: ByteArray, offset: Int, len: Int): Boolean { mIndexPos = 0 while (mIndexPos < 8) { if (mOutPos + mIndexPos < len) { mIV[mIndexPos] = (mIV[mIndexPos] xor ciphertext[mOutPos + offset + mIndexPos]) mIndexPos++ continue } return true } mIV = decode(mIV, 0) mOutPos += 8 mIndexPos = 0 return true } @Suppress("NAME_SHADOWING") fun encrypt(plaintext: ByteArray, offset: Int, len: Int): ByteArray { var len = len var offset = offset mInBlock = ByteArray(8) mIV = ByteArray(8) mOutPos = 0 mPreOutPos = 0 isFirstBlock = true mIndexPos = (len + 10) % 8 if (mIndexPos != 0) { mIndexPos = 8 - mIndexPos } mOutput = ByteArray(mIndexPos + len + 10) mInBlock[0] = (rand() and 0xf8 or mIndexPos).toByte() for (i in 1..mIndexPos) { mInBlock[i] = (rand() and 0xff).toByte() } ++mIndexPos for (i in 0..7) { mIV[i] = 0 } var g = 0 while (g < 2) { if (mIndexPos < 8) { mInBlock[mIndexPos++] = (rand() and 0xff).toByte() ++g } if (mIndexPos == 8) { encodeOneBlock() } } while (len > 0) { if (mIndexPos < 8) { mInBlock[mIndexPos++] = plaintext[offset++] } if (mIndexPos == 8) { encodeOneBlock() } len-- } g = 0 while (g < 7) { if (mIndexPos < 8) { mInBlock[mIndexPos++] = 0.toByte() } if (mIndexPos == 8) { encodeOneBlock() } g++ } return mOutput } fun decrypt(cipherText: ByteArray, offset: Int, len: Int): ByteArray { require(!(len % 8 != 0 || len < 16)) { "data must len % 8 == 0 && len >= 16 but given $len" } mIV = decode(cipherText, offset) mIndexPos = (mIV[0] and 7).toInt() var plen = len - mIndexPos - 10 isFirstBlock = true if (plen < 0) { fail() } mOutput = ByteArray(plen) mPreOutPos = 0 mOutPos = 8 ++mIndexPos var g = 0 while (g < 2) { if (mIndexPos < 8) { ++mIndexPos ++g } if (mIndexPos == 8) { isFirstBlock = false if (!decodeOneBlock(cipherText, offset, len)) { fail() } } } var outpos = 0 while (plen != 0) { if (mIndexPos < 8) { mOutput[outpos++] = if (isFirstBlock) mIV[mIndexPos] else (cipherText[mPreOutPos + offset + mIndexPos] xor mIV[mIndexPos]) ++mIndexPos } if (mIndexPos == 8) { mPreOutPos = mOutPos - 8 isFirstBlock = false if (!decodeOneBlock(cipherText, offset, len)) { fail() } } plen-- } g = 0 while (g < 7) { if (mIndexPos < 8) { if (cipherText[mPreOutPos + offset + mIndexPos].xor(mIV[mIndexPos]).toInt() != 0) { fail() } else { ++mIndexPos } } if (mIndexPos == 8) { mPreOutPos = mOutPos if (!decodeOneBlock(cipherText, offset, len)) { fail() } } g++ } return mOutput } return if (encrypt) { encrypt(data, 0, length) } else { decrypt(data, 0, length) } } private fun fail(): Nothing = throw DecryptionFailedException() /** * 使用 [key] 加密 [source] * * @param key 长度至少为 16 * @throws DecryptionFailedException 解密错误时 */ @JvmStatic fun encrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray = doOption(source, key, length, true) /** * 使用 [key] 解密 [source] * * @param key 长度至少为 16 * @throws DecryptionFailedException 解密错误时 */ @JvmStatic fun decrypt(source: ByteArray, key: ByteArray, length: Int = source.size): ByteArray = doOption(source, key, length, false) private fun ByteArray.pack(offset: Int, len: Int): Long { var result: Long = 0 val maxOffset = if (len > 8) offset + 8 else offset + len for (index in offset until maxOffset) { result = result shl 8 or (this[index].toLong() and 0xffL) } return result shr 32 or (result and UINT32_MASK) } } @Suppress("NOTHING_TO_INLINE") private inline fun ByteArray.checkDataLengthAndReturnSelf(length: Int = this.size): ByteArray { if (!(length % 8 == 0 && length >= 16)) { throw DecryptionFailedException("data must len % 8 == 0 && len >= 16 but given (length=$length) ${this.toUHexString()}") } return this } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/flags.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import kotlin.jvm.JvmInline @JvmInline internal value class MacOrAndroidIdChangeFlag(val value: Long = 0) { fun macChanged(): MacOrAndroidIdChangeFlag = MacOrAndroidIdChangeFlag(this.value or 0x1) fun androidIdChanged(): MacOrAndroidIdChangeFlag = MacOrAndroidIdChangeFlag(this.value or 0x2) fun guidChanged(): MacOrAndroidIdChangeFlag = MacOrAndroidIdChangeFlag(this.value or 0x3) companion object { val NoChange: MacOrAndroidIdChangeFlag get() = MacOrAndroidIdChangeFlag() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/ProtocolStruct.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.io import kotlin.reflect.KClass internal interface ProtocolStruct internal interface ProtoBuf : ProtocolStruct internal interface JceStruct : ProtocolStruct @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.VALUE_PARAMETER) internal annotation class NestedStructure( val serializer: KClass<out NestedStructureDesensitizer<*, *>> ) internal interface NestedStructureDesensitizer<in C : ProtocolStruct, T> { fun deserialize(context: C, byteArray: ByteArray): T? } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/output.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") @file:JvmMultifileClass @file:JvmName("Utils") package net.mamoe.mirai.internal.utils.io import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.utils.coerceAtMostOrFail import net.mamoe.mirai.internal.utils.crypto.TEA import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.withUse import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName internal fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLength: Int) { if (array.size <= maxLength) { writeShort(array.size.toShort()) writeFully(array) } else { writeShort(maxLength.toShort()) repeat(maxLength) { writeByte(array[it]) } } } internal fun BytePacketBuilder.writeResource( resource: ExternalResource, close: Boolean = false, ): Long = resource.input().withUse { copyTo(this@writeResource) }.also { if (close) resource.close() } internal inline fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int { this.writeShort(byteArray.size.toShort()) this.writeFully(byteArray) return byteArray.size } internal inline fun BytePacketBuilder.writeIntLVPacket( tag: UByte? = null, lengthOffset: ((Long) -> Long) = { it }, crossinline builder: BytePacketBuilder.() -> Unit, ): Int = buildPacket(builder).use { if (tag != null) writeByte(tag.toByte()) val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL) writeInt(length.toInt()) writePacket(it) return length.toInt() } internal inline fun BytePacketBuilder.writeShortLVPacket( tag: UByte? = null, lengthOffset: ((Long) -> Long) = { it }, crossinline builder: BytePacketBuilder.() -> Unit, ): Int = buildPacket(builder).use { if (tag != null) writeByte(tag.toByte()) val length = lengthOffset.invoke(it.remaining).coerceAtMostOrFail(0xFFFFFFFFL) writeShort(length.toUShort().toShort()) writePacket(it) return length.toInt() } internal inline fun BytePacketBuilder.writeShortLVString(str: String) = writeShortLVByteArray(str.toByteArray()) internal fun BytePacketBuilder.writeHex(uHex: String) { uHex.split(" ").forEach { if (it.isNotBlank()) { writeByte(it.toUByte(16).toByte()) } } } internal inline fun BytePacketBuilder.encryptAndWrite( key: ByteArray, crossinline encoder: BytePacketBuilder.() -> Unit ) = TEA.encrypt(buildPacket(encoder), key) { decrypted -> writeFully(decrypted) } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/Tars.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate", "unused") package net.mamoe.mirai.internal.utils.io.serialization.tars import io.ktor.utils.io.charsets.* import io.ktor.utils.io.core.* import kotlinx.serialization.BinaryFormat import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerialFormat import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import net.mamoe.mirai.internal.utils.io.serialization.tars.internal.DebugLogger import net.mamoe.mirai.internal.utils.io.serialization.tars.internal.TarsDecoder import net.mamoe.mirai.internal.utils.io.serialization.tars.internal.TarsInput import net.mamoe.mirai.internal.utils.io.serialization.tars.internal.TarsOld import net.mamoe.mirai.utils.read import kotlin.jvm.JvmStatic /** * The main entry point to work with Tars serialization. */ internal class Tars( override val serializersModule: SerializersModule = EmptySerializersModule(), val charset: Charset = Charsets.UTF_8, ) : SerialFormat, BinaryFormat { private val old = TarsOld(charset) fun <T> dumpTo(serializer: SerializationStrategy<T>, ojb: T, output: Output) { old.dumpAsPacket(serializer, ojb).use { output.writePacket(it) } } fun <T> load(deserializer: DeserializationStrategy<T>, input: Input, debugLogger: DebugLogger? = null): T { val l = debugLogger ?: DebugLogger(null) return TarsDecoder(TarsInput(input, charset, l), serializersModule, l).decodeSerializableValue(deserializer) } override fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray { return buildPacket { dumpTo(serializer, value, this) }.readBytes() } override fun <T> decodeFromByteArray(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T { bytes.read { return load(deserializer, this, null) } } companion object { @JvmStatic val UTF_8: Tars = Tars() internal const val BYTE: Byte = 0 internal const val DOUBLE: Byte = 5 internal const val FLOAT: Byte = 4 internal const val INT: Byte = 2 internal const val Tars_MAX_STRING_LENGTH = 104857600 internal const val LIST: Byte = 9 internal const val LONG: Byte = 3 internal const val MAP: Byte = 8 internal const val SHORT: Byte = 1 internal const val SIMPLE_LIST: Byte = 13 internal const val STRING1: Byte = 6 internal const val STRING4: Byte = 7 internal const val STRUCT_BEGIN: Byte = 10 internal const val STRUCT_END: Byte = 11 internal const val ZERO_TYPE: Byte = 12 } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/TarsId.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.io.serialization.tars import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.SerialInfo /** * The serial id used in Tars serialization */ @OptIn(ExperimentalSerializationApi::class) @SerialInfo @Target(AnnotationTarget.PROPERTY) internal annotation class TarsId(val id: Int) ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsDecoder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("PrivatePropertyName") package net.mamoe.mirai.internal.utils.io.serialization.tars.internal import io.ktor.utils.io.core.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.AbstractDecoder import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.internal.TaggedDecoder import kotlinx.serialization.modules.SerializersModule import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import net.mamoe.mirai.utils.MiraiLogger internal class DebugLogger( val out: Output? ) { var structureHierarchy: Int = 0 fun println(message: Any?) { out?.appendLine(" ".repeat(structureHierarchy) + message) } fun println() { out?.appendLine() } inline fun println(lazyMessage: () -> String) { out?.appendLine(" ".repeat(structureHierarchy) + lazyMessage()) } } @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) internal class TarsDecoder( val input: TarsInput, override val serializersModule: SerializersModule, val debugLogger: DebugLogger, ) : TaggedDecoder<TarsTag>() { override fun SerialDescriptor.getTag(index: Int): TarsTag { val annotations = this.getElementAnnotations(index) val id = annotations.filterIsInstance<TarsId>().single().id // ?: error("cannot find @TarsId or @ProtoNumber for ${this.getElementName(index)} in ${this.serialName}") //debugLogger.println("getTag: ${this.getElementName(index)}=$id") return TarsTagCommon(id) } private fun SerialDescriptor.getTarsTagId(index: Int): Int { // higher performance, don't use filterIsInstance val annotation = getElementAnnotations(index).firstOrNull { it is TarsId } ?: error("missing @TarsId for ${getElementName(index)} in ${this.serialName}") return (annotation as TarsId).id } private val SimpleByteArrayReader: SimpleByteArrayReaderImpl = SimpleByteArrayReaderImpl() private inner class SimpleByteArrayReaderImpl : AbstractDecoder() { override val serializersModule: SerializersModule get() = this@TarsDecoder.serializersModule override fun decodeSequentially(): Boolean = true override fun endStructure(descriptor: SerialDescriptor) { this@TarsDecoder.endStructure(descriptor) } override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { this@TarsDecoder.pushTag(TarsTagListElement()) return this@TarsDecoder.beginStructure(descriptor) } override fun decodeByte(): Byte = input.input.readByte() override fun decodeShort(): Short = error("illegal access") override fun decodeInt(): Int = error("illegal access") override fun decodeLong(): Long = error("illegal access") override fun decodeFloat(): Float = error("illegal access") override fun decodeDouble(): Double = error("illegal access") override fun decodeBoolean(): Boolean = error("illegal access") override fun decodeChar(): Char = error("illegal access") override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = error("illegal access") override fun decodeString(): String = error("illegal access") override fun decodeElementIndex(descriptor: SerialDescriptor): Int { error("should not be reached") } override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { // 不要读下一个 head return input.currentHead.let { input.readTarsIntValue(it) }.also { debugLogger.println { "SimpleByteArrayReader.decodeCollectionSize: $it" } } } } private val ListReader: ListReaderImpl = ListReaderImpl() private inner class ListReaderImpl : AbstractDecoder() { override val serializersModule: SerializersModule get() = this@TarsDecoder.serializersModule override fun decodeSequentially(): Boolean = true override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("should not be reached") override fun endStructure(descriptor: SerialDescriptor) { this@TarsDecoder.endStructure(descriptor) } override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { this@TarsDecoder.pushTag(TarsTagListElement()) return this@TarsDecoder.beginStructure(descriptor) } override fun decodeByte(): Byte = input.useHead { input.readTarsByteValue(it) } override fun decodeShort(): Short = input.useHead { input.readTarsShortValue(it) } override fun decodeInt(): Int = input.useHead { input.readTarsIntValue(it) } override fun decodeLong(): Long = input.useHead { input.readTarsLongValue(it) } override fun decodeFloat(): Float = input.useHead { input.readTarsFloatValue(it) } override fun decodeDouble(): Double = input.useHead { input.readTarsDoubleValue(it) } override fun decodeBoolean(): Boolean = input.useHead { input.readTarsBooleanValue(it) } override fun decodeChar(): Char = decodeByte().toInt().toChar() override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt() override fun decodeString(): String = input.useHead { input.readTarsStringValue(it) } override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { //debugLogger.println("decodeCollectionSize: ${descriptor.serialName}") // 不读下一个 head return input.useHead { input.readTarsIntValue(it) } } } private val MapReader: MapReaderImpl = MapReaderImpl() private inner class MapReaderImpl : AbstractDecoder() { override val serializersModule: SerializersModule get() = this@TarsDecoder.serializersModule override fun decodeSequentially(): Boolean = true override fun decodeElementIndex(descriptor: SerialDescriptor): Int = error("stub") override fun endStructure(descriptor: SerialDescriptor) { debugLogger.println { "MapReader.endStructure: ${input.currentHeadOrNull}" } this@TarsDecoder.endStructure(descriptor) } override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { debugLogger.println { "MapReader.beginStructure: ${input.currentHead}" } this@TarsDecoder.pushTag( when (input.currentHead.tag) { 0 -> TarsTagMapEntryKey() 1 -> TarsTagMapEntryValue() else -> error("illegal map entry head: ${input.currentHead.tag}") }.also { debugLogger.println("MapReader.pushTag $it") } ) return this@TarsDecoder.beginStructure(descriptor) } override fun decodeByte(): Byte = input.useHead { input.readTarsByteValue(it) } override fun decodeShort(): Short = input.useHead { input.readTarsShortValue(it) } override fun decodeInt(): Int = input.useHead { input.readTarsIntValue(it) } override fun decodeLong(): Long = input.useHead { input.readTarsLongValue(it) } override fun decodeFloat(): Float = input.useHead { input.readTarsFloatValue(it) } override fun decodeDouble(): Double = input.useHead { input.readTarsDoubleValue(it) } override fun decodeBoolean(): Boolean = input.useHead { input.readTarsBooleanValue(it) } override fun decodeChar(): Char = decodeByte().toInt().toChar() override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = decodeInt() override fun decodeString(): String = input.useHead { head -> input.readTarsStringValue(head).also { debugLogger.println { "MapReader.decodeString: $it" } } } override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { debugLogger.println { "decodeCollectionSize in MapReader: ${descriptor.serialName}" } // 不读下一个 head return input.useHead { head -> input.readTarsIntValue(head).also { debugLogger.println { "decodeCollectionSize = $it" } } } } } override fun endStructure(descriptor: SerialDescriptor) { debugLogger.structureHierarchy-- debugLogger.println { "endStructure: ${descriptor.serialName}, $currentTagOrNull, ${input.currentHeadOrNull}" } if (currentTagOrNull?.isSimpleByteArray == true) { debugLogger.println { "endStructure: prepareNextHead() called" } currentTag.isSimpleByteArray = false input.prepareNextHead() // read to next head } if (descriptor.kind == StructureKind.CLASS) { if (currentTagOrNull == null) { return } while (true) { val currentHead = input.currentHeadOrNull ?: return if (currentHead.type == Tars.STRUCT_END) { input.prepareNextHead() //debugLogger.println("current end") break } //debugLogger.println("current $currentHead") input.skipField(currentHead.type) input.prepareNextHead() } // pushTag(TarsTag(0, true)) // skip STRUCT_END // popTag() } } companion object { val logger = MiraiLogger.Factory.create(TarsDecoder::class, "TarsDecoder") } override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder { debugLogger.println() debugLogger.println { "beginStructure: ${descriptor.serialName}, ${descriptor.kind}" } debugLogger.structureHierarchy++ return when (descriptor.kind) { is PrimitiveKind -> this@TarsDecoder StructureKind.MAP -> { val tag = popTag() // debugLogger.println("!! MAP, tag=$tag") return input.skipToHeadAndUseIfPossibleOrFail(tag.id) { it.checkType(Tars.MAP, "beginStructure", tag, descriptor) MapReader } } StructureKind.LIST -> { //debugLogger.println("!! ByteArray") //debugLogger.println("decoderTag: $currentTagOrNull") //debugLogger.println("TarsHead: " + Tars.currentHeadOrNull) return input.skipToHeadAndUseIfPossibleOrFail(currentTag.id) { // don't check type. it's polymorphic //debugLogger.println("listHead: $it") when (it.type) { Tars.SIMPLE_LIST -> { currentTag.isSimpleByteArray = true input.nextHead() // 无用的元素类型 SimpleByteArrayReader } Tars.LIST -> ListReader else -> error("type mismatch. Expected SIMPLE_LIST or LIST, got $it instead") } } } StructureKind.CLASS -> { currentTagOrNull ?: return this@TarsDecoder // outermost //debugLogger.println("!! CLASS") //debugLogger.println("decoderTag: $currentTag") //debugLogger.println("TarsHead: " + Tars.currentHeadOrNull) val tag = popTag() return input.skipToHeadAndUseIfPossibleOrFail(tag.id) { TarsHead -> TarsHead.checkType(Tars.STRUCT_BEGIN, "beginStructure", tag, descriptor) repeat(descriptor.elementsCount) { pushTag(descriptor.getTag(descriptor.elementsCount - it - 1)) // better performance } this // independent tag stack } } StructureKind.OBJECT -> error("unsupported StructureKind.OBJECT: ${descriptor.serialName}") is SerialKind.ENUM -> error("unsupported UnionKind: ${descriptor.serialName}") is PolymorphicKind -> error("unsupported PolymorphicKind: ${descriptor.serialName}") is SerialKind.CONTEXTUAL -> error("unsupported PolymorphicKind: ${descriptor.serialName}") } } override fun decodeSequentially(): Boolean = false override fun decodeElementIndex(descriptor: SerialDescriptor): Int { var tarsHead = input.currentHeadOrNull ?: kotlin.run { debugLogger.println("decodeElementIndex: currentHead == null") return CompositeDecoder.DECODE_DONE } debugLogger.println { "decodeElementIndex: ${input.currentHead}" } while (!input.input.endOfInput) { if (tarsHead.type == Tars.STRUCT_END) { debugLogger.println { "decodeElementIndex: ${input.currentHead}" } return CompositeDecoder.DECODE_DONE } repeat(descriptor.elementsCount) { val tag = descriptor.getTarsTagId(it) if (tag == tarsHead.tag) { debugLogger.println { "name=" + descriptor.getElementName( it ) } return it } } input.skipField(tarsHead.type) if (!input.prepareNextHead()) { debugLogger.println { "decodeElementIndex EOF" } break } tarsHead = input.currentHead debugLogger.println { "next! $tarsHead" } } return CompositeDecoder.DECODE_DONE // optional support } override fun decodeTaggedInt(tag: TarsTag): Int = kotlin.runCatching { input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsIntValue(it) } }.getOrElse { throw IllegalStateException("$tag", it) } override fun decodeTaggedByte(tag: TarsTag): Byte = kotlin.runCatching { input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsByteValue(it) } }.getOrElse { throw IllegalStateException("$tag", it) } override fun decodeTaggedBoolean(tag: TarsTag): Boolean = input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsBooleanValue(it) } override fun decodeTaggedFloat(tag: TarsTag): Float = input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsFloatValue(it) } override fun decodeTaggedDouble(tag: TarsTag): Double = input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsDoubleValue(it) } override fun decodeTaggedShort(tag: TarsTag): Short = input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsShortValue(it) } override fun decodeTaggedLong(tag: TarsTag): Long = input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsLongValue(it) } override fun decodeTaggedString(tag: TarsTag): String = input.skipToHeadAndUseIfPossibleOrFail(tag.id) { input.readTarsStringValue(it) } override fun decodeTaggedNotNullMark(tag: TarsTag): Boolean { return input.skipToHeadOrNull(tag.id) != null } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsInput.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.io.serialization.tars.internal import io.ktor.utils.io.charsets.* import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars /** * Tars Input. 需要手动管理 head. */ internal class TarsInput( val input: Input, private val charset: Charset, private val debugLogger: DebugLogger, ) { private var _head: TarsHead? = null private var _nextHead: TarsHead? = null val currentHead: TarsHead get() = _head ?: throw EOFException("No current TarsHead available") val currentHeadOrNull: TarsHead? get() = _head init { prepareNextHead() } /** * 预读取下个 [TarsHead] * * 注意: 应该读完 [currentHead] 的值再调用 */ @Suppress("MemberVisibilityCanBePrivate") fun peekNextHead(): TarsHead? { _nextHead?.let { return it } return readNextHeadButDoNotAssignTo_Head(true).also { _nextHead = it; }.also { debugLogger.println("Peek next head: $it") } } /** * 读取下一个 [TarsHead] 并保存. 可通过 [currentHead] 获取这个 [TarsHead]. * * @return 是否成功读取. 返回 `false` 时代表 [Input.endOfInput] */ fun prepareNextHead(): Boolean { return readNextHeadButDoNotAssignTo_Head().also { _head = it; } != null } fun nextHead(): TarsHead { if (!prepareNextHead()) { throw EOFException("No more TarsHead available") } return currentHead } /** * 直接读取下一个 [TarsHead] 并返回. * 返回 `null` 则代表 [Input.endOfInput] */ @Suppress("FunctionName") @OptIn(ExperimentalUnsignedTypes::class) private fun readNextHeadButDoNotAssignTo_Head( ignoreNextHead: Boolean = false, ): TarsHead? { if (!ignoreNextHead) { val n = _nextHead if (n != null) { _nextHead = null return n } } if (input.endOfInput) { return null } val var2 = try { input.readByte().toUByte() } catch (e: EOFException) { // somehow `endOfInput` still returns false return null } val type = var2 and 15u var tag = var2.toUInt() shr 4 if (tag == 15u) { tag = input.readByte().toUByte().toUInt() } return TarsHead( tag = tag.toInt(), type = type.toByte() ) } /** * 使用这个 [TarsHead]. * [block] 结束后将会 [准备下一个 [TarsHead]][prepareNextHead] */ inline fun <R> useHead(crossinline block: (TarsHead) -> R): R { return currentHead.let(block).also { prepareNextHead() } } /** * 跳过 [TarsHead] 和对应的数据值, 直到找到 [tag], 否则返回 `null` */ inline fun <R> skipToHeadAndUseIfPossibleOrNull(tag: Int, crossinline block: (TarsHead) -> R): R? { return skipToHeadOrNull(tag)?.let(block).also { prepareNextHead() } } /** * 跳过 [TarsHead] 和对应的数据值, 直到找到 [tag], 否则抛出异常 */ inline fun <R : Any> skipToHeadAndUseIfPossibleOrFail( tag: Int, crossinline message: () -> String = { "tag not found: $tag" }, crossinline block: (TarsHead) -> R, ): R { return checkNotNull(skipToHeadAndUseIfPossibleOrNull(tag, block), message) } @Suppress("MemberVisibilityCanBePrivate") fun skipToTag(tag: Int): Boolean { while (true) { val hd = peekNextHead() ?: return false if (tag <= hd.tag || hd.type == 11.toByte()) { return tag == hd.tag } debugLogger.println("Discard $tag, $hd, ${hd.size}") input.discardExact(hd.size) skipField(hd.type) } } @Suppress("MemberVisibilityCanBePrivate") fun readInt32(tag: Int): Int { if (!skipToTag(tag)) return 0 return readTarsIntValue(nextHead()) } tailrec fun skipToHeadOrNull(tag: Int): TarsHead? { val current: TarsHead = currentHeadOrNull ?: return null // no backing field return when { current.tag > tag -> null // tag 大了,即找不到 current.tag == tag -> current // 满足需要. else -> { // tag 小了 skipField(current.type) check(prepareNextHead()) { "cannot skip to tag $tag, early EOF" } skipToHeadOrNull(tag) } } } @OptIn(ExperimentalUnsignedTypes::class) @PublishedApi internal fun skipField(type: Byte) { debugLogger.println { "skipping ${ TarsHead.findTarsTypeName( type ) }" } when (type) { Tars.BYTE -> this.input.discardExact(1) Tars.SHORT -> this.input.discardExact(2) Tars.INT -> this.input.discardExact(4) Tars.LONG -> this.input.discardExact(8) Tars.FLOAT -> this.input.discardExact(4) Tars.DOUBLE -> this.input.discardExact(8) Tars.STRING1 -> this.input.discardExact(this.input.readByte().toUByte().toInt()) Tars.STRING4 -> this.input.discardExact(this.input.readInt()) Tars.MAP -> { // map debugLogger.structureHierarchy++ repeat(readInt32(0).also { debugLogger.println("SIZE = $it") } * 2) { skipField(nextHead().type) } debugLogger.structureHierarchy-- } Tars.LIST -> { // list debugLogger.structureHierarchy++ repeat(readInt32(0).also { debugLogger.println("SIZE = $it") }) { skipField(nextHead().type) } debugLogger.structureHierarchy-- } Tars.STRUCT_BEGIN -> { debugLogger.structureHierarchy++ var head: TarsHead do { head = nextHead() if (head.type == Tars.STRUCT_END) { debugLogger.structureHierarchy-- skipField(head.type) break } skipField(head.type) } while (head.type != Tars.STRUCT_END) } Tars.STRUCT_END, Tars.ZERO_TYPE -> { } Tars.SIMPLE_LIST -> { debugLogger.structureHierarchy++ var head = nextHead() check(head.type == Tars.BYTE) { "bad simple list element type: " + head.type + ", $head" } check(head.tag == 0) { "simple list element tag must be 0, but was ${head.tag}" } head = nextHead() check(head.tag == 0) { "tag for size for simple list must be 0, but was ${head.tag}" } this.input.discardExact(readTarsIntValue(head)) debugLogger.structureHierarchy-- } else -> error("invalid type: $type") } } // region readers fun readTarsIntValue(head: TarsHead): Int { //debugLogger.println("readTarsIntValue: $head") return readTarsIntValue(head.type, head) } @Suppress("MemberVisibilityCanBePrivate") fun readTarsIntValue(type: Byte, head: Any = type): Int { return when (type) { Tars.ZERO_TYPE -> 0 Tars.BYTE -> input.readByte().toInt() Tars.SHORT -> input.readShort().toInt() Tars.INT -> input.readInt() else -> error("type mismatch: $head, expecting int.") } } fun readTarsShortValue(head: TarsHead): Short { return when (head.type) { Tars.ZERO_TYPE -> 0 Tars.BYTE -> input.readByte().toShort() Tars.SHORT -> input.readShort() else -> error("type mismatch: $head, expecting short.") } } fun readTarsLongValue(head: TarsHead): Long { return when (head.type) { Tars.ZERO_TYPE -> 0 Tars.BYTE -> input.readByte().toLong() Tars.SHORT -> input.readShort().toLong() Tars.INT -> input.readInt().toLong() Tars.LONG -> input.readLong() else -> error("type mismatch ${head}, expecting long.") } } fun readTarsByteValue(head: TarsHead): Byte { //debugLogger.println("readTarsByteValue: $head") return when (head.type) { Tars.ZERO_TYPE -> 0 Tars.BYTE -> input.readByte() else -> error("type mismatch: $head, expecting byte.") } } fun readTarsFloatValue(head: TarsHead): Float { return when (head.type) { Tars.ZERO_TYPE -> 0f Tars.FLOAT -> input.readFloat() else -> error("type mismatch: $head, expecting float.") } } @OptIn(ExperimentalUnsignedTypes::class) fun readTarsStringValue(head: TarsHead): String { //debugLogger.println("readTarsStringValue: $head") return when (head.type) { Tars.STRING1 -> input.readString(input.readByte().toUByte().toInt(), charset = charset) Tars.STRING4 -> input.readString( input.readInt().toUInt().toInt().also { require(it in 1 until 104857600) { "bad string length: $it" } }, charset = charset ) else -> error("type mismatch: $head, expecting 6 or 7 (for string)") } } fun readTarsDoubleValue(head: TarsHead): Double { return when (head.type.toInt()) { 12 -> 0.0 4 -> input.readFloat().toDouble() 5 -> input.readDouble() else -> error("type mismatch: $head") } } fun readTarsBooleanValue(head: TarsHead): Boolean { return readTarsByteValue(head) == 1.toByte() } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsOld.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.io.serialization.tars.internal import io.ktor.utils.io.charsets.* import io.ktor.utils.io.core.* import kotlinx.serialization.* import kotlinx.serialization.builtins.MapEntrySerializer import kotlinx.serialization.builtins.SetSerializer import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.encoding.CompositeEncoder import kotlinx.serialization.internal.MapLikeSerializer import kotlinx.serialization.internal.TaggedEncoder import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.BYTE import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.DOUBLE import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.FLOAT import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.INT import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.LIST import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.LONG import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.MAP import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.SHORT import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.SIMPLE_LIST import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.STRING1 import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.STRING4 import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.STRUCT_BEGIN import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.STRUCT_END import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.Tars_MAX_STRING_LENGTH import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars.Companion.ZERO_TYPE import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId @OptIn(ExperimentalSerializationApi::class) internal inline fun <reified A : Annotation> SerialDescriptor.findAnnotation(elementIndex: Int): A? { val candidates = getElementAnnotations(elementIndex).filterIsInstance<A>() return when (candidates.size) { 0 -> null 1 -> candidates[0] else -> throw IllegalStateException("There are duplicate annotations of type ${A::class} in the descriptor $this") } } internal fun getSerialId(desc: SerialDescriptor, index: Int): Int? = desc.findAnnotation<TarsId>(index)?.id //@Suppress("DEPRECATION_ERROR") @OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class) internal class TarsOld internal constructor( private val charset: Charset, override val serializersModule: SerializersModule = EmptySerializersModule(), ) : SerialFormat, BinaryFormat { private inner class ListWriter( private val count: Int, private val tag: Int, private val parentEncoder: TarsEncoder, ) : TarsEncoder(BytePacketBuilder()) { override fun SerialDescriptor.getTag(index: Int): Int { return 0 } override fun endEncode(descriptor: SerialDescriptor) { parentEncoder.writeHead(LIST, this.tag) parentEncoder.encodeTaggedInt(0, count) parentEncoder.output.writePacket(this.output.build()) } } private inner class TarsMapWriter( output: BytePacketBuilder, ) : TarsEncoder(output) { override fun SerialDescriptor.getTag(index: Int): Int { return if (index % 2 == 0) 0 else 1 } /* override fun endEncode(desc: SerialDescriptor) { parentEncoder.writeHead(MAP, this.tag) parentEncoder.encodeTaggedInt(Int.STUB_FOR_PRIMITIVE_NUMBERS_GBK, count) // println(this.output.toByteArray().toUHexString()) parentEncoder.output.write(this.output.toByteArray()) }*/ } /** * From: com.qq.taf.Tars.TarsOutputStream */ @Suppress("unused", "MemberVisibilityCanBePrivate") private open inner class TarsEncoder( val output: BytePacketBuilder, ) : TaggedEncoder<Int>() { override val serializersModule get() = this@TarsOld.serializersModule override fun SerialDescriptor.getTag(index: Int): Int { return getSerialId(this, index) ?: error("cannot find @SerialId") } /** * 序列化最开始的时候的 */ override fun beginStructure( descriptor: SerialDescriptor, ): CompositeEncoder = when (descriptor.kind) { StructureKind.LIST -> this StructureKind.MAP -> this StructureKind.CLASS, StructureKind.OBJECT -> this is PolymorphicKind -> this else -> throw SerializationException("Primitives are not supported at top-level") } @Suppress("UNCHECKED_CAST", "NAME_SHADOWING") override fun <T> encodeSerializableValue(serializer: SerializationStrategy<T>, value: T) = when { serializer.descriptor.kind == StructureKind.MAP -> { try { val entries = (value as Map<*, *>).entries val serializer = (serializer as MapLikeSerializer<Any?, Any?, T, *>) val mapEntrySerial = MapEntrySerializer(serializer.keySerializer, serializer.valueSerializer) this.writeHead(MAP, currentTag) this.encodeTaggedInt(0, entries.count()) SetSerializer(mapEntrySerial).serialize(TarsMapWriter(this.output), entries) } catch (e: Exception) { super.encodeSerializableValue(serializer, value) } } serializer.descriptor.kind == StructureKind.LIST && value is ByteArray -> encodeTaggedByteArray(popTag(), value as ByteArray) serializer.descriptor.kind == StructureKind.LIST && serializer.descriptor.getElementDescriptor(0) is PrimitiveKind -> { serializer.serialize( ListWriter( when (value) { is ShortArray -> value.size is IntArray -> value.size is LongArray -> value.size is FloatArray -> value.size is DoubleArray -> value.size is CharArray -> value.size is ByteArray -> value.size is BooleanArray -> value.size else -> error("unknown array type: ${value.getClassName()}") }, popTag(), this ), value ) } serializer.descriptor.kind == StructureKind.LIST && value is Array<*> -> { if (serializer.descriptor.getElementDescriptor(0).kind is PrimitiveKind.BYTE) { encodeTaggedByteArray(popTag(), (value as Array<Byte>).toByteArray()) } else serializer.serialize( ListWriter((value as Array<*>).size, popTag(), this), value ) } serializer.descriptor.kind == StructureKind.LIST -> { serializer.serialize( ListWriter((value as Collection<*>).size, popTag(), this), value ) } serializer.descriptor.kind == StructureKind.CLASS -> { if (currentTagOrNull == null) { serializer.serialize(this, value) } else { this.writeHead(STRUCT_BEGIN, popTag()) serializer.serialize(TarsEncoder(this.output), value) this.writeHead(STRUCT_END, 0) } } else -> { serializer.serialize(this, value) } } public override fun encodeTaggedByte(tag: Int, value: Byte) { if (value.toInt() == 0) { writeHead(ZERO_TYPE, tag) } else { writeHead(BYTE, tag) output.writeByte(value) } } public override fun encodeTaggedShort(tag: Int, value: Short) { if (value in Byte.MIN_VALUE..Byte.MAX_VALUE) { encodeTaggedByte(tag, value.toByte()) } else { writeHead(SHORT, tag) output.writeShort(value) } } public override fun encodeTaggedInt(tag: Int, value: Int) { if (value in Short.MIN_VALUE..Short.MAX_VALUE) { encodeTaggedShort(tag, value.toShort()) } else { writeHead(INT, tag) output.writeInt(value) } } public override fun encodeTaggedFloat(tag: Int, value: Float) { writeHead(FLOAT, tag) output.writeFloat(value) } public override fun encodeTaggedDouble(tag: Int, value: Double) { writeHead(DOUBLE, tag) output.writeDouble(value) } public override fun encodeTaggedLong(tag: Int, value: Long) { if (value in Int.MIN_VALUE..Int.MAX_VALUE) { encodeTaggedInt(tag, value.toInt()) } else { writeHead(LONG, tag) output.writeLong(value) } } public override fun encodeTaggedBoolean(tag: Int, value: Boolean) { encodeTaggedByte(tag, if (value) 1 else 0) } public override fun encodeTaggedChar(tag: Int, value: Char) { encodeTaggedByte(tag, value.code.toByte()) } public override fun encodeTaggedEnum(tag: Int, enumDescriptor: SerialDescriptor, ordinal: Int) { encodeTaggedInt(tag, ordinal) } public override fun encodeTaggedNull(tag: Int) { } fun encodeTaggedByteArray(tag: Int, bytes: ByteArray) { writeHead(SIMPLE_LIST, tag) writeHead(BYTE, 0) encodeTaggedInt(0, bytes.size) output.writeFully(bytes) } public override fun encodeTaggedString(tag: Int, value: String) { require(value.length <= Tars_MAX_STRING_LENGTH) { "string is too long for tag $tag" } val array = value.toByteArray(charset) if (array.size > 255) { writeHead(STRING4, tag) output.writeInt(array.size) output.writeFully(array) } else { writeHead(STRING1, tag) output.writeByte(array.size.toByte()) // one byte output.writeFully(array) } } override fun encodeTaggedValue(tag: Int, value: Any) { when (value) { is Byte -> encodeTaggedByte(tag, value) is Short -> encodeTaggedShort(tag, value) is Int -> encodeTaggedInt(tag, value) is Long -> encodeTaggedLong(tag, value) is Float -> encodeTaggedFloat(tag, value) is Double -> encodeTaggedDouble(tag, value) is Boolean -> encodeTaggedBoolean(tag, value) is String -> encodeTaggedString(tag, value) is Unit -> { } else -> error("unsupported type: ${value.getClassName()}") } } fun writeHead(type: Byte, tag: Int) { if (tag < 15) { this.output.writeByte(((tag shl 4) or type.toInt()).toByte()) return } if (tag < 256) { this.output.writeByte((type.toInt() or 0xF0).toByte()) this.output.writeByte(tag.toByte()) return } error("tag is too large: $tag") } } @Suppress("MemberVisibilityCanBePrivate") companion object : BinaryFormat by TarsOld(Charsets.UTF_8) { private fun Any?.getClassName(): String = (if (this == null) Unit::class else this::class).simpleName?.split(".")?.takeLast(2)?.joinToString(".") ?: "<unnamed class>" } fun <T> dumpAsPacket(serializer: SerializationStrategy<T>, obj: T): ByteReadPacket { val encoder = BytePacketBuilder() val dumper = TarsEncoder(encoder) dumper.encodeSerializableValue(serializer, obj) return encoder.build() } override fun <T> encodeToByteArray(serializer: SerializationStrategy<T>, value: T): ByteArray { return dumpAsPacket(serializer, value).readBytes() } override fun <T> decodeFromByteArray(deserializer: DeserializationStrategy<T>, bytes: ByteArray): T { error("Use TarsNew.") } } internal fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length), charset = charset) ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/serialization/tars/internal/TarsTag.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.io.serialization.tars.internal import io.ktor.utils.io.core.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.descriptors.SerialDescriptor import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars internal abstract class TarsTag { abstract val id: Int internal var isSimpleByteArray: Boolean = false } // For thread-safely, Don't use object internal class TarsTagListElement : TarsTag() { override val id: Int get() = 0 override fun toString(): String { return "TarsTagListElement" } } // For thread-safely, Don't use object internal class TarsTagMapEntryKey : TarsTag() { override val id: Int get() = 0 override fun toString(): String { return "TarsTagMapEntryKey" } } // For thread-safely, Don't use object internal class TarsTagMapEntryValue : TarsTag() { override val id: Int get() = 1 override fun toString(): String { return "TarsTagMapEntryValue" } } internal data class TarsTagCommon( override val id: Int, ) : TarsTag() @OptIn(ExperimentalSerializationApi::class) internal fun TarsHead.checkType(type: Byte, message: String, tag: TarsTag, descriptor: SerialDescriptor) { check(this.type == type) { "type mismatch. " + "Expected ${TarsHead.findTarsTypeName(type)}, " + "actual ${TarsHead.findTarsTypeName(this.type)} for $message. " + "Tag info: " + "id=${tag.id}, " + "name=${descriptor.getElementName(tag.id)} " + "in ${descriptor.serialName}" } } @PublishedApi internal fun Output.writeTarsHead(type: Byte, tag: Int) { if (tag < 15) { writeByte(((tag shl 4) or type.toInt()).toByte()) return } if (tag < 256) { writeByte((type.toInt() or 0xF0).toByte()) writeByte(tag.toByte()) return } error("tag is too large: $tag") } @OptIn(ExperimentalUnsignedTypes::class) internal class TarsHead(private val value: Long) { constructor(tag: Int, type: Byte) : this(tag.toLong().shl(32) or type.toLong()) val tag: Int get() = (value ushr 32).toInt() val type: Byte get() = value.toUInt().toByte() val size: Int get() { if (tag < 15) { return 1 } if (tag < 256) { return 2 } error("tag is too large: $tag") } override fun toString(): String { return "TarsHead(tag=$tag, type=$type(${findTarsTypeName(type)}))" } companion object { fun findTarsTypeName(type: Byte): String { return when (type) { Tars.BYTE -> "Byte" Tars.DOUBLE -> "Double" Tars.FLOAT -> "Float" Tars.INT -> "Int" Tars.LIST -> "List" Tars.LONG -> "Long" Tars.MAP -> "Map" Tars.SHORT -> "Short" Tars.SIMPLE_LIST -> "SimpleList" Tars.STRING1 -> "String1" Tars.STRING4 -> "String4" Tars.STRUCT_BEGIN -> "StructBegin" Tars.STRUCT_END -> "StructEnd" Tars.ZERO_TYPE -> "Zero" else -> error("illegal Tars type: $type") } } } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/io/serialization/utils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("SerializationUtils") @file:JvmMultifileClass @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.internal.utils.io.serialization import io.ktor.utils.io.core.* import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor import net.mamoe.mirai.internal.message.contextualBugReportException import net.mamoe.mirai.internal.network.protocol.data.jce.RequestDataVersion2 import net.mamoe.mirai.internal.network.protocol.data.jce.RequestDataVersion3 import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.internal.network.protocol.data.proto.OidbSso import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars import net.mamoe.mirai.internal.utils.io.serialization.tars.internal.DebugLogger import net.mamoe.mirai.internal.utils.io.serialization.tars.internal.TarsDecoder import net.mamoe.mirai.internal.utils.printStructure import net.mamoe.mirai.utils.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmInline import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName internal typealias KtProtoBuf = kotlinx.serialization.protobuf.ProtoBuf internal fun <T : JceStruct> ByteArray.loadWithUniPacket( deserializer: DeserializationStrategy<T>, name: String? = null, ): T = this.read { readUniPacket(deserializer, name) } internal fun <T : JceStruct> ByteArray.loadAs( deserializer: DeserializationStrategy<T>, offset: Int = 0, length: Int = size - offset, ): T { if (this.size >= 4) { val possibleLength = this.toInt(offset = offset) // return doLoadAs(deserializer, offset = offset + 4, length = possibleLength) if (possibleLength == length || possibleLength == length - 4) { return doLoadAs( deserializer, offset = offset + 4, length = length - 4 ) } } return doLoadAs(deserializer, offset, length) } private fun <T : JceStruct> ByteArray.doLoadAs( deserializer: DeserializationStrategy<T>, offset: Int, length: Int, ): T { try { return this.toReadPacket(offset = offset, length = length).use { input -> Tars.UTF_8.load(deserializer, input) } } catch (originalException: Exception) { BytePacketBuilder().use { log -> val build by lazy { log.build() } try { log.appendLine("\nData: ") log.appendLine(this.toUHexString(offset = offset, length = length)) log.appendLine("Trace:") val value = this.toReadPacket(offset = offset, length = length).use { input -> Tars.UTF_8.load(deserializer, input, debugLogger = DebugLogger(log)) } return value.also { TarsDecoder.logger.warning( contextualBugReportException( "解析 " + deserializer.descriptor.serialName, "启用 debug 模式后解析正常: $value \n\n${build.readText()}", originalException ) ) } } catch (secondFailure: Exception) { throw contextualBugReportException( "解析 " + deserializer.descriptor.serialName, build.readAllText(), ExceptionCollector.compressExceptions(originalException, secondFailure) ) } } } } internal fun <T : JceStruct> BytePacketBuilder.writeJceStruct( serializer: SerializationStrategy<T>, struct: T, ) { Tars.UTF_8.dumpTo(serializer, struct, this) } internal fun <T : JceStruct> ByteReadPacket.readJceStruct( deserializer: DeserializationStrategy<T>, length: Int = this.remaining.toInt(), ): T { return this.useBytes(n = length) { data, arrayLength -> data.loadAs(deserializer, offset = 0, length = arrayLength) } } internal fun <T : JceStruct> BytePacketBuilder.writeJceRequestPacket( version: Int = 3, servantName: String, funcName: String, name: String = funcName, serializer: SerializationStrategy<T>, body: T, ) = writeJceStruct( RequestPacket.serializer(), RequestPacket( requestId = 0, version = version.toShort(), servantName = servantName, funcName = funcName, sBuffer = jceRequestSBuffer(name, serializer, body), ), ) /** * 先解析为 [RequestPacket], 即 `UniRequest`, 再按版本解析 map, 再找出指定数据并反序列化 */ internal fun <T : JceStruct> ByteReadPacket.readUniPacket( deserializer: DeserializationStrategy<T>, name: String? = null, ): T { return decodeUniRequestPacketAndDeserialize(name) { it.read { discardExact(1) this.readJceStruct(deserializer, length = (this.remaining - 1).toInt()) } } } /** * 先解析为 [RequestPacket], 即 `UniRequest`, 再按版本解析 map, 再找出指定数据并反序列化 */ internal fun <T : ProtoBuf> ByteReadPacket.readUniPacket( deserializer: DeserializationStrategy<T>, name: String? = null, ): T { return decodeUniRequestPacketAndDeserialize(name) { it.read { discardExact(1) this.readProtoBuf(deserializer, (this.remaining - 1).toInt()) } } } private fun <K, V> Map<K, V>.singleValue(): V = this.entries.single().value internal fun <R> ByteReadPacket.decodeUniRequestPacketAndDeserialize(name: String? = null, block: (ByteArray) -> R): R { val request = this.readJceStruct(RequestPacket.serializer()) return block( if (name == null) when (request.version?.toInt() ?: 3) { 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.singleValue().singleValue() 3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.singleValue() else -> error("unsupported version ${request.version}") } else when (request.version?.toInt() ?: 3) { 2 -> request.sBuffer.loadAs(RequestDataVersion2.serializer()).map.getOrElse(name) { error("cannot find $name") } .singleValue() 3 -> request.sBuffer.loadAs(RequestDataVersion3.serializer()).map.getOrElse(name) { error("cannot find $name") } else -> error("unsupported version ${request.version}") }, ) } internal fun <T : JceStruct> T.toByteArray( serializer: SerializationStrategy<T>, ): ByteArray = Tars.UTF_8.encodeToByteArray(serializer, this) internal fun <T : ProtoBuf> BytePacketBuilder.writeProtoBuf(serializer: SerializationStrategy<T>, v: T) { this.writeFully(v.toByteArray(serializer)) } internal fun <T : ProtoBuf> BytePacketBuilder.writeOidb( command: Int = 0, serviceType: Int = 0, serializer: SerializationStrategy<T>, v: T, clientVersion: String = "android 8.4.8", ) { return this.writeProtoBuf( OidbSso.OIDBSSOPkg.serializer(), OidbSso.OIDBSSOPkg( command = command, serviceType = serviceType, clientVersion = clientVersion, bodybuffer = v.toByteArray(serializer), ), ) } /** * dump */ internal fun <T : ProtoBuf> T.toByteArray(serializer: SerializationStrategy<T>): ByteArray { return KtProtoBuf.encodeToByteArray(serializer, this) } /** * load */ internal fun <T : ProtoBuf> ByteArray.loadAs(deserializer: DeserializationStrategy<T>, offset: Int = 0): T { if (offset != 0) { require(offset in offset..this.lastIndex) { "invalid offset: $offset" } return this.copyOfRange(offset, this.size).loadAs(deserializer) } return KtProtoBuf.decodeFromByteArray(deserializer, this) } internal fun <T : ProtoBuf> ByteArray.loadOidb(deserializer: DeserializationStrategy<T>, log: Boolean = false): T { val oidb = loadAs(OidbSso.OIDBSSOPkg.serializer()) if (log) { oidb.printStructure("OIDB") } return oidb.bodybuffer.loadAs(deserializer) } /** * load */ internal fun <T : ProtoBuf> ByteReadPacket.readProtoBuf( serializer: DeserializationStrategy<T>, length: Int = this.remaining.toInt(), ): T = KtProtoBuf.decodeFromByteArray(serializer, this.readBytes(length)) @Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") @JvmInline internal value class OidbBodyOrFailure<T : ProtoBuf> private constructor( private val v: Any, ) { internal class Failure( val oidb: OidbSso.OIDBSSOPkg, ) inline fun <R> fold( onSuccess: T.(T) -> R, onFailure: OidbSso.OIDBSSOPkg.(OidbSso.OIDBSSOPkg) -> R, ): R { contract { callsInPlace(onSuccess, InvocationKind.AT_MOST_ONCE) callsInPlace(onFailure, InvocationKind.AT_MOST_ONCE) } @Suppress("UNCHECKED_CAST") return if (v is Failure) { onFailure(v.oidb, v.oidb) } else { val t = v as T onSuccess(t, t) } } companion object { fun <T : ProtoBuf> success(t: T): OidbBodyOrFailure<T> = OidbBodyOrFailure(t) fun <T : ProtoBuf> failure(oidb: OidbSso.OIDBSSOPkg): OidbBodyOrFailure<T> = OidbBodyOrFailure(Failure(oidb)) } } /** * load */ internal inline fun <T : ProtoBuf> ByteReadPacket.readOidbSsoPkg( serializer: DeserializationStrategy<T>, length: Int = this.remaining.toInt(), ): OidbBodyOrFailure<T> { val oidb = readBytes(length).loadAs(OidbSso.OIDBSSOPkg.serializer()) return if (oidb.result == 0) { OidbBodyOrFailure.success(oidb.bodybuffer.loadAs(serializer)) } else { OidbBodyOrFailure.failure(oidb) } } /** * 构造 [RequestPacket] 的 [RequestPacket.sBuffer] */ internal fun <T : JceStruct> jceRequestSBuffer( name: String, serializer: SerializationStrategy<T>, jceStruct: T, ): ByteArray { return RequestDataVersion3( mapOf( name to JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0, ), ).toByteArray(RequestDataVersion3.serializer()) } internal inline fun jceRequestSBuffer(block: JceRequestSBufferBuilder.() -> Unit): ByteArray { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return JceRequestSBufferBuilder().apply(block).complete() } internal class JceRequestSBufferBuilder { val map: MutableMap<String, ByteArray> = LinkedHashMap() operator fun <T : JceStruct> String.invoke( serializer: SerializationStrategy<T>, jceStruct: T, ) { map[this] = JCE_STRUCT_HEAD_OF_TAG_0 + jceStruct.toByteArray(serializer) + JCE_STRUCT_TAIL_OF_TAG_0 } fun complete(): ByteArray = RequestDataVersion3(map).toByteArray(RequestDataVersion3.serializer()) } private val JCE_STRUCT_HEAD_OF_TAG_0 = byteArrayOf(0x0A) private val JCE_STRUCT_TAIL_OF_TAG_0 = byteArrayOf(0x0B) internal inline fun <reified A : Annotation> SerialDescriptor.findAnnotation(elementIndex: Int): A? { val candidates = getElementAnnotations(elementIndex).filterIsInstance<A>() return when (candidates.size) { 0 -> null 1 -> candidates[0] else -> throw IllegalStateException("There are duplicate annotations of type ${A::class} in the descriptor $this") } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/numbers.kt ================================================ /* * 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.utils /** * 要求 [this] 最小为 [min]. */ @PublishedApi internal fun Int.coerceAtLeastOrFail(min: Int): Int { require(this >= min) return this } /** * 要求 [this] 最小为 [min]. */ @PublishedApi internal fun Long.coerceAtLeastOrFail(min: Long): Long { require(this >= min) return this } /** * 要求 [this] 最大为 [max]. */ @PublishedApi internal fun Int.coerceAtMostOrFail(max: Int): Int = if (this >= max) error("value $this is greater than its expected maximum value $max") else this @PublishedApi internal fun Long.coerceAtMostOrFail(max: Long): Long = if (this >= max) error("value $this is greater than its expected maximum value $max") else this ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/printStructure.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.StructureToStringTransformer import net.mamoe.mirai.utils.structureToString private val SoutvLogger: MiraiLogger by lazy { MiraiLogger.Factory.create( StructureToStringTransformer::class, "printStructurally" ) } internal fun Any?.printStructure(name: String = "unnamed") { return SoutvLogger.debug { "$name = ${this.structureToString()}" } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/retryWithServers.kt ================================================ /* * 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.utils import kotlinx.coroutines.withTimeoutOrNull import net.mamoe.mirai.utils.cast import kotlin.math.roundToInt internal suspend inline fun <R, reified IP> Collection<Pair<IP, Int>>.retryWithServers( timeoutMillis: Long, onFail: (exception: Throwable?) -> Nothing, crossinline block: suspend (ip: String, port: Int) -> R, ): R { require(this.isNotEmpty()) { "receiver of retryWithServers must not be empty" } var exception: Throwable? = null for (pair in this) { return kotlin.runCatching { withTimeoutOrNull(timeoutMillis) { if (IP::class == Int::class) { block(pair.first.cast<Int>().toIpV4AddressString(), pair.second) } else { block(pair.first.toString(), pair.second) } } }.recover { if (exception != null) { it.addSuppressed(exception!!) } exception = it // so as to show last exception followed by suppressed others null }.getOrNull() ?: continue } onFail(exception) } internal fun Int.sizeToString() = this.toLong().sizeToString() internal fun Long.sizeToString(): String { return if (this < 1024) { "$this B" } else ((this * 100.0 / 1024).roundToInt() / 100.0).toString() + " KiB" } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/runCoroutineInPlace.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking import net.mamoe.mirai.utils.MiraiLogger import kotlin.coroutines.Continuation import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED import kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn /** * Runs the [coroutine] directly in current thread, **expecting no suspension**. */ internal fun <R> runCoroutineInPlace(coroutine: suspend () -> R): R { var lateResult: CompletableDeferred<R>? = null val result = coroutine.startCoroutineUninterceptedOrReturn(Continuation(EmptyCoroutineContext) { r -> val deferred: CompletableDeferred<R>? = lateResult @Suppress("KotlinConstantConditions") if (deferred != null) { r.fold(onSuccess = { deferred.complete(it) }, onFailure = { deferred.completeExceptionally(it) }) } else { if (logger.isErrorEnabled) { logger.error(IllegalStateException("runCoroutineInPlace reached an unexpected state: coroutine did not finish. ()")) } } }) if (result != COROUTINE_SUSPENDED) { @Suppress("UNCHECKED_CAST") return result as R } lateResult = CompletableDeferred() if (logger.isErrorEnabled) { logger.error(IllegalStateException("runCoroutineInPlace reached an unexpected state: coroutine did not finish.")) } return runBlocking { lateResult.await() } } private val myStubFailure = Exception() private class RunCoroutineInPlace private val logger by lazy { MiraiLogger.Factory.create(RunCoroutineInPlace::class) } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/string.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.contact.info.MemberInfoImpl import net.mamoe.mirai.utils.MiraiInternalApi @Serializable internal data class MessageData( val data: String, val cmd: Int, val text: String, ) internal fun MessageData.toMemberInfo() = MemberInfoImpl( uin = data.toLong(), nick = text, permission = MemberPermission.MEMBER, remark = "", nameCard = "", specialTitle = "", muteTimestamp = 0, anonymousId = null, isOfficialBot = true ) @Suppress("RegExpRedundantEscape") internal val extraJsonPattern = Regex("<(\\{.*?\\})>") internal val jsonInstance = Json { ignoreUnknownKeys = true } @MiraiInternalApi internal fun String.parseToMessageDataList(): Sequence<MessageData> { return extraJsonPattern.findAll(this).filter { it.groups.size == 2 }.mapNotNull { result -> jsonInstance.decodeFromString(MessageData.serializer(), result.groups[1]!!.value) } } ================================================ FILE: mirai-core/src/commonMain/kotlin/utils/type.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.internal.message.data.ForwardMessageInternal import net.mamoe.mirai.internal.message.protocol.impl.ForwardMessageProtocol import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.chineseLength internal fun Int.toIpV4AddressString(): String { @Suppress("NAME_SHADOWING") var var0 = this.toLong() and 0xFFFFFFFF return buildString { for (var2 in 3 downTo 0) { append(255L and var0 % 256L) var0 /= 256L if (var2 != 0) { append('.') } } } } internal fun Iterable<SingleMessage>.estimateLength(target: ContactOrBot, upTo: Int): Int = sumUpTo(upTo) { it, up -> it.estimateLength(target, up) } /** * @see ForwardMessageProtocol.ForwardMessageUploader.checkLength */ internal fun SingleMessage.estimateLength(target: ContactOrBot, upTo: Int): Int { return when (this) { is QuoteReply -> 444 + this.source.originalMessage.estimateLength(target, upTo) // Magic number is Image -> 260 //Magic number is PlainText -> content.chineseLength(upTo) is At -> 60 //Magic number is AtAll -> 60 //Magic number is ForwardMessageInternal -> 0 // verified in ForwardMessageProtocol.ForwardMessageUploader.checkLength else -> this.toString().chineseLength(upTo) } } internal inline fun <T> Iterable<T>.sumUpTo(upTo: Int, selector: (T, remaining: Int) -> Int): Int { var sum = 0 for (element in this) { if (sum >= upTo) { return sum } sum += selector(element, (upTo - sum).coerceAtLeast(0)) } return sum } ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.IMirai ================================================ # # 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/dev/LICENSE # net.mamoe.mirai.internal.MiraiImpl ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.auth.DefaultBotAuthorizationFactory ================================================ # # Copyright 2019-2023 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/dev/LICENSE # net.mamoe.mirai.internal.network.auth.DefaultBotAuthorizationFactoryImpl ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.event.InternalGlobalEventChannelProvider ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.mirai.internal.event.GlobalEventChannelProviderImpl ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.internal.message.protocol.MessageProtocol ================================================ # # Copyright 2019-2023 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/dev/LICENSE # net.mamoe.mirai.internal.message.protocol.impl.AudioProtocol net.mamoe.mirai.internal.message.protocol.impl.CustomMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.FaceProtocol net.mamoe.mirai.internal.message.protocol.impl.FileMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.FlashImageProtocol net.mamoe.mirai.internal.message.protocol.impl.IgnoredMessagesProtocol net.mamoe.mirai.internal.message.protocol.impl.ImageProtocol net.mamoe.mirai.internal.message.protocol.impl.MarketFaceProtocol net.mamoe.mirai.internal.message.protocol.impl.SuperFaceProtocol net.mamoe.mirai.internal.message.protocol.impl.MusicShareProtocol net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.PttMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol net.mamoe.mirai.internal.message.protocol.impl.TextProtocol net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol net.mamoe.mirai.internal.message.protocol.impl.ForwardMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.LongMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.UnsupportedMessageProtocol net.mamoe.mirai.internal.message.protocol.impl.GeneralMessageSenderProtocol ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.message.data.InternalImageProtocol ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.mirai.internal.message.image.InternalImageProtocolImpl ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.message.data.InternalShortVideoProtocol ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.mirai.internal.message.image.InternalShortVideoProtocolImpl ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.message.data.OfflineAudio$Factory ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.mirai.internal.message.data.OfflineAudioFactoryImpl ================================================ FILE: mirai-core/src/commonMain/resources/META-INF/services/net.mamoe.mirai.utils.InternalProtocolDataExchange ================================================ # # Copyright 2019-2023 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/dev/LICENSE # net.mamoe.mirai.internal.utils.MiraiProtocolInternal$Exchange ================================================ FILE: mirai-core/src/commonMain/resources/emoji-pattern.regex ================================================ \x{1F3F4}\x{E0067}\x{E0062}(?:\x{E0077}\x{E006C}\x{E0073}|\x{E0073}\x{E0063}\x{E0074}|\x{E0065}\x{E006E}\x{E0067})\x{E007F}|(?:\x{1F9D1}\x{1F3FF}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FF}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FF}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FE}]|(?:\x{1F9D1}\x{1F3FE}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FE}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FE}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FD}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FD}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FD}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FC}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FC}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FC}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FB}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FB}\x{200D}\x{1FAF2})[\x{1F3FC}-\x{1F3FF}]|\x{1F468}(?:\x{1F3FB}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{1F91D}\x{200D}\x{1F468}[\x{1F3FC}-\x{1F3FF}]|[\x{2695}\x{2696}\x{2708}]\x{FE0F}|[\x{2695}\x{2696}\x{2708}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]))?|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F468}|[\x{1F468}\x{1F469}]\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FE}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FE}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FD}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FC}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])\x{FE0F}|\x{200D}(?:[\x{1F468}\x{1F469}]\x{200D}[\x{1F466}\x{1F467}]|[\x{1F466}\x{1F467}])|\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{200D}[\x{2695}\x{2696}\x{2708}])?|(?:\x{1F469}(?:\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])))|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]\x{200D}\x{1F91D}\x{200D}\x{1F9D1})[\x{1F3FB}-\x{1F3FF}]|\x{1F469}\x{200D}\x{1F469}\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F469}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F9D1}(?:\x{200D}(?:\x{1F91D}\x{200D}\x{1F9D1}|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}[\x{1F466}\x{1F467}]|\x{1F469}\x{200D}\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|(?:\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2757}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}])\x{FE0F}|\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F3F3}\x{FE0F}?\x{200D}\x{1F308}|\x{1F469}\x{200D}\x{1F467}|\x{1F469}\x{200D}\x{1F466}|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F635}\x{200D}\x{1F4AB}|\x{1F62E}\x{200D}\x{1F4A8}|\x{1F415}\x{200D}\x{1F9BA}|\x{1FAF1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F9D1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F469}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|\x{1F1FD}\x{1F1F0}|\x{1F1F6}\x{1F1E6}|\x{1F1F4}\x{1F1F2}|\x{1F408}\x{200D}\x{2B1B}|\x{2764}(?:\x{FE0F}\x{200D}[\x{1F525}\x{1FA79}]|\x{200D}[\x{1F525}\x{1FA79}])|\x{1F441}\x{FE0F}?|\x{1F3F3}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|\x{1F1FF}[\x{1F1E6}\x{1F1F2}\x{1F1FC}]|\x{1F1FE}[\x{1F1EA}\x{1F1F9}]|\x{1F1FC}[\x{1F1EB}\x{1F1F8}]|\x{1F1FB}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1EE}\x{1F1F3}\x{1F1FA}]|\x{1F1FA}[\x{1F1E6}\x{1F1EC}\x{1F1F2}\x{1F1F3}\x{1F1F8}\x{1F1FE}\x{1F1FF}]|\x{1F1F9}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1ED}\x{1F1EF}-\x{1F1F4}\x{1F1F7}\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FF}]|\x{1F1F8}[\x{1F1E6}-\x{1F1EA}\x{1F1EC}-\x{1F1F4}\x{1F1F7}-\x{1F1F9}\x{1F1FB}\x{1F1FD}-\x{1F1FF}]|\x{1F1F7}[\x{1F1EA}\x{1F1F4}\x{1F1F8}\x{1F1FA}\x{1F1FC}]|\x{1F1F5}[\x{1F1E6}\x{1F1EA}-\x{1F1ED}\x{1F1F0}-\x{1F1F3}\x{1F1F7}-\x{1F1F9}\x{1F1FC}\x{1F1FE}]|\x{1F1F3}[\x{1F1E6}\x{1F1E8}\x{1F1EA}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F4}\x{1F1F5}\x{1F1F7}\x{1F1FA}\x{1F1FF}]|\x{1F1F2}[\x{1F1E6}\x{1F1E8}-\x{1F1ED}\x{1F1F0}-\x{1F1FF}]|\x{1F1F1}[\x{1F1E6}-\x{1F1E8}\x{1F1EE}\x{1F1F0}\x{1F1F7}-\x{1F1FB}\x{1F1FE}]|\x{1F1F0}[\x{1F1EA}\x{1F1EC}-\x{1F1EE}\x{1F1F2}\x{1F1F3}\x{1F1F5}\x{1F1F7}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1EF}[\x{1F1EA}\x{1F1F2}\x{1F1F4}\x{1F1F5}]|\x{1F1EE}[\x{1F1E8}-\x{1F1EA}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}]|\x{1F1ED}[\x{1F1F0}\x{1F1F2}\x{1F1F3}\x{1F1F7}\x{1F1F9}\x{1F1FA}]|\x{1F1EC}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EE}\x{1F1F1}-\x{1F1F3}\x{1F1F5}-\x{1F1FA}\x{1F1FC}\x{1F1FE}]|\x{1F1EB}[\x{1F1EE}-\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1F7}]|\x{1F1EA}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1ED}\x{1F1F7}-\x{1F1FA}]|\x{1F1E9}[\x{1F1EA}\x{1F1EC}\x{1F1EF}\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1FF}]|\x{1F1E8}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1EE}\x{1F1F0}-\x{1F1F5}\x{1F1F7}\x{1F1FA}-\x{1F1FF}]|\x{1F1E7}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EF}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1E6}[\x{1F1E8}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F2}\x{1F1F4}\x{1F1F6}-\x{1F1FA}\x{1F1FC}\x{1F1FD}\x{1F1FF}]|[#\*0-9]\x{FE0F}?\x{20E3}|\x{1F93C}[\x{1F3FB}-\x{1F3FF}]|\x{2764}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]?|\x{1F3F4}|[\x{270A}\x{270B}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F57A}\x{1F595}\x{1F596}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}][\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270C}\x{270D}\x{1F574}\x{1F590}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270A}-\x{270D}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F408}\x{1F415}\x{1F43B}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F574}\x{1F57A}\x{1F590}\x{1F595}\x{1F596}\x{1F62E}\x{1F635}\x{1F636}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F93C}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}]|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2757}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}]|[\x{23E9}-\x{23EC}\x{23F0}\x{23F3}\x{25FD}\x{2693}\x{26A1}\x{26AB}\x{26C5}\x{26CE}\x{26D4}\x{26EA}\x{26FD}\x{2705}\x{2728}\x{274C}\x{274E}\x{2753}-\x{2755}\x{2795}-\x{2797}\x{27B0}\x{27BF}\x{2B50}\x{1F0CF}\x{1F18E}\x{1F191}-\x{1F19A}\x{1F201}\x{1F21A}\x{1F22F}\x{1F232}-\x{1F236}\x{1F238}-\x{1F23A}\x{1F250}\x{1F251}\x{1F300}-\x{1F320}\x{1F32D}-\x{1F335}\x{1F337}-\x{1F37C}\x{1F37E}-\x{1F384}\x{1F386}-\x{1F393}\x{1F3A0}-\x{1F3C1}\x{1F3C5}\x{1F3C6}\x{1F3C8}\x{1F3C9}\x{1F3CF}-\x{1F3D3}\x{1F3E0}-\x{1F3F0}\x{1F3F8}-\x{1F407}\x{1F409}-\x{1F414}\x{1F416}-\x{1F43A}\x{1F43C}-\x{1F43E}\x{1F440}\x{1F444}\x{1F445}\x{1F451}-\x{1F465}\x{1F46A}\x{1F479}-\x{1F47B}\x{1F47D}-\x{1F480}\x{1F484}\x{1F488}-\x{1F48E}\x{1F490}\x{1F492}-\x{1F4A9}\x{1F4AB}-\x{1F4FC}\x{1F4FF}-\x{1F53D}\x{1F54B}-\x{1F54E}\x{1F550}-\x{1F567}\x{1F5A4}\x{1F5FB}-\x{1F62D}\x{1F62F}-\x{1F634}\x{1F637}-\x{1F644}\x{1F648}-\x{1F64A}\x{1F680}-\x{1F6A2}\x{1F6A4}-\x{1F6B3}\x{1F6B7}-\x{1F6BF}\x{1F6C1}-\x{1F6C5}\x{1F6D0}-\x{1F6D2}\x{1F6D5}-\x{1F6D7}\x{1F6DD}-\x{1F6DF}\x{1F6EB}\x{1F6EC}\x{1F6F4}-\x{1F6FC}\x{1F7E0}-\x{1F7EB}\x{1F7F0}\x{1F90D}\x{1F90E}\x{1F910}-\x{1F917}\x{1F920}-\x{1F925}\x{1F927}-\x{1F92F}\x{1F93A}\x{1F93F}-\x{1F945}\x{1F947}-\x{1F976}\x{1F978}-\x{1F9B4}\x{1F9B7}\x{1F9BA}\x{1F9BC}-\x{1F9CC}\x{1F9D0}\x{1F9E0}-\x{1F9FF}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7C}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAAC}\x{1FAB0}-\x{1FABA}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD9}\x{1FAE0}-\x{1FAE7}] ================================================ FILE: mirai-core/src/commonTest/kotlin/AbstractTestWithMiraiImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal import net.mamoe.mirai.Mirai import kotlin.test.AfterTest import kotlin.test.BeforeTest internal abstract class AbstractTestWithMiraiImpl : MiraiImpl() { private val originalImpl = Mirai @BeforeTest fun setupMiraiImpl() { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") net.mamoe.mirai._MiraiInstance.set(this) } @AfterTest fun restoreMiraiImpl() { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") net.mamoe.mirai._MiraiInstance.set(originalImpl) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/BotFactoryTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal import kotlinx.coroutines.runBlocking import net.mamoe.mirai.BotFactory import net.mamoe.mirai.internal.test.AbstractTest import kotlin.test.Test internal class BotFactoryTest : AbstractTest() { @Test fun `inlined newBot in Kotlin`() { runBlocking { BotFactory.newBot(123, "") { inheritCoroutineContext() }.close() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/MockBot.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused") @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.component.setAll import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.TestOnly import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.math.absoluteValue import kotlin.random.Random internal val MockAccount = BotAccount(Random.nextLong().absoluteValue.mod(1000L), "pwd") internal val MockConfiguration = BotConfiguration { randomDeviceInfo() } internal class MockBotBuilder( val conf: BotConfiguration = BotConfiguration(), ) { var nhProvider: (QQAndroidBot.(bot: QQAndroidBot) -> NetworkHandler)? = null var additionalComponentsProvider: (QQAndroidBot.(bot: QQAndroidBot) -> ComponentStorage)? = null fun conf(action: BotConfiguration.() -> Unit): MockBotBuilder { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } conf.apply(action) return this } fun networkHandlerProvider(provider: QQAndroidBot.(bot: QQAndroidBot) -> NetworkHandler): MockBotBuilder { this.nhProvider = provider return this } } @Suppress("TestFunctionName") internal fun MockBot(account: BotAccount = MockAccount, conf: MockBotBuilder.() -> Unit = {}): QQAndroidBot { return MockBotBuilder(MockConfiguration.copy()).apply(conf).run { object : QQAndroidBot(account, this.conf) { override fun createBotLevelComponents(): ConcurrentComponentStorage { return super.createBotLevelComponents().apply { val componentsProvider = additionalComponentsProvider if (componentsProvider != null) { setAll(componentsProvider(bot, bot)) } } } override fun createNetworkHandler(): NetworkHandler = nhProvider?.invoke(this, this) ?: super.createNetworkHandler() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/PlatformUtilsTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.deflate import net.mamoe.mirai.utils.gzip import net.mamoe.mirai.utils.inflate import net.mamoe.mirai.utils.ungzip import kotlin.test.Test import kotlin.test.assertEquals internal class PlatformUtilsTest : AbstractTest() { @Test fun testZip() { assertEquals("test", "test".toByteArray().deflate().inflate().decodeToString()) } @Test fun testGZip() { assertEquals("test", "test".toByteArray().gzip().ungzip().decodeToString()) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/ScheduledJobTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal import kotlinx.atomicfu.atomic import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.utils.ScheduledJob import net.mamoe.mirai.utils.AtomicInteger import kotlin.test.Test import kotlin.test.assertEquals internal class ScheduledJobTest : AbstractTest() { @Test fun testScheduledJob() { runBlocking { val scope = CoroutineScope(CoroutineExceptionHandler { _, throwable -> throwable.printStackTrace() }) val invoked = AtomicInteger(0) val job = ScheduledJob(scope.coroutineContext, 1000) { invoked.incrementAndGet() } delay(100) assertEquals(0, invoked.value) job.notice() job.notice() job.notice() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/TypeConversionTest.kt ================================================ /* * 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 import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.toByteArray import net.mamoe.mirai.utils.toUHexString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue internal class TypeConversionTest : AbstractTest() { @ExperimentalUnsignedTypes @Test fun testConversions() { assertEquals("01", byteArrayOf(1).toUHexString()) assertEquals("7F", byteArrayOf(0x7F).toUHexString()) assertEquals("FF", ubyteArrayOf(0xffu).toUHexString()) assertEquals("7F", ubyteArrayOf(0x7fu).toUHexString()) assertTrue { 1994701021.toByteArray().contentEquals("76 E4 B8 DD".hexToBytes()) } assertEquals(byteArrayOf(0, 0, 0, 0x01).toUHexString(), 1.toByteArray().toUHexString()) assertEquals( ubyteArrayOf(0x7fu, 0xffu, 0xffu, 0xffu).toByteArray().toUHexString(), Int.MAX_VALUE.toByteArray().toUHexString() ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/contact/file/AbsoluteFolderTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.file import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.network.notice.BotAware import net.mamoe.mirai.internal.network.protocol.data.proto.GroupFileCommon import net.mamoe.mirai.internal.notice.processors.GroupExtensions import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertEquals internal class AbsoluteFolderTest : AbstractTest(), BotAware, GroupExtensions { override val bot = MockBot { } val group = bot.addGroup(1L, 2L) private val root = group.files.root @Test fun `resolveFolderById always returns null if it is not root`() = runBlockingUnit { val child = root.impl().createChildFolder( GroupFileCommon.FolderInfo( folderId = "/f-1", folderName = "name" ) ) assertEquals(null, child.resolveFolderById("/anything")) } @Test fun `resolveFolderById always returns root for slash`() = runBlockingUnit { val child = root.impl().createChildFolder( GroupFileCommon.FolderInfo( folderId = "/f-1", folderName = "name" ) ) assertEquals(root, root.resolveFolderById("/")) assertEquals(root, child.resolveFolderById("/")) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/event/AbstractEventTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import net.mamoe.mirai.internal.test.AbstractTest internal abstract class AbstractEventTest : AbstractTest() { init { // System.setProperty("mirai.event.trace", "true") // Do not set it to true by default, or the concurrency stress test will become extremely slow. } } ================================================ FILE: mirai-core/src/commonTest/kotlin/event/CancelScopeTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.* import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.globalEventChannel import kotlin.test.Test import kotlin.test.assertFalse internal class CancelScopeTest : AbstractEventTest() { @Test fun testCancelScope() { val scope = CoroutineScope(SupervisorJob()) var got = false scope.globalEventChannel().subscribeAlways<TestEvent> { got = true } runBlocking { scope.coroutineContext[Job]!!.cancelAndJoin() TestEvent().broadcast() } assertFalse { got } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/event/EventChannelFlowTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.async import kotlinx.coroutines.flow.first import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertIs internal class EventChannelFlowTest : AbstractEventTest() { @Test fun asFlow(): Unit = runBlockingUnit() { val channel = GlobalEventChannel val job = async(start = CoroutineStart.UNDISPATCHED) { channel.asFlow().first() } TestEvent().broadcast() assertIs<TestEvent>(job.await()) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/event/EventChannelTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.toList import net.mamoe.mirai.event.* 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 kotlin.coroutines.coroutineContext import kotlin.coroutines.resume import kotlin.coroutines.resumeWithException import kotlin.test.* internal class EventChannelTest : AbstractEventTest() { suspend fun suspendCall() { coroutineContext } data class TE( val x: Int, val y: Int = 1, ) : AbstractEvent() @Test fun singleFilter() { runBlocking { val received = suspendCancellableCoroutine { 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) } } @Test fun multipleFilters() { runBlocking { val received = suspendCancellableCoroutine { cont -> globalEventChannel() .filterIsInstance<TE>() .filter { true } .filter { it.x == 2 } .filter { it.y == 2 } .filter { true } .subscribeOnce<TE> { cont.resume(it.x) } launch { println("Broadcast 1") TE(1, 1).broadcast() println("Broadcast 2") TE(2, 1).broadcast() println("Broadcast 2") TE(2, 3).broadcast() println("Broadcast 2") TE(2, 2).broadcast() println("Broadcast done") } } assertEquals(2, received) } } @Test fun multipleContexts1() { runBlocking { withContext(CoroutineName("1")) { val received = suspendCancellableCoroutine { cont -> globalEventChannel() .context(CoroutineName("2")) .context(CoroutineName("3")) .subscribeOnce<TE>(CoroutineName("4")) { assertEquals("4", currentCoroutineContext()[CoroutineName]!!.name) cont.resume(it.x) } launch { TE(2, 2).broadcast() } } assertEquals(2, received) } } } @Test fun multipleContexts2() { runBlocking { withContext(CoroutineName("1")) { val received = suspendCancellableCoroutine { cont -> globalEventChannel() .context(CoroutineName("2")) .context(CoroutineName("3")) .subscribeOnce<TE> { assertEquals("3", currentCoroutineContext()[CoroutineName]!!.name) cont.resume(it.x) } launch { TE(2, 2).broadcast() } } assertEquals(2, received) } } } @Test fun multipleContexts3() { runBlocking { withContext(CoroutineName("1")) { val received = suspendCancellableCoroutine { cont -> globalEventChannel() .context(CoroutineName("2")) .subscribeOnce<TE> { assertEquals("2", currentCoroutineContext()[CoroutineName]!!.name) cont.resume(it.x) } launch { TE(2, 2).broadcast() } } assertEquals(2, received) } } } @Test fun multipleContexts4() { runBlocking { withContext(CoroutineName("1")) { val received = suspendCancellableCoroutine { cont -> globalEventChannel() .subscribeOnce<TE> { assertEquals("1", currentCoroutineContext()[CoroutineName]!!.name) cont.resume(it.x) } launch { TE(2, 2).broadcast() } } assertEquals(2, received) } } } @Test fun `test forwardToChannel`() { runBlocking { val channel = Channel<TE>(Channel.BUFFERED) val listener = globalEventChannel() .filterIsInstance<TE>() .filter { true } .filter { it.x == 2 } .filter { true } .forwardToChannel(channel) TE(1).broadcast() TE(2).broadcast() listener.complete() TE(2).broadcast() channel.close() val list = channel.receiveAsFlow().toList() assertEquals(1, list.size) assertEquals(TE(2), list.single()) } } @Test fun `test forwardToChannel listener completes if channel closed`() { runBlocking { val channel = Channel<TE>(Channel.BUFFERED) val listener = globalEventChannel() .filterIsInstance<TE>() .filter { true } .filter { it.x == 2 } .filter { true } .forwardToChannel(channel) TE(1).broadcast() TE(2).broadcast() channel.close() assertTrue { listener.isActive } TE(2).broadcast() assertTrue { listener.isCompleted } assertFalse { listener.isActive } val list = channel.receiveAsFlow().toList() assertEquals(1, list.size) assertEquals(TE(2), list.single()) } } @Test fun testExceptionInFilter() { assertFailsWith<ExceptionInEventChannelFilterException> { runBlocking { @Suppress("RemoveExplicitTypeArguments") suspendCancellableCoroutine<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> { suspendCancellableCoroutine<Int> { cont -> val handler = CoroutineExceptionHandler { _, throwable -> cont.resumeWithException(throwable) } val listener = globalEventChannel() .context(handler) .subscribeOnce<TE> { assertSame(handler, currentCoroutineContext()[CoroutineExceptionHandler]) error("test error") } launch { println("Broadcast 1") TE(1).broadcast() println("Broadcast done") listener.complete() } } }.run { assertEquals("test error", message) } } } @Suppress("UNUSED_VARIABLE") @Test fun testVariance() { var global: EventChannel<Event> = GlobalEventChannel val a: EventChannel<MessageEvent> = global.filterIsInstance() val filterLambda: (ev: MessageEvent) -> Boolean = { true } // Kotlin can't resolve to the non-suspend one a.filter { // it: Event suspendCall() // would be allowed in Kotlin it.isIntercepted } val messageEventChannel = a.filterIsInstance<MessageEvent>() // group.asChannel<GroupMessageEvent>() val listener: Listener<GroupMessageEvent> = messageEventChannel.subscribeAlways<GroupEvent> { } global = a global.subscribeMessages { } messageEventChannel.subscribeMessages { } global.subscribeAlways<FriendEvent> { } // inappliable: out cannot passed as in // val b: EventChannel<in FriendMessageEvent> = global.filterIsInstance<FriendMessageEvent>() } } ================================================ FILE: mirai-core/src/commonTest/kotlin/event/EventTests.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import net.mamoe.mirai.event.* import net.mamoe.mirai.utils.AtomicInteger import net.mamoe.mirai.utils.childScope import kotlin.coroutines.EmptyCoroutineContext import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertTrue class TestEvent : AbstractEvent() { var triggered = false } internal class EventTests : AbstractEventTest() { var scope = CoroutineScope(EmptyCoroutineContext) @AfterTest fun finallyReset() { resetEventListeners() } @Test fun testSubscribeInplace() { resetEventListeners() runBlocking(scope.coroutineContext) { val subscriber = globalEventChannel().subscribeAlways<TestEvent> { triggered = true } assertTrue(TestEvent().broadcast().triggered) assertTrue { subscriber.complete() } } } @Test fun testSubscribeGlobalScope() { resetEventListeners() runBlocking { val listener = globalEventChannel().subscribeAlways<TestEvent> { triggered = true } assertTrue(TestEvent().broadcast().triggered) listener.complete() } } @Test fun `test concurrent listening`() { resetEventListeners() var listeners = 0 val counter = AtomicInteger(0) val channel = scope.globalEventChannel() for (p in EventPriority.values()) { repeat(2333) { listeners++ channel.subscribeAlways<ParentEvent> { counter.getAndIncrement() } } } runBlocking { ParentEvent().broadcast() } val called = counter.value println("Registered $listeners listeners and $called called") if (listeners != called) { throw IllegalStateException("Registered $listeners listeners but only $called called") } } @Test fun `test concurrent listening 3`() { resetEventListeners() runBlocking { val called = AtomicInteger(0) val registered = AtomicInteger(0) coroutineScope { println("Step 0") for (priority in EventPriority.values()) { launch { repeat(5000) { registered.getAndIncrement() scope.globalEventChannel().subscribeAlways<ParentEvent>( priority = priority ) { called.getAndIncrement() } } println("Registered $priority") } } println("Step 1") } println("Step 2") ParentEvent().broadcast() println("Step 3") check(called.value == registered.value) println("Done") println("Called ${called.value}, registered ${registered.value}") } } @Test fun `test concurrent listening 2`() = runTest { resetEventListeners() val registered = AtomicInteger(0) val called = AtomicInteger(0) val supervisor = CoroutineScope(SupervisorJob()) coroutineScope { repeat(50) { launch { repeat(444) { registered.getAndIncrement() supervisor.globalEventChannel().subscribeAlways<ParentEvent> { called.getAndIncrement() } } } } } println("All listeners registered") val postCount = 3 coroutineScope { repeat(postCount) { launch { ParentEvent().broadcast() } } } val calledCount = called.value val shouldCalled = registered.value * postCount supervisor.cancel() println("Should call $shouldCalled times and $called called") if (shouldCalled != calledCount) { throw IllegalStateException("?") } } open class ParentEvent : Event, AbstractEvent() { var triggered = false } open class ChildEvent : ParentEvent() open class ChildChildEvent : ChildEvent() @Test fun `broadcast Child to Parent`() { resetEventListeners() runBlocking { val job: CompletableJob job = globalEventChannel().subscribeAlways<ParentEvent> { triggered = true } assertTrue(ChildEvent().broadcast().triggered) job.complete() } } @Test fun `broadcast ChildChild to Parent`() { resetEventListeners() runBlocking { val job: CompletableJob job = globalEventChannel().subscribeAlways<ParentEvent> { triggered = true } assertTrue(ChildChildEvent().broadcast().triggered) job.complete() } } open class PriorityTestEvent : AbstractEvent() private fun singleThreaded(step: StepUtil, invoke: suspend EventChannel<Event>.() -> Unit) { runTest(borrowSingleThreadDispatcher()) { val scope = this.childScope() invoke(scope.globalEventChannel()) scope.cancel() } step.throws() } @Test fun `test handler remove`() { resetEventListeners() val step = StepUtil() singleThreaded(step) { subscribe<Event> { step.step(0) ListeningStatus.STOPPED } ParentEvent().broadcast() ParentEvent().broadcast() } } /* @Test fun `test boom`() { val step = StepUtil() singleThreaded(step) { step.step(0) step.step(0) } } */ private fun resetEventListeners() { scope.cancel() runBlocking { scope.coroutineContext[Job]?.join() } scope = CoroutineScope(EmptyCoroutineContext) } @Test fun `test intercept with always`() { resetEventListeners() val step = StepUtil() singleThreaded(step) { subscribeAlways<ParentEvent> { step.step(0) intercept() } subscribe<Event> { step.step(-1, "Boom") ListeningStatus.LISTENING } ParentEvent().broadcast() } resetEventListeners() } @Test fun `test intercept`() { resetEventListeners() val step = StepUtil() singleThreaded(step) { subscribeAlways<AbstractEvent> { step.step(0) intercept() } subscribe<Event> { step.step(-1, "Boom") ListeningStatus.LISTENING } ParentEvent().broadcast() } } @Test fun `test listener complete`() { resetEventListeners() val step = StepUtil() singleThreaded(step) { val listener = subscribeAlways<ParentEvent> { step.step(0, "boom!") } ParentEvent().broadcast() listener.complete() ParentEvent().broadcast() } } @Test fun `test event priority`() { resetEventListeners() val step = StepUtil() singleThreaded(step) { subscribe<PriorityTestEvent> { step.step(1) ListeningStatus.LISTENING } subscribe<PriorityTestEvent>(priority = EventPriority.HIGH) { step.step(0) ListeningStatus.LISTENING } subscribe<PriorityTestEvent>(priority = EventPriority.LOW) { step.step(3) ListeningStatus.LISTENING } subscribe<PriorityTestEvent> { step.step(2) ListeningStatus.LISTENING } PriorityTestEvent().broadcast() } } @Test fun `test next event and intercept`() { resetEventListeners() GlobalEventChannel.subscribeOnce<TestEvent> { GlobalEventChannel.nextEvent<TestEvent>(priority = EventPriority.HIGH, intercept = true) } GlobalEventChannel.subscribeAlways<TestEvent>(priority = EventPriority.LOW) { this.triggered = true } val tmp = TestEvent() val tmp2 = TestEvent() runBlocking { launch { tmp.broadcast() } launch { tmp2.broadcast() } } assertTrue { (tmp.triggered || tmp2.triggered) && (tmp.triggered != tmp2.triggered) } resetEventListeners() } } ================================================ FILE: mirai-core/src/commonTest/kotlin/event/NextEventTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("DEPRECATION") package net.mamoe.mirai.internal.event import kotlinx.coroutines.* import net.mamoe.mirai.event.* import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.* internal class NextEventTest : AbstractEventTest() { data class TE( val x: Int ) : AbstractEvent() data class TE2( val x: Int ) : AbstractEvent() /////////////////////////////////////////////////////////////////////////// // nextEvent /////////////////////////////////////////////////////////////////////////// @Test fun `nextEvent can receive`() = runBlockingUnit { val channel = GlobalEventChannel val deferred = async(start = CoroutineStart.UNDISPATCHED) { channel.nextEvent<TE>() } TE(1).broadcast() yield() assertTrue { deferred.isCompleted } assertEquals(TE(1), deferred.await()) } @Test fun `nextEvent can filter type`() = runBlockingUnit { val channel = GlobalEventChannel val deferred = async(start = CoroutineStart.UNDISPATCHED) { channel.nextEvent<TE>() } TE2(1).broadcast() yield() assertFalse { deferred.isCompleted } TE(1).broadcast() yield() assertTrue { deferred.isCompleted } assertEquals(TE(1), deferred.await()) } @Test fun `nextEvent can filter by filter`() = runBlockingUnit { val channel = GlobalEventChannel val deferred = async(start = CoroutineStart.UNDISPATCHED) { channel.nextEvent<TE> { it.x == 2 } } TE(1).broadcast() yield() assertFalse { deferred.isCompleted } TE(2).broadcast() yield() assertTrue { deferred.isCompleted } assertEquals(TE(2), deferred.await()) } @Test fun `nextEvent can timeout`() = runBlockingUnit { val channel = GlobalEventChannel assertFailsWith<TimeoutCancellationException> { withTimeout(timeMillis = 1) { channel.nextEvent<TE>(EventPriority.MONITOR) } } } @Test fun `nextEvent can cancel`() = runBlockingUnit { val channel = GlobalEventChannel coroutineScope { val job = launch { val result = kotlin.runCatching { channel.nextEvent<TE>(EventPriority.MONITOR) } assertTrue { result.isFailure } assertIs<CancellationException>(result.exceptionOrNull()) throw result.exceptionOrNull()!! } assertTrue { job.isActive } job.cancelAndJoin() assertTrue { job.isCancelled } } } /////////////////////////////////////////////////////////////////////////// // nextEventOrNull /////////////////////////////////////////////////////////////////////////// @Test fun `nextEventOrNull can receive`() = runBlockingUnit { val deferred = async(start = CoroutineStart.UNDISPATCHED) { withTimeoutOrNull<TE>(5000) { globalEventChannel().nextEvent(EventPriority.MONITOR) } } TE(1).broadcast() yield() assertTrue { deferred.isCompleted } assertEquals(TE(1), deferred.await()) } @Test fun `nextEventOrNull can filter type`() = runBlockingUnit { val deferred = async(start = CoroutineStart.UNDISPATCHED) { withTimeoutOrNull<TE>(5000) { globalEventChannel().nextEvent(EventPriority.MONITOR) } } TE2(1).broadcast() yield() assertFalse { deferred.isCompleted } TE(1).broadcast() yield() assertTrue { deferred.isCompleted } assertEquals(TE(1), deferred.await()) } @Test fun `nextEventOrNull can filter by filter`() = runBlockingUnit { val deferred = async(start = CoroutineStart.UNDISPATCHED) { withTimeoutOrNull<TE>(5000) { globalEventChannel().nextEvent(EventPriority.MONITOR) { it.x == 2 } } } TE(1).broadcast() yield() assertFalse { deferred.isCompleted } TE(2).broadcast() yield() assertTrue { deferred.isCompleted } assertEquals(TE(2), deferred.await()) } @Test fun `nextEventOrNull can timeout`() = runBlockingUnit { assertEquals(null, withTimeoutOrNull<TE>(timeMillis = 1) { globalEventChannel().nextEvent(EventPriority.MONITOR) }) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/event/StepUtil.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.atomicfu.atomic import net.mamoe.mirai.utils.ConcurrentLinkedDeque class StepUtil { val step = atomic(0) val exceptions = ConcurrentLinkedDeque<Throwable>() fun step(step: Int, message: String = "Wrong step") { println("Invoking step $step") if (step != this.step.getAndIncrement()) { throw IllegalStateException(message).also { exceptions.add(it) } } } fun throws() { if (exceptions.isEmpty()) return val root = exceptions.poll()!! while (true) { root.addSuppressed(exceptions.poll() ?: throw root) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/CleanupRubbishMessageElementsTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.data.OnlineAudioImpl import net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol.Companion.UNSUPPORTED_POKE_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol.Companion.UNSUPPORTED_MERGED_MESSAGE_PLAIN import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.message.data.* import kotlin.test.Test import kotlin.test.assertEquals internal class CleanupRubbishMessageElementsTest : AbstractTest() { //region private val replySource = OfflineMessageSourceImplData( kind = MessageSourceKind.GROUP, ids = intArrayOf(1), botId = 1, time = 1, fromId = 87, targetId = 7454, originalMessage = messageChainOf(), internalIds = intArrayOf(8711) ) private val source = OfflineMessageSourceImplData( kind = MessageSourceKind.GROUP, ids = intArrayOf(1), botId = 1, time = 1, fromId = 2, targetId = 3, originalMessage = messageChainOf(), internalIds = intArrayOf(9) ) //endregion private fun assertCleanup(excepted: MessageChain, source: MessageChain) { assertEquals( excepted, source.cleanupRubbishMessageElements() ) assertEquals( noMessageSource(excepted), noMessageSource(source).cleanupRubbishMessageElements() ) } @Test fun testQuoteAtSpace() { // Windows PC QQ assertCleanup( messageChainOf(source, QuoteReply(replySource), PlainText("Hello!")), messageChainOf(source, At(123), PlainText(" "), QuoteReply(replySource), PlainText("Hello!")), ) // QQ Android assertCleanup( messageChainOf(source, QuoteReply(replySource), PlainText("Hello!")), messageChainOf(source, QuoteReply(replySource), At(1234567890), PlainText(" Hello!")), ) } @Test fun testTIMAudio() { val audio = OnlineAudioImpl("0", byteArrayOf(), 0, AudioCodec.SILK, "", 0, null) assertCleanup( messageChainOf(source, audio), messageChainOf(source, audio, UNSUPPORTED_VOICE_MESSAGE_PLAIN), ) } @Test fun testPokeMessageCleanup() { val poke = PokeMessage("", 1, 1) assertCleanup( messageChainOf(source, poke), messageChainOf(source, poke, UNSUPPORTED_POKE_MESSAGE_PLAIN), ) } @Test fun testVipFaceCleanup() { val vf = VipFace(VipFace.Kind(1, "Test!"), 50) assertCleanup( messageChainOf(source, vf), messageChainOf(source, vf, PlainText("----CCCCCTest!")), ) } @Test fun testLongMessageInternalCleanup() { val li = LongMessageInternal("", "") assertCleanup( messageChainOf(source, li), messageChainOf(source, li, UNSUPPORTED_MERGED_MESSAGE_PLAIN), ) } @Test fun testCompressContinuousPlainText() { assertCleanup( messageChainOf(PlainText("1234567890")), "12 3 45 6 789 0".split(" ").map(::PlainText).toMessageChain(), ) assertCleanup( msg(source, At(123456), "Hello! How are you?"), msg(source, At(123456), "Hello", "!", " ", "How", " ", "are ", "you?"), ) } @Test fun testEmptyPlainTextRemoved() { assertCleanup( messageChainOf(), " ".split(" ").map(::PlainText).toMessageChain(), ) assertCleanup( msg(AtAll), msg("", AtAll, "", "", ""), ) } @Test fun testBlankPlainTextLiving() { assertCleanup( msg(" "), msg("", " ", " ", " "), ) } //region private fun msg(vararg msgs: Any?): MessageChain { return msgs.map { elm -> when (elm) { is Message -> elm is String -> PlainText(elm) else -> PlainText(elm.toString()) } }.toMessageChain() } private fun noMessageSource(c: MessageChain): MessageChain { return c.filterNot { it is MessageSource }.toMessageChain() } //endregion } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/ImageBuilderTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package message import net.mamoe.mirai.Mirai import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.ImageType import kotlin.test.Test import kotlin.test.assertEquals internal class ImageBuilderTest : AbstractTest() { companion object { private const val IMAGE_ID = "{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.jpg" private const val IMAGE_ID_PNG = "{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.png" private const val IMAGE_ID_BMP = "{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.bmp" private const val IMAGE_ID_GIF = "{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.gif" private const val IMAGE_ID_UNKNOW = "/01E9451B-70ED-EAE3-B37C-101F1EEBF5B5" } @Test fun create() { // five overloads Image(IMAGE_ID) { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.UNKNOWN, type) assertEquals(false, isEmoji) } Image.newBuilder(IMAGE_ID).run { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.UNKNOWN, type) assertEquals(false, isEmoji) } Image.Builder.newBuilder(IMAGE_ID).run { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.UNKNOWN, type) assertEquals(false, isEmoji) } Image.Builder.newBuilder(IMAGE_ID).build().run { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.JPG, imageType) assertEquals(false, isEmoji) } } @Test fun imageType() { Image(IMAGE_ID).run { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.JPG, imageType) assertEquals(false, isEmoji) } Image(IMAGE_ID_PNG).run { assertEquals(IMAGE_ID_PNG, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.PNG, imageType) assertEquals(false, isEmoji) } Image(IMAGE_ID_BMP).run { assertEquals(IMAGE_ID_BMP, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.BMP, imageType) assertEquals(false, isEmoji) } Image(IMAGE_ID_GIF).run { assertEquals(IMAGE_ID_GIF, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.GIF, imageType) assertEquals(false, isEmoji) } Image(IMAGE_ID_UNKNOW).run { assertEquals(IMAGE_ID_UNKNOW, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.UNKNOWN, imageType) assertEquals(false, isEmoji) } } @Test fun legacyMethods() { // just make sure they work Mirai.createImage(IMAGE_ID).run { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.JPG, imageType) assertEquals(false, isEmoji) } Image.fromId(IMAGE_ID).run { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.JPG, imageType) assertEquals(false, isEmoji) } Image(IMAGE_ID).run { assertEquals(IMAGE_ID, imageId) assertEquals(0, width) assertEquals(0, height) assertEquals(0, size) assertEquals(ImageType.JPG, imageType) assertEquals(false, isEmoji) } Image(IMAGE_ID) { width = 1 height = 2 size = 3 type = ImageType.GIF isEmoji = true }.run { assertEquals(IMAGE_ID, imageId) assertEquals(1, width) assertEquals(2, height) assertEquals(3, size) assertEquals(ImageType.GIF, imageType) assertEquals(true, isEmoji) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/ImageReadingTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message import io.ktor.utils.io.core.EOFException import io.ktor.utils.io.errors.* import net.mamoe.mirai.internal.message.image.calculateImageInfo import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.message.data.ImageType import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.MiraiFile import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.readBytes import net.mamoe.mirai.utils.withUse import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal class ImageReadingTest : AbstractTest() { @Test fun `test read apng`() { "89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 01 E0 00 00 01 90 08 06 00 00 00 76 F6 B3 54 00 00 00 08 61 63 54 4C 00 00 00 22 00 00 00 00 32 4C BC 74 00 00 00 1A 66 63 54 4C 00".testMatch( ImageType.APNG ) assertFailsWith(IOException::class) { "89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00".testMatch(ImageType.APNG) } } @Test fun `test read png`() { "89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 01 E0 00 00 01 90 08 06 00 00 00 76 F6 B3 54 00 00 00 01 73 52 47 42 00 AE".testMatch( ImageType.PNG ) assertFailsWith(IOException::class) { "89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49".testMatch(ImageType.PNG) } } @Test fun `test read gif`() { "47 49 46 38 39 61 E0 01 90 01 F7 FF 00 30 A3 B0".testMatch(ImageType.GIF) assertFailsWith(IOException::class) { "47 49 46 38 39 61 E0".testMatch(ImageType.GIF) } } @Test fun `test read jpg`() { //SOF0 "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF E1 00 5A 45 78 69 66 00 00 4D 4D 00 2A 00 00 00 08 00 05 03 01 00 05 00 00 00 01 00 00 00 4A 03 03 00 01 00 00 00 01 00 00 00 00 51 10 00 01 00 00 00 01 01 00 00 00 51 11 00 04 00 00 00 01 00 00 12 74 51 12 00 04 00 00 00 01 00 00 12 74 00 00 00 00 00 01 86 A0 00 00 B1 8F FF DB 00 43 00 02 01 01 02 01 01 02 02 02 02 02 02 02 02 03 05 03 03 03 03 03 06 04 04 03 05 07 06 07 07 07 06 07 07 08 09 0B 09 08 08 0A 08 07 07 0A 0D 0A 0A 0B 0C 0C 0C 0C 07 09 0E 0F 0D 0C 0E 0B 0C 0C 0C FF DB 00 43 01 02 02 02 03 03 03 06 03 03 06 0C 08 07 08 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C FF C0 00 11 08 01 90 01 E0 03 01 22 00 02 11 01 03 11 01 FF DA".testMatch( ImageType.JPG ) //SOF2 "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF E1 00 5A 45 78 69 66 00 00 4D 4D 00 2A 00 00 00 08 00 05 03 01 00 05 00 00 00 01 00 00 00 4A 03 03 00 01 00 00 00 01 00 00 00 00 51 10 00 01 00 00 00 01 01 00 00 00 51 11 00 04 00 00 00 01 00 00 12 74 51 12 00 04 00 00 00 01 00 00 12 74 00 00 00 00 00 01 86 A0 00 00 B1 8F FF DB 00 43 00 02 01 01 02 01 01 02 02 02 02 02 02 02 02 03 05 03 03 03 03 03 06 04 04 03 05 07 06 07 07 07 06 07 07 08 09 0B 09 08 08 0A 08 07 07 0A 0D 0A 0A 0B 0C 0C 0C 0C 07 09 0E 0F 0D 0C 0E 0B 0C 0C 0C FF DB 00 43 01 02 02 02 03 03 03 06 03 03 06 0C 08 07 08 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C FF C2 00 11 08 01 90 01 E0 03 01 22 00 02 11 01 03 11 01 FF DA".testMatch( ImageType.JPG ) //FF 01 "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF 01 FF E1 00 5A 45 78 69 66 00 00 4D 4D 00 2A 00 00 00 08 00 05 03 01 00 05 00 00 00 01 00 00 00 4A 03 03 00 01 00 00 00 01 00 00 00 00 51 10 00 01 00 00 00 01 01 00 00 00 51 11 00 04 00 00 00 01 00 00 12 74 51 12 00 04 00 00 00 01 00 00 12 74 00 00 00 00 00 01 86 A0 00 00 B1 8F FF DB 00 43 00 02 01 01 02 01 01 02 02 02 02 02 02 02 02 03 05 03 03 03 03 03 06 04 04 03 05 07 06 07 07 07 06 07 07 08 09 0B 09 08 08 0A 08 07 07 0A 0D 0A 0A 0B 0C 0C 0C 0C 07 09 0E 0F 0D 0C 0E 0B 0C 0C 0C FF DB 00 43 01 02 02 02 03 03 03 06 03 03 06 0C 08 07 08 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C FF C2 00 11 08 01 90 01 E0 03 01 22 00 02 11 01 03 11 01 FF DA".testMatch( ImageType.JPG ) //FF 00 "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF 00 FF E1 00 5A 45 78 69 66 00 00 4D 4D 00 2A 00 00 00 08 00 05 03 01 00 05 00 00 00 01 00 00 00 4A 03 03 00 01 00 00 00 01 00 00 00 00 51 10 00 01 00 00 00 01 01 00 00 00 51 11 00 04 00 00 00 01 00 00 12 74 51 12 00 04 00 00 00 01 00 00 12 74 00 00 00 00 00 01 86 A0 00 00 B1 8F FF DB 00 43 00 02 01 01 02 01 01 02 02 02 02 02 02 02 02 03 05 03 03 03 03 03 06 04 04 03 05 07 06 07 07 07 06 07 07 08 09 0B 09 08 08 0A 08 07 07 0A 0D 0A 0A 0B 0C 0C 0C 0C 07 09 0E 0F 0D 0C 0E 0B 0C 0C 0C FF DB 00 43 01 02 02 02 03 03 03 06 03 03 06 0C 08 07 08 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C FF C2 00 11 08 01 90 01 E0 03 01 22 00 02 11 01 03 11 01 FF DA".testMatch( ImageType.JPG ) //RST[0-7] "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF D0 FF D1 FF D2 FF D3 FF D4 FF D5 FF D6 FF D7 FF E1 00 5A 45 78 69 66 00 00 4D 4D 00 2A 00 00 00 08 00 05 03 01 00 05 00 00 00 01 00 00 00 4A 03 03 00 01 00 00 00 01 00 00 00 00 51 10 00 01 00 00 00 01 01 00 00 00 51 11 00 04 00 00 00 01 00 00 12 74 51 12 00 04 00 00 00 01 00 00 12 74 00 00 00 00 00 01 86 A0 00 00 B1 8F FF DB 00 43 00 02 01 01 02 01 01 02 02 02 02 02 02 02 02 03 05 03 03 03 03 03 06 04 04 03 05 07 06 07 07 07 06 07 07 08 09 0B 09 08 08 0A 08 07 07 0A 0D 0A 0A 0B 0C 0C 0C 0C 07 09 0E 0F 0D 0C 0E 0B 0C 0C 0C FF DB 00 43 01 02 02 02 03 03 03 06 03 03 06 0C 08 07 08 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C FF C2 00 11 08 01 90 01 E0 03 01 22 00 02 11 01 03 11 01 FF DA".testMatch( ImageType.JPG ) println("Current path: " + net.mamoe.mirai.utils.MiraiFile.create(".").absolutePath) //Issue 1610 MiraiFile.create("./src/commonTest/resources/image/jpeg-header-issue-1610.bin").readBytes().testRead( ImageType.JPG ) //Failed to find assertFailsWith(IllegalArgumentException::class) { "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF E1 00 5A 45 78 69 66 00 00 4D 4D 00 2A 00 00 00 08 00 05 03 01 00 05 00 00 00 01 00 00 00 4A 03 03 00 01 00 00 00 01 00 00 00 00 51 10 00 01 00 00 00 01 01 00 00 00 51 11 00 04 00 00 00 01 00 00 12 74 51 12 00 04 00 00 00 01 00 00 12 74 00 00 00 00 00 01 86 A0 00 00 B1 8F FF DB 00 43 00 02 01 01 02 01 01 02 02 02 02 02 02 02 02 03 05 03 03 03 03 03 06 04 04 03 05 07 06 07 07 07 06 07 07 08 09 0B 09 08 08 0A 08 07 07 0A 0D 0A 0A 0B 0C 0C 0C 0C 07 09 0E 0F 0D 0C 0E 0B 0C 0C 0C FF DB 00 43 01 02 02 02 03 03 03 06 03 03 06 0C 08 07 08 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C FF DA".testMatch( ImageType.JPG ) } assertFailsWith(EOFException::class) { "FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 78 00 78 00 00 FF E1 00 5A".testMatch( ImageType.JPG ) } } @Test fun `test read bmp`() { "42 4D 36 CA 08 00 00 00 00 00 36 00 00 00 28 00 00 00 E0 01 00 00 90 01 00 00 01 00 18 00 00 00 00 00 00 CA 08 00 74 12 00 00 74 12 00 00 00 00 00 00 00 00 00 00" .testMatch(ImageType.BMP) assertFailsWith(IOException::class) { "42 4D 36 CA 08 00 00 00 00 00 36 00 00 00 28 00 00 00 E0 01 00 00 90".testMatch( ImageType.BMP ) } } @Test fun `test read fail`() { assertFailsWith(IllegalArgumentException::class) { "CA FE FE".testMatch(ImageType.BMP) } } private fun ByteArray.testRead(type: ImageType) { this.toExternalResource().withUse { calculateImageInfo().run { assertEquals(type, imageType, "imageType") } } } private fun String.testMatch(type: ImageType) { this.hexToBytes().toExternalResource().withUse { calculateImageInfo().run { assertEquals(480, width, "width") assertEquals(400, height, "height") assertEquals(type, imageType, "imageType") } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/InternalImageProtocolImplTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message import net.mamoe.mirai.internal.message.image.InternalImageProtocolImpl import net.mamoe.mirai.internal.notice.processors.AbstractNoticeProcessorTest import kotlin.test.Test import kotlin.test.assertIs internal class InternalImageProtocolImplTest : AbstractNoticeProcessorTest() { // borrow Bot testkit val instance = InternalImageProtocolImpl() @Test fun testFindChecker() { assertIs<InternalImageProtocolImpl.ImageUploadedCheckerGroup>(instance.findChecker(setBot(1).addGroup(2, 3))) assertIs<InternalImageProtocolImpl.ImageUploadedCheckerUser>(instance.findChecker(setBot(1).addFriend(2))) assertIs<InternalImageProtocolImpl.ImageUploadedCheckerFallback>(instance.findChecker(null)) // these 3 tests are complete -- no need to add more when adding more checkers. } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/RefineContextTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message import net.mamoe.mirai.internal.test.AbstractTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue internal class RefineContextTest : AbstractTest() { @Test fun `merge test`() { val Key1 = RefineContextKey<Int>("KeyInt") val Key2 = RefineContextKey<Double>("KeyDouble") val Key3 = RefineContextKey<String>("KeyString") val Key4 = RefineContextKey<ByteArray>("KeyBytes") val context1 = SimpleRefineContext( Key1 to 114514, Key2 to 1919.810, Key3 to "sodayo" ) val context2 = SimpleRefineContext( Key2 to 1919.811, Key3 to "yarimasune", Key4 to byteArrayOf(11, 45, 14) ) val combinedOverride = context1.merge(context2, override = true) val combinedNotOverride = context1.merge(context2, override = false) val context3 = SimpleRefineContext( Key2 to 1919.811, Key3 to "yarimasune" ) assertEquals(context1, context1.merge(context3, false)) assertTrue(combinedOverride != combinedNotOverride) assertEquals(4, combinedOverride.entries().size) assertEquals(1919.811, combinedOverride[Key2]) assertEquals(1919.810, combinedNotOverride[Key2]) assertEquals("sodayo", combinedNotOverride[Key3]) assertTrue(byteArrayOf(11, 45, 14).contentEquals(combinedNotOverride[Key4])) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/code/TestMiraiCode.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // MiraiCodeParser package net.mamoe.mirai.internal.message.code import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode import net.mamoe.mirai.message.code.internal.MiraiCodeParser import net.mamoe.mirai.message.data.* import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull internal class TestMiraiCode : AbstractTest() { @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 `test serialization`() { assertEquals("[mirai:file:id,1,name,2]", FileMessage("id", 1, "name", 2).serializeToMiraiCode()) } @Test fun `test deserialization`() { assertEquals(AtAll.toMessageChain(), "[mirai:atall]".deserializeMiraiCode()) assertEquals(PlainText("[Hello").toMessageChain(), "\\[Hello".deserializeMiraiCode()) assertEquals(buildMessageChain { +PlainText("1") +AtAll +PlainText("2345") +AtAll }, "1[mirai:atall]2345[mirai:atall]".deserializeMiraiCode()) assertEquals(buildMessageChain { +PlainText("1") +AtAll +PlainText("2345[mirai:atall") }, "1[mirai:atall]2345[mirai:atall".deserializeMiraiCode()) assertEquals(buildMessageChain { +PlainText("[mirai:atall]") }, "\\[mirai:atall]".deserializeMiraiCode()) assertEquals(buildMessageChain { +PlainText("[mirai:atall]") }, "[mirai:atall\\]".deserializeMiraiCode()) assertEquals(buildMessageChain { +PlainText("[mirai:atall]") }, "[mirai\\:atall]".deserializeMiraiCode()) assertEquals(buildMessageChain { +SimpleServiceMessage(1, "[HiHi!!!\\]") +PlainText(" XE") }, "[mirai:service:1,\\[HiHi!!!\\\\\\]] XE".deserializeMiraiCode()) assertEquals(buildMessageChain { +Dice(1) }, "[mirai:dice:1]".deserializeMiraiCode()) assertEquals(FileMessage("id", 1, "name", 2), "[mirai:file:id,1,name,2]".deserializeMiraiCode().single()) 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()) assertEquals( messageChainOf(RockPaperScissors.ROCK), "[mirai:rps:rock]".deserializeMiraiCode() ) assertEquals( messageChainOf(RockPaperScissors.SCISSORS), "[mirai:rps:scissors]".deserializeMiraiCode() ) assertEquals( messageChainOf(RockPaperScissors.PAPER), "[mirai:rps:paper]".deserializeMiraiCode() ) assertEquals( messageChainOf(SuperFace.from(Face(Face.LAN_QIU))), "[mirai:superface:114,13,2]".deserializeMiraiCode() ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/data/AudioTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.internal.message.data.OnlineAudioImpl.Companion.DOWNLOAD_URL import net.mamoe.mirai.internal.message.data.OnlineAudioImpl.Companion.refineUrl import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.message.data.AudioCodec import net.mamoe.mirai.message.data.OfflineAudio import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFalse internal class AudioTest : AbstractTest() { @Test fun `test factory`() { assertEquals( OfflineAudio("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf()), OfflineAudio("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf()) ) } @Test fun `invalid extraData is refreshed`() { assertContentEquals( OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, null).extraData, OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf(1, 2, 3)).extraData, ) } @Test fun `test equality`() { assertEquals( OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null), OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null) ) assertEquals( OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null), OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null) ) assertEquals( OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, originalPtt = null), OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, originalPtt = null) ) assertEquals( OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf()), OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf()) ) assertEquals( OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, ImMsgBody.Ptt(srcUin = 2)), OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, ImMsgBody.Ptt(srcUin = 2)) ) } @Test fun `test refineUrl`() { assertFalse { DOWNLOAD_URL.endsWith("/") } assertEquals("", refineUrl("")) assertEquals("$DOWNLOAD_URL/test", refineUrl("/test")) assertEquals("$DOWNLOAD_URL/test", refineUrl("test")) assertEquals("https://custom.com", refineUrl("https://custom.com")) assertEquals("http://localhost", refineUrl("http://localhost")) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/data/ContentEqualsTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.safeCast import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue internal class TestConstrainSingleMessage : ConstrainSingle, Any() { companion object Key : AbstractMessageKey<TestConstrainSingleMessage>({ it.safeCast() }) override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>" override fun contentToString(): String = "" override val key: MessageKey<TestConstrainSingleMessage> get() = Key } internal class ContentEqualsTest: AbstractTest() { @Test fun testContentEquals() { val mySource = TestConstrainSingleMessage() val image = Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai") assertTrue { buildMessageChain { +"test" }.contentEquals(buildMessageChain { +"te" +mySource +"st" }) } assertFalse { buildMessageChain { +"tests" }.contentEquals(buildMessageChain { +"te" +"st" }) } assertEquals("test", buildMessageChain { +mySource +"test" +mySource }.content) assertTrue { buildMessageChain { +"test" }.contentEquals(buildMessageChain { +"te" +"st" +mySource }) } assertTrue { buildMessageChain { +"test" +image }.contentEquals(buildMessageChain { +"te" +mySource +"st" +image }) } assertEquals("test", buildMessageChain { +mySource +"test" +mySource }.content) assertTrue { buildMessageChain { +"test" +image }.contentEquals(buildMessageChain { +"te" +"st" +image +mySource }) } assertFalse { buildMessageChain { +image +"test" +mySource }.contentEquals("test") } assertFalse { buildMessageChain { +"test" +image }.contentEquals("test") } assertFalse { buildMessageChain { +image +"test" }.contentEquals(buildMessageChain { +"te" +"st" +image +mySource }) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/data/ForwardRefineTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.AbstractTestWithMiraiImpl import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.SimpleRefineContext import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.structureToString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue internal class ForwardRefineTest : AbstractTestWithMiraiImpl() { private val bot = MockBot() companion object { private const val content = """ <?xml version="1.0" encoding="utf-8"?> <msg brief="[聊天记录]" m_fileName="C85BCFB5-3143-41E2-860B-172D5E3FAFAC" action="viewMultiMsg" tSum="2" flag="3" m_resid="ZnAsFPOuieB0CJt7HJ6zC1Rq0MRqfso44OAvY99wqePeYYcGr03NI6UXB24QqefR" serviceID="35" m_fileSize="171" > <item layout="1"> <title color="#000000" size="34" > 群聊的聊天记录 </title> <title color="#000000" size="26" > A:@B ‘ </title> <title color="#000000" size="26" > A:@B ’ </title> <hr></hr> <summary color="#808080" size="26" > 查看转发消息 </summary> </item><source name="聊天记录"></source> </msg> """ private const val resId = "ZnAsFPOuieB0CJt7HJ6zC1Rq0MRqfso44OAvY99wqePeYYcGr03NI6UXB24QqefR" private val nodes = listOf(ForwardMessage.Node(1, 1, "sender", AtAll)) } override suspend fun downloadForwardMessage(bot: Bot, resourceId: String): List<ForwardMessage.Node> { assertEquals(resId, resourceId) return nodes } @Test fun `can refine ForwardMessageInternal deep`() = runBlockingUnit { val internal = ForwardMessageInternal(content, resId, null) val expectedOrigin = SimpleServiceMessage(internal.serviceId, content) // we do not expose ForwardMessageInternal val refine = internal.toMessageChain().refineDeep(bot, SimpleRefineContext().apply { set(ForwardMessageInternal.MsgTransmits, mapOf()) }) println(refine.size) println(refine.first()::class) println(refine.structureToString()) assertTrue { refine.first() is MessageOrigin } assertTrue { refine.drop(1).first() is ForwardMessage } assertEquals( ForwardMessage( preview = listOf("A:@B ‘", "A:@B ’"), title = "群聊的聊天记录", brief = "[聊天记录]", source = "聊天记录", summary = "查看转发消息", nodeList = nodes ), refine[ForwardMessage] ) assertEquals( expectedOrigin, refine[MessageOrigin]!!.origin ) assertEquals( MessageOrigin(expectedOrigin, resId, MessageOriginKind.FORWARD), refine[MessageOrigin] ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/data/MessageReceiptTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.protocol.impl.GeneralMessageSenderProtocol import net.mamoe.mirai.internal.message.protocol.outgoing.* import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl import net.mamoe.mirai.internal.message.source.createMessageReceipt import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.internal.notice.processors.GroupExtensions import net.mamoe.mirai.internal.pipeline.replaceProcessor import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.message.data.ForwardMessage import net.mamoe.mirai.message.data.buildForwardMessage import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.utils.Clock import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.test.* internal class MessageReceiptTest : AbstractTest(), GroupExtensions { private val bot = MockBot() @Test fun `refine ForwardMessageInternal for MessageReceipt's original MessageChain`() = runBlockingUnit { val group = bot.addGroup(123, 2) val forward = buildForwardMessage(group) { 2 says "ok" } val message = forward.toMessageChain() val facade = MessageProtocolFacade.INSTANCE.copy() assertTrue { facade.outgoingPipeline.replaceProcessor( { it is GeneralMessageSenderProtocol.GeneralMessageSender }, OutgoingMessageProcessorAdapter(object : OutgoingMessageSender { override suspend fun OutgoingMessagePipelineContext.process() { assertIs<ForwardMessageInternal>(currentMessageChain[ForwardMessageInternal]) assertSame(forward, currentMessageChain[ForwardMessageInternal]?.origin) val source = OnlineMessageSourceToGroupImpl( group, internalIds = intArrayOf(1), sender = bot, target = group, time = currentTimeSeconds().toInt(), originalMessage = currentMessageChain //, // sourceMessage = message ) collect(source.createMessageReceipt(group, true)) } }) ) } val result = facade.preprocessAndSendOutgoing(group, message, ConcurrentComponentStorage { set(MessageProtocolStrategy, object : GroupMessageProtocolStrategy(group) { }) set(HighwayUploader, object : HighwayUploader { override suspend fun uploadMessages( contact: AbstractContact, components: ComponentStorage, nodes: Collection<ForwardMessage.INode>, isLong: Boolean, senderName: String ): String { return "id" } }) set(ClockHolder, object : ClockHolder() { override val local: Clock = object : Clock { override fun currentTimeMillis(): Long { return 160023456 } } }) }) assertIs<ForwardMessage>(result.source.originalMessage[ForwardMessage]) assertEquals(message, result.source.originalMessage) assertSame(forward, result.source.originalMessage[ForwardMessage]) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/data/MessageRefineTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import kotlinx.serialization.decodeFromHexString import kotlinx.serialization.protobuf.ProtoBuf import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.AbstractTestWithMiraiImpl import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.getMiraiImpl import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight import net.mamoe.mirai.internal.message.RefinableMessage import net.mamoe.mirai.internal.message.RefineContext import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData 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.network.protocol.data.proto.MsgTransmit import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.isSameType import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals open class TM(private val name: String = Random.nextInt().toString()) : SingleMessage { override fun toString(): String = name override fun contentToString(): String = name override fun equals(other: Any?): Boolean { if (this === other) return true if (!isSameType(this, other)) return false if (name != other.name) return false return true } override fun hashCode(): Int = name.hashCode() } private val bot = MockBot() private suspend fun testRefineAll( before: Message, after: MessageChain, ) { testRefineLight(before, after) testRefineDeep(before, after) } private suspend fun testRefineDeep( before: Message, after: MessageChain ) = assertEquals(after.toMessageChain(), before.toMessageChain().refineDeep(bot)) private fun testRefineLight( before: Message, after: MessageChain ) = assertEquals(after.toMessageChain(), before.toMessageChain().refineLight(bot)) @Suppress("TestFunctionName") private fun RefinableMessage0( refine: () -> Message? ): RefinableMessage { return object : RefinableMessage, TM() { override fun tryRefine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message? { return refine() } } } internal class MessageRefineTest : AbstractTestWithMiraiImpl() { @Test fun `can remove self`() = runBlockingUnit { testRefineAll( RefinableMessage0 { null }, messageChainOf() ) } @Test fun `can replace`() = runBlockingUnit { testRefineAll( RefinableMessage0 { TM("1") }, messageChainOf(TM("1")) ) } @Test fun `ignore non-refinable`() = runBlockingUnit { testRefineAll( TM("1"), messageChainOf(TM("1")) ) } @Test fun `can replace flatten`() = runBlockingUnit { testRefineAll( buildMessageChain { +RefinableMessage0 { TM("1") + TM("2") } +TM("3") +RefinableMessage0 { TM("4") + TM("5") } }, messageChainOf(TM("1"), TM("2"), TM("3"), TM("4"), TM("5")) ) } private val testCases = object { /** * 单个 quote 包含 at 和 plain */ val simpleQuote = decodeProto("087aea027708a2fc1010d285d8cc0418f9e7b4830620012a0d0a0b0a09e999a4e99d9e363438420a18aedd90f380808080014a480a2d08d285d8cc0410d285d8cc04185228a2fc1030f9e7b4830638aedd90f380808080014a0608d285d8cc04e001011a170a15120d0a0b0a09e999a4e99d9e36343812044a0208591a0a180a0740e9bb84e889b21a0d00010000000300499602d20000050a030a01201a0a180a0740e9bb84e889b21a0d00010000000300499602d20000070a050a032073624baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021002900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803") /** * 一个引用另一个 quote */ val nestedQuote2 = decodeProto("0631ea022e08a4fc1010d285d8cc041885e8b4830620012a0e0a0c0a0a40e9bb84e889b2207362420a1896fee2d386808080011b0a190a0840616161746573741a0d00010000000800499602d20000080a060a04207878784baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803") /** * quote -> quote -> quote[at + plain] */ val nestedQuote3 = decodeProto("062aea022708a6fc1010d285d8cc0418b0e8b4830620012a070a050a03787878420a18b584a7ca80808080011b0a190a0840616161746573741a0d00010000000800499602d200000a0a080a062061616161614baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803") /** * [Dice.value] 4 */ val dice4 = decodeProto("056432620a0e5be99a8fe69cbae9aab0e5ad905d1006180122104823d3adb15df08014ce5d6796b76ee128c85930033a103430396532613639623136393138663950c80158c8016211727363547970653f313b76616c75653d336a0a0a0608c80110c8014001120a100a0e5be99a8fe69cbae9aab0e5ad905d4baa02489a014508017800900101c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803") /** * quote -> dice4 */ val quoteDice4 = decodeProto("06e601ea02e20108adfc1010d285d8cc04188feab4830620012a120a100a0e5be99a8fe69cbae9aab0e5ad905d420a1894bbc6f481808080014aad010a2d08d285d8cc0410d285d8cc04185228adfc10308feab483063894bbc6f481808080014a0608d285d8cc04e001011a7c0a7a125e325c0a0e5be99a8fe69cbae9aab0e5ad905d1006180122104823d3adb15df08014ce5d6796b76ee128c85930033a1034303965326136396231363931386639480050c80158c8016211727363547970653f313b76616c75653d336a02400112120a100a0e5be99a8fe69cbae9aab0e5ad905d12044a0208001b0a190a0840616161746573741a0d00010000000800499602d20000080a060a04206162634baa02489a0145080120cb507800c80100f00100f80100900200c80200980300a00300b00300c00300d00300e803008a04021003900480c0829004b80400c00400ca0400f804dc8002880500044a0240011082010d0a076161617465737418012803") /** * forward[quote + dice + forward] */ val complexForward = "044b0a3a08d285d8cc041852288f831130dfffb6830638a7c4dfde87808080014a0e08d285d8cc042206e7b289e889b2a2010b10b0f083f7fbffffffff011a0d0a0b12050a030a016112024a00dc010a3a08d285d8cc0418522891831130e4ffb68306388fc594dd85808080014a0e08d285d8cc042206e7b289e889b2a2010b10b1f083f7fbffffffff011a9d010a9a011270ea026d088f831110d285d8cc0418dfffb6830620012a050a030a0161420a18a7c4dfde87808080014a400a2d08d285d8cc0410d285d8cc041852288f831130dfffb6830638a7c4dfde87808080014a0608d285d8cc04e001011a0f0a0d12050a030a016112044a02080050d285d8cc04121a0a180a0740e7b289e889b21a0d00010000000300499602d2000012060a040a02207212024a00520a3a08d285d8cc0418522892831130f2ffb6830638a8e4d1eb80808080014a0e08d285d8cc042206e7b289e889b2a2010b10b2f083f7fbffffffff011a140a12120c0a0a0a085be9aab0e5ad905d12024a00700a3a08d285d8cc04185228978311308d80b7830638a0e8bfa582808080014a0e08d285d8cc042206e7b289e889b2a2010b10b3f083f7fbffffffff011a320a30122a0a280a265be59088e5b9b6e8bdace58f915de8afb7e58d87e7baa7e696b0e78988e69cace69fa5e79c8b12024a00" .let { s -> ProtoBuf.decodeFromHexString<List<MsgComm.Msg>>(s).flatMap { it.msgBody.richText.elems } } val nestedForward = "0a790a3008d285d8cc041852200028f9263084f99c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a450a430a0618d5f0fbdf0412390a370a315be59088e5b9b6e8bdace58f915de8afb7e4bdbfe794a8e6898be69cba5151e69c80e696b0e78988e69cace69fa5e79c8b1a005a000a620a3f08d285d8cc041852200028fb2630f5f99c83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a1f0a1d0a0618df98a9a10d12090a070a01351a005a0012084a060890e60260000a97010a3f08d285d8cc0418522000288a2730ef999d83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a540a520a0618a19bc4fe09122fea022c08fb2610d285d8cc0418f5f99c830620012a050a030a01353001420a188fefa5ae8a8080800150d285d8cc04120d0a0b0a0561736566661a005a0012084a060890e602600012fe060a084d756c74694d736712f1060af0040a3008d285d8cc041852200028f9263084f99c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011abb040ab8040a0618d5f0fbdf0412ad0462aa040aa30401789cbd535d6f1241147df7574cc6676497a5c036bb3408bbb608550a02d234cdb2ccc2da1db6ccccb2edbe359af8fdf5604c6cad8969134d8d9a98544d2c0ffe1617f0c9bfe080561b5f7c30f14e723373ef9c73ef99dc51e636b003fa8850dbedaa503c234080baa6dbb2bb6d157acc8aa4e05cfa14e0a660da064d62234b85cbe3ad5be1decbf1ebb7e1d1a31508f0aa653b68d1c0488545cf617691b6571389999ca8c96224a38952249ee43b5917e488248852327936abcb991c0486c9a6a5fb36f28fa110b0b287551883c0720cde88040145a46f9b6821c74f33c715cb76c02b8a02ef1aa48162338481636cba1ee351c823cc660e02a6ebb84485a785a971ae294c8a430e1a0df6b898d1936b272501253a45fe85219698300c770f460fde7d7eff75fbc5f8dec75950a15cc23f3164caf99a96af674dcbcad44c400d622d87db83d1cdeb2bffb933fe3ae1eed57067f0edd39de1e3c32f1fee723f7cba3f7e7ed0366741f55c3568cc2fe1a5b546bd12d3a592b0982b89eb976b58971af54ea715bbe417af14e58b55d6aaf17ca3baae558235bfd835850b755d0a7a3d9f90423320fd3c08923579a3d9d419958c8542b613b082ee5332df4f56b4780a8bbee9457b74be10b43cdfa71ea3e7b576aee4aabf0501a543d24a943ba0500f63836cfe52971226eb8ff779b63fdab93d3e7a15de7f383cbc31dc7ac329a23f919c233a19a8b4425d8f980874a7e37d724c202ff62339b9ccff47fa3b18253b9f10231a000a620a3f08d285d8cc041852200028fb2630f5f99c83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a1f0a1d0a0618df98a9a10d12090a070a01351a005a0012084a060890e60260000a97010a3f08d285d8cc0418522000288a2730ef999d83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a540a520a0618a19bc4fe09122fea022c08fb2610d285d8cc0418f5f99c830620012a050a030a01353001420a188fefa5ae8a8080800150d285d8cc04120d0a0b0a0561736566661a005a0012084a060890e602600012f3040a2d4d756c74694d73675f36363544314539312d414531332d343739312d394630392d33303133373742434639414412c1040a4e0a3108d285d8cc041852200028ffb20130fef89c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a190a170a061881f9d1ae02120d0a0b0a0554734d73671a005a000ab9010a3108d285d8cc041852200028ffb20130fff89c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a83010a80010a0618e3fde8a20b121b0a190a1341534a57454a58436366664157630a736172661a005a00125942571220463042383130354243463033374133323733413644343133453941333043393338b0bd858e0940b787ace70b485050005a0060006a10f0b8105bcf037a3273a6d413e9a30c93a00100b001ac02b8018302c801b85c0a4e0a3108d285d8cc041852200028ffb2013080f99c83064a1608d285d8cc04220ee69eabe790b3c2b7e99ba8e88eb9a2010210011a190a170a0618d1afffda02120d0a0b0a0554734d73671a005a000ae2010a4008d285d8cc041852200028ffb2013081f99c83064a2508d285d8cc04221de7bea4e59e83e59cbeefbc8ce697b6e4b88de697b6e69da5e8a2ab6763a2010210011a9d010a9a010a0618bfb1ebfb0f128f010a8c010a85015647567a5a48526d526b5a585432463351304e4451317059576d46335a586868643255774d6a4d3950567464573246335a567045547a6b774d6e63304f5846337a71717772724c627a72764a0a7a3757397862624674733361494c43687a744c46777372487637544534386d317763752f7173484c7a64757777737574734b456744516f3d1a005a00" .hexToBytes().loadAs(MsgTransmit.PbMultiMsgTransmit.serializer()) private fun decodeProto(p: String) = ProtoBuf.decodeFromHexString<List<ImMsgBody.Elem>>(p) } // override suspend fun downloadForwardMessage(bot: Bot, resourceId: String): List<ForwardMessage.Node> { // return super.downloadForwardMessage(bot, resourceId) // } /** * We cannot test LongMessage and MusicShare in unit tests (for now), but these tests will be sufficient */ @Test fun `recursive refinement`() = runBlockingUnit { val map = listOf( RefineTest(testCases.simpleQuote) { expected { +QuoteReply(sourceStub(buildMessageChain { +"除非648" })) +At(1234567890) // sent by official client, redundant At? +" " +At(1234567890) +" sb" } light() deep() }, RefineTest(testCases.nestedQuote2) { expected { +QuoteReply(sourceStub(buildMessageChain { +"@黄色 sb" // this is sent by official client. })) +At(1234567890) // mentions self +" xxx" } light() deep() }, RefineTest(testCases.nestedQuote3) { expected { +QuoteReply(sourceStub(buildMessageChain { +"xxx" // official client does not handle nested quotes. })) +At(1234567890) // mentions self +" aaaaa" } light() deep() }, RefineTest(testCases.dice4) { expected { +Dice(4) } light() deep() }, RefineTest(testCases.quoteDice4) { expected { +QuoteReply(sourceStub(PlainText("[随机骰子]"))) +At(1234567890) +" abc" } light() deep() }, RefineTest(testCases.complexForward) { expected { +"a" +QuoteReply(sourceStub(PlainText("a"))) +At(1234567890) +PlainText(" r") +PlainText("[骰子]") // client does not support +PlainText("[合并转发]请升级新版本查看") // client support but mirai does not. } deep() // deep only } ) for (test in map) { if (test.testLight) { testRecursiveRefine(test.list, test.expected, true) } if (test.testDeep) { testRecursiveRefine(test.list, test.expected, false) } } } @Test fun `test nested forward refinement`() = runBlockingUnit { val redefined = getMiraiImpl().run { testCases.nestedForward.toForwardMessageNodes(bot) } assertNodesEquals( listOf( ForwardMessage.Node( senderId = 1234567890, time = 1617378436, senderName = "枫琳·雨莹", messageChain = buildMessageChain { +redefined[0].messageChain[MessageOrigin]!! val rf = redefined[0].messageChain[ForwardMessage]!! +ForwardMessage( preview = rf.preview, title = rf.title, brief = rf.brief, source = rf.source, summary = rf.summary, nodeList = listOf( ForwardMessage.Node(1234567890, 1617378430, "枫琳·雨莹", PlainText("TsMsg")), ForwardMessage.Node( 1234567890, 1617378431, "枫琳·雨莹", PlainText("ASJWEJXCcffAWc\nsarf") + Image("{F0B8105B-CF03-7A32-73A6-D413E9A30C93}.mirai") ), ForwardMessage.Node(1234567890, 1617378432, "枫琳·雨莹", PlainText("TsMsg")), ForwardMessage.Node( 1234567890, 1617378433, "群垃圾,时不时来被gc", PlainText("VGVzZHRmRkZXT2F3Q0NDQ1pYWmF3ZXhhd2UwMjM9PVtdW2F3ZVpETzkwMnc0OXF3zqqwrrLbzrvJ\nz7W9xbbFts3aILChztLFwsrHv7TE48m1wcu/qsHLzduwwsutsKEgDQo=") ), ) ) } ), ForwardMessage.Node( 1234567890, 1617378549, "群垃圾,时不时来被gc", PlainText("5") ), ForwardMessage.Node( 1234567890, 1617382639, "群垃圾,时不时来被gc", redefined[2].messageChain[QuoteReply]!! + PlainText("aseff") ), ), redefined, ) } } private fun sourceStub( originalMessage: Message ): OfflineMessageSourceImplData { return OfflineMessageSourceImplData( MessageSourceKind.GROUP, intArrayOf(), bot.id, 0, 0, 0, originalMessage.toMessageChain(), intArrayOf() ) } private suspend fun testRecursiveRefine(list: List<ImMsgBody.Elem>, expected: MessageChain, isLight: Boolean) { val actual = buildMessageChain { MessageProtocolFacade.decode(list, 0, MessageSourceKind.GROUP, bot, this, null) }.let { c -> if (isLight) { c.refineLight(bot) } else { c.refineDeep(bot) } } assertMessageChainEquals(expected, actual) } private fun assertNodesEquals(excepted: List<ForwardMessage.Node>, actual: List<ForwardMessage.Node>) { assertEquals(excepted.size, actual.size, "Length not match") for (i in excepted.indices) { val en = excepted[i] val an = actual[i] assertEquals(en.senderId, an.senderId) assertEquals(en.time, an.time) assertEquals(en.senderName, an.senderName) assertMessageChainEquals(en.messageChain, an.messageChain) } } @Suppress("unused") private object Color { const val RESET = "\u001b[0m" const val WHITE = "\u001b[97m" const val RED = "\u001b[31m" const val EMERALD_GREEN = "\u001b[32m" const val GOLD = "\u001b[33m" const val BLUE = "\u001b[34m" const val PURPLE = "\u001b[35m" const val GREEN = "\u001b[36m" const val GRAY = "\u001b[90m" const val LIGHT_RED = "\u001b[91m" const val LIGHT_GREEN = "\u001b[92m" const val LIGHT_YELLOW = "\u001b[93m" const val LIGHT_BLUE = "\u001b[94m" const val LIGHT_PURPLE = "\u001b[95m" const val LIGHT_CYAN = "\u001b[96m" } private fun assertMessageChainEquals(expected: MessageChain, actual: MessageChain) { val color = object { val yellow get() = Color.LIGHT_YELLOW val green get() = Color.LIGHT_GREEN val reset get() = Color.RESET } fun compare(expected: MessageChain, actual: MessageChain): Boolean { if (expected.size != actual.size) return false for ((e, a) in expected.zip(actual)) { when (e) { is QuoteReply -> { if (a !is QuoteReply) return false if (!compare(e.source.originalMessage, a.source.originalMessage)) return false } is MessageSource -> { if (a !is MessageSource) return false if (!compare(e.originalMessage, a.originalMessage)) return false } is ForwardMessage -> { if (a !is ForwardMessage) return false if (e.brief != a.brief) return false if (e.summary != a.summary) return false if (e.source != a.source) return false if (e.title != a.title) return false if (e.preview != a.preview) return false assertNodesEquals(e.nodeList, a.nodeList) } is Image -> { if (a !is Image) return false if (e.imageId != a.imageId) return false } else -> { if (e != a) return false } } } return true } if (!compare(expected, actual)) throw AssertionError( "\n" + """ Expected str:${color.green}${expected}${color.reset} Actual str:${color.green}${actual}${color.reset} Expected json:${color.yellow}${expected.serializeToJsonString()}${color.reset} Actual json:${color.yellow}${actual.serializeToJsonString()}${color.reset} """.trimIndent() + "\n" ) } private class RefineTest( val list: List<ImMsgBody.Elem>, ) { lateinit var expected: MessageChain fun expected(chain: MessageChainBuilder.() -> Unit) { expected = buildMessageChain(chain) } var testLight: Boolean = false var testDeep: Boolean = false fun deep() { testDeep = true } fun light() { testLight = true } } @Suppress("TestFunctionName") private fun RefineTest(list: List<ImMsgBody.Elem>, action: RefineTest.() -> Unit): RefineTest { return RefineTest(list).apply(action) } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/data/MessageSerializationTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.data import kotlinx.serialization.KSerializer import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable import kotlinx.serialization.json.* import kotlinx.serialization.serializer import net.mamoe.mirai.Mirai import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.mapToByteArray import net.mamoe.mirai.utils.structureToString import kotlin.test.* internal class MessageSerializationTest : AbstractTest() { @Suppress("DEPRECATION_ERROR") private val module get() = MessageSerializers.serializersModule private val format get() = Json { serializersModule = module useArrayPolymorphism = false ignoreUnknownKeys = true } private inline fun <reified T : Any> T.serialize(serializer: KSerializer<T> = module.serializer()): String { return format.encodeToString(serializer, this) } private inline fun <reified T : Any> String.deserialize(serializer: KSerializer<T> = module.serializer()): T { return format.decodeFromString(serializer, this) } private inline fun <reified T : Any> testSerialization(t: T, serializer: KSerializer<T> = module.serializer()) { val deserialized = kotlin.runCatching { println("Testing ${t::class.simpleName} with serializer $serializer") val serialized = t.serialize(serializer) println("Result: ${serializer.descriptor.serialName} $serialized") serialized.deserialize(serializer) }.getOrElse { throw IllegalStateException("Failed to serialize $t", it) } assertEquals( t, deserialized, message = "serialized string: ${t.serialize(serializer)}\ndeserialized string: ${ deserialized.serialize( serializer ) }\n" ) } private val image = Image("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai") private val testMessageContentInstances: Array<out MessageContent> = arrayOf( PlainText("test"), At(123456), AtAll, image, Face(Face.AI_NI), RockPaperScissors.PAPER, UnsupportedMessageImpl(ImMsgBody.Elem()) ) private val emptySource = Mirai.constructMessageSource( 1L, MessageSourceKind.FRIEND, 1, 2, intArrayOf(1), 1, intArrayOf(1), messageChainOf() ) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") private val testConstrainSingleMessageInstances: Array<out ConstrainSingle> = arrayOf( Mirai.constructMessageSource( 1L, MessageSourceKind.FRIEND, 1, 2, intArrayOf(1), 1, intArrayOf(1), messageChainOf(emptySource, image) ), VipFace(VipFace.AiXin, 1), PokeMessage.BaoBeiQiu, MarketFaceImpl(ImMsgBody.MarketFace()), SimpleServiceMessage(1, "SSM"), LightApp("lightApp"), image.flash(), image.toForwardMessage(1L, "test"), MusicShare(MusicKind.NeteaseCloudMusic, "123", "123", "123", "123", "123", "123"), MessageOrigin(SimpleServiceMessage(1, "content"), "resource id", MessageOriginKind.LONG), ShowImageFlag, Dice(1), FileMessageImpl("id", 2, "name", 1) ) @Serializable data class W( val m: FileMessage ) @Test fun `test FileMessage serialization`() { val w = W(FileMessageImpl("id", 2, "name", 1)) println(w.serialize(W.serializer())) assertEquals(w, w.serialize(W.serializer()).deserialize(W.serializer())) } @Test fun `test Image serialization`() { val string = image.serialize() val element = string.deserialize<JsonElement>() element as JsonObject assertEquals(string.deserialize(), image) val image2 = Image(image.imageId) { type = ImageType.GIF width = 123 height = 456 } val string2 = image2.serialize() val element2 = string2.deserialize<JsonElement>() element2 as JsonObject assertEquals(element2["imageType"]?.jsonPrimitive?.content, image2.imageType.name) assertEquals(element2["width"]?.jsonPrimitive?.int, image2.width) assertEquals(element2["height"]?.jsonPrimitive?.int, image2.height) val decoded: Image = string2.deserialize() assertEquals(decoded.imageId, image2.imageId) assertEquals(decoded.imageType, image2.imageType) assertEquals(decoded.width, image2.width) assertEquals(decoded.height, image2.height) val string3 = """ { "imageType": "GIF", "width": 123, "height": 456, "imageId": "${image.imageId}" } """.trimIndent() val decoded2: Image = string3.deserialize() assertEquals(decoded2.imageId, image2.imageId) assertEquals(decoded2.imageType, image2.imageType) assertEquals(decoded2.width, image2.width) assertEquals(decoded2.height, image2.height) } @Serializable data class RichWrapper( val richMessage: RichMessage ) @Test fun `test polymorphic serialization`() { val string = format.encodeToString(RichWrapper.serializer(), RichWrapper(SimpleServiceMessage(1, "content"))) println(string) var element = format.parseToJsonElement(string) element as JsonObject element = element["richMessage"] as JsonObject assertEquals("SimpleServiceMessage", element["type"]?.cast<JsonPrimitive>()?.content) assertEquals("content", element["content"]?.cast<JsonPrimitive>()?.content) assertEquals(1, element["serviceId"]?.cast<JsonPrimitive>()?.content?.toInt()) } @Serializable data class Wrapper( val message: @Polymorphic SingleMessage ) @Test fun `test ShowImageFlag serialization`() { val string = format.encodeToString(Wrapper.serializer(), Wrapper(ShowImageFlag)) println(string) var element = format.parseToJsonElement(string) element as JsonObject element = element["message"] as JsonObject assertEquals("ShowImageFlag", element["type"]?.cast<JsonPrimitive>()?.content) assertTrue { element.size == 1 } assertEquals(ShowImageFlag, format.decodeFromString(Wrapper.serializer(), string).message) } @Test fun `test contextual serialization`() { for (message in testMessageContentInstances) { testSerialization( message, module.getContextual(message::class)?.cast() ?: error("No contextual serializer found for ${message::class}") ) } for (message in testConstrainSingleMessageInstances) { testSerialization( message, module.getContextual(message::class)?.cast() ?: error("No contextual serializer found for ${message::class}") ) } } @Test fun `test serialize message chain`() { val chain = testMessageContentInstances.toMessageChain() + emptySource println(chain.serialize()) // [["net.mamoe.mirai.message.data.PlainText",{"content":"test"}],["net.mamoe.mirai.message.data.At",{"target":123456,"display":""}],["net.mamoe.mirai.message.data.AtAll",{}],["net.mamoe.mirai.internal.message.OfflineGroupImage",{"imageId":"{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai"}]] testSerialization(chain) } @Test fun `test MessageSource serializable from issue 1273`() { // #1273 val a = """ {"kind":"GROUP","botId":692928873,"ids":[44],"internalIds":[-933057735],"time":1621607925,"fromId":1930893235,"targetId":1067474509,"originalMessage":[{"type":"Image","imageId":"{47B45B11-1491-3E85-E816-467029444C3F}.jpg"}]} """.trimIndent() val j = Json { serializersModule = module ignoreUnknownKeys = true } val source = j.decodeFromString(MessageSerializers.serializersModule.serializer<MessageSource>(), a) println(source.structureToString()) assertEquals( expected = Mirai.buildMessageSource(692928873, MessageSourceKind.GROUP) { id(44) internalId(-933057735) time(1621607925) sender(1930893235) target(1067474509) messages { +Image("{47B45B11-1491-3E85-E816-467029444C3F}.jpg") } }, actual = source ) } @Serializable data class AudioTestStandard( val online: OnlineAudio, val offline: OfflineAudio, val onlineAsRec: Audio, val offlineAsRec: Audio, ) @Test fun `test Audio standard`() { val origin = AudioTestStandard( OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null), OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, null), OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null), OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, null), ) assertEquals( AudioCodec.SILK.id, format.encodeToJsonElement(origin).jsonObject["offline"]!!.jsonObject["codec"]!!.jsonPrimitive.content.toInt() ) // use custom serializer assertEquals( AudioCodec.SILK.id, format.encodeToJsonElement(origin).jsonObject["online"]!!.jsonObject["codec"]!!.jsonPrimitive.content.toInt() ) // use custom serializer assertEquals( "OnlineAudio", format.encodeToJsonElement(origin).jsonObject["online"]!!.jsonObject["type"]!!.jsonPrimitive.content ) assertEquals( "OfflineAudio", format.encodeToJsonElement(origin).jsonObject["offline"]!!.jsonObject["type"]!!.jsonPrimitive.content ) assertEquals( "OnlineAudio", format.encodeToJsonElement(origin).jsonObject["onlineAsRec"]!!.jsonObject["type"]!!.jsonPrimitive.content ) assertEquals( "OfflineAudio", format.encodeToJsonElement(origin).jsonObject["offlineAsRec"]!!.jsonObject["type"]!!.jsonPrimitive.content ) val result = origin.serialize().deserialize<AudioTestStandard>() assertEquals(origin.online::class, result.online::class) assertEquals(origin.offline::class, result.offline::class) assertEquals(origin.onlineAsRec::class, result.onlineAsRec::class) assertEquals(origin.offlineAsRec::class, result.offlineAsRec::class) assertEquals(origin.online, result.online) assertEquals(origin.offline, result.offline) assertEquals(origin.onlineAsRec, result.onlineAsRec) assertEquals(origin.offlineAsRec, result.offlineAsRec) assertEquals(origin, result) } @Serializable data class AudioTestWithPtt( val online: OnlineAudio, val offline: OfflineAudio, val onlineAsRec: Audio, val offlineAsRec: Audio, ) @Test fun `test Audio with ptt`() { val origin = AudioTestWithPtt( OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, ImMsgBody.Ptt(1)), OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf(1, 2)), OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, ImMsgBody.Ptt(1)), OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf(1, 2)), ) val result = origin.serialize().deserialize<AudioTestWithPtt>() assertEquals(origin.online::class, result.online::class) assertEquals(origin.offline::class, result.offline::class) assertEquals(origin.onlineAsRec::class, result.onlineAsRec::class) assertEquals(origin.offlineAsRec::class, result.offlineAsRec::class) assertEquals(origin.online, result.online) assertEquals(origin.offline, result.offline) assertEquals(origin.onlineAsRec, result.onlineAsRec) assertEquals(origin.offlineAsRec, result.offlineAsRec) assertEquals(origin, result) } @Test fun `test Audio extraData`() { val origin = OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, ImMsgBody.Ptt(1)) val result = format.parseToJsonElement(origin.serialize()).jsonObject assertNotNull(origin.extraData) assertContentEquals( origin.extraData, result["extraData"]?.jsonArray?.mapToByteArray { it.jsonPrimitive.int.toByte() }) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/MessageProtocolFacadeTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol import net.mamoe.mirai.internal.test.AbstractTest import kotlin.test.Test import kotlin.test.assertEquals internal class MessageProtocolFacadeTest : AbstractTest() { @Test fun `can load`() { assertEquals( """ QuoteReplyProtocol AudioProtocol CustomMessageProtocol FaceProtocol FileMessageProtocol FlashImageProtocol ImageProtocol MarketFaceProtocol SuperFaceProtocol MusicShareProtocol PokeMessageProtocol PttMessageProtocol RichMessageProtocol ShortVideoProtocol TextProtocol VipFaceProtocol ForwardMessageProtocol LongMessageProtocol IgnoredMessagesProtocol UnsupportedMessageProtocol GeneralMessageSenderProtocol """.trimIndent(), MessageProtocolFacadeImpl().loaded.joinToString("\n") { it::class.simpleName.toString() } ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/AbstractMessageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import io.ktor.utils.io.core.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.serialization.* 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 kotlinx.serialization.json.* import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.data.inferMessageSourceKind import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacadeImpl import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderPipelineImpl import net.mamoe.mirai.internal.message.protocol.decodeAndRefineLight import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderPipelineImpl import net.mamoe.mirai.internal.message.protocol.outgoing.HighwayUploader import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToFriendImpl import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.components.ClockHolder import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg import net.mamoe.mirai.internal.notice.processors.GroupExtensions import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.internal.testFramework.dynamicTest import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.utils.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.reflect.KClass import kotlin.test.* @OptIn(TestOnly::class) internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandlerTest(), GroupExtensions { init { setSystemProp("mirai.message.protocol.log.full", "true") setSystemProp("mirai.message.outgoing.pipeline.log.full", "true") } override fun createAccount(): BotAccount = BotAccount(1230001L, "pwd") protected abstract val protocols: Array<out MessageProtocol> protected var defaultTarget: ContactOrBot by lateinitMutableProperty { bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } private var decoderLoggerEnabled = false private var encoderLoggerEnabled = false @BeforeTest fun beforeEach() { decoderLoggerEnabled = MessageDecoderPipelineImpl.defaultTraceLogging.isEnabled MessageDecoderPipelineImpl.defaultTraceLogging.enable() encoderLoggerEnabled = MessageEncoderPipelineImpl.defaultTraceLogging.isEnabled MessageEncoderPipelineImpl.defaultTraceLogging.enable() } @AfterTest fun afterEach() { if (!decoderLoggerEnabled) { MessageDecoderPipelineImpl.defaultTraceLogging.disable() } if (!encoderLoggerEnabled) { MessageEncoderPipelineImpl.defaultTraceLogging.disable() } } protected fun facadeOf(vararg protocols: MessageProtocol): MessageProtocolFacade { return MessageProtocolFacadeImpl( protocols.toList(), remark = "MessageProtocolFacade with ${protocols.joinToString { it::class.simpleName!! }}" ) } /////////////////////////////////////////////////////////////////////////// // coding /////////////////////////////////////////////////////////////////////////// protected fun doEncoderChecks( expectedStruct: List<ImMsgBody.Elem>, protocols: Array<out MessageProtocol>, encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem> ) { asserter.assertEquals( expectedStruct, facadeOf(*protocols).encode(), message = "Failed to check single Protocol" ) asserter.assertEquals( expectedStruct, MessageProtocolFacade.INSTANCE.encode(), message = "Failed to check with all protocols" ) } var asserter: EqualityAsserter = EqualityAsserter.OrdinaryThenStructural fun useOrdinaryEquality() { asserter = EqualityAsserter.Ordinary } fun useStructuralEquality() { asserter = EqualityAsserter.Structural } protected fun doDecoderChecks( expectedChain: MessageChain, protocols: Array<out MessageProtocol> = this.protocols, decode: MessageProtocolFacade.() -> MessageChain ) { asserter.assertEquals( expectedChain.toList(), facadeOf(*protocols).decode().toList(), message = "Failed to check single Protocol" ) asserter.assertEquals( expectedChain.toList(), MessageProtocolFacade.INSTANCE.decode().toList(), message = "Failed to check with all protocols" ) } protected fun doEncoderChecks( vararg expectedStruct: ImMsgBody.Elem, protocols: Array<out MessageProtocol> = this.protocols, encode: MessageProtocolFacade.() -> List<ImMsgBody.Elem> ): Unit = doEncoderChecks(expectedStruct.toList(), protocols, encode) inner class CodingChecksBuilder { var elems: MutableList<ImMsgBody.Elem> = mutableListOf() var messages: MessageChainBuilder = MessageChainBuilder() var groupIdOrZero: Long = 0 var target: ContactOrBot = defaultTarget var messageSourceKind: MessageSourceKind by lateinitMutableProperty { target.inferMessageSourceKind() } var withGeneralFlags = true var isForward = false fun elem(vararg elem: ImMsgBody.Elem) { elems.addAll(elem) } fun message(vararg message: SingleMessage) { messages.addAll(message) } fun target(target: ContactOrBot) { this.target = target messageSourceKind = target.inferMessageSourceKind() if (target is Group) { groupIdOrZero = target.id } } fun forward() { this.isForward = true } fun build() = ChecksConfiguration( elems.toList(), messages.build(), groupIdOrZero, messageSourceKind, target, withGeneralFlags, isForward ) } class ChecksConfiguration( val elems: List<ImMsgBody.Elem>, val messageChain: MessageChain, val groupIdOrZero: Long, val messageSourceKind: MessageSourceKind, val target: ContactOrBot?, val withGeneralFlags: Boolean, val isForward: Boolean, ) @Suppress("DeferredIsResult") protected fun buildCodingChecks( builderAction: CodingChecksBuilder.() -> Unit, ): Deferred<ChecksConfiguration> { // IDE will warn you if you forget to call .do contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } return CompletableDeferred(CodingChecksBuilder().apply(builderAction).build()) } @OptIn(ExperimentalCoroutinesApi::class) protected open fun Deferred<ChecksConfiguration>.doEncoderChecks() { val config = this.getCompleted() doEncoderChecks(config.elems, protocols) { encode( config.messageChain, config.target, withGeneralFlags = config.withGeneralFlags, isForward = config.isForward ) } } @OptIn(ExperimentalCoroutinesApi::class) protected open fun Deferred<ChecksConfiguration>.doDecoderChecks() { val config = this.getCompleted() doDecoderChecks(config.messageChain, protocols) { decodeAndRefineLight( config.elems, config.groupIdOrZero, config.messageSourceKind, bot ) } } protected open fun Deferred<ChecksConfiguration>.doBothChecks() { doEncoderChecks() doDecoderChecks() } /////////////////////////////////////////////////////////////////////////// // sending /////////////////////////////////////////////////////////////////////////// open inner class TestMessageProtocolStrategy : MessageProtocolStrategy<AbstractContact> { override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { assertEquals(0x123, packet.sequenceId) return MessageSvcPbSendMsg.Response.SUCCESS(123) } override suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: AbstractContact, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit ): List<OutgoingPacket> { sourceCallback(CompletableDeferred(constructSourceForSpecialMessage(originalMessage, 1000))) return listOf(OutgoingPacket("Test", "test", 0x123, ByteReadPacket.Empty)) } override suspend fun constructSourceForSpecialMessage( originalMessage: MessageChain, fromAppId: Int ): OnlineMessageSource.Outgoing { return when (val defaultTarget = defaultTarget) { is Group -> OnlineMessageSourceToGroupImpl( coroutineScope = defaultTarget, internalIds = intArrayOf(1), time = 1, originalMessage = originalMessage, sender = bot, target = defaultTarget ) is Friend -> OnlineMessageSourceToFriendImpl( sequenceIds = intArrayOf(1), internalIds = intArrayOf(1), time = 1, originalMessage = originalMessage, sender = bot, target = defaultTarget ) else -> error("Unexpected target: $defaultTarget") } } } init { components[MessageProtocolStrategy] = TestMessageProtocolStrategy() components[HighwayUploader] = object : HighwayUploader { override suspend fun uploadMessages( contact: AbstractContact, components: ComponentStorage, nodes: Collection<ForwardMessage.INode>, isLong: Boolean, senderName: String ): String { return "(size=${nodes.size})${ nodes.joinToString().replace(bot.id.toString(), "123123").md5().toUHexString("") }" } } components[ClockHolder] = object : ClockHolder() { override val local: Clock = object : Clock { override fun currentTimeMillis(): Long = 160023456 } } } fun runWithFacade(action: suspend MessageProtocolFacade.() -> Unit) { runBlockingUnit { facadeOf(*protocols).run { action() } MessageProtocolFacade.INSTANCE.copy().run { action() } } } companion object { fun assertMessageEquals(expected: Message, actual: Message) { val expectedChain = expected.toMessageChain() val actualChain = actual.toMessageChain() val message = """ Expected: $1 Actual: $2 """.trimIndent().replace("$1", expectedChain.render()).replace("$2", actualChain.render()) assertEquals(expectedChain.size, actualChain.size, message) asserter.assertEquals(message, expectedChain, actualChain) } fun MessageProtocolFacade.assertMessageEquals(expected: Message, actual: Message) { val expectedChain = expected.toMessageChain() val actualChain = actual.toMessageChain() val message = """ Facade: ${this.remark} Expected: $1 Actual: $2 """.trimIndent().replace("$1", expectedChain.render()).replace("$2", actualChain.render()) assertEquals(expectedChain.size, actualChain.size, message) asserter.assertEquals(message, expectedChain, actualChain) } inline fun Asserter.assertEquals(crossinline message: () -> String, expected: Any?, actual: Any?) { assertTrue({ message() + ". Expected <$expected>, actual <$actual>." }, actual == expected) } fun MessageChain.render(): String = buildString { appendLine("size = $size") for (singleMessage in distinct()) { val count = this@render.count { it == singleMessage } appendLine("$count x [${singleMessage::class.simpleName}] $singleMessage") } } } /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// open val format: Json // `serializersModule` is volatile, always return new Json instances. get() = Json { prettyPrint = true serializersModule = MessageSerializers.serializersModule } /////////////////////////////////////////////////////////////////////////// // serialization - polymorphism /////////////////////////////////////////////////////////////////////////// interface PolymorphicWrapper { val message: SingleMessage } /** * @param expectedSerialName also known as *poly discriminator*, give `null` to check for no discriminator's presence */ protected open fun <M : SingleMessage, P : PolymorphicWrapper> testPolymorphicIn( polySerializer: KSerializer<P>, polyConstructor: (message: M) -> P, data: M, expectedSerialName: String?, expectedInstance: M = data, ) { val string = format.encodeToString( polySerializer, polyConstructor(data) ) println(string) var element = format.parseToJsonElement(string) element as JsonObject element = element["message"] as JsonObject if (expectedSerialName != null) { assertEquals(expectedSerialName, element["type"]?.cast<JsonPrimitive>()?.content) } else { assertEquals(null, element["type"]) } assertEquals( expectedInstance, format.decodeFromString(polySerializer, string).message ) } @Serializable data class PolymorphicWrapperSingleMessage( override val message: @Polymorphic SingleMessage ) : PolymorphicWrapper protected open fun <M : SingleMessage> testPolymorphicInSingleMessage( data: M, expectedSerialName: String, expectedInstance: M = data, ) = listOf(dynamicTest("testPolymorphicInSingleMessage") { testPolymorphicIn( polySerializer = PolymorphicWrapperSingleMessage.serializer(), polyConstructor = ::PolymorphicWrapperSingleMessage, data = data, expectedSerialName = expectedSerialName, expectedInstance = expectedInstance ) }) @Serializable data class PolymorphicWrapperMessageContent( override val message: @Polymorphic MessageContent ) : PolymorphicWrapper protected open fun <M : MessageContent> testPolymorphicInMessageContent( data: M, expectedSerialName: String, expectedInstance: M = data, ) = listOf(dynamicTest("testPolymorphicInMessageContent") { testPolymorphicIn( polySerializer = PolymorphicWrapperMessageContent.serializer(), polyConstructor = ::PolymorphicWrapperMessageContent, data = data, expectedSerialName = expectedSerialName, expectedInstance = expectedInstance ) }) @Serializable data class PolymorphicWrapperMessageMetadata( override val message: @Polymorphic MessageMetadata ) : PolymorphicWrapper protected open fun <M : MessageMetadata> testPolymorphicInMessageMetadata( data: M, expectedSerialName: String, expectedInstance: M = data, ) = listOf(dynamicTest("testPolymorphicInMessageMetadata") { testPolymorphicIn( polySerializer = PolymorphicWrapperMessageMetadata.serializer(), polyConstructor = ::PolymorphicWrapperMessageMetadata, data = data, expectedSerialName = expectedSerialName, expectedInstance = expectedInstance ) }) /////////////////////////////////////////////////////////////////////////// // serialization - in MessageChain /////////////////////////////////////////////////////////////////////////// protected open fun <M : SingleMessage> testInsideMessageChain( data: M, expectedSerialName: String, expectedInstance: M = data, ) = listOf(dynamicTest("testInsideMessageChain") { val chain = messageChainOf(data) val string = chain.serializeToJsonString(format) println(string) val element = format.parseToJsonElement(string).jsonArray.single().jsonObject assertEquals(expectedSerialName, element["type"]?.cast<JsonPrimitive>()?.content) assertEquals( expectedInstance, MessageChain.deserializeFromJsonString(string).single() ) }) /////////////////////////////////////////////////////////////////////////// // serialization - contextual /////////////////////////////////////////////////////////////////////////// @Serializable data class ContextualWrapper( val message: @Contextual SingleMessage, ) @Serializable data class GenericTypedWrapper<T : SingleMessage>( val message: T, ) protected open fun <M : T, T : SingleMessage> testContextual( data: M, expectedSerialName: String, expectedInstance: M = data, targetType: KClass<out T> = data::class, ) = listOf( testContextualWithWrapper<M, T>(data, expectedSerialName, expectedInstance), testContextualWithStaticType(targetType, data, expectedInstance), testContextualGeneric(targetType, data, expectedInstance), testContextualWithoutWrapper<M, T>(data, expectedInstance) ).flatten() private fun <M : T, T : SingleMessage> testContextualWithoutWrapper( data: M, expectedInstance: M ) = listOf(dynamicTest("testContextualWithoutWrapper") { @Suppress("UNCHECKED_CAST") val serializer = ContextualSerializer(data::class) as KSerializer<M> val string = format.encodeToString(serializer, data) println(string) val element = format.parseToJsonElement(string).jsonObject assertEquals(null, element["type"]) assertEquals( expectedInstance, format.decodeFromString(serializer, string) ) }) private fun <M : T, T : SingleMessage> testContextualGeneric( targetType: KClass<out T>, data: M, expectedInstance: M ) = listOf(dynamicTest("testContextualGeneric") { val messageSerializer: ContextualSerializer<SingleMessage> = ContextualSerializer(targetType).cast() /** * ``` * data class StaticTypedWrapper( * val message: T * ) * ``` * without concern of generic types. */ /** * ``` * data class StaticTypedWrapper( * val message: T * ) * ``` * without concern of generic types. */ val serializer = GenericTypedWrapper.serializer(messageSerializer) val string = format.encodeToString(serializer, GenericTypedWrapper(data)) println(string) val element = format.parseToJsonElement(string).jsonObject["message"]!!.jsonObject assertEquals(null, element["type"]?.jsonPrimitive?.content) assertEquals( expectedInstance, format.decodeFromString(serializer, string).message ) }) private fun <M : T, T : SingleMessage> testContextualWithStaticType( targetType: KClass<out T>, data: M, expectedInstance: M, ) = listOf(dynamicTest("testContextualWithStaticType") { val messageSerializer: ContextualSerializer<SingleMessage> = ContextualSerializer(targetType).cast() /** * ``` * data class StaticTypedWrapper( * val message: T * ) * ``` * without concern of generic types. */ /** * ``` * data class StaticTypedWrapper( * val message: T * ) * ``` * without concern of generic types. */ val serializer = object : KSerializer<SingleMessage> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("StaticTypedWrapper") { element("message", messageSerializer.descriptor, listOf(Contextual())) } override fun deserialize(decoder: Decoder): SingleMessage { return decoder.decodeStructure(descriptor) { if (this.decodeSequentially()) { this.decodeSerializableElement( descriptor.getElementDescriptor(0), 0, messageSerializer ) } else { val index = this.decodeElementIndex(descriptor) check(index == 0) this.decodeSerializableElement(descriptor, index, messageSerializer) } } } override fun serialize(encoder: Encoder, value: SingleMessage) { encoder.encodeStructure(descriptor) { encodeSerializableElement(descriptor, 0, messageSerializer, value) } } } val string = format.encodeToString(serializer, data) println(string) val element = format.parseToJsonElement(string).jsonObject["message"]!!.jsonObject assertEquals(null, element["type"]?.jsonPrimitive?.content) assertEquals( expectedInstance, format.decodeFromString(serializer, string) ) }) private fun <M : T, T : SingleMessage> testContextualWithWrapper( data: M, expectedSerialName: String, expectedInstance: M, afterSerialization: (element: JsonObject) -> Unit = {} ) = listOf(dynamicTest("testContextualWithWrapper") { val string = format.encodeToString(ContextualWrapper.serializer(), ContextualWrapper(data)) println(string) val element = format.parseToJsonElement(string).jsonObject["message"]!!.jsonObject afterSerialization(element) assertEquals(expectedSerialName, element["type"]?.jsonPrimitive?.content) assertEquals( expectedInstance, format.decodeFromString(ContextualWrapper.serializer(), string).message ) }) } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/CustomMessageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.data.CustomMessage import net.mamoe.mirai.message.data.CustomMessageMetadata import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test internal class CustomMessageProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(CustomMessageProtocol(), TextProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @BeforeTest fun init() { MyCustomMessage(1) // register } @Serializable class MyCustomMessage( val int: Int, ) : CustomMessageMetadata(), ProtoBuf { override fun getFactory(): Factory = Factory object Factory : CustomMessage.Factory<MyCustomMessage>("MyCustomMessage") { override fun dump(message: MyCustomMessage): ByteArray { return message.toByteArray(serializer()) } override fun load(input: ByteArray): MyCustomMessage { return input.loadAs(serializer()) } } } @Test fun `test encode`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomElem( data = "00 00 00 17 08 01 12 0F 4D 79 43 75 73 74 6F 6D 4D 65 73 73 61 67 65 1A 02 08 01".hexToBytes(), enumType = 103904510, ), ) ) message(MyCustomMessage(1)) }.doEncoderChecks() } @Test fun `test decode`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomElem( data = "00 00 00 17 08 01 12 0F 4D 79 43 75 73 74 6F 6D 4D 65 73 73 61 67 65 1A 02 08 01".hexToBytes(), enumType = 103904510, ), ) ) message(MyCustomMessage(1)) }.doEncoderChecks() } // not supported, see https://github.com/mamoe/mirai/issues/2144 // @TestFactory // fun `test serialization`(): DynamicTestsResult { // val data = MyCustomMessage(1) // val serialName = "CustomMessage" // return runDynamicTests( // testPolymorphicInMessageMetadata(data, serialName), // testPolymorphicInSingleMessage(data, serialName), // testInsideMessageChain(data, serialName), // testContextual(data), // ) // } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/EqualityAsserter.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.serialization.KSerializer import kotlinx.serialization.json.Json import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.utils.structureToStringIfAvailable import kotlin.test.assertNotNull import kotlin.test.asserter internal interface EqualityAsserter { @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") fun <@kotlin.internal.OnlyInputTypes T> assertEquals( expected: List<T>, actual: List<T>, message: String? = null ) object Ordinary : EqualityAsserter { override fun <T> assertEquals(expected: List<T>, actual: List<T>, message: String?) { if (expected.size == actual.size) { if (expected.size == 1 && expected.singleOrNull() == actual.singleOrNull()) { return asserter.assertEquals(message, expected.single(), actual.single()) } if (expected.zip(actual).all { (e, a) -> e == a }) return asserter.assertEquals(message, expected, actual) } else { asserter.assertEquals(message, expected, actual) } } } object Structural : EqualityAsserter { override fun <T> assertEquals(expected: List<T>, actual: List<T>, message: String?) { if (expected.size == 1 && actual.size == 1) { val e = expected.single() val a = actual.single() if (a == null || e == null) { asserter.assertEquals( "[Null] $message", structureToStringOrOrdinaryString(e), structureToStringOrOrdinaryString(a) ) assertNotNull(a, message) assertNotNull(e, message) } @Suppress("UNNECESSARY_NOT_NULL_ASSERTION") if (!e!!::class.isInstance(a) && !a!!::class.isInstance(e)) { asserter.assertEquals( "[Incompatible type] $message", structureToStringOrOrdinaryString(e), structureToStringOrOrdinaryString(a) ) return } asserter.assertEquals( message, structureToStringOrOrdinaryString(e), structureToStringOrOrdinaryString(a) ) } else { asserter.assertEquals( message, expected.joinToString { structureToStringOrOrdinaryString(it) }, actual.joinToString { structureToStringOrOrdinaryString(it) }) } } private val json = Json { isLenient = true prettyPrint = true serializersModule = MessageSerializers.serializersModule } private fun <T> structureToStringOrOrdinaryString(value: T): String { if (value == null) return "null" val valueNotNull: T & Any = value @Suppress("UNCHECKED_CAST") return valueNotNull.structureToStringIfAvailable() // fallback for native ?: kotlin.run { if (valueNotNull is SingleMessage) { messageChainOf(valueNotNull).serializeToJsonString(json) // use the stable serialization approach } else json.encodeToString( when (valueNotNull) { is ImMsgBody.Elem -> ImMsgBody.Elem.serializer() else -> error("Unsupported type: $valueNotNull") } as KSerializer<Any>, valueNotNull ) } } } object OrdinaryThenStructural : EqualityAsserter { override fun <T> assertEquals(expected: List<T>, actual: List<T>, message: String?) { try { Ordinary.assertEquals(expected, actual, message) return } catch (e: AssertionError) { Structural.assertEquals(expected, actual, message) return } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/FaceProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.decodeAndRefineLight import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.Face import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.utils.hexToBytes import kotlin.test.Test internal class FaceProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(FaceProtocol()) @Test fun `can encode`() { doEncoderChecks( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( face = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Face( index = 1, old = "14 42".hexToBytes(), buf = "00 01 00 04 52 CC F5 D0".hexToBytes(), ), ), ) { encode( messageChainOf(Face(Face.PIE_ZUI)), messageTarget = null, withGeneralFlags = true, isForward = false ) } } @Test fun `can decode`() { doDecoderChecks( messageChainOf(Face(Face.YIN_XIAN)), ) { decodeAndRefineLight( listOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( face = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Face( index = 108, old = "14 AD".hexToBytes(), ), ) ), groupIdOrZero = 0, MessageSourceKind.GROUP, bot, ) } } @TestFactory fun `test serialization`(): DynamicTestsResult { val data = Face(1) val serialName = Face.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/FileMessageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.flags.AllowSendFileMessage import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertFailsWith internal class FileMessageProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(FileMessageProtocol(), TextProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `test decode`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( elemFlags2 = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ElemFlags2( compatibleId = 1, msgRptCnt = 1, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( transElemInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.TransElem( elemType = 24, elemValue = "01 00 7A 08 06 12 0C 73 65 73 73 69 6F 6E 2E 6C 6F 63 6B 1A 05 38 42 79 74 65 3A 61 12 5F 08 66 12 25 2F 38 34 33 35 32 37 64 38 2D 64 39 31 35 2D 31 31 65 63 2D 62 32 34 30 2D 35 34 35 32 30 30 37 62 64 61 61 34 18 08 22 0C 73 65 73 73 69 6F 6E 2E 6C 6F 63 6B 28 00 3A 00 42 20 39 30 33 65 39 36 34 35 36 38 38 62 63 62 32 35 35 64 30 36 64 31 64 61 31 35 33 66 64 36 32 64".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( glamourLevel = 3, pbReserve = "08 0A 78 00 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 10 0B 90 04 80 01 B8 04 02 C0 04 01 CA 04 00 F8 04 00 88 05 00".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( nick = "Nick", level = 1, flags = 8, groupMask = 1, ), ) ) message(FileMessage("/843527d8-d915-11ec-b240-5452007bdaa4", 102, "session.lock", 8)) useOrdinaryEquality() }.doDecoderChecks() } @TestFactory fun `test serialization`(): DynamicTestsResult { val data = FileMessage("id", 1, "name", 2) val serialName = FileMessage.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } @Test fun `test manual send forbidden`() { val data = FileMessage("id", 1, "name", 2) assertFailsWith<IllegalStateException> { FileMessageProtocol.verifyFileMessage(data.toMessageChain()) } } @Test fun `test auto send allowed`() { val data = FileMessage("id", 1, "name", 2) FileMessageProtocol.verifyFileMessage(AllowSendFileMessage + data) } @Test fun `test auto send allowed2`() { val data = FileMessage("id", 1, "name", 2) FileMessageProtocol.verifyFileMessage(data + AllowSendFileMessage) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/ForwardMessageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags import net.mamoe.mirai.internal.message.data.ForwardMessageInternal import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.castUp import net.mamoe.mirai.utils.getRandomString import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals internal class ForwardMessageProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf( TextProtocol(), ImageProtocol(), ForwardMessageProtocol(), GeneralMessageSenderProtocol(), ) init { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun precondition() { assertEquals(getRandomString(5000, Random(1)), getRandomString(5000, Random(1))) assertMessageEquals( "test".toPlainText() + getRandomString(5000, Random(1)) + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200), "test".toPlainText() + getRandomString(5000, Random(1)) + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200) ) } @Test fun `can convert ForwardMessage to ForwardMessageInternal`() { var message = buildForwardMessage(defaultTarget.cast()) { currentTime = 16000000 1 named "Name1" says "Hello" }.toMessageChain() message += IgnoreLengthCheck runWithFacade { preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> val receipt = receipts.single() assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) assertMessageEquals( ForwardMessageInternal( """<?xml version="1.0" encoding="utf-8"?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="[聊天记录]" m_resid="(size=1)501389E3070B20D87A80A67961F4EA0E" m_fileName="160023" tSum="3" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="0"> <item layout="1" advertiser_id="0" aid="0"> <title size="34" maxLines="2" lineSpace="12">群聊的聊天记录</title> <title size="26" color="#777777" maxLines="2" lineSpace="12">Name1: Hello</title> <hr hidden="false" style="0"/> <summary size="26" color="#777777">查看1条转发消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg>""", "(size=1)501389E3070B20D87A80A67961F4EA0E", null, origin = message[ForwardMessage] ) + IgnoreLengthCheck, context.currentMessageChain ) } } } @Test fun `can convert empty ForwardMessage`() { val message = buildForwardMessage(defaultTarget.cast()) {}.toMessageChain() runWithFacade { preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> val receipt = receipts.single() assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) assertMessageEquals( ForwardMessageInternal( """<?xml version="1.0" encoding="utf-8"?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="[聊天记录]" m_resid="(size=0)D41D8CD98F00B204E9800998ECF8427E" m_fileName="160023" tSum="3" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="0"> <item layout="1" advertiser_id="0" aid="0"> <title size="34" maxLines="2" lineSpace="12">群聊的聊天记录</title> <hr hidden="false" style="0"/> <summary size="26" color="#777777">查看0条转发消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg>""", "(size=0)D41D8CD98F00B204E9800998ECF8427E", null, origin = message[ForwardMessage] ), context.currentMessageChain ) } } } // // TODO: 2022/5/23 test for download ForwardMessage // @Test // fun `can receive and download ForwardMessage`() { // val message = runTest { // runWithFacade { // net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( // msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( // fromUin = 1230001, // toUin = 1230002, // msgType = 166, // c2cCmd = 11, // msgSeq = 34437, // msgTime = 1653334690, // msgUid = 72057594524997436, // wseqInC2cMsghead = 34437, // ), // msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( // richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( // attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( // codePage = 0, // time = 1653334690, // random = 487069500, // effect = 0, // charSet = 134, // pitchAndFamily = 2, // fontName = "宋体", // ), // elems = mutableListOf( // net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( // richMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichMsg( // template1 = "01 78 9C B5 92 4F 6F D3 30 18 C6 BF 8A 65 0E 39 4C 23 4D FA 2F A0 38 53 B7 29 EB D8 12 89 65 45 FC 11 9A DC C4 C9 2C D9 49 65 3B DD DA DB 24 0E 08 0E 88 03 17 04 42 48 70 40 42 C0 89 DB 3E 4E BB 7E 0C 1C 77 12 E3 80 C4 85 F8 10 47 AF F3 F3 F3 3C EF EB 6F 9D 73 06 A6 44 48 5A 95 C8 72 6E B7 2C 40 CA B4 CA 68 59 20 6B 74 1C 6E 7A 16 90 0A 97 19 66 55 49 90 35 23 D2 02 5B 81 CF 65 01 24 11 53 9A 92 FD 5D 04 DB 5D 08 14 E1 13 86 95 F9 76 20 C0 A9 6A 98 70 4A C9 59 54 33 45 23 59 40 30 16 94 E4 08 3E 59 5D BC 58 7C FA B2 FA F6 63 71 F9 E6 29 04 FC 44 10 49 33 04 C7 C7 E1 91 3C 4C F2 78 F4 28 89 C2 68 7B 63 E0 B9 03 B1 71 FF E1 78 87 1E A4 C5 38 8E 6B 6F 6F BB FF 60 CF 3E 9A 77 0E 1E C7 C3 D1 4E 31 BC 37 8F C2 DD 3C 12 83 44 34 A8 9C 32 12 63 4E 10 EC 3B 2D BD BC 8E 73 C7 E9 BA 1D A7 D7 ED 35 3A 93 9A 23 E8 42 20 AB 5A A4 44 CB DA D7 17 B7 20 A8 05 43 10 82 9C E1 42 3B D2 0E 32 1D 4C 42 8B D2 54 F9 B5 87 D0 94 5B 30 F0 A9 76 0C 18 9E 55 B5 5A 3B 6E CE 2B AA 63 39 A1 6B 22 5E BF 03 5F 51 C5 08 90 74 AE 45 B5 3B 1A 86 CF 0F 69 49 A4 D1 C1 F4 2E 99 E0 54 D7 1C 17 06 43 CA 1D CF E3 15 B9 7A FB EC 66 4C BE 6D 28 7F C2 DC 1E 04 69 C5 2A 81 E0 AD BE 79 FE 11 7E 17 80 FC 2C A3 ED FF 4C 3D 15 E0 94 66 19 D1 19 E6 98 49 A2 53 57 33 46 4C 3A 76 E0 CB 9A 73 2C 66 7F BF 36 58 7E F8 7C F5 EE A5 BB 7C FF 71 75 F9 75 F1 EA F5 F2 E7 F3 E5 C5 77 DF BE FE 33 F0 ED A6 0D 9A 64 9A 09 4A D3 F7 9B B1 41 40 D3 66 0E 7F 4F A4 DE 4D 26 4D 67 36 1D 23 C2 D6 C3 1C FC 02 21 C3 0A 7E".hexToBytes(), // serviceId = 35, // ), // ), // net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( // generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( // pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 CA 04 00 D2 05 02 08 6A".hexToBytes(), // ), // ), // net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( // ), // ), // ), // ), // ).toMessageChainOnline(bot, facade = this) // } // } // } @TestFactory fun `test serialization`(): DynamicTestsResult { val data = buildForwardMessage(defaultTarget.castUp()) { add(1, "senderName", time = 123, message = PlainText("simple text")) add(1, "senderName", time = 123) { +PlainText("simple") +Face(1) +Image("{90CCED1C-2D64-313B-5D66-46625CAB31D7}.jpg") } } val serialName = ForwardMessage.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/GeneralMessageSenderProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.coroutines.Deferred import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags import net.mamoe.mirai.internal.message.flags.ForceAsFragmentedMessage import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.castUp import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertTrue internal class GeneralMessageSenderProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol(), GeneralMessageSenderProtocol()) @Test fun `can convert messages failed to send to fragmented`() { val message = messageChainOf(PlainText("test"), PlainText("test")) runWithFacade { components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() { var count = 0 override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { println("MessageProtocolStrategy.sendPacket called: $count") if (count++ <= 1) { // fail the first and second attempt return MessageSvcPbSendMsg.Response.MessageTooLarge } return super.sendPacket(bot, packet) } override suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: AbstractContact, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit ): List<OutgoingPacket> { if (count == 2) { assertTrue { fragmented } } else { assertFalse { fragmented } } return super.createPacketsForGeneralMessage( client, contact, message, originalMessage, fragmented, sourceCallback ) } } preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> val receipt = receipts.single() assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) assertMessageEquals(message.dropMiraiInternalFlags(), context.currentMessageChain) } } } @Test fun `can convert messages to fragmented`() { val message = messageChainOf(PlainText("test"), PlainText("test"), ForceAsFragmentedMessage) runWithFacade { components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() { override suspend fun createPacketsForGeneralMessage( client: QQAndroidClient, contact: AbstractContact, message: MessageChain, originalMessage: MessageChain, fragmented: Boolean, sourceCallback: (Deferred<OnlineMessageSource.Outgoing>) -> Unit ): List<OutgoingPacket> { assertTrue { fragmented } return super.createPacketsForGeneralMessage( client, contact, message, originalMessage, fragmented, sourceCallback ) } } preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> val receipt = receipts.single() assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) assertMessageEquals(message, context.currentMessageChain) } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/ImageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import io.ktor.utils.io.core.* import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.image.OfflineFriendImage import net.mamoe.mirai.internal.message.image.OfflineGroupImage import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.dynamicTest import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.ImageType import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertIs internal class ImageProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(ImageProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } /////////////////////////////////////////////////////////////////////////// // receive from macOS client /////////////////////////////////////////////////////////////////////////// @Test fun `group Image receive from macOS`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace( filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg", fileId = -1866484636, useful = 1, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), thumbUrl = "/gchatpic_new/123456/12345678-2428482660-A7CBB52943A2127CE42659D29BAA8515/198?term=2", origUrl = "/gchatpic_new/123456/12345678-2428482660-A7CBB52943A2127CE42659D29BAA8515/0?term=2", width = 904, height = 1214, size = 170426, thumbWidth = 147, thumbHeight = 198, _400Url = "/gchatpic_new/123456/12345678-2428482660-A7CBB52943A2127CE42659D29BAA8515/400?term=2", _400Width = 285, _400Height = 384, ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = false }) targetGroup() useOrdinaryEquality() }.doDecoderChecks() } @Test fun `friend Image receive from macOS`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg", fileLen = 170426, downloadPath = "/123456-306012740-A7CBB52943A2127CE42659D29BAA8515", oldVerSendFile = "16 20 31 32 32 31 30 31 31 31 31 41 42 20 20 20 20 31 37 30 34 32 36 6B 7B 41 37 43 42 42 35 32 39 2D 34 33 41 32 2D 31 32 37 43 2D 45 34 32 36 2D 35 39 44 32 39 42 41 41 38 35 31 35 7D 2E 6A 70 67 77 2F 31 30 34 30 34 30 30 32 39 30 2D 33 30 36 30 31 32 37 34 30 2D 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 41".hexToBytes(), picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), picHeight = 1214, picWidth = 904, resId = "/123456-306012740-A7CBB52943A2127CE42659D29BAA8515", thumbUrl = "/offpic_new/123456//123456-306012740-A7CBB52943A2127CE42659D29BAA8515/198?term=2", origUrl = "/offpic_new/123456//123456-306012740-A7CBB52943A2127CE42659D29BAA8515/0?term=2", thumbWidth = 147, thumbHeight = 198, _400Url = "/offpic_new/123456//123456-306012740-A7CBB52943A2127CE42659D29BAA8515/400?term=2", _400Width = 285, _400Height = 384, ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = false }) targetFriend() }.doDecoderChecks() } /////////////////////////////////////////////////////////////////////////// // receive from Android /////////////////////////////////////////////////////////////////////////// @Test fun `group Image receive from Android`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace( filePath = "A7CBB52943A2127CE42659D29BAA8515.jpg", oldData = "15 36 20 38 36 65 41 31 42 61 66 34 35 64 37 38 63 36 66 31 65 39 30 33 66 20 20 20 20 20 20 35 30 57 4A 4B 53 53 71 52 79 61 52 46 42 7A 77 38 34 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 2E 6A 70 67 41".hexToBytes(), fileId = -1354377332, serverIp = 1864273983, serverPort = 80, fileType = 66, signature = "WJKSSqRyaRFBzw84".toByteArray(), /* 57 4A 4B 53 53 71 52 79 61 52 46 42 7A 77 38 34 */ useful = 1, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), thumbUrl = "/gchatpic_new/123456/622457678-2940589964-A7CBB52943A2127CE42659D29BAA8515/198?term=2", origUrl = "/gchatpic_new/123456/622457678-2940589964-A7CBB52943A2127CE42659D29BAA8515/0?term=2", imageType = 1000, width = 904, height = 1214, source = 103, size = 170426, thumbWidth = 147, thumbHeight = 198, _400Url = "/gchatpic_new/123456/622457678-2940589964-A7CBB52943A2127CE42659D29BAA8515/400?term=2", _400Width = 285, _400Height = 384, pbReserve = "08 01 10 00 32 00 4A 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 06".hexToBytes(), ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = true }) targetGroup() useOrdinaryEquality() }.doDecoderChecks() } @Test fun `friend Image receive from Android`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "A7CBB52943A2127CE42659D29BAA8515.jpg", fileLen = 170426, downloadPath = "/123456-113241016-A7CBB52943A2127CE42659D29BAA8515", oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 37 30 34 32 36 65 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 2E 6A 70 67 77 2F 31 30 34 30 34 30 30 32 39 30 2D 31 31 33 32 34 31 30 31 36 2D 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 41".hexToBytes(), imgType = 1000, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), picHeight = 1214, picWidth = 904, resId = "/123456-113241016-A7CBB52943A2127CE42659D29BAA8515", thumbUrl = "/offpic_new/123456//123456-113241016-A7CBB52943A2127CE42659D29BAA8515/198?term=2", origUrl = "/offpic_new/123456//123456-113241016-A7CBB52943A2127CE42659D29BAA8515/0?term=2", bizType = 5, thumbWidth = 147, thumbHeight = 198, _400Url = "/offpic_new/123456//123456-113241016-A7CBB52943A2127CE42659D29BAA8515/400?term=2", _400Width = 285, _400Height = 384, pbReserve = "08 01 10 00 32 00 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 06".hexToBytes(), ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = true }) targetFriend() useOrdinaryEquality() }.doDecoderChecks() } /////////////////////////////////////////////////////////////////////////// // receive from iOS /////////////////////////////////////////////////////////////////////////// @Test fun `group Image receive from iOS`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "C344D6240014DA35BB63A958BC435134.png", fileLen = 108536, downloadPath = "/123456-346835805-C344D6240014DA35BB63A958BC435134", oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 30 38 35 33 36 65 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 2E 70 6E 67 77 2F 32 36 35 32 33 38 36 32 32 38 2D 33 34 36 38 33 35 38 30 35 2D 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 41".hexToBytes(), imgType = 1000, picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(), picHeight = 1214, picWidth = 904, resId = "/123456-346835805-C344D6240014DA35BB63A958BC435134", thumbUrl = "/offpic_new/123456//123456-346835805-C344D6240014DA35BB63A958BC435134/198?term=2", origUrl = "/offpic_new/123456//123456-346835805-C344D6240014DA35BB63A958BC435134/0?term=2", bizType = 4, thumbWidth = 147, thumbHeight = 198, _400Url = "/offpic_new/123456//123456-346835805-C344D6240014DA35BB63A958BC435134/400?term=2", _400Width = 285, _400Height = 384, pbReserve = "08 00 10 00 18 00 50 00 78 04".hexToBytes(), ), ) ) message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") { width = 904 height = 1214 size = 108536 type = ImageType.JPG isEmoji = false }) targetGroup() useOrdinaryEquality() }.doDecoderChecks() } @Test fun `friend Image receive from iOS`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "C344D6240014DA35BB63A958BC435134.png", fileLen = 108536, downloadPath = "/123455-346835805-C344D6240014DA35BB63A958BC435134", oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 30 38 35 33 36 65 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 2E 70 6E 67 77 2F 32 36 35 32 33 38 36 32 32 38 2D 33 34 36 38 33 35 38 30 35 2D 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 41".hexToBytes(), imgType = 1000, picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(), picHeight = 1214, picWidth = 904, resId = "/123455-346835805-C344D6240014DA35BB63A958BC435134", thumbUrl = "/offpic_new/123455//123455-346835805-C344D6240014DA35BB63A958BC435134/198?term=2", origUrl = "/offpic_new/123455//123455-346835805-C344D6240014DA35BB63A958BC435134/0?term=2", bizType = 4, thumbWidth = 147, thumbHeight = 198, _400Url = "/offpic_new/123455//123455-346835805-C344D6240014DA35BB63A958BC435134/400?term=2", _400Width = 285, _400Height = 384, pbReserve = "08 00 10 00 18 00 50 00 78 04".hexToBytes(), ), ) ) message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") { width = 904 height = 1214 size = 108536 type = ImageType.JPG isEmoji = false }) targetFriend() useOrdinaryEquality() }.doDecoderChecks() } /////////////////////////////////////////////////////////////////////////// // receive from Windows /////////////////////////////////////////////////////////////////////////// @Test fun `group Image receive from Windows`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace( filePath = "{C344D624-0014-DA35-BB63-A958BC435134}.jpg", flag = "00 00 00 00".hexToBytes(), oldData = "15 36 20 39 32 6B 41 31 43 39 32 65 39 64 35 30 64 34 39 38 64 33 33 37 39 20 20 20 20 20 20 35 30 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 7B 43 33 34 34 44 36 32 34 2D 30 30 31 34 2D 44 41 33 35 2D 42 42 36 33 2D 41 39 35 38 42 43 34 33 35 31 33 34 7D 2E 6A 70 67 41".hexToBytes(), fileId = -1830169331, serverIp = 1233990521, serverPort = 80, fileType = 67, useful = 1, picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(), thumbUrl = "/gchatpic_new/123456/123456-2464797965-C344D6240014DA35BB63A958BC435134/198?term=2", origUrl = "/gchatpic_new/123456/123456-2464797965-C344D6240014DA35BB63A958BC435134/0?term=2", imageType = 1000, width = 904, height = 1214, size = 108536, thumbWidth = 147, thumbHeight = 198, _400Url = "/gchatpic_new/123456/123456-2464797965-C344D6240014DA35BB63A958BC435134/400?term=2", _400Width = 285, _400Height = 384, ), ) ) message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") { width = 904 height = 1214 size = 108536 type = ImageType.JPG isEmoji = false }) targetGroup() }.doDecoderChecks() } @Test fun `friend Image receive from Windows`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "J9R_MJL9@9Z02}QZ0LTTX77.jpg", fileLen = 108536, downloadPath = "/123456-2313394132-C344D6240014DA35BB63A958BC435134", oldVerSendFile = "16 20 31 31 37 31 30 31 30 36 31 43 42 20 20 20 20 31 30 38 35 33 36 65 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 2E 6A 70 67 78 2F 33 32 37 39 38 32 36 34 38 34 2D 32 33 31 33 33 39 34 31 33 32 2D 43 33 34 34 44 36 32 34 30 30 31 34 44 41 33 35 42 42 36 33 41 39 35 38 42 43 34 33 35 31 33 34 41".hexToBytes(), imgType = 1000, picMd5 = "C3 44 D6 24 00 14 DA 35 BB 63 A9 58 BC 43 51 34".hexToBytes(), picHeight = 1214, picWidth = 904, resId = "/123456-2313394132-C344D6240014DA35BB63A958BC435134", flag = "00 00 00 00".hexToBytes(), thumbUrl = "/offpic_new/123456//123456-2313394132-C344D6240014DA35BB63A958BC435134/198?term=2", origUrl = "/offpic_new/123456//123456-2313394132-C344D6240014DA35BB63A958BC435134/0?term=2", thumbWidth = 147, thumbHeight = 198, _400Url = "/offpic_new/123456//123456-2313394132-C344D6240014DA35BB63A958BC435134/400?term=2", _400Width = 285, _400Height = 384, ), ) ) message(Image("{C344D624-0014-DA35-BB63-A958BC435134}.jpg") { width = 904 height = 1214 size = 108536 type = ImageType.JPG isEmoji = false }) targetFriend() useOrdinaryEquality() }.doDecoderChecks() } /////////////////////////////////////////////////////////////////////////// // receive from iPadOS /////////////////////////////////////////////////////////////////////////// @Test fun `group Image receive from iPadOS`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace( filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg", oldData = "15 36 20 39 32 6B 41 31 E8 38 63 34 65 39 63 38 39 20 20 20 20 20 20 20 30 20 20 20 20 20 20 20 30 71 76 4A 51 79 6D 7A 37 4D 77 7A 7A 33 6A 74 4E 7B 41 37 43 42 42 35 32 39 2D 34 33 41 32 2D 31 32 37 43 2D 45 34 32 36 2D 35 39 44 32 39 42 41 41 38 35 31 35 7D 2E 6A 70 67 41".hexToBytes(), fileId = -1941005175, fileType = -24, signature = "qvJQymz7Mwzz3jtN".toByteArray(), /* 71 76 4A 51 79 6D 7A 37 4D 77 7A 7A 33 6A 74 4E */ useful = 1, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), thumbUrl = "/gchatpic_new/123456/123456-2353962121-A7CBB52943A2127CE42659D29BAA8515/198?term=2", origUrl = "/gchatpic_new/123456/123456-2353962121-A7CBB52943A2127CE42659D29BAA8515/0?term=2", imageType = 1000, width = 904, height = 1214, source = 203, size = 170426, thumbWidth = 147, thumbHeight = 198, _400Url = "/gchatpic_new/123456/123456-2353962121-A7CBB52943A2127CE42659D29BAA8515/400?term=2", _400Width = 285, _400Height = 384, pbReserve = "08 01 10 00 18 00 2A 0C E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 4A 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05".hexToBytes(), ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = true }) targetGroup() }.doDecoderChecks() } @Test fun `friend Image receive from iPadOS`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "A7CBB52943A2127CE42659D29BAA8515.png", fileLen = 170426, downloadPath = "/1040400290-197707644-A7CBB52943A2127CE42659D29BAA8515", oldVerSendFile = "16 20 31 31 36 31 30 31 30 35 31 41 42 20 20 20 20 31 37 30 34 32 36 65 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 2E 70 6E 67 77 2F 31 30 34 30 34 30 30 32 39 30 2D 31 39 37 37 30 37 36 34 34 2D 41 37 43 42 42 35 32 39 34 33 41 32 31 32 37 43 45 34 32 36 35 39 44 32 39 42 41 41 38 35 31 35 41".hexToBytes(), imgType = 1000, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), picHeight = 1214, picWidth = 904, resId = "/123456-197707644-A7CBB52943A2127CE42659D29BAA8515", thumbUrl = "/offpic_new/123456//123456-197707644-A7CBB52943A2127CE42659D29BAA8515/198?term=2", origUrl = "/offpic_new/123456//123456-197707644-A7CBB52943A2127CE42659D29BAA8515/0?term=2", bizType = 5, thumbWidth = 147, thumbHeight = 198, _400Url = "/offpic_new/123456//123456-197707644-A7CBB52943A2127CE42659D29BAA8515/400?term=2", _400Width = 285, _400Height = 384, pbReserve = "08 01 10 00 18 00 2A 0C E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 42 0E 5B E5 8A A8 E7 94 BB E8 A1 A8 E6 83 85 5D 50 00 78 05".hexToBytes(), ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = true }) targetFriend() useOrdinaryEquality() }.doDecoderChecks() } /////////////////////////////////////////////////////////////////////////// // send without dimension /////////////////////////////////////////////////////////////////////////// @Test fun `group Image send without dimension`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace( filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg", flag = byteArrayOf(0, 0, 0, 0), fileType = 66, useful = 1, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), bizType = 5, imageType = 1000, width = 1, height = 1, origin = 1, ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg")) targetGroup() }.doEncoderChecks() } @Test fun `friend Image send without dimension`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg", downloadPath = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515", imgType = 1000, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), picHeight = 1, picWidth = 1, resId = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515", original = 1, bizType = 5, pbReserve = "x".toByteArray(), /* 78 02 */ ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg")) targetFriend() }.doEncoderChecks() } /////////////////////////////////////////////////////////////////////////// // send with dimension /////////////////////////////////////////////////////////////////////////// @Test fun `group Image send with dimension`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( customFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CustomFace( filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg", flag = byteArrayOf(0, 0, 0, 0), fileType = 66, useful = 1, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), bizType = 5, imageType = 1000, width = 904, height = 1214, size = 170426, origin = 1, ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = false }) targetGroup() }.doEncoderChecks() } private fun CodingChecksBuilder.targetGroup() { target(bot.addGroup(1, 1)) } @Test fun `friend Image send with dimension`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( notOnlineImage = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.NotOnlineImage( filePath = "{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg", fileLen = 170426, downloadPath = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515", imgType = 1000, picMd5 = "A7 CB B5 29 43 A2 12 7C E4 26 59 D2 9B AA 85 15".hexToBytes(), picHeight = 1214, picWidth = 904, resId = "/000000000-000000000-A7CBB52943A2127CE42659D29BAA8515", original = 1, bizType = 5, pbReserve = "x".toByteArray(), /* 78 02 */ ), ) ) message(Image("{A7CBB529-43A2-127C-E426-59D29BAA8515}.jpg") { width = 904 height = 1214 size = 170426 type = ImageType.JPG isEmoji = false }) targetFriend() }.doEncoderChecks() } private fun CodingChecksBuilder.targetFriend() { target(bot.addFriend(1)) } /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// @Serializable data class PolymorphicWrapperImage( override val message: @Polymorphic Image ) : PolymorphicWrapper private fun <M : Image> testPolymorphicInImage( data: M, expectedInstance: M = data, ) = listOf(dynamicTest("testPolymorphicInImage") { testPolymorphicIn( polySerializer = PolymorphicWrapperImage.serializer(), polyConstructor = ::PolymorphicWrapperImage, data = data, expectedSerialName = null, expectedInstance = expectedInstance, ) }) @TestFactory fun `test serialization for OfflineGroupImage`(): DynamicTestsResult { val data = Image("{90CCED1C-2D64-313B-5D66-46625CAB31D7}.jpg") assertIs<OfflineGroupImage>(data) val serialName = Image.SERIAL_NAME return runDynamicTests( testPolymorphicInImage(data), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } @TestFactory fun `test serialization for OfflineFriendImage type 1`(): DynamicTestsResult { val data = Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") // type 1 assertIs<OfflineFriendImage>(data) val serialName = Image.SERIAL_NAME return runDynamicTests( testPolymorphicInImage(data), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } @TestFactory fun `test serialization for OfflineFriendImage type 2`(): DynamicTestsResult { val data = Image("/000000000-3814297509-BFB7027B9354B8F899A062061D74E206") // type 1 assertIs<OfflineFriendImage>(data) val serialName = Image.SERIAL_NAME return runDynamicTests( testPolymorphicInImage(data), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/LongMessageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.message.LightMessageRefiner.dropMiraiInternalFlags import net.mamoe.mirai.internal.message.data.LongMessageInternal import net.mamoe.mirai.internal.message.flags.ForceAsLongMessage import net.mamoe.mirai.internal.message.flags.IgnoreLengthCheck import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbSendMsg import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.repeat import net.mamoe.mirai.message.data.toMessageChain import net.mamoe.mirai.message.data.toPlainText import net.mamoe.mirai.utils.castUp import net.mamoe.mirai.utils.getRandomString import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals internal class LongMessageProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf( TextProtocol(), ImageProtocol(), LongMessageProtocol(), GeneralMessageSenderProtocol(), ) init { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun precondition() { assertEquals(getRandomString(5000, Random(1)), getRandomString(5000, Random(1))) assertMessageEquals( "test".toPlainText() + getRandomString(5000, Random(1)) + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200), "test".toPlainText() + getRandomString(5000, Random(1)) + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200) ) } @Test fun `can convert messages to LongMessageInternal`() { var message = "test".toPlainText() + getRandomString(5000, Random(1)) + Image("{40A7C56B-45C9-23AE-0CFA-23F095B71035}.jpg").repeat(200) message += IgnoreLengthCheck message += ForceAsLongMessage runWithFacade { preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> val receipt = receipts.single() assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) assertMessageEquals( LongMessageInternal( """ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="testqGnJ1R..." m_resid="(size=1)6C6FD4AEC362AA8E54058A27B422FA42" m_fileName="160023" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="1"> <item layout="1"> <title>testqGnJ1R...</title> <hr hidden="false" style="0"/> <summary>点击查看完整消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg> """.trimIndent(), "(size=1)6C6FD4AEC362AA8E54058A27B422FA42" ) + IgnoreLengthCheck + ForceAsLongMessage, context.currentMessageChain ) } } } @Test fun `can convert messages failed to send at FIRST step to LongMessageInternal`() { val message = "test".toPlainText().toMessageChain() runWithFacade { components[MessageProtocolStrategy] = object : TestMessageProtocolStrategy() { var count = 0 override suspend fun sendPacket(bot: AbstractBot, packet: OutgoingPacket): Packet { println("MessageProtocolStrategy.sendPacket called: $count") if (count++ == 0) { // fail the first attempt return MessageSvcPbSendMsg.Response.MessageTooLarge } return super.sendPacket(bot, packet) } } preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> val receipt = receipts.single() assertMessageEquals(message.dropMiraiInternalFlags(), receipt.source.originalMessage) assertMessageEquals( LongMessageInternal( """ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?> <msg serviceID="35" templateID="1" action="viewMultiMsg" brief="test" m_resid="(size=1)8698F15C27DA63648CEF9A93EA76B084" m_fileName="160023" sourceMsgId="0" url="" flag="3" adverSign="0" multiMsgFlag="1"> <item layout="1"> <title>test</title> <hr hidden="false" style="0"/> <summary>点击查看完整消息</summary> </item> <source name="聊天记录" icon="" action="" appid="-1"/> </msg> """.trimIndent(), "(size=1)8698F15C27DA63648CEF9A93EA76B084" ), context.currentMessageChain ) } } } // should add tests for refining received LongMessage to normal messages (with a MessageOrigin) } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/MarketFaceProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import io.ktor.utils.io.core.* import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.data.MarketFaceImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.dynamicTest import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.Dice import net.mamoe.mirai.message.data.MarketFace import net.mamoe.mirai.message.data.RockPaperScissors import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test internal class MarketFaceProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(MarketFaceProtocol(), TextProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `decode Dice`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "5B E9 9A 8F E6 9C BA E9 AA B0 E5 AD 90 5D".hexToBytes(), itemType = 6, faceInfo = 1, faceId = "48 23 D3 AD B1 5D F0 80 14 CE 5D 67 96 B7 6E E1".hexToBytes(), tabId = 11464, subType = 3, key = "409e2a69b16918f9".toByteArray(), /* 34 30 39 65 32 61 36 39 62 31 36 39 31 38 66 39 */ imageWidth = 200, imageHeight = 200, mobileParam = "72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 34".hexToBytes(), pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[随机骰子]", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 90 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 CA 04 00 D2 05 02 08 37".hexToBytes(), ), ) ) message(Dice(5)) useOrdinaryEquality() }.doDecoderChecks() } @Test fun `encode Dice`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "5B E9 AA B0 E5 AD 90 5D".hexToBytes(), // [骰子] itemType = 6, faceInfo = 1, faceId = "48 23 D3 AD B1 5D F0 80 14 CE 5D 67 96 B7 6E E1".hexToBytes(), tabId = 11464, subType = 3, key = "409e2a69b16918f9".toByteArray(), /* 34 30 39 65 32 61 36 39 62 31 36 39 31 38 66 39 */ imageWidth = 200, imageHeight = 200, mobileParam = "72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 34".hexToBytes(), pbReserve = "0A 06 08 C8 01 10 C8 01 40 01 58 00 62 09 23 30 30 30 30 30 30 30 30 6A 09 23 30 30 30 30 30 30 30 30".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[骰子]", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( flags = 8, groupMask = 1, ), ) ) message(Dice(5)) }.doEncoderChecks() } @Test fun `decode RockPaperScissors`() { // region WinQQ PC buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( itemType = 6, faceInfo = 1, faceId = "E5 D8 89 F1 DF 79 B2 B4 51 83 F6 25 58 44 65 D3".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 100, imageHeight = 100, pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", attr7Buf = "01".hexToBytes(), ), ), ) message(RockPaperScissors.ROCK) useOrdinaryEquality() }.doDecoderChecks() buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( itemType = 6, faceInfo = 1, faceId = "62 8F A4 AB 7B 6C 2B CC FC DC D0 C2 DA F7 A6 0C".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 100, imageHeight = 100, pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", attr7Buf = "01".hexToBytes(), ), ), ) message(RockPaperScissors.SCISSORS) useOrdinaryEquality() }.doDecoderChecks() buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( itemType = 6, faceInfo = 1, faceId = "45 7C DE 42 0F 59 8E B4 24 CE D2 E9 05 D3 8D 8B".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 100, imageHeight = 100, pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", attr7Buf = "01".hexToBytes(), ), ), ) message(RockPaperScissors.PAPER) useOrdinaryEquality() }.doDecoderChecks() // endregion // region AndroidQQ 8.4.18.49145 buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=2".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 32 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), ) message(RockPaperScissors.PAPER) useOrdinaryEquality() }.doDecoderChecks() buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=0".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 30 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), ) message(RockPaperScissors.ROCK) useOrdinaryEquality() }.doDecoderChecks() buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=1".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 31 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), ) message(RockPaperScissors.SCISSORS) useOrdinaryEquality() }.doDecoderChecks() // endregion // region MacOS buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=0".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 30 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), ) message(RockPaperScissors.ROCK) useOrdinaryEquality() }.doDecoderChecks() // endregion // region iOS buildCodingChecks { elem( // ROCK net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=0".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 30 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), ) message(RockPaperScissors.ROCK) useOrdinaryEquality() }.doDecoderChecks() buildCodingChecks { // paper elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=2".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 32 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), ) message(RockPaperScissors.PAPER) useOrdinaryEquality() }.doDecoderChecks() // endregion } @Test fun `encode RockPaperScissors`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=0".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 30 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( flags = 8, groupMask = 1, ), ), ) message(RockPaperScissors.ROCK) }.doBothChecks() buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=1".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 31 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( flags = 8, groupMask = 1, ), ), ) message(RockPaperScissors.SCISSORS) }.doBothChecks() buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "[猜拳]".toByteArray(), /* 5B E7 8C 9C E6 8B B3 5D */ itemType = 6, faceInfo = 1, faceId = "83 C8 A2 93 AE 65 CA 14 0F 34 81 20 A7 74 48 EE".hexToBytes(), tabId = 11415, subType = 3, key = "7de39febcf45e6db".toByteArray(), /* 37 64 65 33 39 66 65 62 63 66 34 35 65 36 64 62 */ imageWidth = 200, imageHeight = 200, mobileParam = "rscType?1;value=2".toByteArray(), /* 72 73 63 54 79 70 65 3F 31 3B 76 61 6C 75 65 3D 32 */ pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[猜拳]", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( flags = 8, groupMask = 1, ), ) ) message(RockPaperScissors.PAPER) }.doBothChecks() } @Test fun `encode decode MarketFace from Android`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(), itemType = 6, faceInfo = 1, faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(), tabId = 10278, subType = 3, key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */ imageWidth = 200, imageHeight = 200, pbReserve = "0A 06 08 C8 01 10 C8 01 10 64 1A 0B 51 51 E5 A4 A7 E9 BB 84 E8 84 B8 22 40 68 74 74 70 73 3A 2F 2F 7A 62 2E 76 69 70 2E 71 71 2E 63 6F 6D 2F 69 70 3F 5F 77 76 3D 31 36 37 37 38 32 34 31 26 66 72 6F 6D 3D 61 69 6F 45 6D 6F 6A 69 4E 65 77 26 69 64 3D 31 30 38 39 31 30 2A 06 E6 9D A5 E8 87 AA 30 B5 BB B4 E3 0D 38 B5 BB B4 E3 0D 40 01 50 00".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[发呆]", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( flags = 8, groupMask = 1, ), ) ) // MarketFaceImpl 不支持手动构造 message( MarketFaceImpl( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(), itemType = 6, faceInfo = 1, faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(), tabId = 10278, subType = 3, key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */ imageWidth = 200, imageHeight = 200, pbReserve = "0A 06 08 C8 01 10 C8 01 10 64 1A 0B 51 51 E5 A4 A7 E9 BB 84 E8 84 B8 22 40 68 74 74 70 73 3A 2F 2F 7A 62 2E 76 69 70 2E 71 71 2E 63 6F 6D 2F 69 70 3F 5F 77 76 3D 31 36 37 37 38 32 34 31 26 66 72 6F 6D 3D 61 69 6F 45 6D 6F 6A 69 4E 65 77 26 69 64 3D 31 30 38 39 31 30 2A 06 E6 9D A5 E8 87 AA 30 B5 BB B4 E3 0D 38 B5 BB B4 E3 0D 40 01 50 00".hexToBytes(), ) ) ) }.doBothChecks() } @Test fun `encode decode MarketFace from macOS`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( marketFace = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(), itemType = 6, faceInfo = 1, faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(), tabId = 10278, subType = 3, key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */ imageWidth = 200, imageHeight = 200, pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[发呆]", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( flags = 8, groupMask = 1, ), ) ) // MarketFaceImpl 不支持手动构造 message( MarketFaceImpl( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(), itemType = 6, faceInfo = 1, faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(), tabId = 10278, subType = 3, key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */ imageWidth = 200, imageHeight = 200, pbReserve = "0A 06 08 C8 01 10 C8 01 40 01".hexToBytes(), ) ) ) }.doBothChecks() } /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// @Serializable data class PolymorphicWrapperMarketFace( override val message: @Polymorphic MarketFace ) : PolymorphicWrapper @Serializable data class StaticWrapperDice( override val message: Dice ) : PolymorphicWrapper @Serializable data class StaticWrapperRockPaperScissors( override val message: RockPaperScissors ) : PolymorphicWrapper private fun <M : MarketFace> testPolymorphicInMarketFace( data: M, expectedSerialName: String, expectedInstance: M = data, ) = listOf(dynamicTest("testPolymorphicInMarketFace") { testPolymorphicIn( polySerializer = PolymorphicWrapperMarketFace.serializer(), polyConstructor = ::PolymorphicWrapperMarketFace, data = data, expectedSerialName = expectedSerialName, // MarketFaceImpl is 'MarketFace', Dice is 'Dice', should include discriminator expectedInstance = expectedInstance, ) }) private fun testStaticDice( data: Dice, expectedInstance: Dice = data, ) = listOf(dynamicTest("testStaticDice") { testPolymorphicIn( polySerializer = StaticWrapperDice.serializer(), polyConstructor = ::StaticWrapperDice, data = data, expectedSerialName = null, expectedInstance = expectedInstance, ) }) private fun testStaticRockPaperScissors( data: RockPaperScissors, expectedInstance: RockPaperScissors = data, ) = listOf(dynamicTest("testStaticRockPaperScissors") { testPolymorphicIn( polySerializer = StaticWrapperRockPaperScissors.serializer(), polyConstructor = ::StaticWrapperRockPaperScissors, data = data, expectedSerialName = null, expectedInstance = expectedInstance, ) }) @TestFactory fun `test serialization for MarketFaceImpl`(): DynamicTestsResult { val data = MarketFaceImpl( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MarketFace( faceName = "5B E5 8F 91 E5 91 86 5D".hexToBytes(), itemType = 6, faceInfo = 1, faceId = "71 26 44 B5 27 94 46 11 99 8A EC 31 86 75 19 D2".hexToBytes(), tabId = 10278, subType = 3, key = "726a53a5372b7289".toByteArray(), /* 37 32 36 61 35 33 61 35 33 37 32 62 37 32 38 39 */ imageWidth = 200, imageHeight = 200, pbReserve = "0A 06 08 C8 01 10 C8 01 10 64 1A 0B 51 51 E5 A4 A7 E9 BB 84 E8 84 B8 22 40 68 74 74 70 73 3A 2F 2F 7A 62 2E 76 69 70 2E 71 71 2E 63 6F 6D 2F 69 70 3F 5F 77 76 3D 31 36 37 37 38 32 34 31 26 66 72 6F 6D 3D 61 69 6F 45 6D 6F 6A 69 4E 65 77 26 69 64 3D 31 30 38 39 31 30 2A 06 E6 9D A5 E8 87 AA 30 B5 BB B4 E3 0D 38 B5 BB B4 E3 0D 40 01 50 00".hexToBytes(), ) ) val serialName = MarketFaceImpl.SERIAL_NAME return runDynamicTests( testPolymorphicInMarketFace(data, serialName), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName, targetType = MarketFace::class), ) } @TestFactory fun `test serialization for RockPaperScissors`(): DynamicTestsResult { val data = RockPaperScissors.PAPER val serialName = RockPaperScissors.SERIAL_NAME return runDynamicTests( testPolymorphicInMarketFace(data, serialName), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), testContextual(data, serialName, targetType = MarketFace::class), testStaticRockPaperScissors(data), ) } @TestFactory fun `test serialization for Dice`(): DynamicTestsResult { val data = Dice(1) val serialName = Dice.SERIAL_NAME return runDynamicTests( testPolymorphicInMarketFace(data, serialName), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName, targetType = Dice::class), testStaticDice(data), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/MusicShareProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.AbstractContact import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.protocol.outgoing.MessageProtocolStrategy import net.mamoe.mirai.internal.message.protocol.outgoing.OutgoingMessageProcessorAdapter import net.mamoe.mirai.internal.pipeline.replaceProcessor import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.LightApp import net.mamoe.mirai.message.data.MessageOrigin import net.mamoe.mirai.message.data.MessageOriginKind import net.mamoe.mirai.message.data.MusicKind.NeteaseCloudMusic import net.mamoe.mirai.message.data.MusicShare import net.mamoe.mirai.utils.castUp import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test import kotlin.test.assertTrue internal class MusicShareProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol(), MusicShareProtocol(), RichMessageProtocol(), GeneralMessageSenderProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `decode from Android`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( lightApp = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.LightAppElem( data = "01 78 9C 7D 53 5D 6B D4 40 14 FD 2F 81 EE 53 99 CC E4 63 76 53 08 42 15 A5 16 2D B5 08 16 23 61 3A 99 4D D3 6E 66 86 64 D2 A5 96 7D D9 80 F8 68 11 C1 37 1F 44 44 AB 4F 3E A8 45 7F 4D AA E2 BF F0 4E 58 B7 50 44 32 24 F7 DC B9 33 E7 7E 9C 9C 38 4C 6B 67 CD E1 AA 44 46 48 2E A4 41 B5 A9 1A 6E CA 3A 77 56 9D 4C D4 1C B6 7F BF FA 74 F1 F5 19 E0 A3 42 4C 01 97 4D 5D 70 0B 45 05 08 23 FB 10 C0 BA 52 A5 36 E0 7A F8 E3 E9 93 8B F3 B3 47 DD FC 4B 37 7F DB B5 EF BB F6 5B D7 BE E8 E6 AF BB B6 ED E6 9F BB F6 0D 84 97 C2 30 67 ED 64 71 1D 18 8C 9B 42 49 38 0F 9B 4C 66 95 2A B2 54 1F E6 A9 64 A5 58 78 B5 4E CD B1 06 44 7A 50 64 60 61 1C 44 21 1E 85 AB 0E 9C B7 91 84 86 5E 44 A9 EF 7B CB 12 76 1B A9 CA 22 71 BB F6 63 9F CB 3B FB 9E 7F E8 ED 33 B8 F8 A0 29 F5 FD 6A 02 91 FB C6 E8 7A 2D 71 13 F7 18 F5 99 21 42 7D 04 1D 4A 5C 58 B5 92 F9 B5 22 8B 43 EA 85 11 6C D0 01 34 2B DE DE C4 1B 5B 7C C5 BB B9 73 7D 63 6B 94 AF DF BD B5 E2 AD 4F F9 5E 9D AF F8 37 60 0D 6C DE D0 AD 1A CA 8B 47 68 88 02 6A EB B7 B7 5F 92 F6 9C 57 18 2D 1D 38 45 56 B0 C4 55 8D 11 55 E2 36 D5 E4 4A 06 B5 A8 C0 E1 7B 01 1E 52 1F 0F 07 35 8F A7 E5 D1 C0 C8 B8 1F 8A 58 8C 6D 49 A2 C9 DF CA 3C 8A A4 30 89 BB C9 76 77 F6 22 B5 BB FD 78 7F E2 3D 58 17 B7 F9 C1 E8 5E 1E C7 89 4B 70 14 85 04 3A 4A BC 90 62 32 C4 1E 3A D0 56 1B B5 6A 2A 2E EE D4 F9 46 66 45 B0 F4 A4 05 EF 67 78 D9 C7 02 E5 30 97 1C 71 09 35 68 01 6F DB 0E 1B 96 B8 18 27 6E 10 25 6E 08 DF 51 68 E9 16 C3 4C C1 4A 4B A4 E5 25 57 DA F4 BD 02 6C 58 0E C6 AF EF A7 3F 5F 3E BF 38 3F 5D EA D3 14 66 62 95 F2 7F DD 35 05 E4 47 3C 3F 08 E9 6C 06 A2 51 72 5C E4 56 7F FF 90 CF 58 55 53 56 41 85 F0 57 08 20 50 87 C2 16 47 30 E1 41 38 1E 31 3F DB C3 8C 92 30 8B 08 1B B2 60 8C 23 1A 8D B9 6F 73 E9 65 EA 48 55 95 6C E2 CC 66 7F 00 45 5B 33 67".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = """ |[分享]ジェリーフィッシュ |Yunomi / ローラーガール |https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46 |来自: 网易云音乐 """.trimMargin(), ), ), ) message( MessageOrigin( LightApp( """ {"app":"com.tencent.structmsg","desc":"音乐","view":"music","ver":"0.0.0.1","prompt":"[分享]ジェリーフィッシュ","meta":{"music":{"action":"","android_pkg_name":"","app_type":1,"appid":100495085,"ctime":1652966332,"desc":"Yunomi\/ローラーガール","jumpUrl":"https:\/\/y.music.163.com\/m\/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46","musicUrl":"http:\/\/music.163.com\/song\/media\/outer\/url?id=562591636&userid=324076307&sc=wmv&tn=","preview":"http:\/\/p1.music.126.net\/KaYSb9oYQzhl2XBeJcj8Rg==\/109951165125601702.jpg","sourceMsgId":"0","source_icon":"https:\/\/i.gtimg.cn\/open\/app_icon\/00\/49\/50\/85\/100495085_100_m.png","source_url":"","tag":"网易云音乐","title":"ジェリーフィッシュ","uin":123456}},"config":{"ctime":1652966332,"forward":true,"token":"101c45f8a3db0a615d91a7a4f0969fc3","type":"normal"}} """.trimIndent() ), null, MessageOriginKind.MUSIC_SHARE ), MusicShare( kind = NeteaseCloudMusic, title = "ジェリーフィッシュ", summary = "Yunomi/ローラーガール", jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46", pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg", musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&userid=324076307&sc=wmv&tn=", brief = "[分享]ジェリーフィッシュ", ) ) }.doDecoderChecks() } @Test fun `can send MusicShare to group`() { val message = MusicShare( kind = NeteaseCloudMusic, title = "ジェリーフィッシュ", summary = "Yunomi/ローラーガール", jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46", pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg", musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&userid=324076307&sc=wmv&tn=", brief = "[分享]ジェリーフィッシュ", ) runWithFacade { assertTrue { outgoingPipeline.replaceProcessor( { it is MusicShareProtocol.Sender }, OutgoingMessageProcessorAdapter(object : MusicShareProtocol.Sender() { override suspend fun sendMusicSharePacket( bot: QQAndroidBot, musicShare: MusicShare, contact: AbstractContact, strategy: MessageProtocolStrategy<*> ) { // nop } }) ) } preprocessAndSendOutgoingImpl(defaultTarget.castUp(), message, components).let { (context, receipts) -> val receipt = receipts.single() assertMessageEquals(message, receipt.source.originalMessage) assertMessageEquals(message, context.currentMessageChain) } } } @TestFactory fun `test serialization for MusicShare`(): DynamicTestsResult { val data = MusicShare( kind = NeteaseCloudMusic, title = "ジェリーフィッシュ", summary = "Yunomi/ローラーガール", jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46", pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg", musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&userid=324076307&sc=wmv&tn=", brief = "[分享]ジェリーフィッシュ", ) val serialName = MusicShare.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/PokeMessageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.PokeMessage import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test internal class PokeMessageProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol(), PokeMessageProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `test PokeMessage`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( commonElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CommonElem( serviceType = 2, pbElem = "08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00".hexToBytes(), businessType = 1, ), ) ) message(PokeMessage("戳一戳", 1, -1)) useOrdinaryEquality() }.doDecoderChecks() } // Unsupported kinds // @Test // fun `test PokeMessage`() { // buildChecks { // elem( // net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( // commonElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CommonElem( // serviceType = 23, // pbElem = "08 0A 10 01 1A 09 E7 95 A5 E7 95 A5 E7 95 A5".hexToBytes(), // businessType = 10, // ), // ) // ) // message(PokeMessage("略略略", -1, 1)) // useOrdinaryEquality() // }.doDecoderChecks() // } @Test fun encode() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( commonElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CommonElem( serviceType = 2, pbElem = "08 01 20 FF FF FF FF FF FF FF FF FF 01 2A 09 E6 88 B3 E4 B8 80 E6 88 B3 32 05 37 2E 32 2E 30".hexToBytes(), businessType = 1, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[戳一戳]请使用最新版手机QQ体验新功能。", ), ) ) message(PokeMessage.ChuoYiChuo) }.doEncoderChecks() } /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// @TestFactory fun `test serialization for PokeMessage`(): DynamicTestsResult { val data = PokeMessage.ChuoYiChuo val serialName = PokeMessage.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/QuoteReplyProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.message.source.OfflineMessageSourceImplData import net.mamoe.mirai.internal.message.toMessageChainOnline import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.dynamicTest import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.internal.utils.runCoroutineInPlace import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.utils.hexToBytes import kotlin.test.Test internal class QuoteReplyProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(QuoteReplyProtocol(), TextProtocol()) @Test fun `decode referencing online group message in group`() { buildCodingChecks { targetGroup() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( origSeqs = intArrayOf(1803), senderUin = 1230001, time = 1653147259, flag = 1, elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), ), pbReserve = "18 AB 85 9D 81 82 80 80 80 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "s", ), ), ) message( QuoteReply( OfflineMessageSourceImplData( ids = intArrayOf(1803), internalIds = intArrayOf(539443883), time = 1653147259, originalMessage = messageChainOf(PlainText("a")), kind = messageSourceKind, fromId = 1230001, targetId = 1, botId = bot.id, ) ), PlainText("s") ) }.doDecoderChecks() } @Test fun `encode referencing offline group message in group`() { buildCodingChecks { targetGroup() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( origSeqs = intArrayOf(-31257), senderUin = 1230001, time = 1653326514, flag = 1, elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), ), pbReserve = "18 AE FB A2 F7 86 80 80 80 01".hexToBytes(), srcMsg = "0A 2C 08 B1 89 4B 10 DD F1 92 B7 07 18 09 20 0B 28 E7 8B FE FF FF FF FF FF FF 01 30 B2 85 AF 94 06 38 AE FB A2 F7 86 80 80 80 01 E0 01 01 1A 0D 0A 0B 12 05 0A 03 0A 01 61 12 02 4A 00".hexToBytes(), toUin = 1994701021, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "@", attr6Buf = "00 01 00 00 00 01 00 00 12 C4 B1 00 00".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "s", ), ), ) message( QuoteReply( OfflineMessageSourceImplData( ids = intArrayOf(-31257), internalIds = intArrayOf(1860746670), time = 1653326514, originalMessage = messageChainOf(PlainText("a")), kind = messageSourceKind, fromId = 1230001, targetId = 1994701021, botId = bot.id, ) ), PlainText("s") ) }.doEncoderChecks() } private val onlineIncomingGroupMessage = runCoroutineInPlace { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 1230001, toUin = 1230002, msgType = 166, c2cCmd = 11, msgSeq = 31245, msgTime = 1653330864, msgUid = 72057594652150074, wseqInC2cMsghead = 31245, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( codePage = 0, time = 1653330864, random = 614222138, size = 9, effect = 0, charSet = 134, pitchAndFamily = 0, fontName = "Helvetica", ), elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61".hexToBytes(), ), ), ), ), ), ).toMessageChainOnline(bot) } @Test fun `encode referencing online incoming group message in group`() { buildCodingChecks { targetGroup() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( origSeqs = intArrayOf(31245), senderUin = 1230001, time = 1653330864, flag = 1, elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61".hexToBytes(), ), ), ), pbReserve = "18 BA 92 F1 A4 82 80 80 80 01".hexToBytes(), srcMsg = "0A 24 08 B1 89 4B 10 B2 89 4B 18 A6 01 20 0B 28 8D F4 01 30 B0 A7 AF 94 06 38 BA 92 F1 A4 82 80 80 80 01 E0 01 01 1A 2D 0A 2B 12 05 0A 03 0A 01 61 12 00 12 1C AA 02 19 9A 01 16 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 61 12 02 4A 00".hexToBytes(), toUin = 1230002, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "s", ), ) ) message(onlineIncomingGroupMessage.quote(), PlainText("s")) }.doEncoderChecks() } // stranger and group temp are almost the same for friend. @Test fun `decode referencing online incoming private message in friend`() { buildCodingChecks { targetFriend() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( origSeqs = intArrayOf(34279), senderUin = 1230001, time = 1653326514, flag = 1, elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), ), pbReserve = "18 AE FB A2 F7 86 80 80 80 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "s", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 51".hexToBytes(), ), ), ) message( QuoteReply( OfflineMessageSourceImplData( ids = intArrayOf(34279), internalIds = intArrayOf(1860746670), time = 1653326514, originalMessage = messageChainOf(PlainText("a")), kind = messageSourceKind, fromId = 1230001, targetId = 0, // the referenced message was actually sending from friend 1230001 to bot. botId = bot.id, ) ), PlainText("s") ) }.doDecoderChecks() } @Test fun `decode referencing online outgoing private message in friend`() { buildCodingChecks { targetFriend() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( origSeqs = intArrayOf(49858), senderUin = 1230002, time = 1653329998, flag = 1, elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "b", ), ), ), pbReserve = "18 C3 94 C4 B3 84 80 80 80 01".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "s", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 5E".hexToBytes(), ), ), ) message( QuoteReply( OfflineMessageSourceImplData( ids = intArrayOf(49858), internalIds = intArrayOf(1181813315), time = 1653329998, originalMessage = messageChainOf(PlainText("b")), kind = messageSourceKind, fromId = 1230002, // bot id targetId = 0, // the referenced message was actually sending from bot to the friend 1230001. botId = bot.id, ) ), PlainText("s") ) }.doDecoderChecks() } @Test fun `encode referencing offline private message in friend`() { buildCodingChecks { targetFriend() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( origSeqs = intArrayOf(-31257), senderUin = 1230001, time = 1653326514, flag = 1, elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), ), pbReserve = "18 AE FB A2 F7 86 80 80 80 01".hexToBytes(), srcMsg = "0A 2C 08 B1 89 4B 10 DD F1 92 B7 07 18 09 20 0B 28 E7 8B FE FF FF FF FF FF FF 01 30 B2 85 AF 94 06 38 AE FB A2 F7 86 80 80 80 01 E0 01 01 1A 0D 0A 0B 12 05 0A 03 0A 01 61 12 02 4A 00".hexToBytes(), toUin = 1994701021, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "s", ), ), ) message( QuoteReply( OfflineMessageSourceImplData( ids = intArrayOf(-31257), internalIds = intArrayOf(1860746670), time = 1653326514, originalMessage = messageChainOf(PlainText("a")), kind = messageSourceKind, fromId = 1230001, targetId = 1994701021, botId = bot.id, ) ), PlainText("s") ) }.doEncoderChecks() } init { bot.addFriend(1230001) } private val onlineIncomingFriendMessage: MessageChain = runCoroutineInPlace { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 1230001, toUin = 1230002, msgType = 166, c2cCmd = 11, msgSeq = 31222, msgTime = 1653328003, msgUid = 72057595832827069, wseqInC2cMsghead = 31222, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( codePage = 0, time = 1653328002, random = 1794899133, size = 9, effect = 0, charSet = 134, pitchAndFamily = 0, fontName = "Helvetica", ), elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F".hexToBytes(), ), ), ), ), ), ).toMessageChainOnline(bot) } @Test fun `encode referencing online incoming private message in friend`() { buildCodingChecks { targetFriend() elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( srcMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.SourceMsg( origSeqs = intArrayOf(31222), senderUin = 1230001, time = 1653328003, flag = 1, elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "a", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( ), // Don't worry about this empty Elem, it's from official net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F".hexToBytes(), ), ), ), pbReserve = "18 BD F9 EF D7 86 80 80 80 01".hexToBytes(), srcMsg = "0A 24 08 B1 89 4B 10 B2 89 4B 18 A6 01 20 0B 28 F6 F3 01 30 83 91 AF 94 06 38 BD F9 EF D7 86 80 80 80 01 E0 01 01 1A 2D 0A 2B 12 05 0A 03 0A 01 61 12 00 12 1C AA 02 19 9A 01 16 78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00 D2 05 02 08 4F 12 02 4A 00".hexToBytes(), toUin = 1230002, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "s", ), ) ) message(onlineIncomingFriendMessage.quote(), PlainText("s")) }.doEncoderChecks() } private fun CodingChecksBuilder.targetGroup() { target(bot.addGroup(1, 1)) } private fun CodingChecksBuilder.targetFriend() { target(bot.getFriendOrFail(1230001)) } /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// @TestFactory fun `test serialization for QuoteReply`(): DynamicTestsResult { val source = MessageSourceBuilder() .sender(123) .target(123) .messages { append("test") } .build(123, MessageSourceKind.FRIEND) val data = QuoteReply(source) val serialName = QuoteReply.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageMetadata(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } /////////////////////////////////////////////////////////////////////////// // MessageSource serialization /////////////////////////////////////////////////////////////////////////// @Serializable data class PolymorphicWrapperMessageSource( override val message: @Polymorphic MessageSource ) : PolymorphicWrapper @Serializable data class StaticWrapperMessageSource( override val message: MessageSource ) : PolymorphicWrapper private fun <M : MessageSource> testPolymorphicInMessageSource( data: M, expectedInstance: M = data, ) = listOf(dynamicTest("testPolymorphicInMessageSource") { testPolymorphicIn( polySerializer = PolymorphicWrapperMessageSource.serializer(), polyConstructor = ::PolymorphicWrapperMessageSource, data = data, expectedInstance = expectedInstance, expectedSerialName = null, ) }) private fun <M : MessageSource> testStaticInMessageSource( data: M, expectedInstance: M = data, ) = listOf(dynamicTest("testStaticInMessageSource") { testPolymorphicIn( polySerializer = StaticWrapperMessageSource.serializer(), polyConstructor = ::StaticWrapperMessageSource, data = data, expectedInstance = expectedInstance, expectedSerialName = null, ) }) @TestFactory fun `test serialization for OfflineMessageSource`(): DynamicTestsResult { val data = MessageSourceBuilder() .sender(123) .target(123) .messages { append("test") } .build(123, MessageSourceKind.FRIEND) val serialName = MessageSource.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageSource(data), testPolymorphicInMessageMetadata(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), testStaticInMessageSource(data), ) } @TestFactory fun `test serialization for OnlineMessageSource`(): DynamicTestsResult { val data = onlineIncomingGroupMessage[MessageSource]!! val expected = (data as OnlineMessageSource).toOffline() val serialName = MessageSource.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageSource(data, expectedInstance = expected), testPolymorphicInMessageMetadata(data, serialName, expectedInstance = expected), testPolymorphicInSingleMessage(data, serialName, expectedInstance = expected), testInsideMessageChain(data, serialName, expectedInstance = expected), testContextual(data, serialName, expectedInstance = expected), testStaticInMessageSource(data, expectedInstance = expected), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/RichMessageProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import kotlinx.serialization.Polymorphic import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.dynamicTest import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.RichMessage import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test internal class RichMessageProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol(), RichMessageProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `decode from Android`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( richMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichMsg( template1 = "01 78 9C 7D 52 41 6F D3 30 14 FE 2B 96 A5 D1 13 71 92 36 59 03 71 2A 95 AA A8 4C 30 8D 82 44 85 50 E5 BA 9E EB 29 76 A2 C4 D9 28 C7 46 42 1C 99 10 12 37 0E 08 21 18 9C 38 00 13 FC 9A 14 10 FF 02 27 5D 27 4E 48 D6 F3 7B F6 F7 FC FC 7D EF 85 BD C7 32 06 C7 2C CB 45 A2 70 CB B1 EC 16 60 8A 26 73 A1 38 6E DD BF 37 BC DA 6D 81 5C 13 35 27 71 A2 18 6E 2D 59 DE 02 BD 28 94 39 07 39 CB 8E 05 65 A3 01 86 0E 04 9A C9 34 26 7A 13 BA 6D 08 08 D5 F5 A3 10 82 59 26 D8 21 86 0F 7F 3E 7B BA 3E 3F 7B 54 AD BE 56 AB 77 55 F9 A1 2A BF 57 E5 CB 6A F5 A6 2A CB 6A F5 A5 2A DF 42 90 27 45 46 D9 ED 9C 8F E6 18 DA 10 14 59 8C E1 42 EB 34 BF 86 D0 D2 92 45 2E A8 E5 F8 6D 8B 26 12 49 94 27 8A F7 C4 1C 7B BE EB 05 E6 D8 BF 42 64 7A BD A0 1A 1F EC D9 A3 7D BA E3 0E C7 37 46 FB 5D DE BF 73 73 C7 ED 9F D0 59 CE 77 DA 03 B3 1A 20 49 D3 E9 96 7D D7 DA B5 3A 3E 04 87 31 E1 4D 69 32 37 57 63 C1 55 13 C9 22 D6 C2 FC 6B 78 71 1D 85 C2 50 06 31 59 26 85 C6 D0 BD C0 6B 61 64 99 8A CD E7 C9 66 8F C2 54 50 5D 64 0C D0 C4 40 36 7C 0C 9D D4 D9 F2 71 7D 4B 31 8D F6 C8 64 3C 0B 92 C9 C1 93 45 EC 3E E8 B3 5B F4 A8 7B 97 63 8C 1C 3B 08 3C C7 F1 3D C7 F5 7C DB D9 B5 5D EB 28 E5 10 9C 34 65 16 8D 45 51 A8 85 8E 59 F4 1F 7D 43 B4 81 84 79 21 25 C9 96 D1 A4 50 89 14 A8 2A 3F 35 E0 F7 B5 5D 7D 6C FC B3 10 6D 51 21 AA B9 9A AC A6 39 40 11 C9 30 FC FD E3 F4 D7 AB 17 EB F3 D3 3F AF 3F AF BF 3D 87 40 D0 BA DD DB 5E 09 8B 6B 21 B9 45 15 4A 52 A6 50 2D 75 8D 40 B6 8D 3A 01 F2 6C D4 F5 0C 2F BB 13 78 76 D7 9B 1A 6F 2A AD 54 F1 7F 07 87 4C 37 FE 80 68 82 A1 36 93 C9 94 BE CC 31 45 0C 24 4D 6B 91 2F 0F 1B 1D 90 19 CF E8 2F D6 1B 08 55".hexToBytes(), serviceId = 1, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 C8 02 00 CA 04 00 D2 05 02 08 41".hexToBytes(), ), ) ) message( net.mamoe.mirai.message.data.SimpleServiceMessage( serviceId = 1, content = """ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="1" templateID="123" action="" brief="[分享]ジェリーフィッシュ" sourceMsgId="0" url="https://y.music.163.com/m/song?id=562591636&amp;uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&amp;app_version=8.7.46" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2" advertiser_id="0" aid="0"><picture cover="http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg" w="0" h="0" /><title>ジェリーフィッシュ</title><summary>Yunomi/ローラーガール</summary></item><source name="网易云音乐" icon="https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png" action="" a_actionData="tencent100495085://" appid="100495085" /></msg> """.trimIndent() ) ) }.doDecoderChecks() } @Test fun encode() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( richMsg = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichMsg( template1 = "01 78 9C 7D 52 41 6F D3 30 14 FE 2B 96 A5 D1 13 71 92 36 59 03 71 2A 95 AA A8 4C 30 8D 82 44 85 50 E5 BA 9E EB 29 76 A2 C4 D9 28 C7 46 42 1C 99 10 12 37 0E 08 21 18 9C 38 00 13 FC 9A 14 10 FF 02 27 5D 27 4E 48 D6 F3 7B F6 F7 FC FC 7D EF 85 BD C7 32 06 C7 2C CB 45 A2 70 CB B1 EC 16 60 8A 26 73 A1 38 6E DD BF 37 BC DA 6D 81 5C 13 35 27 71 A2 18 6E 2D 59 DE 02 BD 28 94 39 07 39 CB 8E 05 65 A3 01 86 0E 04 9A C9 34 26 7A 13 BA 6D 08 08 D5 F5 A3 10 82 59 26 D8 21 86 0F 7F 3E 7B BA 3E 3F 7B 54 AD BE 56 AB 77 55 F9 A1 2A BF 57 E5 CB 6A F5 A6 2A CB 6A F5 A5 2A DF 42 90 27 45 46 D9 ED 9C 8F E6 18 DA 10 14 59 8C E1 42 EB 34 BF 86 D0 D2 92 45 2E A8 E5 F8 6D 8B 26 12 49 94 27 8A F7 C4 1C 7B BE EB 05 E6 D8 BF 42 64 7A BD A0 1A 1F EC D9 A3 7D BA E3 0E C7 37 46 FB 5D DE BF 73 73 C7 ED 9F D0 59 CE 77 DA 03 B3 1A 20 49 D3 E9 96 7D D7 DA B5 3A 3E 04 87 31 E1 4D 69 32 37 57 63 C1 55 13 C9 22 D6 C2 FC 6B 78 71 1D 85 C2 50 06 31 59 26 85 C6 D0 BD C0 6B 61 64 99 8A CD E7 C9 66 8F C2 54 50 5D 64 0C D0 C4 40 36 7C 0C 9D D4 D9 F2 71 7D 4B 31 8D F6 C8 64 3C 0B 92 C9 C1 93 45 EC 3E E8 B3 5B F4 A8 7B 97 63 8C 1C 3B 08 3C C7 F1 3D C7 F5 7C DB D9 B5 5D EB 28 E5 10 9C 34 65 16 8D 45 51 A8 85 8E 59 F4 1F 7D 43 B4 81 84 79 21 25 C9 96 D1 A4 50 89 14 A8 2A 3F 35 E0 F7 B5 5D 7D 6C FC B3 10 6D 51 21 AA B9 9A AC A6 39 40 11 C9 30 FC FD E3 F4 D7 AB 17 EB F3 D3 3F AF 3F AF BF 3D 87 40 D0 BA DD DB 5E 09 8B 6B 21 B9 45 15 4A 52 A6 50 2D 75 8D 40 B6 8D 3A 01 F2 6C D4 F5 0C 2F BB 13 78 76 D7 9B 1A 6F 2A AD 54 F1 7F 07 87 4C 37 FE 80 68 82 A1 36 93 C9 94 BE CC 31 45 0C 24 4D 6B 91 2F 0F 1B 1D 90 19 CF E8 2F D6 1B 08 55".hexToBytes(), serviceId = 1, ), ), ) message( net.mamoe.mirai.message.data.SimpleServiceMessage( serviceId = 1, content = """ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="1" templateID="123" action="" brief="[分享]ジェリーフィッシュ" sourceMsgId="0" url="https://y.music.163.com/m/song?id=562591636&amp;uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&amp;app_version=8.7.46" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2" advertiser_id="0" aid="0"><picture cover="http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg" w="0" h="0" /><title>ジェリーフィッシュ</title><summary>Yunomi/ローラーガール</summary></item><source name="网易云音乐" icon="https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png" action="" a_actionData="tencent100495085://" appid="100495085" /></msg> """.trimIndent() ) ) }.doEncoderChecks() } // no encoder. specially handled, no test for now. /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// @Serializable data class PolymorphicWrapperRichMessage( override val message: @Polymorphic RichMessage ) : PolymorphicWrapper private fun <M : RichMessage> testPolymorphicInRichMessage( data: M, expectedSerialName: String, expectedInstance: M = data, ) = listOf(dynamicTest("testPolymorphicInRichMessage") { testPolymorphicIn( polySerializer = PolymorphicWrapperRichMessage.serializer(), polyConstructor = ::PolymorphicWrapperRichMessage, data = data, expectedSerialName = expectedSerialName, expectedInstance = expectedInstance ) }) @TestFactory fun `test serialization for RichMessage`(): DynamicTestsResult { val data = net.mamoe.mirai.message.data.SimpleServiceMessage( serviceId = 1, content = """ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="1" templateID="123" action="" brief="[分享]ジェリーフィッシュ" sourceMsgId="0" url="https://y.music.163.com/m/song?id=562591636&amp;uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&amp;app_version=8.7.46" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2" advertiser_id="0" aid="0"><picture cover="http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg" w="0" h="0" /><title>ジェリーフィッシュ</title><summary>Yunomi/ローラーガール</summary></item><source name="网易云音乐" icon="https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png" action="" a_actionData="tencent100495085://" appid="100495085" /></msg> """.trimIndent() ) val serialName = net.mamoe.mirai.message.data.SimpleServiceMessage.SERIAL_NAME return runDynamicTests( testPolymorphicInRichMessage(data, serialName), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } @TestFactory fun `test serialization for LightApp`(): DynamicTestsResult { val data = net.mamoe.mirai.message.data.LightApp( content = """ <?xml version='1.0' encoding='UTF-8' standalone='yes' ?><msg serviceID="1" templateID="123" action="" brief="[分享]ジェリーフィッシュ" sourceMsgId="0" url="https://y.music.163.com/m/song?id=562591636&amp;uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&amp;app_version=8.7.46" flag="0" adverSign="0" multiMsgFlag="0"><item layout="2" advertiser_id="0" aid="0"><picture cover="http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg" w="0" h="0" /><title>ジェリーフィッシュ</title><summary>Yunomi/ローラーガール</summary></item><source name="网易云音乐" icon="https://i.gtimg.cn/open/app_icon/00/49/50/85/100495085_100_m.png" action="" a_actionData="tencent100495085://" appid="100495085" /></msg> """.trimIndent() ) val serialName = net.mamoe.mirai.message.data.LightApp.SERIAL_NAME return runDynamicTests( testPolymorphicInRichMessage(data, serialName), testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/SuperFaceProtocolTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.internal.utils.io.serialization.loadAs import net.mamoe.mirai.message.data.Face import net.mamoe.mirai.message.data.SuperFace import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test internal class SuperFaceProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(SuperFaceProtocol(), TextProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `group AnimatedSticker receive from Android client`() { buildCodingChecks { elem( "AA 03 20 08 25 12 1A 0A 01 31 12 02 31 36 18 05 20 01 28 01 32 00 3A 07 2F E6 B5 81 E6 B3 AA 48 01 18 01".hexToBytes() .loadAs(net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem.serializer()) ) elem( "AA 03 23 08 25 12 1D 0A 01 31 12 02 31 33 18 72 20 01 28 02 32 01 35 3A 07 2F E7 AF AE E7 90 83 42 00 48 01 18 02".hexToBytes() .loadAs(net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem.serializer()) ) message( SuperFace.from(Face(Face.LAN_QIU)) ) }.doDecoderChecks() } @TestFactory fun `test serialization`(): DynamicTestsResult { val data = SuperFace.from(Face(Face.LAN_QIU)) val serialName = SuperFace.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/TextProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.At import net.mamoe.mirai.message.data.AtAll import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test internal class TextProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(TextProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `test PlainText`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "hello", ), ) ) message(PlainText("hello")) }.doBothChecks() } @Test fun `test AtAll`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "@全体成员", attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes(), ), ) ) message(AtAll) }.doBothChecks() } @Test fun `AtAll auto append spaces`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "@全体成员", attr6Buf = "00 01 00 00 00 05 01 00 00 00 00 00 00".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "Hi", ), ), ) message(AtAll, PlainText("Hi")) }.doEncoderChecks() } @Test fun `test At`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "@user3", attr6Buf = "00 01 00 00 00 06 00 00 12 C4 B3 00 00".hexToBytes(), ), ) ) message(At(1230003)) }.doBothChecks() } @Test fun `At auto append spaces`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "@user3", attr6Buf = "00 01 00 00 00 06 00 00 12 C4 B3 00 00".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = " ", ), ), ) message(At(1230003)) message(PlainText(" ")) target(bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) }) }.doBothChecks() } /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// @TestFactory fun `test serialization for PlainText`(): DynamicTestsResult { val data = PlainText( content = """foo""", ) val serialName = PlainText.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } @TestFactory fun `test serialization for At`(): DynamicTestsResult { val data = At( 100 ) val serialName = At.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } @TestFactory fun `test serialization for AtAll`(): DynamicTestsResult { val data = AtAll val serialName = AtAll.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/protocol/impl/VipFaceProtocolTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.internal.message.protocol.MessageProtocol import net.mamoe.mirai.internal.testFramework.DynamicTestsResult import net.mamoe.mirai.internal.testFramework.TestFactory import net.mamoe.mirai.internal.testFramework.runDynamicTests import net.mamoe.mirai.message.data.VipFace import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest import kotlin.test.Test internal class VipFaceProtocolTest : AbstractMessageProtocolTest() { override val protocols: Array<out MessageProtocol> = arrayOf(VipFaceProtocol(), TextProtocol()) @BeforeTest fun `init group`() { defaultTarget = bot.addGroup(123, 1230003).apply { addMember(1230003, "user3", MemberPermission.OWNER) } } @Test fun `test decode`() { buildCodingChecks { elem( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( commonElem = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.CommonElem( serviceType = 23, pbElem = "08 09 10 01 1A 06 E6 A6 B4 E8 8E B2".hexToBytes(), businessType = 9, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "[榴莲]x1", pbReserve = "0A 34 5B E6 A6 B4 E8 8E B2 5D E8 AF B7 E4 BD BF E7 94 A8 E6 9C 80 E6 96 B0 E7 89 88 E6 89 8B E6 9C BA 51 51 E4 BD 93 E9 AA 8C E6 96 B0 E5 8A 9F E8 83 BD E3 80 82".hexToBytes(), ), ) ) message(VipFace(VipFace.LiuLian, 1)) useOrdinaryEquality() }.doDecoderChecks() } /////////////////////////////////////////////////////////////////////////// // serialization /////////////////////////////////////////////////////////////////////////// @TestFactory fun `test serialization for VipFace`(): DynamicTestsResult { val data = VipFace( VipFace.LiuLian, 1 ) val serialName = VipFace.SERIAL_NAME return runDynamicTests( testPolymorphicInMessageContent(data, serialName), testPolymorphicInSingleMessage(data, serialName), testInsideMessageChain(data, serialName), testContextual(data, serialName), ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/message/serialization/AbstractMessageSerializationTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.serialization import net.mamoe.mirai.internal.test.AbstractTest internal abstract class AbstractMessageSerializationTest : AbstractTest() ================================================ FILE: mirai-core/src/commonTest/kotlin/network/AwaitStateTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network import kotlinx.coroutines.* import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.selects.select import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* import net.mamoe.mirai.internal.network.handler.awaitState import net.mamoe.mirai.internal.network.handler.awaitStateChange import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.ConcurrentLinkedDeque import kotlin.coroutines.ContinuationInterceptor import kotlin.test.* internal class AwaitStateTest : AbstractMockNetworkHandlerTest() { @Test fun `test select onStateChanged`() = runBlockingUnit { createNetworkHandler().run { assertState(INITIALIZED) val queue = ConcurrentLinkedDeque<NetworkHandler.State>() launch(start = CoroutineStart.UNDISPATCHED) { select<Unit> { stateChannel.onReceive { queue.add(it) } } assertEquals(1, queue.size) assertEquals(OK, queue.first()) } launch { assertNotNull(setState(OK)) } } } @Test fun `test whileSelect onStateChanged on demand`() = runBlockingUnit(singleThreadDispatcher + Job()) { createNetworkHandler().run { assertState(INITIALIZED) val queue = ConcurrentLinkedDeque<NetworkHandler.State>() val selector = launch( singleThreadDispatcher + CoroutineExceptionHandler { _, throwable -> if (throwable !is CancellationException) throwable.printStackTrace() }, start = CoroutineStart.UNDISPATCHED ) { assertSame(singleThreadDispatcher, coroutineContext[ContinuationInterceptor]) stateChannel.consumeAsFlow().collect { queue.add(it) } } setState(OK) yield() setState(CLOSED) yield() selector.cancel() runCatching { selector.join() } assertEquals(2, queue.size) assertEquals(OK, queue.first()) assertEquals(CLOSED, queue.drop(1).first()) } } // single thread so we can use [yield] to transfer dispatch private val singleThreadDispatcher: CoroutineDispatcher = borrowSingleThreadDispatcher() @Test fun `test whileSelect onStateChanged drop if not listening`() = runBlockingUnit(singleThreadDispatcher + Job()) { createNetworkHandler().run { assertState(INITIALIZED) val queue = ConcurrentLinkedDeque<NetworkHandler.State>() assertNotNull(setState(CONNECTING)) assertNotNull(setState(LOADING)) val selector = launch( singleThreadDispatcher + CoroutineExceptionHandler { _, throwable -> if (throwable !is CancellationException) throwable.printStackTrace() }, start = CoroutineStart.UNDISPATCHED ) { // while (select { stateChannel.onReceive { queue.add(it); true } }); stateChannel.consumeAsFlow().collect { queue.add(it) } } assertNotNull(setState(OK)) yield() // yields the thread to run coroutine of select assertNotNull(setState(CLOSED)) yield() selector.cancel() runCatching { selector.join() } assertEquals(OK, queue.first(), queue.toString()) assertEquals(2, queue.size) assertEquals(CLOSED, queue.drop(1).first()) } } @Test fun `can await`() = runBlockingUnit(singleThreadDispatcher + Job()) { createNetworkHandler().run { assertState(INITIALIZED) val job = launch(start = CoroutineStart.UNDISPATCHED) { awaitState(CLOSED) assertState(CLOSED) } yield() assertTrue { job.isActive } setState(OK) yield() assertTrue { job.isActive } setState(CLOSED) yield() } } @Test fun `can await change`() = runBlockingUnit(singleThreadDispatcher + Job()) { createNetworkHandler().run { assertState(INITIALIZED) val job = launch(start = CoroutineStart.UNDISPATCHED) { awaitStateChange() } yield() assertTrue { job.isActive } setState(CLOSED) yield() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/PacketCodecTest.kt ================================================ /* * 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 net.mamoe.mirai.internal.test.AbstractTest internal class PacketCodecTest : AbstractCodecTest() internal abstract class AbstractCodecTest : AbstractTest() ================================================ FILE: mirai-core/src/commonTest/kotlin/network/ServerListTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network import net.mamoe.mirai.internal.network.components.ServerAddress import net.mamoe.mirai.internal.network.components.ServerList import net.mamoe.mirai.internal.network.components.ServerListImpl import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.TestOnly import kotlin.test.* internal class ServerListTest : AbstractTest() { @Test fun canInitializeDefaults() { assertNotEquals(0, ServerList.DEFAULT_SERVER_LIST.size) } @Test fun `can poll current for initial`() { assertNotNull(ServerListImpl().pollCurrent()) } @Test fun `last poll ip is updated when polled`() { val instance = ServerListImpl() val old = instance.getLastPolledIP() assertNotNull(old) assertTrue { old.isEmpty() } assertNotNull(instance.pollCurrent()) val new = instance.getLastPolledIP() assertNotNull(new) assertNotEquals(old, new) } @Test fun `not empty for initial`() { assertNotNull(ServerListImpl().pollAny()) } @Test fun `poll current will end with null`() { val instance = ServerListImpl() repeat(100) { instance.pollCurrent() } assertNull(instance.pollCurrent()) } @Test fun `poll any is always not null`() { val instance = ServerListImpl() repeat(100) { instance.pollAny() } assertNotNull(instance.pollAny()) } @Test fun `preferred cannot be empty`() { assertFailsWith<IllegalArgumentException> { ServerListImpl().setPreferred(emptyList()) } } @Test fun `use preferred`() { val instance = ServerListImpl() val addr = ServerAddress("test", 1) instance.setPreferred(listOf(addr)) repeat(100) { instance.pollAny() } assertSame(addr, instance.pollAny()) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/auth/AbstractBotAuthTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.auth import net.mamoe.mirai.auth.BotAuthInfo import net.mamoe.mirai.auth.BotAuthResult import net.mamoe.mirai.auth.BotAuthSession import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.components.SsoProcessorContext import net.mamoe.mirai.internal.network.components.SsoProcessorImpl import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTestWithSelector import net.mamoe.mirai.internal.network.framework.PacketReplierDslBuilder import net.mamoe.mirai.internal.network.framework.buildPacketReplier internal abstract class AbstractBotAuthTest : AbstractCommonNHTestWithSelector() { init { // Use real processor to test login overrideComponents[SsoProcessor] = SsoProcessorImpl(overrideComponents[SsoProcessorContext]) } protected fun setAuthorization(authorize: suspend (session: BotAuthSession, info: BotAuthInfo) -> BotAuthResult) { // Run a real SsoProcessor, just without sending packets bot.account.authorization = object : BotAuthorization { override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult { return authorize(session, info) } } } // Use the same replier even after reconnection protected fun usePacketReplierThroughout(builderAction: PacketReplierDslBuilder.() -> Unit) { val replier = buildPacketReplier { builderAction() } onEachNetworkInstance { addPacketReplier(replier) // share the decisions } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/auth/AuthorizationReasonTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.auth import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import net.mamoe.mirai.auth.AuthReason import net.mamoe.mirai.auth.BotAuthResult import net.mamoe.mirai.internal.MockAccount import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.components.AccountSecretsManager import net.mamoe.mirai.internal.network.framework.createWLoginSigInfo import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPushForceOffline import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import org.junit.jupiter.api.Disabled import java.util.concurrent.atomic.AtomicReference import kotlin.test.Test import kotlin.test.assertFalse import kotlin.test.assertIs import kotlin.test.assertTrue internal class AuthorizationReasonTest : AbstractBotAuthTest() { @Test fun `first login without fast login`() = runTest { var isFirstLogin: Boolean = false var authReason: AuthReason? = AuthReason.Unknown(bot, null) setAuthorization { auth, info -> isFirstLogin = info.isFirstLogin authReason = info.reason auth.authByPassword("") return@setAuthorization object : BotAuthResult {} } usePacketReplierThroughout { expect(WtLogin.Login) reply { bot.components[AccountSecretsManager].getSecrets(MockAccount) ?.wLoginSigInfo = createWLoginSigInfo(bot.id) WtLogin.Login.LoginPacketResponse.Success(bot) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } } bot.components[AccountSecretsManager].getSecrets(MockAccount)?.wLoginSigInfoField = null bot.login() assertTrue(isFirstLogin) assertIs<AuthReason.FreshLogin>(authReason) } @Test fun `first login with fast login success`() = runTest { var authorizationCalled = false setAuthorization { auth, _ -> authorizationCalled = true auth.authByPassword("") return@setAuthorization object : BotAuthResult {} } usePacketReplierThroughout { expect(WtLogin.ExchangeEmp) reply { WtLogin.Login.LoginPacketResponse.Success(bot) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } } bot.login() assertFalse(authorizationCalled) } @Test fun `first login with fast login fail`() = runTest { var isFirstLogin: Boolean = false var authReason: AuthReason? = null setAuthorization { auth, info -> isFirstLogin = info.isFirstLogin authReason = info.reason auth.authByPassword("") return@setAuthorization object : BotAuthResult {} } usePacketReplierThroughout { expect(WtLogin.ExchangeEmp) reply { WtLogin.Login.LoginPacketResponse.Error(bot, 1, "", "", ", ") } expect(WtLogin.Login) reply { WtLogin.Login.LoginPacketResponse.Success(bot) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } } bot.login() assertTrue(isFirstLogin) assertIs<AuthReason.FastLoginError>(authReason) } @Test @Disabled // FIXME - bad github ci, but it is ok on local computer, reason unknown fun `force offline`() = runTest { var isFirstLogin: Boolean = true // volatile val authReason = AtomicReference<AuthReason?>(null) setAuthorization { auth, info -> isFirstLogin = info.isFirstLogin authReason.set(info.reason) auth.authByPassword("") return@setAuthorization object : BotAuthResult {} } usePacketReplierThroughout { expect(WtLogin.ExchangeEmp) reply { WtLogin.Login.LoginPacketResponse.Success(bot) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } expect(WtLogin.Login) reply { bot.components[AccountSecretsManager].getSecrets(MockAccount) ?.wLoginSigInfo = createWLoginSigInfo(bot.id) WtLogin.Login.LoginPacketResponse.Success(bot) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } } bot.configuration.autoReconnectOnForceOffline = true bot.login() network.currentInstance().collectReceived( IncomingPacket( MessageSvcPushForceOffline.commandName, RequestPushForceOffline(bot.uin, tips = "force offline manually in test") ) ) eventDispatcher.joinBroadcast() // why test finished before code reaches end?? yield() assertFalse(isFirstLogin) assertIs<AuthReason.ForceOffline>(authReason.get(), message = authReason.toString()) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/auth/BotAuthorizationTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.auth import kotlinx.coroutines.test.runTest import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import net.mamoe.mirai.network.BotAuthorizationException import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertIs internal class BotAuthorizationTest : AbstractBotAuthTest() { @Test fun `authorization failure throws BotAuthorizationException`() = runTest { // Run a real SsoProcessor, just without sending packets setAuthorization { _, _ -> throw IllegalStateException("Oops") } usePacketReplierThroughout { expect(WtLogin.ExchangeEmp) reply { WtLogin.Login.LoginPacketResponse.Error(bot, 1, "", "", "") } expect(WtLogin.Login) reply { WtLogin.Login.LoginPacketResponse.Success(bot) } expect(StatSvc.Register) reply { StatSvc.Register.Response(SvcRespRegister()) } } assertFailsWith<BotAuthorizationException> { bot.login() }.run { val cause = cause assertIs<IllegalStateException>(cause) assertEquals("Oops", cause.message) } // Stacktrace like this: //net.mamoe.mirai.network.BotAuthorizationException: BotAuthorization(net.mamoe.mirai.internal.network.auth.AbstractBotAuthTest$authorization failure throws BotAuthorizationException$1$2@157c6b0f) threw an exception during authorization process. See cause below. // at net.mamoe.mirai.internal.network.components.SsoProcessorImpl.login(SsoProcessor.kt:232) //Caused by: java.lang.IllegalStateException: Oops // at net.mamoe.mirai.internal.network.auth.AbstractBotAuthTest$authorization failure throws BotAuthorizationException$1$2.authorize(AbstractBotAuthTest.kt:44) // (Coroutine boundary) // at net.mamoe.mirai.utils.channels.CoroutineOnDemandReceiveChannel.receiveOrNull(OnDemandChannelImpl.kt:237) // (Coroutine creation stacktrace) // at net.mamoe.mirai.internal.network.handler.CommonNetworkHandler$StateConnecting.startState(CommonNetworkHandler.kt:244) //Caused by: java.lang.IllegalStateException: Oops // at net.mamoe.mirai.internal.network.auth.AbstractBotAuthTest$authorization failure throws BotAuthorizationException$1$2.authorize(AbstractBotAuthTest.kt:44) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/component/AbstractMutableComponentStorageTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import net.mamoe.mirai.internal.test.AbstractTest import kotlin.test.Test import kotlin.test.assertEquals internal abstract class AbstractMutableComponentStorageTest : AbstractTest() { internal data class TestComponent2( val value: Int ) { companion object : ComponentKey<TestComponent2> } internal data class TestComponent3( val value: Int ) { companion object : ComponentKey<TestComponent3> } protected abstract fun createStorage(): MutableComponentStorage @Test fun `can put component`() { val storage = createStorage().apply { set(TestComponent2, TestComponent2(1)) } assertEquals(1, storage.size) } @Test fun `can put multiple components with different key`() { val storage = createStorage().apply { set(TestComponent2, TestComponent2(1)) set(TestComponent3, TestComponent3(1)) } assertEquals(2, storage.size) } @Test fun `can get component`() { val storage = createStorage().apply { set(TestComponent2, TestComponent2(1)) } assertEquals(1, storage.size) assertEquals(TestComponent2(1), storage[TestComponent2]) } @Test fun `can override component with same key`() { val storage = createStorage().apply { set(TestComponent2, TestComponent2(1)) set(TestComponent2, TestComponent2(2)) } assertEquals(1, storage.size) assertEquals(TestComponent2(2), storage[TestComponent2]) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/component/BotAuthControlTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import net.mamoe.mirai.auth.* import net.mamoe.mirai.internal.network.auth.AuthControl import net.mamoe.mirai.internal.network.components.SsoProcessorContext import net.mamoe.mirai.internal.network.components.SsoProcessorImpl import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.network.CustomLoginFailedException import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.DeviceInfo import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import kotlin.properties.Delegates import kotlin.reflect.KClass import kotlin.test.Test import kotlin.test.assertFailsWith import kotlin.test.fail internal class BotAuthControlTest : AbstractCommonNHTest() { private val botAuthInfo = object : BotAuthInfo { override val id: Long get() = bot.id override val deviceInfo: DeviceInfo get() = bot.components[SsoProcessorContext].device override val configuration: BotConfiguration get() = bot.configuration override val isFirstLogin: Boolean get() = false override val reason: AuthReason by Delegates.notNull() } private suspend fun AuthControl.assertRequire(exceptedType: KClass<*>) { println("Requiring auth method") val nextAuth = acquireAuth() println("Got $nextAuth") yield() if (nextAuth is SsoProcessorImpl.AuthMethod.Error) { fail(cause = nextAuth.exception) } if (exceptedType.isInstance(nextAuth)) return fail("Type not match, excepted $exceptedType but got ${nextAuth::class}") } @Test fun `auth test`() = runTest { val control = AuthControl(botAuthInfo, object : BotAuthorization { override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult { return session.authByPassword(EMPTY_BYTE_ARRAY) } }, bot.logger, backgroundScope.coroutineContext) control.start() control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class) control.actComplete() control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class) } @Test fun `test auth failed and reselect`() = runTest { class MyLoginFailedException : CustomLoginFailedException(killBot = false) val control = AuthControl(botAuthInfo, object : BotAuthorization { override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult { assertFailsWith<MyLoginFailedException> { session.authByPassword(EMPTY_BYTE_ARRAY); println("!") } println("114514") return session.authByPassword(EMPTY_BYTE_ARRAY) } }, bot.logger, backgroundScope.coroutineContext) control.start() control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class) control.actMethodFailed(MyLoginFailedException()) control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class) control.actComplete() control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class) } @Test fun `failed when login complete`() = runTest { val control = AuthControl(botAuthInfo, object : BotAuthorization { override suspend fun authorize(session: BotAuthSession, info: BotAuthInfo): BotAuthResult { val rsp = session.authByPassword(EMPTY_BYTE_ARRAY) assertFailsWith<IllegalStateException> { session.authByPassword(EMPTY_BYTE_ARRAY) } assertFailsWith<IllegalStateException> { session.authByPassword(EMPTY_BYTE_ARRAY) } assertFailsWith<IllegalStateException> { session.authByPassword(EMPTY_BYTE_ARRAY) } return rsp } }, bot.logger, backgroundScope.coroutineContext) control.start() control.assertRequire(SsoProcessorImpl.AuthMethod.Pwd::class) control.actComplete() control.assertRequire(SsoProcessorImpl.AuthMethod.NotAvailable::class) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/component/BotInitProcessorTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.component import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.isActive import kotlinx.coroutines.test.runTest import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.components.BotInitProcessor import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTestWithSelector import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPushForceOffline import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.TestOnly import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue internal class BotInitProcessorTest { class WithoutSelector : AbstractCommonNHTest() { @Test fun `BotInitProcessor halted`() = runTest { val p = setComponent(BotInitProcessor, object : BotInitProcessor { var ranTimes = 0 var haltedTimes = 0 var def = CompletableDeferred<Unit>() override suspend fun init() { ranTimes++ def.await() } override fun setLoginHalted() { haltedTimes += 1 } }) assertTrue { network.isActive } network.setStateLoading(conn) assertEquals(1, p.ranTimes) assertEquals(0, p.haltedTimes) assertState(NetworkHandler.State.LOADING) network.collectReceived( IncomingPacket( MessageSvcPushForceOffline.commandName, RequestPushForceOffline(bot.uin) ) ) assertEquals(1, p.ranTimes) assertEquals(1, p.haltedTimes) eventDispatcher.joinBroadcast() assertFalse { network.isActive } network.assertState(NetworkHandler.State.CLOSED) // we do not use selector in this test so it will be CLOSED. It will recover (reconnect) instead in real. } } class WithSelector : AbstractCommonNHTestWithSelector() { @Test fun `BotInitProcessor halted`() = runBlockingUnit { bot.configuration.autoReconnectOnForceOffline = true val p = setComponent(BotInitProcessor, object : BotInitProcessor { var ranTimes = 0 var haltedTimes = 0 var def = CompletableDeferred<Unit>() override suspend fun init() { ranTimes++ def.await() } override fun setLoginHalted() { haltedTimes += 1 } }) assertTrue { network.isActive } network.setStateLoading(conn) assertEquals(1, p.ranTimes) assertEquals(0, p.haltedTimes) assertState(NetworkHandler.State.LOADING) network.currentInstance().collectReceived( IncomingPacket( MessageSvcPushForceOffline.commandName, RequestPushForceOffline(bot.uin) ) ) // all jobs launched during `collectReceived` are UNDISPATCHED, `collectReceived` returns on `def.await()` (suspension point) // first run is CANCELLED. assertEquals(1, p.ranTimes) assertEquals(1, p.haltedTimes) p.def.complete(Unit) // then BotInitProcessor.init finishes async. network.resumeConnection() // join async assertEquals(2, p.ranTimes) // we expect selector has run `init` assertEquals(1, p.haltedTimes) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/component/CombinedStorageTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.component import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.TestOnly import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertSame internal class CombinedStorageTest : AbstractTest() { internal data class TestComponent2( val value: Int ) { companion object : ComponentKey<TestComponent2> } internal data class TestComponent3( val value: Int ) { companion object : ComponentKey<TestComponent3> } @Test fun `can get from main`() { val storage = ConcurrentComponentStorage().apply { set(TestComponent2, TestComponent2(1)) }.withFallback(ConcurrentComponentStorage()) assertEquals(TestComponent2(1), storage[TestComponent2]) } @Test fun `can get from fallback`() { val storage = ConcurrentComponentStorage().withFallback(ConcurrentComponentStorage().apply { set(TestComponent2, TestComponent2(1)) }) assertEquals(TestComponent2(1), storage[TestComponent2]) } @Test fun `size is sum of sizes of two storages`() { val storage = ConcurrentComponentStorage().apply { set(TestComponent3, TestComponent3(1)) }.withFallback(ConcurrentComponentStorage().apply { set(TestComponent2, TestComponent2(1)) }) assertEquals(2, storage.size) } @Test fun `size can be zero`() { val storage = ConcurrentComponentStorage().withFallback(ConcurrentComponentStorage()) assertEquals(0, storage.size) } @Test fun `main prevails than fallback`() { val storage = ConcurrentComponentStorage().apply { set(TestComponent2, TestComponent2(1)) }.withFallback(ConcurrentComponentStorage().apply { set(TestComponent2, TestComponent2(2)) }) assertEquals(TestComponent2(1), storage[TestComponent2]) } @Test fun `returns empty if both are null`() { val x: ComponentStorage = null.withFallback(null) assertEquals(ComponentStorage.EMPTY, x) } @Test fun `returns first if second if null`() { val x = ConcurrentComponentStorage().apply { set(TestComponent2, TestComponent2(1)) } assertSame(x, x.withFallback(null)) } @Test fun `returns second if first if null`() { val x = ConcurrentComponentStorage().apply { set(TestComponent2, TestComponent2(1)) } assertSame(x, null.withFallback(x)) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/component/EventDispatcherTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.component import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.job import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.test.assertEventBroadcasts import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.TestOnly import network.framework.components.TestEventDispatcherImpl import kotlin.test.Test internal class EventDispatcherTest : AbstractTest() { private class Ev : AbstractEvent() private val dispatcher = TestEventDispatcherImpl( SupervisorJob(), MiraiLogger.Factory.create(EventDispatcherTest::class) ) @Test fun `can broadcast`() = runBlockingUnit { assertEventBroadcasts<Ev>(1) { dispatcher.broadcast(Ev()) } } @Test fun `can async`() = runBlockingUnit { assertEventBroadcasts<Ev>(1) { dispatcher.broadcastAsync(Ev()) (dispatcher.coroutineContext.job as CompletableJob).run { complete() join() } } } @Test fun `can join`() = runBlockingUnit { assertEventBroadcasts<Ev>(20) { repeat(20) { dispatcher.broadcastAsync(Ev()) } dispatcher.joinBroadcast() } } @Test fun `broadcastAsync starts job immediately`() = runBlockingUnit { assertEventBroadcasts<Ev>(1) { dispatcher.broadcastAsync(Ev()) dispatcher.joinBroadcast() } } @Test fun `broadcastAsync starts job immediately parallel`() = runBlockingUnit { assertEventBroadcasts<Ev>(20) { repeat(20) { dispatcher.broadcastAsync(Ev()) } dispatcher.joinBroadcast() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/AbstractCommonNHTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.handler.* import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.utils.ExceptionCollector /** * You may need to override [createConnection] */ internal abstract class TestCommonNetworkHandler( override val bot: QQAndroidBot, context: NetworkHandlerContext, address: SocketAddress, ) : CommonNetworkHandler<PlatformConn>(context, address), ITestNetworkHandler<PlatformConn> { override suspend fun createConnection(): PlatformConn { return PlatformConn() } override fun PlatformConn.writeAndFlushOrCloseAsync(packet: OutgoingPacket) { for (packetReplier in packetRepliers) { packetReplier.run { object : PacketReplierContext { override val coroutineScope: CoroutineScope get() = CoroutineScope(coroutineContext) override fun reply(incoming: IncomingPacket) { collectReceived(incoming) } override fun reply(incoming: Packet) { reply(IncomingPacket(packet.commandName, packet.sequenceId, incoming)) } override fun reply(incoming: Throwable) { reply(IncomingPacket(packet.commandName, packet.sequenceId, incoming)) } }.onSend(packet) } } } @Suppress("EXTENSION_SHADOWED_BY_MEMBER") override fun PlatformConn.close() { } override fun setStateClosed(exception: Throwable?): NetworkHandlerSupport.BaseStateImpl? { return setState { StateClosed(exception) } } override fun setStateConnecting(exception: Throwable?): NetworkHandlerSupport.BaseStateImpl? { return setState { StateConnecting(ExceptionCollector(exception)) } } override fun setStateOK(conn: PlatformConn, exception: Throwable?): NetworkHandlerSupport.BaseStateImpl? { exception?.printStackTrace() return setState { StateOK(conn) } } override fun setStateLoading(conn: PlatformConn): NetworkHandlerSupport.BaseStateImpl? { return setState { StateLoading(conn) } } private val packetRepliers = mutableListOf<PacketReplier>() fun addPacketReplier(packetReplier: PacketReplier) { packetRepliers.add(packetReplier) } inline fun addPacketReplierDsl(crossinline action: PacketReplierDslBuilder.() -> Unit) { packetRepliers.add(buildPacketReplier(action)) } } /** * Without selector. When network is closed, it will not reconnect, so that you can check for its states. * * @see AbstractCommonNHTestWithSelector */ internal expect abstract class AbstractCommonNHTest() : AbstractRealNetworkHandlerTest<TestCommonNetworkHandler> { val conn: PlatformConn override val factory: NetworkHandlerFactory<TestCommonNetworkHandler> protected fun removeOutgoingPacketEncoder() } internal expect class PlatformConn() ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/AbstractCommonNHTestWithSelector.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.internal.network.framework import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitor import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitorImpl import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.internal.network.handler.TestSelector import net.mamoe.mirai.internal.network.handler.selector.NetworkHandlerSelector import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler /** * When network is closed, it will reconnect, so that you test for real environment, * but you cannot check for its states (it will never be CLOSED until some fatal error, just like in real). */ internal abstract class AbstractCommonNHTestWithSelector : AbstractRealNetworkHandlerTest<TestSelectorNetworkHandler>() { init { overrideComponents[BotOfflineEventMonitor] = BotOfflineEventMonitorImpl() } val conn = PlatformConn() val selector = TestSelector<TestCommonNetworkHandler> { object : TestCommonNetworkHandler(bot, createContext(), createAddress()) { // override suspend fun createConnection(decodePipeline: PacketDecodePipeline): PlatformConn = channel override suspend fun createConnection(): PlatformConn { return conn } }.apply { applyToInstances.forEach { it.invoke(this) } } } override val factory: NetworkHandlerFactory<TestSelectorNetworkHandler> = NetworkHandlerFactory { _, _ -> TestSelectorNetworkHandler(selector, bot) } private val applyToInstances = mutableListOf<TestCommonNetworkHandler.() -> Unit>() fun onEachNetworkInstance(action: TestCommonNetworkHandler.() -> Unit) { applyToInstances.add(action) } } internal class TestSelectorNetworkHandler( selector: NetworkHandlerSelector<TestCommonNetworkHandler>, override val bot: QQAndroidBot, ) : ITestNetworkHandler<PlatformConn>, SelectorNetworkHandler<TestCommonNetworkHandler>(selector) { fun currentInstance() = selector.getCurrentInstanceOrCreate() fun currentInstanceOrNull() = selector.getCurrentInstanceOrNull() private val applyToInstances = mutableListOf<TestCommonNetworkHandler.() -> Unit>() fun onEachInstance(action: TestCommonNetworkHandler.() -> Unit) { applyToInstances.add(action) } override fun setStateClosed(exception: Throwable?): NetworkHandlerSupport.BaseStateImpl? { return selector.getCurrentInstanceOrCreate().setStateClosed(exception) } override fun setStateConnecting(exception: Throwable?): NetworkHandlerSupport.BaseStateImpl? { return selector.getCurrentInstanceOrCreate().setStateConnecting(exception) } override fun setStateOK(conn: PlatformConn, exception: Throwable?): NetworkHandlerSupport.BaseStateImpl? { return selector.getCurrentInstanceOrCreate().setStateOK(conn, exception) } override fun setStateLoading(conn: PlatformConn): NetworkHandlerSupport.BaseStateImpl? { return selector.getCurrentInstanceOrCreate().setStateLoading(conn) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/AbstractMockNetworkHandlerTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.internal.network.framework import kotlinx.coroutines.SupervisorJob import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.framework.components.TestImagePatcher import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver import net.mamoe.mirai.internal.network.handler.state.SafeStateObserver import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.internal.utils.ImagePatcher import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.MiraiLogger import network.framework.components.TestEventDispatcherImpl import kotlin.math.absoluteValue import kotlin.random.Random import kotlin.test.AfterTest import kotlin.test.assertEquals /** * Test with mock [NetworkHandler], and without selector. */ internal abstract class AbstractMockNetworkHandlerTest : AbstractNetworkHandlerTest() { protected open fun createNetworkHandlerContext() = TestNetworkHandlerContext(bot, logger, components) protected open fun createNetworkHandler() = TestNetworkHandler(bot, createNetworkHandlerContext()) protected open fun createAccount() = BotAccount(Random.nextLong().absoluteValue.mod(1000L), "pwd") protected val bot: QQAndroidBot by lazy { MockBot(createAccount()) { nhProvider = { createNetworkHandler() } additionalComponentsProvider = { this@AbstractMockNetworkHandlerTest.components } } } protected val logger = MiraiLogger.Factory.create(Bot::class, "test") private val eventDispatcherJob = SupervisorJob() @AfterTest private fun cancelJob() { eventDispatcherJob.cancel() } protected val components = ConcurrentComponentStorage().apply { set(SsoProcessor, TestSsoProcessor(bot)) set( EventDispatcher, // Note that in real we use 'bot.coroutineContext', but here we override with a new, independent job // to allow BotOfflineEvent.Active to be broadcast and joinBroadcast works even if bot coroutineScope is closed. TestEventDispatcherImpl( bot.coroutineContext + eventDispatcherJob, bot.logger.subLogger("TestEventDispatcherImpl") ) ) set( StateObserver, SafeStateObserver( LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States")), MiraiLogger.Factory.create(SafeStateObserver::class, "StateObserver errors") ) ) set(ImagePatcher, TestImagePatcher()) set(PacketLoggingStrategy, PacketLoggingStrategyImpl(bot)) set(AccountSecretsManager, MemoryAccountSecretsManager()) set(HttpClientProvider, HttpClientProviderImpl()) } fun NetworkHandler.assertState(state: NetworkHandler.State) { assertEquals(this.state, state) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/AbstractNetworkHandlerTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import net.mamoe.mirai.internal.network.handler.selector.AbstractKeepAliveNetworkHandlerSelector import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.setSystemProp import kotlin.test.AfterTest import kotlin.test.BeforeTest /** * @see AbstractCommonNHTest * @see AbstractCommonNHTestWithSelector * @see AbstractMockNetworkHandlerTest */ internal sealed class AbstractNetworkHandlerTest : AbstractTest() { /////////////////////////////////////////////////////////////////////////// // Defaults /////////////////////////////////////////////////////////////////////////// init { setSystemProp("mirai.event.launch.undispatched", "true") // allow us to do some } @BeforeTest fun be() { AbstractKeepAliveNetworkHandlerSelector.RECONNECT_DELAY = 0 } @AfterTest fun af() { AbstractKeepAliveNetworkHandlerSelector.RECONNECT_DELAY = 3000 } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/AbstractRealNetworkHandlerTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.framework import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import net.mamoe.mirai.internal.* import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.KeyWithCreationTime import net.mamoe.mirai.internal.network.KeyWithExpiry import net.mamoe.mirai.internal.network.WLoginSigInfo import net.mamoe.mirai.internal.network.WLoginSimpleInfo import net.mamoe.mirai.internal.network.component.ComponentKey import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.component.setAll import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor import net.mamoe.mirai.internal.network.handler.* import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.internal.utils.crypto.QQEcdh import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.* import network.framework.components.TestEventDispatcherImpl import kotlin.random.Random import kotlin.test.AfterTest import kotlin.test.assertEquals /** * With real factory and components as in [QQAndroidBot.components]. * * Extend [AbstractCommonNHTestWithSelector] or [AbstractCommonNHTest]. */ internal abstract class AbstractRealNetworkHandlerTest<H : NetworkHandler> : AbstractNetworkHandlerTest() { abstract val factory: NetworkHandlerFactory<H> /** * This is shared for all [createBot] by default. `network === bot.network`, unless you change it. */ open val network: H by lateinitMutableProperty { factory.create(createContext(), createAddress()) } private var botInit = false var bot: QQAndroidBot by lateinitMutableProperty { botInit = true createBot() } @AfterTest fun afterEach() { println("Test finished, closing Bot") if (botInit) { bot.close() println("joining Bot") runBlockingUnit { bot.join() } println("cleanup ok") } } protected open fun createBot(account: BotAccount = MockAccount): QQAndroidBot { return object : QQAndroidBot(account, MockConfiguration.copy()) { override fun createBotLevelComponents(): ConcurrentComponentStorage = super.createBotLevelComponents().apply { setAll(overrideComponents) get(AccountSecretsManager).getSecretsOrCreate( account, DeviceInfo.random(Random(1)) ).wLoginSigInfo = createWLoginSigInfo(uin) } override fun createNetworkHandler(): NetworkHandler = this@AbstractRealNetworkHandlerTest.network } } open val networkLogger = MiraiLogger.Factory.create(NetworkHandler::class, "network") sealed class NHEvent { object Login : NHEvent() object Logout : NHEvent() object DoHeartbeatNow : NHEvent() object Init : NHEvent() object SetLoginHalted : NHEvent() } val nhEvents = ConcurrentLinkedQueue<NHEvent>() private val eventDispatcherJob = SupervisorJob() @AfterTest private fun cancelJob() { eventDispatcherJob.cancel() println("Test finished, joining eventDispatcherJob") runBlockingUnit { eventDispatcherJob.join() } } /** * This overrides [QQAndroidBot.components] */ @OptIn(TestOnly::class) val overrideComponents = ConcurrentComponentStorage().apply { set(SsoProcessorContext, SsoProcessorContextImpl(bot)) set(SsoProcessor, object : TestSsoProcessor(bot) { override suspend fun login(handler: NetworkHandler) { nhEvents.add(NHEvent.Login) super.login(handler) } override suspend fun logout(handler: NetworkHandler) { nhEvents.add(NHEvent.Logout) super.logout(handler) } }) set(HeartbeatProcessor, object : HeartbeatProcessor { override suspend fun doAliveHeartbeatNow(networkHandler: NetworkHandler) { nhEvents.add(NHEvent.DoHeartbeatNow) networkLogger.debug { "HeartbeatProcessor.doAliveHeartbeatNow" } } override suspend fun doStatHeartbeatNow(networkHandler: NetworkHandler) { nhEvents.add(NHEvent.DoHeartbeatNow) networkLogger.debug { "HeartbeatProcessor.doStatHeartbeatNow" } } override suspend fun doRegisterNow(networkHandler: NetworkHandler): StatSvc.Register.Response { nhEvents.add(NHEvent.DoHeartbeatNow) networkLogger.debug { "HeartbeatProcessor.doRegisterNow" } return StatSvc.Register.Response(SvcRespRegister()) } }) set(KeyRefreshProcessor, object : KeyRefreshProcessor { override suspend fun keyRefreshLoop(handler: NetworkHandler) {} override suspend fun refreshKeysNow(handler: NetworkHandler) {} }) set(ConfigPushProcessor, object : ConfigPushProcessor { override suspend fun syncConfigPush(network: NetworkHandler) {} }) set(BotInitProcessor, object : BotInitProcessor { override fun setLoginHalted() { nhEvents.add(NHEvent.SetLoginHalted) } override suspend fun init() { nhEvents.add(NHEvent.Init) networkLogger.debug { "BotInitProcessor.init" } bot.components[SsoProcessor].setFirstLoginResult(FirstLoginResult.PASSED) } }) set(ServerList, ServerListImpl()) set( EventDispatcher, // Note that in real we use 'bot.coroutineContext', but here we override with a new, independent job // to allow BotOfflineEvent.Active to be broadcast and joinBroadcast works even if bot coroutineScope is closed. TestEventDispatcherImpl( bot.coroutineContext + eventDispatcherJob, bot.logger.subLogger("TestEventDispatcherImpl") ) ) set(BotOfflineEventMonitor, object : BotOfflineEventMonitor { override fun attachJob(bot: AbstractBot, scope: CoroutineScope) { } }) set(EcdhInitialPublicKeyUpdater, object : EcdhInitialPublicKeyUpdater { override suspend fun refreshInitialPublicKeyAndApplyEcdh() { } override suspend fun initializeSsoSecureEcdh() { } override fun getQQEcdh(): QQEcdh = QQEcdh() }) set(AccountSecretsManager, MemoryAccountSecretsManager()) // set(StateObserver, bot.run { stateObserverChain() }) } fun <T : Any> setComponent(key: ComponentKey<in T>, instance: T): T { overrideComponents[key] = instance return instance } open fun createContext(): NetworkHandlerContextImpl = NetworkHandlerContextImpl(bot, networkLogger, bot.createNetworkLevelComponents()) //Use overrideComponents to avoid StackOverflowError when applying components open fun createAddress(): SocketAddress = overrideComponents[ServerList].pollAny().let { createSocketAddress(it.host, it.port) } /////////////////////////////////////////////////////////////////////////// // Assertions /////////////////////////////////////////////////////////////////////////// fun assertState(state: State) { assertEquals(state, network.state) } fun assertState(vararg accepted: State) { val s = network.state if (s !in accepted) { throw AssertionError("Expected: ${accepted.joinToString()}, actual: $s") } } fun NetworkHandler.assertState(state: State) { assertEquals(state, this.state) } val eventDispatcher get() = bot.components[EventDispatcher] var firstLoginResult: FirstLoginResult? get() = bot.components[SsoProcessor].firstLoginResult set(value) { bot.components[SsoProcessor].setFirstLoginResult(value) } } internal fun AbstractRealNetworkHandlerTest<*>.setSsoProcessor(action: suspend SsoProcessor.(handler: NetworkHandler) -> Unit) { overrideComponents[SsoProcessor] = object : SsoProcessor by overrideComponents[SsoProcessor] { override suspend fun login(handler: NetworkHandler) = action(handler) } } internal fun createWLoginSigInfo( uin: Long, creationTime: Long = currentTimeSeconds(), random: Random = Random(1) ): WLoginSigInfo { return WLoginSigInfo( uin = uin, encryptA1 = null, noPicSig = null, simpleInfo = WLoginSimpleInfo( uin = uin, imgType = EMPTY_BYTE_ARRAY, imgFormat = EMPTY_BYTE_ARRAY, imgUrl = EMPTY_BYTE_ARRAY, mainDisplayName = EMPTY_BYTE_ARRAY ), // defaults {}, from asyncContext._G appPri = 4294967295L, // defaults {}, from asyncContext._G a2ExpiryTime = creationTime + 2160000L, // or from asyncContext._t403.get_body_data() loginBitmap = 0, tgt = getRandomByteArray(16, random), a2CreationTime = creationTime, tgtKey = getRandomByteArray(16, random), // from asyncContext._login_bitmap userStSig = KeyWithCreationTime(getRandomByteArray(16, random), creationTime), userStKey = EMPTY_BYTE_ARRAY, userStWebSig = KeyWithExpiry( EMPTY_BYTE_ARRAY, creationTime, creationTime + 6000 ), userA5 = KeyWithCreationTime(getRandomByteArray(16, random), creationTime), userA8 = KeyWithExpiry( EMPTY_BYTE_ARRAY, creationTime, creationTime + 72000L ), lsKey = KeyWithExpiry( EMPTY_BYTE_ARRAY, creationTime, creationTime + 1641600L ), sKey = KeyWithExpiry( EMPTY_BYTE_ARRAY, creationTime, creationTime + 86400L ), userSig64 = KeyWithCreationTime(EMPTY_BYTE_ARRAY, creationTime), openId = EMPTY_BYTE_ARRAY, openKey = KeyWithCreationTime(EMPTY_BYTE_ARRAY, creationTime), vKey = KeyWithExpiry( EMPTY_BYTE_ARRAY, creationTime, creationTime + 1728000L ), accessToken = KeyWithCreationTime(EMPTY_BYTE_ARRAY, creationTime), d2 = KeyWithExpiry( getRandomByteArray(16, random), creationTime, creationTime + 1728000L ), d2Key = getRandomByteArray(16, random), sid = KeyWithExpiry( EMPTY_BYTE_ARRAY, creationTime, creationTime + 1728000L ), aqSig = KeyWithCreationTime(EMPTY_BYTE_ARRAY, creationTime), psKeyMap = mutableMapOf(), pt4TokenMap = mutableMapOf(), superKey = EMPTY_BYTE_ARRAY, payToken = EMPTY_BYTE_ARRAY, pf = EMPTY_BYTE_ARRAY, pfKey = EMPTY_BYTE_ARRAY, da2 = EMPTY_BYTE_ARRAY, wtSessionTicket = KeyWithCreationTime(EMPTY_BYTE_ARRAY, creationTime), wtSessionTicketKey = EMPTY_BYTE_ARRAY, deviceToken = EMPTY_BYTE_ARRAY, encryptedDownloadSession = null ) } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/AbstractRealTimeActionTestUnit.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.KeyWithCreationTime import net.mamoe.mirai.internal.network.KeyWithExpiry import net.mamoe.mirai.internal.network.WLoginSigInfo import net.mamoe.mirai.internal.network.WLoginSimpleInfo import net.mamoe.mirai.internal.notice.processors.GroupExtensions import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.hexToBytes import kotlin.test.BeforeTest internal abstract class AbstractRealTimeActionTestUnit : AbstractCommonNHTest(), GroupExtensions { @BeforeTest internal fun prepareEnv() { bot.client.wLoginSigInfoField = WLoginSigInfo( uin = bot.id, encryptA1 = "01 23 33 AF EA".hexToBytes(), noPicSig = "55 47 20 23 54".hexToBytes(), simpleInfo = WLoginSimpleInfo( uin = bot.id, imgType = EMPTY_BYTE_ARRAY, imgFormat = EMPTY_BYTE_ARRAY, imgUrl = EMPTY_BYTE_ARRAY, mainDisplayName = EMPTY_BYTE_ARRAY, ), appPri = 0, a2ExpiryTime = 0, a2CreationTime = 849415181, loginBitmap = 1145141919810, tgt = "EA 5B CE FA 6C".hexToBytes(), tgtKey = "66 F5 A9 B8 FF".hexToBytes(), userStSig = KeyWithCreationTime(data = "3C FF FF FF 07".hexToBytes(), creationTime = 0), userStKey = "07 F5 A9 B8 0B".hexToBytes(), userStWebSig = KeyWithExpiry(data = "A1 5B CE FA 60".hexToBytes(), creationTime = 0, expireTime = 0), userA5 = KeyWithCreationTime(data = "66 CC FF AA AA".hexToBytes(), creationTime = 0), userA8 = KeyWithExpiry(data = "65 c1 B9 7A 1F".hexToBytes(), creationTime = 0, expireTime = 0), lsKey = KeyWithExpiry(data = "65 c1 B9 7A 1F".hexToBytes(), creationTime = 0, expireTime = 0), sKey = KeyWithExpiry(data = "D6 B1 9C 66 3A".hexToBytes(), creationTime = 0, expireTime = 0), userSig64 = KeyWithCreationTime(data = "D6 B1 9C 66 3A".hexToBytes(), creationTime = 0), openId = "D6 B1 9C 66 3A".hexToBytes(), openKey = KeyWithCreationTime(data = "B4 6E 5E 7A 3C".hexToBytes(), creationTime = 0), vKey = KeyWithExpiry(data = "A1 34 17 48 21".hexToBytes(), creationTime = 0, expireTime = 0), accessToken = KeyWithCreationTime(data = "12 35 87 14 A1".hexToBytes(), creationTime = 0), aqSig = KeyWithExpiry(data = "22 0C DC AC 30".hexToBytes(), creationTime = 0, expireTime = 0), superKey = "22 33 66 CC FF".hexToBytes(), sid = KeyWithExpiry(data = "11 45 14 19 19".hexToBytes(), creationTime = 0, expireTime = 0), psKeyMap = mutableMapOf(), pt4TokenMap = mutableMapOf(), d2 = KeyWithExpiry(data = "81 00 07 64 11".hexToBytes(), creationTime = 0, expireTime = 0), d2Key = "404 not found!!!!!!".toByteArray(), payToken = "What's this".toByteArray(), pf = "> 1 + 1 == 11\n< true".toByteArray(), pfKey = "Don't change anything if it runs".toByteArray(), da2 = "sudo rm -rf /".toByteArray(), wtSessionTicket = KeyWithCreationTime(data = "deluser root".toByteArray(), creationTime = 0), wtSessionTicketKey = "500 Server Internal Error".toByteArray(), deviceToken = "Winserver datacenter 2077".toByteArray(), ) bot.client._bot = bot network.setStateOK(conn) removeOutgoingPacketEncoder() } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/ITestNetworkHandler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.components.EventDispatcher import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport internal interface ITestNetworkHandler<Conn> : NetworkHandler { val bot: QQAndroidBot fun setStateClosed(exception: Throwable? = null): NetworkHandlerSupport.BaseStateImpl? fun setStateConnecting(exception: Throwable? = null): NetworkHandlerSupport.BaseStateImpl? fun setStateOK(conn: Conn, exception: Throwable? = null): NetworkHandlerSupport.BaseStateImpl? fun setStateLoading(conn: Conn): NetworkHandlerSupport.BaseStateImpl? } internal val ITestNetworkHandler<*>.eventDispatcher get() = bot.components[EventDispatcher] internal val ITestNetworkHandler<*>.ssoProcessor get() = bot.components[SsoProcessor] ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/PacketReplier.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory import kotlin.jvm.JvmName /** * 应答器, 模拟服务器返回. */ internal fun interface PacketReplier { fun PacketReplierContext.onSend(packet: OutgoingPacket) } internal inline fun buildPacketReplier(crossinline builderAction: PacketReplierDslBuilder.() -> Unit): PacketReplier { return PacketReplierDslBuilder().apply { builderAction() }.build() } internal interface PacketReplierContext { val context: PacketReplierContext get() = this val coroutineScope: CoroutineScope fun reply(incoming: IncomingPacket) fun reply(incoming: Packet) fun reply(incoming: Throwable) fun ignore() {} } internal sealed class PacketReplierDecision { data class Reply(val action: PacketReplierContext.(outgoingPacket: OutgoingPacket) -> Unit) : PacketReplierDecision() data object Ignore : PacketReplierDecision() } internal class PacketReplierDslBuilder { val decisions: MutableList<PacketReplierDecision> = mutableListOf() class On<T : Packet?>( val fromFactories: List<OutgoingPacketFactory<T>>, ) /** * Expects the next packet to be exactly */ fun <T : Packet?> expect( vararg from: OutgoingPacketFactory<T>, ): On<T> = On(from.toList()) fun <T : Packet?> expect( vararg from: OutgoingPacketFactory<T>, action: PacketReplierContext.(outgoingPacket: OutgoingPacket) -> Unit ): Unit = On(from.toList()).invoke(action) operator fun <T : Packet?> On<T>.invoke( action: PacketReplierContext.(outgoingPacket: OutgoingPacket) -> Unit ) { decisions.add(PacketReplierDecision.Reply { outgoing -> fromFactories .find { it.commandName == outgoing.commandName } ?.let { return@Reply action(this, outgoing) } ?: run { val factories = fromFactories.joinToString(prefix = "[", postfix = "]") { it.commandName } throw AssertionError( "Expected client to send a packet from factories $factories, but client sent ${outgoing.commandName}" ) } }) } @JvmName("replyPacket") @OverloadResolutionByLambdaReturnType inline infix fun <T : Packet?> On<T>.reply(crossinline lazyIncoming: () -> Packet) { invoke { context.reply(lazyIncoming()) } } @JvmName("replyIncomingPacket") @OverloadResolutionByLambdaReturnType inline infix fun <T : Packet?> On<T>.reply(crossinline lazyIncoming: () -> IncomingPacket) { invoke { context.reply(lazyIncoming()) } } @JvmName("replyThrowable") @OverloadResolutionByLambdaReturnType inline infix fun <T : Packet?> On<T>.reply(crossinline lazyIncoming: () -> Throwable) { invoke { context.reply(lazyIncoming()) } } inline infix fun <T : Packet?> On<T>.ignore(crossinline lazy: () -> Unit) { invoke { lazy() context.ignore() } } /** * Ignore the next packet. */ fun ignore() { decisions.add(PacketReplierDecision.Ignore) } internal fun build(): PacketReplier { return PacketReplier { outgoing -> val context = this coroutineScope.launch { when (val decision = decisions.removeFirstOrNull() ?: throw AssertionError("Client sent a packet ${outgoing.commandName} while not expected") ) { is PacketReplierDecision.Ignore -> return@launch is PacketReplierDecision.Reply -> { decision.action.invoke(context, outgoing) } } } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/SynchronizedStdoutLogger.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import kotlinx.atomicfu.locks.reentrantLock import kotlinx.atomicfu.locks.withLock private val lock = reentrantLock() @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") internal class SynchronizedStdoutLogger(override val identity: String?) : net.mamoe.mirai.internal.utils.StdoutLogger( identity ) { override val output: (String) -> Unit = { str -> lock.withLock { println(str) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/TestNetworkHandler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext import net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.utils.ConcurrentLinkedQueue import net.mamoe.mirai.utils.TestOnly /** * States are manually set. */ internal open class TestNetworkHandler( override val bot: QQAndroidBot, context: NetworkHandlerContext, ) : NetworkHandlerSupport(context), ITestNetworkHandler<TestNetworkHandler.Connection> { class Connection @Suppress("EXPOSED_SUPER_CLASS") internal abstract inner class TestState( correspondingState: NetworkHandler.State ) : BaseStateImpl(correspondingState) { val resumeDeferred = CompletableDeferred<Unit>() val resumeCount = atomic(0) val onResume get() = resumeDeferred.onJoin private val mutex = Mutex() override suspend fun resumeConnection0(): Unit = mutex.withLock { resumeCount.incrementAndGet() resumeDeferred.complete(Unit) when (this.correspondingState) { INITIALIZED -> { setState(CONNECTING) } else -> { } } } override fun toString(): String { return "TestState($correspondingState)" } } internal inner class TestStateInitial : TestState(INITIALIZED) internal inner class TestStateConnecting : TestState(CONNECTING) internal inner class TestStateLoading : TestState(LOADING) internal inner class TestStateOK : TestState(OK) internal inner class TestStateClosed : TestState(CLOSED) @OptIn(TestOnly::class) fun setState(correspondingState: NetworkHandler.State): TestState? { // `null` means ignore checks. All test states have same type TestState. val state: TestState = when (correspondingState) { INITIALIZED -> TestStateInitial() CONNECTING -> TestStateConnecting() LOADING -> TestStateLoading() OK -> TestStateOK() CLOSED -> TestStateClosed() } return setStateImpl(TestState::class) { state } } private val initialState = TestStateInitial() override fun initialState(): BaseStateImpl = initialState val sendPacket get() = ConcurrentLinkedQueue<OutgoingPacket>() override suspend fun sendPacketImpl(packet: OutgoingPacket) { logger.info("sendPacketImpl: ${packet.displayName}") sendPacket.add(packet) } override fun setStateClosed(exception: Throwable?): TestState? { return setState(CLOSED) } override fun setStateConnecting(exception: Throwable?): TestState? { return setState(CONNECTING) } override fun setStateOK(conn: Connection, exception: Throwable?): TestState? { exception?.printStackTrace() return setState(OK) } override fun setStateLoading(conn: Connection): TestState? { return setState(LOADING) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/TestNetworkHandlerContext.kt ================================================ /* * 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.framework import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.components.SsoProcessorContextImpl import net.mamoe.mirai.internal.network.components.SsoProcessorImpl import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext import net.mamoe.mirai.internal.network.handler.state.LoggingStateObserver import net.mamoe.mirai.internal.network.handler.state.SafeStateObserver import net.mamoe.mirai.internal.network.handler.state.StateObserver import net.mamoe.mirai.utils.MiraiLogger internal class TestNetworkHandlerContext( override val bot: QQAndroidBot = MockBot(), override val logger: MiraiLogger = MiraiLogger.Factory.create(TestNetworkHandlerContext::class, "Test"), components: ComponentStorage = ConcurrentComponentStorage().apply { set(SsoProcessor, SsoProcessorImpl(SsoProcessorContextImpl(bot))) set( StateObserver, SafeStateObserver( LoggingStateObserver(MiraiLogger.Factory.create(LoggingStateObserver::class, "States")), MiraiLogger.Factory.create(LoggingStateObserver::class, "StateObserver errors") ) ) } ) : NetworkHandlerContext, ComponentStorage by components ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/components/TestEventDispatcherImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package network.framework.components import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.job import kotlinx.coroutines.launch import net.mamoe.mirai.event.Event import net.mamoe.mirai.internal.network.components.EventDispatcherImpl import net.mamoe.mirai.internal.network.handler.NetworkHandler.Companion.runUnwrapCancellationException import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.TestOnly import kotlin.coroutines.CoroutineContext internal open class TestEventDispatcherImpl( lifecycleContext: CoroutineContext, logger: MiraiLogger, ) : EventDispatcherImpl(lifecycleContext, logger) { override suspend fun broadcast(event: Event) { runUnwrapCancellationException { if (isActive) { // This requires the scope to be active, while the original one doesn't. // so that [joinBroadcast] works. launch( start = CoroutineStart.UNDISPATCHED ) { super.broadcast(event) }.join() } else { // Scope closed, typically when broadcasting `BotOfflineEvent` by StateObserver from `bot.close` super.broadcast(event) } } } @OptIn(TestOnly::class) override suspend fun joinBroadcast() { for (child in coroutineContext.job.children) { child.join() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/components/TestImagePatcher.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework.components import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.message.image.FriendImage import net.mamoe.mirai.internal.message.image.OfflineGroupImage import net.mamoe.mirai.internal.utils.ImageCache import net.mamoe.mirai.internal.utils.ImagePatcher import net.mamoe.mirai.internal.utils.ImagePatcherImpl internal class TestImagePatcher : ImagePatcher { val patchedOfflineGroupImages: MutableList<OfflineGroupImage> = mutableListOf() val patchedFriendImages: MutableList<FriendImage> = mutableListOf() private val patcher = ImagePatcherImpl() override fun findCacheByImageId(id: String): ImageCache? { return patcher.findCacheByImageId(id) } override fun putCache(image: OfflineGroupImage) { patcher.putCache(image) } override suspend fun patchOfflineGroupImage(group: GroupImpl, image: OfflineGroupImage) { patchedOfflineGroupImages.add(image) } override suspend fun patchFriendImageToGroupImage(group: GroupImpl, image: FriendImage): OfflineGroupImage { patchedFriendImages.add(image) return OfflineGroupImage(image.imageId, image.width, image.height, image.size, image.imageType, image.isEmoji) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/components/TestSsoProcessor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework.components import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import net.mamoe.mirai.auth.AuthReason import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.logger import net.mamoe.mirai.internal.network.protocol.data.jce.SvcRespRegister import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.utils.DeviceInfo import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.lateinitMutableProperty import kotlin.properties.Delegates import kotlin.random.Random internal open class TestSsoProcessor(private val bot: QQAndroidBot) : SsoProcessor { val deviceInfo = bot.configuration.createDeviceInfo(bot) override var client: QQAndroidClient by lateinitMutableProperty { QQAndroidClient( bot.account, device = deviceInfo, accountSecrets = bot.components[AccountSecretsManager].getSecretsOrCreate( bot.account, DeviceInfo.random(Random(1)) ) ).also { it._bot = bot } } override val ssoSession: SsoSession get() = bot.client private val _firstLoginResult: AtomicRef<FirstLoginResult?> = atomic(null) override val firstLoginResult get() = _firstLoginResult.value override fun casFirstLoginResult(expect: FirstLoginResult?, update: FirstLoginResult?): Boolean { return _firstLoginResult.compareAndSet(expect, update) } override fun setFirstLoginResult(value: FirstLoginResult?) { _firstLoginResult.value = value } override var registerResp: StatSvc.Register.Response? = null override var authReason: AuthReason by Delegates.notNull() override var isFirstLogin: Boolean = true override suspend fun login(handler: NetworkHandler) { bot.network.logger.debug { "SsoProcessor.login" } } override suspend fun logout(handler: NetworkHandler) { bot.network.logger.debug { "SsoProcessor.logout" } } override suspend fun sendRegister(handler: NetworkHandler): StatSvc.Register.Response { bot.network.logger.debug { "SsoProcessor.sendRegister" } return StatSvc.Register.Response(SvcRespRegister()) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/networkUtils.kt ================================================ /* * 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.framework import net.mamoe.mirai.internal.network.handler.NetworkHandler import kotlin.contracts.InvocationKind import kotlin.contracts.contract internal fun <R> NetworkHandler.useNetworkHandler(action: NetworkHandler.(handler: NetworkHandler) -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } val r = kotlin.runCatching { action(this) } r.fold( onSuccess = { close(null) }, onFailure = { close(it) } ) return r.getOrThrow() } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/sessionUtils.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.QQAndroidClient import net.mamoe.mirai.internal.network.WLoginSigInfo import net.mamoe.mirai.internal.network.components.AccountSecrets import net.mamoe.mirai.internal.network.components.AccountSecretsImpl import net.mamoe.mirai.internal.network.components.SsoSession import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY import net.mamoe.mirai.utils.MiraiFile import net.mamoe.mirai.utils.debug import net.mamoe.mirai.utils.writeBytes internal class TestSsoSession( private val accountSecrets: AccountSecrets, override var outgoingPacketSessionId: ByteArray = byteArrayOf(1, 2, 3, 4), override var loginState: Int = 0, ) : SsoSession { override var wLoginSigInfo: WLoginSigInfo by accountSecrets::wLoginSigInfo override val randomKey: ByteArray by accountSecrets::randomKey } //internal fun loadSession( // resourceName: String, //): AccountSecretsImpl { // val bytes = ClassLoader.getSystemResourceAsStream(resourceName)?.withUse { readBytes() } // ?: error("AccountSecrets resource '$resourceName' not found.") // return bytes.loadAs(AccountSecretsImpl.serializer()) //} /** * Secure to share with others. Designed to save real data for tests. */ internal fun QQAndroidClient.dumpSessionSafe(): ByteArray { val secrets = AccountSecretsImpl(device).copy( wLoginSigInfoField = wLoginSigInfo.copy( tgt = EMPTY_BYTE_ARRAY, encryptA1 = EMPTY_BYTE_ARRAY, ) ) return secrets.toByteArray(AccountSecretsImpl.serializer()) } internal fun QQAndroidBot.scheduleSafeSessionDump(outputFile: MiraiFile) { this.eventChannel.subscribeAlways<BotOnlineEvent> { outputFile.writeBytes(client.dumpSessionSafe()) bot.logger.debug { "Dumped safe session to " } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/framework/test/FrameworkEventTest.kt ================================================ /* * 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.framework.test import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.test.assertEventBroadcasts import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertFails internal class FrameworkEventTest : AbstractTest() { class TestEvent : AbstractEvent() class TestEvent2 : AbstractEvent() @Test fun `can observe event`() = runBlockingUnit { assertEventBroadcasts<TestEvent> { TestEvent().broadcast() } } @Test fun `observes expected event`() = runBlockingUnit { assertEventBroadcasts<TestEvent>(1) { TestEvent().broadcast() TestEvent2().broadcast() } } @Test fun `can observe event multiple times`() = runBlockingUnit { assertEventBroadcasts<TestEvent>(2) { TestEvent().broadcast() TestEvent().broadcast() } } @Test fun `can observe event only in block`() = runBlockingUnit { TestEvent().broadcast() assertEventBroadcasts<TestEvent>(1) { TestEvent().broadcast() } } @Test fun `fails if times not match`() = runBlockingUnit { assertFails { assertEventBroadcasts<TestEvent>(2) { TestEvent().broadcast() } } assertFails { assertEventBroadcasts<TestEvent>(0) { TestEvent().broadcast() } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/handler/KeepAliveNetworkHandlerSelectorTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.handler import kotlinx.atomicfu.atomic import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.selector.AbstractKeepAliveNetworkHandlerSelector import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.TestOnly import kotlin.test.* import kotlin.time.Duration.Companion.seconds internal val selectorLogger = MiraiLogger.Factory.create(TestSelector::class, "selector") internal class TestSelector<H : NetworkHandler> : AbstractKeepAliveNetworkHandlerSelector<H> { val createInstance0: () -> H constructor(createInstance0: () -> H) : super(logger = selectorLogger) { this.createInstance0 = createInstance0 } constructor(maxAttempts: Int, createInstance0: () -> H) : super(maxAttempts, selectorLogger) { this.createInstance0 = createInstance0 } private val _createInstanceCount = atomic(0) val createdInstanceCount get() = _createInstanceCount.value override fun createInstance(): H { _createInstanceCount.incrementAndGet() return this.createInstance0() } } internal class KeepAliveNetworkHandlerSelectorTest : AbstractMockNetworkHandlerTest() { @Test fun `can initialize instance`() { val selector = TestSelector { createNetworkHandler().apply { setState(State.OK) } } runBlockingUnit(timeout = 1.seconds) { selector.awaitResumeInstance() } assertNotNull(selector.getCurrentInstanceOrNull()) } @Test fun `no redundant initialization`() { val selector = TestSelector<NetworkHandler> { fail("initialize called") } val handler = createNetworkHandler() selector.setCurrent(handler) assertSame(handler, selector.getCurrentInstanceOrNull()) } @Test fun `initialize another when closed`() { val selector = TestSelector { createNetworkHandler().apply { setState(State.OK) } } val handler = createNetworkHandler() selector.setCurrent(handler) assertSame(handler, selector.getCurrentInstanceOrNull()) handler.setState(State.CLOSED) runBlockingUnit(timeout = 3.seconds) { selector.awaitResumeInstance() } assertEquals(1, selector.createdInstanceCount) } @Test fun `limited attempts`() = runBlockingUnit { val selector = TestSelector(3) { createNetworkHandler().apply { setState(State.CLOSED) } } assertFailsWith<IllegalStateException> { selector.awaitResumeInstance() } assertEquals(3, selector.createdInstanceCount) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/handler/SelectorHeartbeatRecoveryTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.handler import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTestWithSelector import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.network.NoServerAvailableException import net.mamoe.mirai.network.WrongPasswordException import net.mamoe.mirai.utils.TestOnly import kotlin.test.* /** * Test whether the selector can recover the connection after first successful login. */ internal class SelectorHeartbeatRecoveryTest : AbstractCommonNHTestWithSelector() { // @BeforeTest // fun beforeTest(info: TestInfo) { // println("=".repeat(30) + "BEGIN: ${info.displayName}" + "=".repeat(30)) // } // // @AfterTest // fun afterTest(info: TestInfo) { // println("=".repeat(31) + "END: ${info.displayName}" + "=".repeat(31)) // } /** * @see NetworkHandler.State.CONNECTING */ var throwExceptionOnLogin: (() -> Unit)? = null init { overrideComponents[SsoProcessor] = object : TestSsoProcessor(bot) { private val delegate = overrideComponents[SsoProcessor] override suspend fun login(handler: NetworkHandler) { delegate.login(handler) throwExceptionOnLogin?.invoke() } }.apply { setFirstLoginResult(FirstLoginResult.PASSED) } } @Test fun `stop on manual close`() = runBlockingUnit { network.resumeConnection() network.close(IllegalStateException("Closed by test")) assertFails { network.resumeConnection() } } /** * Emulates system hibernation and network failure. */ @Test fun `can recover on heartbeat failure with NettyChannelException`() = runBlockingUnit { // We allow NetworkException to cause a reconnect. testRecoverWhenHeartbeatFailWith { NetworkException("test IO ex", true) } bot.components[EventDispatcher].joinBroadcast() // Wait our async connector to complete. // BotOfflineMonitor immediately launches a recovery which is UNDISPATCHED, so connection is immediately recovered. assertTrue { bot.network.state != NetworkHandler.State.CLOSED } } @Test fun `can recover on LoginFailedException with killBot=false`() = runBlockingUnit { throwExceptionOnLogin = { if (selector.createdInstanceCount != 3) { throw NoServerAvailableException(null) } } bot.login() eventDispatcher.joinBroadcast() assertState(NetworkHandler.State.OK) assertEquals(3, selector.createdInstanceCount) } @Test fun `can recover on LoginFailedException with killBot=true`() = runBlockingUnit { throwExceptionOnLogin = { throw WrongPasswordException("Congratulations! Your bot has been blocked!") } assertFailsWith<WrongPasswordException> { bot.login() } eventDispatcher.joinBroadcast() assertState(NetworkHandler.State.CLOSED) assertEquals(1, selector.createdInstanceCount) } @Test fun `can recover on MsfOffline but fail and close bot on next login`() = runBlockingUnit { bot.login() firstLoginResult = FirstLoginResult.PASSED eventDispatcher.joinBroadcast() // Now first login succeed, and all events have been processed. assertState(NetworkHandler.State.OK) // Assume bot is blocked. throwExceptionOnLogin = { throw WrongPasswordException("Congratulations! Your bot has been blocked!") } // When blocked, server sends this event. eventDispatcher.broadcast(BotOfflineEvent.MsfOffline(bot, StatSvc.ReqMSFOffline.MsfOfflineToken(bot.uin, 1, 1))) eventDispatcher.joinBroadcast() // Sync for processing the async recovery launched by [BotOfflineEventMonitor] // Now we should expect assertState(NetworkHandler.State.CLOSED) } @Test fun `cannot recover on other failures`() = runBlockingUnit { // ISE is considered as an internal error (bug). testRecoverWhenHeartbeatFailWith { IllegalStateException() } assertState(NetworkHandler.State.CLOSED) } private suspend fun testRecoverWhenHeartbeatFailWith(exception: () -> Exception) { val heartbeatScheduler = object : HeartbeatScheduler { lateinit var onHeartFailure: HeartbeatFailureHandler override fun launchJobsIn( network: NetworkHandlerSupport, scope: CoroutineScope, onHeartFailure: HeartbeatFailureHandler, ): List<Job> { this.onHeartFailure = onHeartFailure return listOf(Job()) } } overrideComponents[HeartbeatScheduler] = heartbeatScheduler bot.login() // Now first login succeed. bot.network.context[EventDispatcher].joinBroadcast() assertState(NetworkHandler.State.OK) println("1") heartbeatScheduler.onHeartFailure("Test", exception()) println("2") } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/handler/SelectorLoginRecoveryTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import kotlinx.coroutines.test.runTest import net.mamoe.mirai.internal.network.components.FirstLoginResult import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.framework.AbstractRealNetworkHandlerTest import net.mamoe.mirai.internal.network.framework.TestCommonNetworkHandler import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler import net.mamoe.mirai.internal.network.protocol.packet.login.WtLogin import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal class SelectorLoginRecoveryTest : AbstractRealNetworkHandlerTest<SelectorNetworkHandler<TestCommonNetworkHandler>>() { // does not use selector override val factory: NetworkHandlerFactory<SelectorNetworkHandler<TestCommonNetworkHandler>> = NetworkHandlerFactory<SelectorNetworkHandler<TestCommonNetworkHandler>> { context, address -> SelectorNetworkHandler(TestSelector { object : TestCommonNetworkHandler(bot, context, address) { } }) } /** * 登录时遇到未知错误, [WtLogin] 会抛 [IllegalStateException] (即抛不可挽救的异常), * selector 应该停止登录, 不要重试, 重新抛出捕获的异常. */ @Test fun `rethrow exception caught during Bot_login`() = runTest { val exceptionMessage = "Login failed!" setComponent(SsoProcessor, object : TestSsoProcessor(bot) { override suspend fun login(handler: NetworkHandler) { throw IllegalStateException(exceptionMessage) } }) assertFailsWith<IllegalStateException> { bot.login() }.let { e -> assertEquals(exceptionMessage, e.message) } } /** * 登录时遇到未知错误, [WtLogin] 会抛 [IllegalStateException] (即抛不可挽救的异常), * selector 应该 close Bot, 不要 logout, 要重新抛出捕获的异常. */ @Test fun `do not call logout when closing bot due to failed to login`() = runTest { val exceptionMessage = "Login failed!" setComponent(SsoProcessor, object : TestSsoProcessor(bot) { override suspend fun login(handler: NetworkHandler) { throw IllegalStateException(exceptionMessage) } override suspend fun logout(handler: NetworkHandler) { if (firstLoginSucceed) { throw AssertionError("Congratulations! You called logout!") } } }) assertEquals(null, bot.components[SsoProcessor].firstLoginResult) bot.components[SsoProcessor].setFirstLoginResult(null) assertFailsWith<IllegalStateException> { bot.login() }.let { e -> assertEquals(exceptionMessage, e.message) } assertEquals(FirstLoginResult.OTHER_FAILURE, bot.components[SsoProcessor].firstLoginResult) bot.close() } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/handler/StandaloneSelectorTests.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import kotlinx.atomicfu.atomic import net.mamoe.mirai.internal.network.components.FirstLoginResult import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.framework.PlatformConn import net.mamoe.mirai.internal.network.framework.TestCommonNetworkHandler import net.mamoe.mirai.internal.network.handler.selector.MaxAttemptsReachedException import net.mamoe.mirai.internal.network.handler.selector.NetworkException import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.AtomicInteger import kotlin.test.* /** * Every function can have its own selector. */ internal class StandaloneSelectorTests : AbstractCommonNHTest() { private class MyException : Exception() { override fun toString(): String { return "MyException" } } /** * This simulates an error on [NetworkHandler.State.CONNECTING] */ private var throwExceptionOnConnecting: (() -> Nothing)? = null // does not use selector override val factory: NetworkHandlerFactory<TestCommonNetworkHandler> = NetworkHandlerFactory<TestCommonNetworkHandler> { context, address -> object : TestCommonNetworkHandler(bot, context, address) { override suspend fun createConnection(): PlatformConn { return throwExceptionOnConnecting?.invoke() ?: PlatformConn() } } } @Test fun `should not tolerant any exception except NetworkException thrown by states`() = runBlockingUnit { // selector should not tolerant any exception during state initialization, or in the Jobs launched in states. throwExceptionOnConnecting = { throw MyException() } val selector = TestSelector(3) { factory.create(createContext(), createAddress()) } assertFailsWith<Throwable> { selector.awaitResumeInstance() } } // Since #1963, any error during first login will close the bot. So we assume first login succeed to do our test. @BeforeTest private fun setFirstLoginPassed() { assertEquals(null, bot.components[SsoProcessor].firstLoginResult) bot.components[SsoProcessor].setFirstLoginResult(FirstLoginResult.PASSED) } @Test fun `NetworkException can suggest retrying`() = runBlockingUnit { // selector should not tolerant any exception during state initialization, or in the Jobs launched in states. throwExceptionOnConnecting = { throw object : NetworkException(true) {} } val selector = TestSelector(3) { factory.create(createContext(), createAddress()) } assertFailsWith<MaxAttemptsReachedException> { selector.awaitResumeInstance() }.let { assertIs<NetworkException>(it.cause) } } @Test fun `NetworkException does not cause retrying if recoverable=false`() = runBlockingUnit { // selector should not tolerant any exception during state initialization, or in the Jobs launched in states. val times = AtomicInteger(0) val theException = object : NetworkException(false) {} throwExceptionOnConnecting = { times.incrementAndGet() throw theException } val selector = TestSelector(3) { factory.create(createContext(), createAddress()) } assertFailsWith<NetworkException> { selector.awaitResumeInstance() }.let { assertEquals(1, times.value) assertSame(theException, it) } } @Test fun `other exceptions considered as internal error and does not trigger reconnect`() = runBlockingUnit { throwExceptionOnConnecting = { throw MyException() } val selector = TestSelector(3) { factory.create(createContext(), createAddress()) } assertFailsWith<MyException> { selector.awaitResumeInstance() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/handler/StateObserverTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import net.mamoe.mirai.internal.network.framework.AbstractMockNetworkHandlerTest import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.CONNECTING import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.INITIALIZED import net.mamoe.mirai.internal.network.handler.state.CombinedStateObserver import net.mamoe.mirai.internal.network.handler.state.CombinedStateObserver.Companion.plus import net.mamoe.mirai.internal.network.handler.state.StateChangedObserver import net.mamoe.mirai.internal.network.handler.state.StateObserver import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class StateObserverTest : AbstractMockNetworkHandlerTest() { @Test fun `can trigger observer`() { val called = ArrayList<Pair<NetworkHandlerSupport.BaseStateImpl, NetworkHandlerSupport.BaseStateImpl>>() components[StateObserver] = object : StateObserver { override fun stateChanged( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl ) { called.add(previous to new) } } val handler = createNetworkHandler() assertEquals(0, called.size) handler.setState(INITIALIZED) assertEquals(1, called.size) assertEquals(INITIALIZED, called[0].first.correspondingState) assertEquals(INITIALIZED, called[0].second.correspondingState) handler.setState(CONNECTING) assertEquals(2, called.size) assertEquals(INITIALIZED, called[1].first.correspondingState) assertEquals(CONNECTING, called[1].second.correspondingState) } @Test fun `test StateChangedObserver`() { val called = ArrayList<Pair<NetworkHandlerSupport.BaseStateImpl, NetworkHandlerSupport.BaseStateImpl>>() components[StateObserver] = object : StateChangedObserver(CONNECTING) { override fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl ) { called.add(previous to new) } } val handler = createNetworkHandler() assertEquals(0, called.size) handler.setState(INITIALIZED) assertEquals(0, called.size) handler.setState(CONNECTING) assertEquals(1, called.size) assertEquals(INITIALIZED, called[0].first.correspondingState) assertEquals(CONNECTING, called[0].second.correspondingState) } @Test fun `test StateChangedObserver2`() { val called = ArrayList<NetworkHandlerSupport.BaseStateImpl>() components[StateObserver] = StateChangedObserver("test", INITIALIZED, CONNECTING) { new -> called.add(new) } val handler = createNetworkHandler() assertEquals(0, called.size) handler.setState(INITIALIZED) assertEquals(0, called.size) handler.setState(CONNECTING) assertEquals(1, called.size) assertEquals(CONNECTING, called[0].correspondingState) } @Test fun `can combine`() { val called = ArrayList<Pair<NetworkHandlerSupport.BaseStateImpl, NetworkHandlerSupport.BaseStateImpl>>() val added = object : StateChangedObserver(CONNECTING) { override fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl ) { called.add(previous to new) } override fun toString(): String = "1" } + object : StateChangedObserver(CONNECTING) { override fun stateChanged0( networkHandler: NetworkHandlerSupport, previous: NetworkHandlerSupport.BaseStateImpl, new: NetworkHandlerSupport.BaseStateImpl ) { called.add(previous to new) } override fun toString(): String = "2" } assertIs<CombinedStateObserver>(added) assertEquals("CombinedStateObserver[1 -> 2]", added.toString()) components[StateObserver] = added val handler = createNetworkHandler() assertEquals(0, called.size) handler.setState(INITIALIZED) assertEquals(0, called.size) handler.setState(CONNECTING) assertEquals(2, called.size) assertEquals(INITIALIZED, called[0].first.correspondingState) assertEquals(CONNECTING, called[0].second.correspondingState) assertEquals(INITIALIZED, called[1].first.correspondingState) assertEquals(CONNECTING, called[1].second.correspondingState) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/AccountSecretsTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.common import net.mamoe.mirai.internal.network.components.AccountSecretsImpl import net.mamoe.mirai.internal.network.components.AccountSecretsManager import net.mamoe.mirai.internal.network.components.FileCacheAccountSecretsManager import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.internal.utils.accountSecretsFile import net.mamoe.mirai.utils.DeviceInfo import net.mamoe.mirai.utils.SecretsProtection import net.mamoe.mirai.utils.getRandomByteArray import net.mamoe.mirai.utils.writeBytes import kotlin.test.Test import kotlin.test.assertEquals internal class AccountSecretsTest : AbstractCommonNHTest() { init { overrideComponents.remove(AccountSecretsManager) bot.account.accountSecretsKeyBuffer = SecretsProtection.EscapedByteBuffer(ByteArray(16)) } @Test fun `can login with no secrets`() = runBlockingUnit { val file = bot.configuration.accountSecretsFile() file.delete() bot.login() bot.network.assertState(NetworkHandler.State.OK) } @Test fun `can login with good secrets`() = runBlockingUnit { val file = bot.configuration.accountSecretsFile() val s = AccountSecretsImpl(DeviceInfo.random()) FileCacheAccountSecretsManager.saveSecretsToFile(file, bot.account, s) bot.login() bot.network.assertState(NetworkHandler.State.OK) assertEquals(s, bot.components[AccountSecretsManager].getSecrets(bot.account)) } @Test fun `can login with bad secrets`() = runBlockingUnit { val file = bot.configuration.accountSecretsFile() file.writeBytes(getRandomByteArray(16)) bot.login() bot.network.assertState(NetworkHandler.State.OK) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/BotLifecycleTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.impl.common import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.isActive import kotlinx.coroutines.job import kotlinx.coroutines.test.runTest import net.mamoe.mirai.internal.MockBot import net.mamoe.mirai.internal.network.components.EventDispatcher import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.framework.components.TestSsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.supervisorJob import net.mamoe.mirai.utils.TestOnly import kotlin.test.* internal class BotLifecycleTest : AbstractCommonNHTest() { // not allowed anymore // @Test // fun `closed on Force offline with BotOfflineEventMonitor`() = runBlockingUnit { // defaultComponents[BotOfflineEventMonitor] = BotOfflineEventMonitorImpl() // bot.login() // assertState(OK) // BotOfflineEvent.Force(bot, "test", "test").broadcast() // assertState(CLOSED) // assertFalse { network.isActive } // assertTrue { bot.isActive } // } // @Test // fun `closed on Active offline with BotOfflineEventMonitor`() = runBlockingUnit { // defaultComponents[BotOfflineEventMonitor] = BotOfflineEventMonitorImpl() // bot.login() // assertState(OK) // BotOfflineEvent.Active(bot, null).broadcast() // assertState(CLOSED) // assertFalse { network.isActive } // assertTrue { bot.isActive } // } @Test fun `state is CLOSED after Bot close`() = runBlockingUnit { bot.network.assertState(INITIALIZED) bot.login() bot.network.assertState(OK) bot.closeAndJoin() bot.network.assertState(CLOSED) bot.components[EventDispatcher].joinBroadcast() bot.network.assertState(CLOSED) } @Test fun `send logout on exit`() = runBlockingUnit { assertIs<TestSsoProcessor>(bot.components[SsoProcessor]) bot.network.assertState(INITIALIZED) bot.login() bot.network.assertState(OK) bot.closeAndJoin() // send logout blocking eventDispatcher.joinBroadcast() bot.network.assertState(CLOSED) assertTrue { nhEvents.any { it is NHEvent.Logout } } } @Test fun `can override context`() = runBlockingUnit { bot = MockBot { conf { parentCoroutineContext = CoroutineName("Overrode") } networkHandlerProvider { factory.create(createContext(), createAddress()) } } assertEquals("Overrode", bot.coroutineContext[CoroutineName]!!.name) } @Test fun `job attached`() = runBlockingUnit { val parentJob = SupervisorJob() bot = MockBot { conf { parentCoroutineContext = parentJob } networkHandlerProvider { factory.create(createContext(), createAddress()) } } assertEquals(1, parentJob.children.count()) assertEquals(bot.supervisorJob, parentJob.children.first()) } @Test fun `network scope closed on bot close`() = runBlockingUnit { assertTrue { network.isActive } bot.closeAndJoin() assertFalse { network.isActive } } @Test fun `network closed on SimpleGet Error`() = runBlockingUnit { assertTrue { network.isActive } bot.login() assertTrue { network.isActive } network.collectReceived( IncomingPacket( commandName = StatSvc.SimpleGet.commandName, sequenceId = 1, data = StatSvc.SimpleGet.Response.Error(1, "test error"), ) ) network.coroutineContext.job.join() network.assertState(CLOSED) // we do not use selector in this test so it will be CLOSED. It will recover (reconnect) instead in real. assertFalse { network.isActive } } @Test // #2266 fun `no StackOverflowError on Bot close`() = runTest { assertTrue { network.isActive } bot.login() assertTrue { network.isActive } overrideComponents[SsoProcessor] = object : TestSsoProcessor(bot) { override suspend fun logout(handler: NetworkHandler) { bot.close() } } bot.close() network.assertState(CLOSED) assertFalse { network.isActive } } @Test fun `isOnline returns false if network not initialized`() = runBlockingUnit { assertFalse { bot.isOnline } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/CommonNHAddressChangedTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.common import net.mamoe.mirai.internal.network.components.ServerList import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.framework.TestCommonNetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertTrue internal class CommonNHAddressChangedTest : AbstractCommonNHTest() { @Test fun `test login ip changes`() = runBlockingUnit { networkLogger.debug("before login, Assuming both ip is empty") val lastConnectedIpOld = bot.components[ServerList].lastConnectedIP val lastDisconnectedIpOld = bot.components[ServerList].lastDisconnectedIP assertTrue(lastConnectedIpOld.isEmpty(), "Assuming lastConnectedIp is empty") assertTrue(lastDisconnectedIpOld.isEmpty(), "Assuming lastDisconnectedIp is empty") networkLogger.debug("Do login, Assuming lastConnectedIp is NOT empty") bot.login() assertState(NetworkHandler.State.OK) assertNotEquals( lastConnectedIpOld, bot.components[ServerList].lastConnectedIP, "Assuming lastConnectedIp is NOT empty" ) networkLogger.debug("Offline the bot, Assuming lastConnectedIp is equals lastDisconnectedIp") (bot.network as TestCommonNetworkHandler).setStateClosed() assertState(NetworkHandler.State.CLOSED) assertEquals( bot.components[ServerList].lastConnectedIP, bot.components[ServerList].lastDisconnectedIP, "Assuming lastConnectedIp is equals lastDisconnectedIp" ) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/CommonNHBotNormalLoginTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.impl.common import io.ktor.utils.io.errors.* import kotlinx.coroutines.* import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.MockConfiguration import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.component.setAll import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitor import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitorImpl import net.mamoe.mirai.internal.network.components.FirstLoginResult import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.framework.TestCommonNetworkHandler import net.mamoe.mirai.internal.network.framework.setSsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector import net.mamoe.mirai.internal.network.handler.selector.NetworkChannelException import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler import net.mamoe.mirai.internal.network.handler.selectorLogger import net.mamoe.mirai.internal.network.impl.HeartbeatFailedException import net.mamoe.mirai.internal.network.protocol.packet.login.StatSvc import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.network.CustomLoginFailedException import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.cast import kotlin.test.* internal class CommonNHBotNormalLoginTest : AbstractCommonNHTest() { init { overrideComponents[BotOfflineEventMonitor] = BotOfflineEventMonitorImpl() } val selector = KeepAliveNetworkHandlerSelector(selectorLogger) { factory.create(createContext(), createAddress()) } override val network: TestCommonNetworkHandler get() = selector.getCurrentInstanceOrCreate().cast() override fun createBot(account: BotAccount): QQAndroidBot { return object : QQAndroidBot(account, MockConfiguration.copy()) { override fun createBotLevelComponents(): ConcurrentComponentStorage = super.createBotLevelComponents().apply { setAll(overrideComponents) } override fun createNetworkHandler(): NetworkHandler = SelectorNetworkHandler(selector) } } class CusLoginException(message: String?) : CustomLoginFailedException(true, message) @AfterTest fun `close bot`() = runBlockingUnit { bot.logger.info("[TEST UNIT] Releasing bot....") bot.closeAndJoin() } @Test fun `test login fail`() = runBlockingUnit { setSsoProcessor { throw CusLoginException("A") } assertFailsWith<CusLoginException>("A") { bot.login() } assertFalse(bot.isActive) } // #1963 @Test fun `test first login failure with internally handled exceptions`() = runBlockingUnit { setSsoProcessor { throw IOException("test Connection reset by peer") } assertFailsWith<IOException>("test Connection reset by peer") { bot.login() } assertState(NetworkHandler.State.CLOSED) } // #1963 @Test fun `test first login failure with internally handled exceptions2`() = runBlockingUnit { setSsoProcessor { throw NetworkChannelException("test Connection reset by peer") } assertFailsWith<NetworkChannelException>("test Connection reset by peer") { bot.login() } assertState(NetworkHandler.State.CLOSED) } // #1963 @Test fun `test first login failure with internally handled exceptions3`() = runBlockingUnit { setSsoProcessor { //Throw TimeoutCancellationException withTimeout(1) { delay(1000) } } assertFailsWith<TimeoutCancellationException>("test TimeoutCancellationException") { bot.login() } assertState(NetworkHandler.State.CLOSED) } // 经过 #1963 考虑后在初次登录遇到任何错误都终止并传递异常 // @Test // fun `test network broken`() = runBlockingUnit { // var retryCounter = 0 // setSsoProcessor { // eventDispatcher.joinBroadcast() // if (retryCounter++ >= 15) { // return@setSsoProcessor // } // channel.pipeline().fireExceptionCaught(IOException("TestNetworkBroken")) // awaitCancellation() // receive exception from "network" // } // bot.login() // } @Test fun `test resume after MsfOffline received after first login`() = runBlockingUnit { bot.login() assertEquals(FirstLoginResult.PASSED, firstLoginResult) bot.network.close(StatSvc.ReqMSFOffline.MsfOfflineToken(0, 0, 0)) eventDispatcher.joinBroadcast() yield() // auto resume in BotOfflineEventMonitor eventDispatcher.joinBroadcast() assertState(NetworkHandler.State.OK) } // #2504, #2488 @Test fun `test resume failed with TimeoutCancellationException`() = runBlockingUnit { var first = true var failCount = 3 setSsoProcessor { if (first) { first = false } else { if (failCount > 0) { failCount-- //Throw TimeoutCancellationException withTimeout(1) { delay(1000) } } } } bot.login() bot.network.close(HeartbeatFailedException("Heartbeat Timeout", RuntimeException("Timeout stub"), true)) eventDispatcher.joinBroadcast() yield() // auto resume in BotOfflineEventMonitor eventDispatcher.joinBroadcast() assertState(NetworkHandler.State.OK) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/CommonNHEventTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.impl.common import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.isActive import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotReloginEvent import net.mamoe.mirai.internal.network.components.FirstLoginResult import net.mamoe.mirai.internal.network.components.SsoProcessor import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.framework.eventDispatcher import net.mamoe.mirai.internal.network.framework.setSsoProcessor import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* import net.mamoe.mirai.internal.test.assertEventBroadcasts import net.mamoe.mirai.internal.test.assertEventNotBroadcast import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.supervisorJob import net.mamoe.mirai.utils.TestOnly import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue internal class CommonNHEventTest : AbstractCommonNHTest() { @Test fun `BotOnlineEvent after successful logon`() = runBlockingUnit { assertEventBroadcasts<BotOnlineEvent> { assertEquals(INITIALIZED, network.state) bot.login() // launches a job which broadcasts the event eventDispatcher.joinBroadcast() assertEquals(OK, network.state) } } @Test fun `BotOnlineEvent after successful reconnection`() = runBlockingUnit { assertEquals(INITIALIZED, network.state) bot.login() bot.components[SsoProcessor].setFirstLoginResult(FirstLoginResult.PASSED) assertEquals(OK, network.state) eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event assertEventBroadcasts<BotOnlineEvent>(1) { network.setStateConnecting() network.resumeConnection() eventDispatcher.joinBroadcast() assertEquals(OK, network.state) } } @Test fun `BotOfflineEvent after successful reconnection`() = runBlockingUnit { assertEquals(INITIALIZED, network.state) bot.login() bot.components[SsoProcessor].setFirstLoginResult(FirstLoginResult.PASSED) assertEquals(OK, network.state) eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event assertEventBroadcasts<BotOfflineEvent>(1) { network.setStateClosed() eventDispatcher.joinBroadcast() assertState(CLOSED) } } @Test fun `from CONNECTING TO OK the first time`() = runBlockingUnit { val ok = CompletableDeferred<Unit>() setSsoProcessor { ok.join() } assertState(INITIALIZED) network.setStateConnecting() assertEventBroadcasts<Event>(1) { ok.complete(Unit) network.resumeConnection() eventDispatcher.joinBroadcast() }.let { event -> assertEquals(BotOnlineEvent::class, event[0]::class) } } @Test fun `from CONNECTING TO OK the second time`() = runBlockingUnit { val ok = object { val v = atomic(CompletableDeferred<Unit>()) } setSsoProcessor { ok.v.value.join() } assertState(INITIALIZED) network.setStateConnecting() ok.v.value.complete(Unit) network.resumeConnection() assertState(OK) ok.v.value = CompletableDeferred() network.setStateConnecting() eventDispatcher.joinBroadcast() println("Starting receiving events") assertEventBroadcasts<Event>(2) { ok.v.value.complete(Unit) network.resumeConnection() eventDispatcher.joinBroadcast() }.let { event -> assertEquals(BotOnlineEvent::class, event[0]::class) assertEquals(BotReloginEvent::class, event[1]::class) } } @Test fun `BotOffline from OK TO CLOSED`() = runBlockingUnit { bot.login() assertState(OK) eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event assertEventBroadcasts<Event>(1) { network.close(null) assertState(CLOSED) eventDispatcher.joinBroadcast() }.let { event -> assertEquals(BotOfflineEvent.Active::class, event[0]::class) } } @Test fun `BotOffline from OK TO CLOSED by bot close`() = runBlockingUnit { bot.login() assertState(OK) assertEquals(FirstLoginResult.PASSED, firstLoginResult) eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event assertEventBroadcasts<Event>(1) { assertTrue { bot.isActive } bot.close(null) bot.supervisorJob.join() assertState(CLOSED) eventDispatcher.joinBroadcast() }.let { event -> assertEquals(BotOfflineEvent.Active::class, event[0]::class) } } @Test fun `no BotOffline from CONNECTING TO CLOSED`() = runBlockingUnit { network.setStateConnecting() eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event assertEventNotBroadcast { network.setStateClosed() network.resumeConnection() eventDispatcher.joinBroadcast() } } @Test fun `BotReloginEvent after successful reconnection`() = runBlockingUnit { assertEventBroadcasts<BotReloginEvent> { assertState(INITIALIZED) bot.login() assertState(OK) bot.components[SsoProcessor].setFirstLoginResult(FirstLoginResult.PASSED) network.setStateConnecting() network.resumeConnection() assertState(OK) network.eventDispatcher.joinBroadcast() assertState(OK) } } // // @Test // fun `no event from INITIALIZED TO OK`() = runBlockingUnit { // assertState(INITIALIZED) // bot.login() // assertState(OK) // network.setStateConnecting() // eventDispatcher.joinBroadcast() // `login` launches a job which broadcasts the event // assertEventBroadcasts<Event>(0) { // network.resumeConnection() // eventDispatcher.joinBroadcast() // } // } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/ResumeConnectionTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.netty import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertFailsWith internal class ResumeConnectionTest : AbstractCommonNHTest() { private val packet = OutgoingPacket("", "", 1, ByteReadPacket.Empty) @Test fun `cannot resume on CLOSED`() = runBlockingUnit { network.setStateClosed() assertFailsWith<IllegalStateException> { network.sendWithoutExpect(packet) } } @Test fun `resumeConnection switches a state that can send packet on INITIALIZED`() = runBlockingUnit { network.resumeConnection() network.sendWithoutExpect(packet) } @Test fun `resumeConnection switches a state that can send packet on CONNECTING`() = runBlockingUnit { network.setStateConnecting() network.resumeConnection() network.sendWithoutExpect(packet) } @Test fun `resumeConnection switches a state that can send packet on LOADING`() = runBlockingUnit { network.setStateLoading(conn) network.resumeConnection() network.sendWithoutExpect(packet) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/SendPacketTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.common import io.ktor.utils.io.core.* import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.launch import kotlinx.coroutines.yield import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.AtomicBoolean import kotlin.test.Test import kotlin.test.assertNotNull import kotlin.test.assertTrue internal class SendPacketTest : AbstractCommonNHTest() { // single thread so we can use [yield] to transfer dispatch private val singleThreadDispatcher = borrowSingleThreadDispatcher() @Test fun `sendPacketImpl suspends until a valid state`() = runBlockingUnit(singleThreadDispatcher) { val expectStop = AtomicBoolean(false) // coroutine starts immediately and suspends at `net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler.sendPacketImpl` launch(singleThreadDispatcher, start = CoroutineStart.UNDISPATCHED) { assertNotNull(network.sendAndExpect(OutgoingPacket("name", "cmd", 1, ByteReadPacket.Empty))) assertTrue { expectStop.value } } network.setStateOK(conn) // then we can send packet. yield() // yields the thread to run `sendAndExpect` // when we got thread here again, `sendAndExpect` is suspending for response [Packet]. network.collectReceived(IncomingPacket("cmd", 1, object : Packet {})) // now `sendAndExpect` should finish (asynchronously). expectStop.value = true } @Test fun `sendPacketImpl does not suspend if state is valid`() = runBlockingUnit(singleThreadDispatcher) { network.setStateOK(conn) // then we can send packet. val expectStop = AtomicBoolean(false) val job = launch(singleThreadDispatcher, start = CoroutineStart.UNDISPATCHED) { assertNotNull(network.sendAndExpect(OutgoingPacket("name", "cmd", 1, ByteReadPacket.Empty))) assertTrue { expectStop.value } // ensures `sendAndExpect` does not finish immediately. (We expect one suspension.) } expectStop.value = true network.collectReceived(IncomingPacket("cmd", 1, object : Packet {})) yield() assertTrue(job.isCompleted) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/network/impl/common/SetStateTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.network.impl.common import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.network.components.BotOfflineEventMonitor import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.handler.NetworkHandler.State.* import net.mamoe.mirai.internal.test.assertEventBroadcasts import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.TestOnly import kotlin.test.* internal class SetStateTest : AbstractCommonNHTest() { @Test fun `setState should ignore duplications INITIALIZED to CLOSED to CLOSED`() { assertState(INITIALIZED) assertNotNull(network.setStateClosed(IllegalStateException("1"))) assertState(CLOSED) assertNull(network.setStateClosed(IllegalStateException("2"))) assertState(CLOSED) } @Test fun `setState should ignore duplications OK to CLOSED to CLOSED`() { assertNotNull(network.setStateOK(conn)) assertState(OK) assertNotNull(network.setStateClosed(IllegalStateException("1"))) assertState(CLOSED) assertNull(network.setStateClosed(IllegalStateException("2"))) assertState(CLOSED) } @Test fun `setState should ignore duplications 2 OK to CLOSED to CLOSED`() = runBlockingUnit { overrideComponents[BotOfflineEventMonitor] = object : BotOfflineEventMonitor { override fun attachJob(bot: AbstractBot, scope: CoroutineScope) { } } assertNotNull(network.setStateOK(conn)) assertState(OK) assertEventBroadcasts<Event> { assertNotNull(network.setStateClosed(IllegalStateException("1"))) assertState(CLOSED) assertNull(network.setStateClosed(IllegalStateException("2"))) assertState(CLOSED) eventDispatcher.joinBroadcast() }.let { list -> assertEquals(1, list.size) assertIs<BotOfflineEvent.Active>(list[0]) } } @Test fun `Precondition - setState should ignore duplications 2 OK to CLOSED to CLOSED`() = runBlockingUnit { assertNotNull(network.setStateOK(conn)) assertState(OK) assertEventBroadcasts<Event> { assertNotNull(network.setStateClosed(IllegalStateException("1"))) assertState(CLOSED) eventDispatcher.joinBroadcast() }.let { list -> assertEquals(1, list.size) assertIs<BotOfflineEvent.Active>(list[0]) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/AbstractNoticeProcessorTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.internal.notice.processors import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.serializer import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* import net.mamoe.mirai.internal.BotAccount import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.contact.* 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.network.Packet import net.mamoe.mirai.internal.network.components.* import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline.Companion.noticeProcessorPipeline import net.mamoe.mirai.internal.network.framework.AbstractCommonNHTest import net.mamoe.mirai.internal.network.protocol.packet.IncomingPacket import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars import net.mamoe.mirai.utils.* /** * To add breakpoint, see [NoticeProcessorPipelineImpl.process] */ internal abstract class AbstractNoticeProcessorTest : AbstractCommonNHTest(), GroupExtensions { init { setSystemProp("mirai.network.notice.pipeline.log.full", "true") } protected class UseTestContext( val attributes: MutableTypeSafeMap ) { val EMPTY_BYTE_ARRAY get() = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY fun String.hexToBytes() = hexToUBytes().toByteArray() internal inline fun <reified T : JceStruct> T.toByteArray( serializer: SerializationStrategy<T> = serializer(), ): ByteArray = Tars.UTF_8.encodeToByteArray(serializer, this) } protected suspend inline fun use( attributes: TypeSafeMap = createTypeSafeMap(), pipeline: NoticeProcessorPipeline = bot.components.noticeProcessorPipeline, block: UseTestContext.() -> ProtocolStruct ): Collection<Packet> { bot.components[SsoProcessor].setFirstLoginResult(FirstLoginResult.PASSED) val handler = LoggingPacketHandlerAdapter(PacketLoggingStrategyImpl(bot), bot.logger) val context = UseTestContext(attributes.toMutableTypeSafeMap()) return pipeline.process(block(context), context.attributes).collected.also { list -> for (packet in list) { handler.handlePacket(IncomingPacket("test", packet)) } } } protected suspend inline fun use( attributes: TypeSafeMap = createTypeSafeMap(), crossinline createContext: NoticeProcessorPipelineImpl.(attributes: TypeSafeMap) -> NoticeProcessorPipelineImpl.ContextImpl, block: UseTestContext.() -> ProtocolStruct ): Collection<Packet> = use(attributes, pipeline = object : NoticeProcessorPipelineImpl(bot) { init { bot.components.noticeProcessorPipeline.processors.forEach { registerProcessor(it.value) } } override fun createContext(data: ProtocolStruct, attributes: TypeSafeMap): NoticePipelineContext = createContext(this, attributes) }, block) open fun setBot(id: Long): QQAndroidBot { bot = createBot(BotAccount(id, "a")) return bot } } internal interface GroupExtensions { @Suppress("TestFunctionName") fun GroupInfo( uin: Long, owner: Long, groupCode: Long, memo: String = "", name: String, allowMemberInvite: Boolean = false, allowAnonymousChat: Boolean = false, autoApprove: Boolean = false, confessTalk: Boolean = false, muteAll: Boolean = false, botMuteTimestamp: Int = 0, honorShow: Boolean = false, titleShow: Boolean = false, temperatureShow: Boolean = false, rankTitles: Map<Int, String> = emptyMap(), temperatureTitles: Map<Int, String> = emptyMap(), ): GroupInfoImpl = GroupInfoImpl( uin, owner, groupCode, memo, name, allowMemberInvite, allowAnonymousChat, autoApprove, confessTalk, muteAll, botMuteTimestamp, honorShow, titleShow, temperatureShow, rankTitles, temperatureTitles ) fun Bot.addGroup(group: Group) { groups.delegate.add(group) } fun Bot.addFriend(friend: Friend) { friends.delegate.add(friend) } fun Bot.addFriend(id: Long, nick: String = "friend$id", remark: String = "", friendGroupId: Int = 0): FriendImpl { return FriendImpl(bot.cast(), bot.coroutineContext, FriendInfoImpl(id, nick, remark, friendGroupId)).also { friends.delegate.add(it) } } fun Bot.addStranger(id: Long, nick: String = "stranger$id", fromGroupId: Long = 0): StrangerImpl { return StrangerImpl(bot.cast(), bot.coroutineContext, StrangerInfoImpl(id, nick, fromGroupId)).also { strangers.delegate.add(it) } } fun Group.addMember(member: NormalMember) { members.delegate.add(member) } fun Bot.addGroup( id: Long, owner: Long, botPermission: MemberPermission = MemberPermission.MEMBER, memo: String = "", name: String = "Test Group", allowMemberInvite: Boolean = false, allowAnonymousChat: Boolean = false, autoApprove: Boolean = false, confessTalk: Boolean = false, muteAll: Boolean = false, botMuteTimestamp: Int = 0, ): GroupImpl { val impl = GroupImpl( bot.cast(), coroutineContext, id, GroupInfo( @Suppress("DEPRECATION") Mirai.calculateGroupUinByGroupCode(id), owner, id, memo, name, allowMemberInvite, allowAnonymousChat, autoApprove, confessTalk, muteAll, botMuteTimestamp ), ContactList(), ) addGroup(impl) impl.botAsMember = impl.addMember(bot.id, nick = bot.nick, permission = botPermission) return impl } fun Bot.addGroup( id: Long, info: GroupInfoImpl, botPermission: MemberPermission = MemberPermission.MEMBER, ): Group { val impl = GroupImpl( bot.cast(), coroutineContext, id, info, ContactList(), ) addGroup(impl) impl.botAsMember = impl.addMember(bot.id, nick = bot.nick, permission = botPermission) return impl } fun Group.addMember( id: Long, nick: String = "user$id", permission: MemberPermission, remark: String = "", nameCard: String = "", specialTitle: String = "", muteTimestamp: Int = 0, anonymousId: String? = null, joinTimestamp: Int = currentTimeSeconds().toInt(), lastSpeakTimestamp: Int = 0, isOfficialBot: Boolean = false, ): NormalMemberImpl { val member = NormalMemberImpl( this.cast(), this.coroutineContext, MemberInfoImpl( id, nick, permission, remark, nameCard, specialTitle, muteTimestamp, anonymousId, joinTimestamp, lastSpeakTimestamp, isOfficialBot ) ) members.delegate.add( member ) return member } fun Group.addMember( id: Long, info: MemberInfoImpl, ): Group { members.delegate.add(NormalMemberImpl(this.cast(), this.coroutineContext, info)) return this } fun Bot.addOtherClient( appId: Int, platform: Platform = Platform.IOS, deviceName: String = "my device $appId", deviceKind: String = "my device kind $appId" ): OtherClient { return bot.asQQAndroidBot().createOtherClient(OtherClientInfo(appId, platform, deviceName, deviceKind)).also { this.otherClients.delegate.add(it) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/BotInvitedJoinTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.BotJoinGroupEvent import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.network.components.NoticeProcessorPipelineImpl import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class BotInvitedJoinTest : AbstractNoticeProcessorTest() { @Test fun `invited join`() = runBlockingUnit { suspend fun runTest() = use { Structmsg.StructMsg( version = 1, msgType = 2, msgSeq = 1630, msgTime = 1630, reqUin = 1230, msg = Structmsg.SystemMsg( subType = 1, msgTitle = "邀请加群", msgDescribe = "邀请你加入 %group_name%", actions = mutableListOf( Structmsg.SystemMsgAction( name = "拒绝", result = "已拒绝", actionInfo = Structmsg.SystemMsgActionInfo( type = 12, groupCode = 2230203, ), detailName = "拒绝", ), Structmsg.SystemMsgAction( name = "同意", result = "已同意", actionInfo = Structmsg.SystemMsgActionInfo( type = 11, groupCode = 2230203, ), detailName = "同意", ), Structmsg.SystemMsgAction( name = "忽略", result = "已忽略", actionInfo = Structmsg.SystemMsgActionInfo( type = 14, groupCode = 2230203, ), detailName = "忽略", ) ), groupCode = 2230203, actionUin = 1230001, groupMsgType = 2, groupInviterRole = 1, groupInfo = Structmsg.GroupInfo( appPrivilegeFlag = 67698880, ), msgInviteExtinfo = Structmsg.MsgInviteExt( ), reqUinNick = "user3", groupName = "testtest", actionUinNick = "user1", groupExtFlag = 1075905600, actionUinQqNick = "user1", reqUinGender = 255, c2cInviteJoinGroupFlag = 1, ), ) } setBot(1230003) runTest().toList().run { assertEquals(1, size, toString()) get(0).run { assertIs<BotInvitedJoinGroupRequestEvent>(this) assertEquals(1230001, invitorId) assertEquals("user1", invitorNick) assertEquals(2230203, groupId) assertEquals("testtest", groupName) assertEquals(1630, eventId) } } } @Test fun `invited join accepted`() = runBlockingUnit { // https://github.com/mamoe/mirai/issues/1213 suspend fun runTest() = use( createContext = { attributes -> object : NoticeProcessorPipelineImpl.ContextImpl(attributes) { override suspend fun QQAndroidBot.addNewGroupByCode(code: Long): GroupImpl { assertEquals(2230203, code) return bot.addGroup(2230203, 1230001, name = "testtest").apply { addMember(1230003, permission = MemberPermission.MEMBER) addMember(1230001, permission = MemberPermission.OWNER) } } } } ) { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 2230203, toUin = 1230002, msgType = 33, msgSeq = 593, msgTime = 1632, msgUid = 14411518, authUin = 1230002, authNick = "user2", extGroupKeyInfo = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ExtGroupKeyInfo( curMaxSeq = 1652, curTime = 16327, ), authSex = 2, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( msgContent = "00 22 07 BB 01 00 12 C4 B2 03 00 12 C4 B1 06 B4 B4 BD A8 D5 DF 00 30 31 39 37 31 44 32 34 34 31 44 30 42 30 45 41 44 42 35 35 32 46 43 33 31 35 32 36 33 43 45 39 39 43 45 43 43 35 46 30 35 45 32 46 38 39 44 41 33".hexToBytes(), ), ) } setBot(1230002) runTest().toList().run { assertEquals(1, size, toString()) get(0).run { assertIs<BotJoinGroupEvent.Invite>(this) assertEquals(1230001, invitor.id) } } } @Test fun `invitation accepted`() = runBlockingUnit { suspend fun runTest() = use(createContext = { attributes -> object : NoticeProcessorPipelineImpl.ContextImpl(attributes) { override suspend fun QQAndroidBot.addNewGroupByCode(code: Long): GroupImpl { assertEquals(2230203, code) return bot.addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) } } } }) { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 2230203, toUin = 1230003, msgType = 33, msgSeq = 61485, msgTime = 1630, msgUid = 1441, authUin = 1230003, authNick = "user3", extGroupKeyInfo = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ExtGroupKeyInfo( curMaxSeq = 1631, curTime = 1630, ), ), contentHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ContentHead( ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( ), msgContent = "00 22 07 BB 01 00 12 C4 B3 03 00 12 C4 B1 00 00 30 34 32 42 39 44 46 43 34 39 45 42 34 30 46 41 42 45 45 32 33 36 34 37 45 46 39 35 31 44 44 42 31 31 32 36 31 31 38 44 43 46 44 32 37 42 30 42 45".hexToBytes(), ), ) } setBot(1230003) runTest().toList().run { assertEquals(1, size, toString()) get(0).run { assertIs<BotJoinGroupEvent.Invite>(this) assertEquals(1230001, invitor.id) assertEquals(2230203, group.id) } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/FriendNickChangeTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.event.events.FriendNickChangedEvent import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.jce.MsgType0x210 import net.mamoe.mirai.internal.network.protocol.data.jce.OnlinePushPack import net.mamoe.mirai.internal.network.protocol.data.jce.ShareData import net.mamoe.mirai.internal.network.protocol.data.proto.Submsgtype0x27.SubMsgType0x27 import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class FriendNickChangeTest : AbstractNoticeProcessorTest() { @Test fun `nick changed`() = runBlockingUnit { // FriendNickChangedEvent 内容异常 https://github.com/mamoe/mirai/issues/1356 suspend fun runTest() = use { OnlinePushPack.SvcReqPushMsg( uin = 1230002, uMsgTime = 1633037660, vMsgInfos = mutableListOf( MsgInfo( lFromUin = 1230002, shMsgType = 528, shMsgSeq = 142, strMsg = "", uRealMsgTime = 160, vMsg = MsgType0x210( uSubMsgType = 39, vProtobuf = SubMsgType0x27.SubMsgType0x27MsgBody( msgModInfos = mutableListOf( SubMsgType0x27.ForwardBody( opType = 20, msgModProfile = SubMsgType0x27.ModProfile( uin = 1230001, msgProfileInfos = mutableListOf( SubMsgType0x27.ProfileInfo( field = 20002, value = "ABC", ) ), ), ) ), ) .toByteArray(SubMsgType0x27.SubMsgType0x27MsgBody.serializer()), ).toByteArray(), uAppShareID = 0, vMsgCookies = "08 90 04 10 90 84 A0 81 80 80 80 80 02 18 00 20 E3 86 03".hexToBytes(), lMsgUid = 14411, lLastChangeTime = 1, vCPicInfo = mutableListOf(), stShareData = ShareData( ), lFromInstId = 0, vRemarkOfSender = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, strFromMobile = "", strFromName = "", vNickName = mutableListOf(), ) ), svrip = 1273521418, ) } setBot(1230002).apply { addFriend(1230001, nick = "aaa") } runTest().toList().run { assertEquals(1, size, toString()) get(0).run { assertIs<FriendNickChangedEvent>(this) assertEquals(1230001, friend.id) assertEquals("aaa", from) assertEquals("ABC", to) } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/GroupRetrieveTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent import net.mamoe.mirai.event.events.MemberPermissionChangeEvent import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class GroupRetrieveTest : AbstractNoticeProcessorTest() { @Test fun `other member retrieves group from another member when they are in the group`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 27, msgUid = 14411, msgTime = 1629, realMsgTime = 164, msgData = "00 22 07 BB 01 FF 00 12 C4 B1 00 12 C4 B2".hexToBytes(), svrIp = -9623, ) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.OWNER) addMember(1230002, permission = MemberPermission.MEMBER) } runTest().toList().run { assertEquals(2, size, toString()) get(0).run { assertIs<MemberPermissionChangeEvent>(this) assertEquals(1230001, member.id) assertEquals(MemberPermission.OWNER, origin) assertEquals(MemberPermission.MEMBER, new) assertEquals(MemberPermission.MEMBER, group.members[1230001]!!.permission) } get(1).run { assertIs<MemberPermissionChangeEvent>(this) assertEquals(1230002, member.id) assertEquals(MemberPermission.MEMBER, origin) assertEquals(MemberPermission.OWNER, new) assertEquals(MemberPermission.OWNER, group.members[1230002]!!.permission) } } } @Test fun `other member retrieves group from bot when they are in the group`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 459, msgUid = 14411518, msgTime = 1629, realMsgTime = 1629, msgData = "00 22 07 BB 01 FF 00 12 C4 B3 00 12 C4 B2".hexToBytes(), svrIp = -164, ) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.MEMBER) botAsMember.permission = MemberPermission.OWNER } runTest().toList().run { assertEquals(2, size, toString()) get(0).run { assertIs<BotGroupPermissionChangeEvent>(this) assertEquals(MemberPermission.OWNER, origin) assertEquals(MemberPermission.MEMBER, new) assertEquals(MemberPermission.MEMBER, group.botPermission) } get(1).run { assertIs<MemberPermissionChangeEvent>(this) assertEquals(1230002, member.id) assertEquals(MemberPermission.MEMBER, origin) assertEquals(MemberPermission.OWNER, new) assertEquals(MemberPermission.OWNER, group.members[1230002]!!.permission) } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/GroupTransferTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent import net.mamoe.mirai.event.events.MemberPermissionChangeEvent import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class GroupTransferTest : AbstractNoticeProcessorTest() { @Test fun `owner transfers group to other member`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 439, msgUid = 14411520, msgTime = 162974, realMsgTime = 163874, msgData = "00 22 07 BB 01 FF 00 12 C4 B2 00 12 C4 B1".hexToBytes(), svrIp = 194, ) } val group = setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.OWNER) } runTest().toList().run { assertEquals(2, size) get(0).run { assertIs<MemberPermissionChangeEvent>(this) assertEquals(1230002, member.id) assertEquals(MemberPermission.OWNER, origin) assertEquals(MemberPermission.MEMBER, new) assertEquals(MemberPermission.MEMBER, group.members[1230002]!!.permission) } get(1).run { assertIs<MemberPermissionChangeEvent>(this) assertEquals(1230001, member.id) assertEquals(MemberPermission.MEMBER, origin) assertEquals(MemberPermission.OWNER, new) assertEquals(MemberPermission.OWNER, group.members[1230001]!!.permission) } } } @Test fun `owner transfers group to bot`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 291, msgUid = 144115188, msgTime = 16298, realMsgTime = 16298, msgData = "00 22 07 BB 01 FF 00 12 C4 B2 00 12 C4 B3".hexToBytes(), svrIp = -14676, ) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.OWNER) } runTest().toList().run { assertEquals(2, size) get(0).run { assertIs<MemberPermissionChangeEvent>(this) assertEquals(1230002, member.id) assertEquals(MemberPermission.OWNER, origin) assertEquals(MemberPermission.MEMBER, new) assertEquals(MemberPermission.MEMBER, group.members[1230002]!!.permission) } get(1).run { assertIs<BotGroupPermissionChangeEvent>(this) assertEquals(MemberPermission.MEMBER, origin) assertEquals(MemberPermission.OWNER, new) assertEquals(MemberPermission.OWNER, group.botPermission) } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/MemberAdminChangeTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent import net.mamoe.mirai.event.events.MemberPermissionChangeEvent import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs /** * Set/cancel admin permission for bot/member */ internal class MemberAdminChangeTest : AbstractNoticeProcessorTest() { @Test fun `bot member to admin`() = runBlockingUnit { suspend fun runTest() = use { OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 4827, msgUid = 144, msgTime = 162, realMsgTime = 1629, msgData = "00 22 07 BB 01 01 00 12 C4 B3 01".hexToBytes(), svrIp = 12165, ) } // bot was MEMBER val group = setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.OWNER) } runTest().run { assertEquals(1, size) val event = single() assertIs<BotGroupPermissionChangeEvent>(event) assertEquals(MemberPermission.MEMBER, event.origin) assertEquals(MemberPermission.ADMINISTRATOR, event.new) assertEquals(MemberPermission.ADMINISTRATOR, group.botPermission) } // bot was ADMINISTRATOR setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.OWNER) botAsMember.permission = MemberPermission.ADMINISTRATOR } runTest().run { assertEquals(0, size) } } @Test fun `bot admin to member`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 483, msgUid = 14411512, msgTime = 1629863, realMsgTime = 1623063, msgData = "00 22 07 BB 01 00 00 12 C4 B3 00".hexToBytes(), svrIp = 2039273, ) } val group = setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.OWNER) botAsMember.permission = MemberPermission.ADMINISTRATOR } runTest().run { assertEquals(1, size) val event = single() assertIs<BotGroupPermissionChangeEvent>(event) assertEquals(MemberPermission.ADMINISTRATOR, event.origin) assertEquals(MemberPermission.MEMBER, event.new) assertEquals(MemberPermission.MEMBER, group.botPermission) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.OWNER) botAsMember.permission = MemberPermission.MEMBER // already member } runTest().run { assertEquals(0, size) } } @Test fun `member member to admin`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 5639, msgUid = 1441812, msgTime = 1623204, realMsgTime = 1623204, msgData = "00 22 07 BB 01 01 00 12 C4 B1 01".hexToBytes(), svrIp = -20900855, ) } // member was MEMBER val group = setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) addMember(1230002, permission = MemberPermission.OWNER) } runTest().run { assertEquals(1, size) val event = single() assertIs<MemberPermissionChangeEvent>(event) assertEquals(1230001, event.member.id) assertEquals(MemberPermission.MEMBER, event.origin) assertEquals(MemberPermission.ADMINISTRATOR, event.new) assertEquals(MemberPermission.ADMINISTRATOR, group.members[1230001]!!.permission) } // member was already ADMINISTRATOR setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.ADMINISTRATOR) addMember(1230002, permission = MemberPermission.OWNER) } runTest().run { assertEquals(0, size) } } @Test fun `member admin to member`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 44, msgSeq = 745, msgUid = 144115576812, msgTime = 162250, realMsgTime = 16290, msgData = "00 22 07 BB 01 00 00 12 C4 B1 00".hexToBytes(), svrIp = 277969, ) } val group = setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) .permission = MemberPermission.ADMINISTRATOR addMember(1230002, permission = MemberPermission.OWNER) } runTest().run { assertEquals(1, size) val event = single() assertIs<MemberPermissionChangeEvent>(event) assertEquals(1230001, event.member.id) assertEquals(MemberPermission.ADMINISTRATOR, event.origin) assertEquals(MemberPermission.MEMBER, event.new) assertEquals(MemberPermission.MEMBER, group.members[1230001]!!.permission) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER) .permission = MemberPermission.MEMBER // already member addMember(1230002, permission = MemberPermission.OWNER) } runTest().run { assertEquals(0, size) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/MemberJoinTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.* internal class MemberJoinTest : AbstractNoticeProcessorTest() { @Test fun `member actively request join`() = runBlockingUnit { suspend fun runTest() = use { Structmsg.StructMsg( version = 1, msgType = 2, msgSeq = 16300, msgTime = 1630, reqUin = 1230001, msg = Structmsg.SystemMsg( subType = 1, msgTitle = "加群申请", msgDescribe = "申请加入 %group_name%", msgAdditional = "verification message", srcId = 1, subSrcId = 5, actions = mutableListOf( Structmsg.SystemMsgAction( name = "拒绝", result = "已拒绝", actionInfo = Structmsg.SystemMsgActionInfo( type = 12, groupCode = 2230203, ), detailName = "拒绝", ), Structmsg.SystemMsgAction( name = "同意", result = "已同意", actionInfo = Structmsg.SystemMsgActionInfo( type = 11, groupCode = 2230203, ), detailName = "同意", ), Structmsg.SystemMsgAction( name = "忽略", result = "已忽略", actionInfo = Structmsg.SystemMsgActionInfo( type = 14, groupCode = 2230203, ), detailName = "忽略", ) ), groupCode = 2230203, groupMsgType = 1, groupInfo = Structmsg.GroupInfo( appPrivilegeFlag = 67698880, ), groupFlagext3 = 128, reqUinFaceid = 7425, reqUinNick = "user1", groupName = "testtest", groupExtFlag = 1075905600, actionUinQqNick = "user1", reqUinGender = 1, reqUinAge = 19, ), ) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest", botPermission = MemberPermission.ADMINISTRATOR).apply { addMember(1230002, "user2", MemberPermission.OWNER) } runTest().run { assertEquals(1, size) val event = single() assertIs<MemberJoinRequestEvent>(event) assertEquals(1230001, event.fromId) assertEquals(2230203, event.groupId) assertEquals("verification message", event.message) assertEquals("testtest", event.groupName) } } @Test fun `member request accepted by other admin`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 2230203, toUin = 1230003, msgType = 33, msgSeq = 45, msgTime = 16, msgUid = 1441, authUin = 1230001, authNick = "user1", extGroupKeyInfo = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ExtGroupKeyInfo( curMaxSeq = 1628, curTime = 1630, ), authSex = 2, ), contentHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ContentHead( ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( ), msgContent = "00 22 07 BB 01 00 12 C4 B1 02 00 12 C4 B3 06 B9 DC C0 ED D4 B1 00 30 44 38 32 41 43 32 46 33 30 36 46 44 34 35 30 30 36 38 32 46 36 41 38 32 30 31 38 34 41 42 30 43 43 30 32 43 41 33 33 37 41 31 30 38 43 32 36 36".hexToBytes(), ), ) } val group = setBot(1230003) .addGroup(2230203, 1230002, name = "testtest", botPermission = MemberPermission.ADMINISTRATOR).apply { addMember(1230002, "user2", MemberPermission.OWNER) } assertNull(group.members[1230001]) runTest().run { assertEquals(1, size) val event = single() assertIs<MemberJoinEvent.Active>(event) assertEquals(2230203, event.groupId) assertEquals(1230001, event.member.id) assertNotNull(group.members[1230001]) } } @Test fun `member request accepted by bot as admin`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 2230203, toUin = 1230002, msgType = 33, msgSeq = 45576, msgTime = 1640123193, msgUid = 144115188080508961, authUin = 1230003, authNick = "user3", extGroupKeyInfo = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ExtGroupKeyInfo( curMaxSeq = 1773, curTime = 1640123193, ), ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( msgContent = "00 22 07 BB 01 00 12 C4 B3 82 00 12 C4 B2 06 B9 DC C0 ED D4 B1 00 30 30 38 38 32 32 30 42 30 31 35 42 34 42 42 30 32 44 43 38 30 41 38 37 45 45 45 46 38 42 41 37 45 31 43 32 44 37 32 30 43 37 32 41 34 42 31 39 32".hexToBytes(), ), ) } val group = setBot(1230002) .addGroup(2230203, 1230001, name = "testtest", botPermission = MemberPermission.ADMINISTRATOR).apply { addMember(1230001, "user2", MemberPermission.OWNER) addMember(1230002, "bot", MemberPermission.ADMINISTRATOR) } assertNull(group.members[1230003]) runTest().run { assertEquals(1, size) val event = single() assertIs<MemberJoinEvent.Active>(event) assertEquals(2230203, event.groupId) assertEquals(1230003, event.member.id) assertNotNull(group.members[1230003]) } } @Test fun `member request rejected by other admin`() { // There is no corresponding event } @Test fun `member joins directly when group allows anyone`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 2230203, toUin = 1230003, msgType = 33, msgSeq = 45, msgTime = 16, msgUid = 1441, authUin = 1230001, authNick = "user1", extGroupKeyInfo = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ExtGroupKeyInfo( curMaxSeq = 1628, curTime = 1630, ), authSex = 2, ), contentHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ContentHead( ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( ), msgContent = "00 22 07 BB 01 00 12 C4 B1 02 00 12 C4 B3 06 B9 DC C0 ED D4 B1 00 30 44 38 32 41 43 32 46 33 30 36 46 44 34 35 30 30 36 38 32 46 36 41 38 32 30 31 38 34 41 42 30 43 43 30 32 43 41 33 33 37 41 31 30 38 43 32 36 36".hexToBytes(), ), ) } val group = setBot(1230003) .addGroup(2230203, 1230002, name = "testtest", botPermission = MemberPermission.ADMINISTRATOR).apply { addMember(1230002, "user2", MemberPermission.OWNER) } assertNull(group.members[1230001]) runTest().run { assertEquals(1, size) val event = single() assertIs<MemberJoinEvent.Active>(event) assertEquals(2230203, event.groupId) assertEquals(1230001, event.member.id) assertNotNull(group.members[1230001]) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/MemberQuitTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.MemberLeaveEvent import net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans import net.mamoe.mirai.internal.test.runBlockingUnit import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class MemberQuitTest : AbstractNoticeProcessorTest() { @Test fun `member active quit`() = runBlockingUnit { suspend fun runTest() = use { OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230003, msgType = 34, msgSeq = 266, msgUid = 1441151, msgTime = 16298, realMsgTime = 1629, msgData = "00 22 07 BB 01 00 12 C4 B1 02 00 30 39 41 36 36 41 32 31 32 33 35 37 32 43 39 35 38 42 42 36 38 45 32 36 44 34 34 32 38 45 32 32 37 32 36 44 39 44 45 41 31 34 41 44 37 30 31 46 31".hexToBytes(), svrIp = 618, extGroupKeyInfo = OnlinePushTrans.ExtGroupKeyInfo( curMaxSeq = 1626, curTime = 16298, ), generalFlag = 1, ) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, "user1", MemberPermission.MEMBER) addMember(1230002, "user2", MemberPermission.OWNER) } runTest().run { assertEquals(1, size) single().run { assertIs<MemberLeaveEvent.Quit>(this) assertEquals(1230001, member.id) assertEquals(null, group.members[1230001]) } } } @Test fun `member kick`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.PbMsgInfo( fromUin = 2230203, toUin = 1230002, msgType = 34, msgSeq = 430, msgUid = 1441, msgTime = 16298, realMsgTime = 1629, msgData = "00 22 07 BB 01 00 12 C4 B1 03 00 12 C4 B3 06 B4 B4 BD A8 D5 DF 00 30 45 31 39 41 35 43 41 37 34 36 44 37 38 31 36 45 34 46 36 37 41 39 35 36 46 32 34 46 46 38 33 41 32 30 34 44 41 33 38 30 35 41 38 34 39 45 44 32".hexToBytes(), svrIp = 54562, extGroupKeyInfo = net.mamoe.mirai.internal.network.protocol.data.proto.OnlinePushTrans.ExtGroupKeyInfo( curMaxSeq = 1627, curTime = 1629, ), generalFlag = 1, ) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, "user1", MemberPermission.MEMBER) addMember(1230002, "user2", MemberPermission.OWNER) } runTest().run { assertEquals(1, size) single().run { assertIs<MemberLeaveEvent.Kick>(this) assertEquals(1230001, member.id) assertEquals(null, group.members[1230001]) } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/MessageSyncTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.FriendMessageSyncEvent import net.mamoe.mirai.event.events.GroupMessageSyncEvent import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC 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.network.protocol.data.proto.MsgOnlinePush import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.message.data.content import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class MessageSyncTest : AbstractNoticeProcessorTest() { @Test fun `can receive group sync from macOS client`() = runBlockingUnit { suspend fun runTest() = use { MsgOnlinePush.PbPushMsg( msg = MsgComm.Msg( msgHead = MsgComm.MsgHead( fromUin = 1230002, toUin = 1230002, msgType = 82, msgSeq = 1772, msgTime = 1640029614, msgUid = 144115188088832082, groupInfo = MsgComm.GroupInfo( groupCode = 2230203, groupType = 1, groupInfoSeq = 657, groupCard = "user2", groupLevel = 1, groupCardType = 2, groupName = "testtest".toByteArray(), /* 74 65 73 74 74 65 73 74 */ ), fromAppid = 1001, fromInstid = 537067835, userActive = 1, ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( attr = ImMsgBody.Attr( codePage = 0, time = 1640029614, random = 25984994, size = 9, effect = 0, charSet = 134, pitchAndFamily = 0, fontName = "Helvetica", ), elems = mutableListOf( ImMsgBody.Elem( text = ImMsgBody.Text( str = "s", ), ), ImMsgBody.Elem( elemFlags2 = ImMsgBody.ElemFlags2( msgRptCnt = 1, ), ), ImMsgBody.Elem( generalFlags = ImMsgBody.GeneralFlags( pbReserve = "".hexToBytes(), ), ), ImMsgBody.Elem( extraInfo = ImMsgBody.ExtraInfo( nick = "user2", level = 1, groupMask = 1, ), ), ), ), ), ), svrip = 1579509002, generalFlag = 1, ) } setBot(1230002).apply { addGroup(2230203L, 1230001).apply { addMember(1230001, permission = MemberPermission.OWNER) addMember(1230002, permission = MemberPermission.MEMBER) } addOtherClient(537067835) } runTest().toList().run { assertEquals(1, size, toString()) get(0).run { assertIs<GroupMessageSyncEvent>(this) assertEquals(2230203, group.id) assertEquals(1230002, sender.id) assertEquals("s", message.content) } } } @Test fun `can receive friend sync from macOS client`() = runBlockingUnit { suspend fun runTest() = use { attributes[KEY_FROM_SYNC] = true net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 1230002, toUin = 1230001, msgType = 166, c2cCmd = 11, msgSeq = 13887, msgTime = 1640030199, msgUid = 72057594845425959, fromAppid = 1001, fromInstid = 537067835, userActive = 1, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( codePage = 0, time = 1640030199, random = 807498023, size = 9, effect = 0, charSet = 134, pitchAndFamily = 0, fontName = "Helvetica", ), elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "hi", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "78 00 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00".hexToBytes(), ), ), ), ), ), ) } setBot(1230002).apply { addFriend(1230001) addOtherClient(537067835) } runTest().toList().run { assertEquals(1, size, toString()) get(0).run { assertIs<FriendMessageSyncEvent>(this) assertEquals(1230001, friend.id) assertEquals("hi", message.content) } } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/MessageTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import io.ktor.utils.io.core.* import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.FriendMessageEvent import net.mamoe.mirai.event.events.GroupMessageEvent import net.mamoe.mirai.event.events.GroupTempMessageEvent import net.mamoe.mirai.internal.network.components.NoticePipelineContext.Companion.KEY_FROM_SYNC import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.message.data.* import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertIs internal class MessageTest : AbstractNoticeProcessorTest() { @Test fun `group message test`() = runBlockingUnit { suspend fun runTest() = use { net.mamoe.mirai.internal.network.protocol.data.proto.MsgOnlinePush.PbPushMsg( msg = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 1230001, toUin = 1230003, msgType = 82, msgSeq = 1629, // id msgTime = 1630, msgUid = 14411, // neither id nor internalId groupInfo = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.GroupInfo( groupCode = 2230203, groupType = 1, groupInfoSeq = 626, groupCard = "user1", groupLevel = 1, groupCardType = 2, groupName = "testtest".toByteArray(), /* 74 65 73 74 74 65 73 74 */ ), fromAppid = 1, fromInstid = 1, userActive = 1, ), contentHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ContentHead( pkgNum = 1, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( codePage = 0, time = 1630, random = -1469, // internal id size = 12, effect = 0, charSet = 134, pitchAndFamily = 34, fontName = "微软雅黑", ), elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "hello", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( elemFlags2 = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ElemFlags2( msgRptCnt = 1, ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( glamourLevel = 3, pbReserve = "08 0A 20 CB 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 D1 02 A0 03 40 B0 03 00 C0 03 00 D0 03 00 E8 03 00 90 04 80 01 B8 04 02 C0 04 00 CA 04 00 F8 04 00 88 05 00".hexToBytes(), ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( nick = "user1", level = 1, groupMask = 3, ), ) ), ), ), ), svrip = 2057, generalFlag = 1, ) } setBot(1230003) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER, nick = "user1") } runTest().run { assertEquals(1, size) val event = single() assertIs<GroupMessageEvent>(event) assertEquals(1630, event.time) assertEquals(1230001, event.sender.id) assertEquals("user1", event.senderName) assertEquals("hello", event.message.content) event.message.run { assertEquals(2, size) get(0).run { assertIs<OnlineMessageSource.Incoming.FromGroup>(this) assertContentEquals(intArrayOf(1629), ids) assertContentEquals(intArrayOf(-1469), internalIds) assertEquals(1630, time) assertEquals(1230001, fromId) assertEquals(2230203, targetId) assertEquals(event.message.filterNot { it is MessageSource }.toMessageChain(), originalMessage) } assertIs<PlainText>(get(1)) assertEquals("hello", get(1).content) } } } @Test fun `friend message test`() = runBlockingUnit { suspend fun runTest() = use(KEY_FROM_SYNC to false) { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 1230001, toUin = 1230003, msgType = 166, c2cCmd = 11, msgSeq = 13985, msgTime = 1630, msgUid = 72057, wseqInC2cMsghead = 25159, ), contentHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ContentHead( pkgNum = 1, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( codePage = 0, time = 1630, random = -5872, size = 12, effect = 0, charSet = 134, pitchAndFamily = 34, fontName = "微软雅黑", ), elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "123", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( elemFlags2 = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ElemFlags2( ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00".hexToBytes(), ), ) ), ), ), ) } setBot(1230003) .addFriend(1230001, "user1") runTest().run { assertEquals(1, size) val event = single() assertIs<FriendMessageEvent>(event) assertEquals(1630, event.time) assertEquals(1230001, event.sender.id) assertEquals("user1", event.senderName) assertEquals("123", event.message.content) event.message.run { assertEquals(2, size) get(0).run { assertIs<OnlineMessageSource.Incoming.FromFriend>(this) assertContentEquals(intArrayOf(13985), ids) assertContentEquals(intArrayOf(-5872), internalIds) assertEquals(1630, time) assertEquals(1230001, fromId) assertEquals(1230003, targetId) assertEquals(event.message.filterNot { it is MessageSource }.toMessageChain(), originalMessage) } assertIs<PlainText>(get(1)) assertEquals("123", get(1).content) } } } @Test fun `group temp message test`() = runBlockingUnit { suspend fun runTest() = use(KEY_FROM_SYNC to false) { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 1230001, toUin = 1230003, msgType = 141, c2cCmd = 11, msgSeq = 11080, msgTime = 1630, msgUid = 720, c2cTmpMsgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.C2CTmpMsgHead( c2cType = 2, groupUin = 2230203, groupCode = 2230203, sig = "38 59 CD 1E 22 9A 0A BA 28 59 46 BE FA 51 36 D0 F1 7A 5D 54 F5 04 05 7E 66 C7 36 4F 73 BF 45 96 00 39 7C 8F F5 43 57 74 B0 EB D9 5E 0F 1F 9B CF".hexToBytes(), ), wseqInC2cMsghead = 25160, ), contentHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ContentHead( pkgNum = 1, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( codePage = 0, time = 1630, random = 1854, size = 12, effect = 0, charSet = 134, pitchAndFamily = 34, fontName = "微软雅黑", ), elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "hello", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( nick = "user1", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( elemFlags2 = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ElemFlags2( ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00".hexToBytes(), ), ) ), ), ), ) } setBot(1230003).apply { addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER, nick = "user1") } addStranger(1230001, "user1", fromGroupId = 2230203) } runTest().run { assertEquals(1, size) val event = single() assertIs<GroupTempMessageEvent>(event) assertEquals(1630, event.time) assertEquals(1230001, event.sender.id) assertEquals("user1", event.senderName) assertEquals("hello", event.message.content) event.message.run { assertEquals(2, size) get(0).run { assertIs<OnlineMessageSource.Incoming.FromTemp>(this) assertContentEquals(intArrayOf(11080), ids) assertContentEquals(intArrayOf(1854), internalIds) assertEquals(1630, time) assertEquals(1230001, fromId) assertEquals(1230003, targetId) assertEquals(event.message.filterNot { it is MessageSource }.toMessageChain(), originalMessage) } assertIs<PlainText>(get(1)) assertEquals("hello", get(1).content) } } } // for #1410 @Test fun `group temp message test for issue 1410`() = runBlockingUnit { suspend fun runTest() = use(KEY_FROM_SYNC to false) { net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.Msg( msgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.MsgHead( fromUin = 1230001, toUin = 1230003, msgType = 141, c2cCmd = 11, msgSeq = 11080, msgTime = 1630, msgUid = 720, c2cTmpMsgHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.C2CTmpMsgHead( c2cType = 2, groupUin = 2055561833, groupCode = 112561833, sig = "38 59 CD 1E 22 9A 0A BA 28 59 46 BE FA 51 36 D0 F1 7A 5D 54 F5 04 05 7E 66 C7 36 4F 73 BF 45 96 00 39 7C 8F F5 43 57 74 B0 EB D9 5E 0F 1F 9B CF".hexToBytes(), ), wseqInC2cMsghead = 25160, ), contentHead = net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm.ContentHead( pkgNum = 1, ), msgBody = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.MsgBody( richText = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.RichText( attr = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Attr( codePage = 0, time = 1630, random = 1854, size = 12, effect = 0, charSet = 134, pitchAndFamily = 34, fontName = "微软雅黑", ), elems = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( text = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Text( str = "hello", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( extraInfo = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ExtraInfo( nick = "user1", ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( elemFlags2 = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.ElemFlags2( ), ), net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.Elem( generalFlags = net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody.GeneralFlags( pbReserve = "80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 CA 04 00".hexToBytes(), ), ) ), ), ), ) } setBot(1230003).apply { addGroup(112561833, 1230002, name = "testtest").apply { addMember(1230001, permission = MemberPermission.MEMBER, nick = "user1") } addStranger(1230001, "user1", fromGroupId = 2230203) } runTest().run { assertEquals(1, size) val event = single() assertIs<GroupTempMessageEvent>(event) assertEquals(1630, event.time) assertEquals(1230001, event.sender.id) assertEquals("user1", event.senderName) assertEquals("hello", event.message.content) event.message.run { assertEquals(2, size) get(0).run { assertIs<OnlineMessageSource.Incoming.FromTemp>(this) assertContentEquals(intArrayOf(11080), ids) assertContentEquals(intArrayOf(1854), internalIds) assertEquals(1630, time) assertEquals(1230001, fromId) assertEquals(1230003, targetId) assertEquals(event.message.filterNot { it is MessageSource }.toMessageChain(), originalMessage) } assertIs<PlainText>(get(1)) assertEquals("hello", get(1).content) } } } @Test fun `stranger message test`() { // TODO: 2021/8/27 cannot start a such conversation } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/MuteTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.BotMuteEvent import net.mamoe.mirai.event.events.BotUnmuteEvent import net.mamoe.mirai.event.events.MemberMuteEvent import net.mamoe.mirai.event.events.MemberUnmuteEvent import net.mamoe.mirai.internal.network.protocol.data.jce.MsgInfo import net.mamoe.mirai.internal.network.protocol.data.jce.OnlinePushPack import net.mamoe.mirai.internal.network.protocol.data.jce.ShareData import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class MuteTest : AbstractNoticeProcessorTest() { @Test fun `bot mute`() = runBlockingUnit { suspend fun MuteTest.runTest() = use { OnlinePushPack.SvcReqPushMsg( uin = 1230001, uMsgTime = 1629868940, vMsgInfos = mutableListOf( MsgInfo( lFromUin = 2230203, shMsgType = 732, shMsgSeq = 8352, strMsg = "", uRealMsgTime = 16298, vMsg = "00 22 07 BB 0C 01 00 12 C4 B2 61 25 D3 8D 00 01 00 12 C4 B1 00 00 02 58".hexToBytes(), uAppShareID = 0, vMsgCookies = "08 DC 05 10 DC 85 E0 80 80 80 80 80 02 18 03 20 DE 86 03".hexToBytes(), lMsgUid = 1441151, lLastChangeTime = 1, vCPicInfo = mutableListOf(), stShareData = ShareData( ), lFromInstId = 0, vRemarkOfSender = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, strFromMobile = "", strFromName = "", vNickName = mutableListOf(), ) ), svrip = -1467, vSyncCookie = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, vUinPairMsg = mutableListOf(), mPreviews = mutableMapOf( ), ) } setBot(1230001) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230002, "user2", MemberPermission.OWNER) } runTest().run { assertEquals(1, size) val event = single() assertIs<BotMuteEvent>(event) assertEquals(600, event.durationSeconds) assertEquals(1230002, event.operator.id) } } @Test fun `bot unmute`() = runBlockingUnit { suspend fun MuteTest.runTest() = use { OnlinePushPack.SvcReqPushMsg( uin = 1230001, uMsgTime = 1629869459, vMsgInfos = mutableListOf( MsgInfo( lFromUin = 2230203, shMsgType = 732, shMsgSeq = -26716, strMsg = "", uRealMsgTime = 1629, vMsg = "00 22 07 BB 0C 01 00 12 C4 B2 61 25 D5 93 00 01 00 12 C4 B1 00 00 00 00".hexToBytes(), uAppShareID = 0, vMsgCookies = "08 DC 05 10 DC 85 E0 80 80 80 80 80 02 18 03 20 DE 86 03".hexToBytes(), lMsgUid = 1441151, lLastChangeTime = 1, vCPicInfo = mutableListOf(), stShareData = ShareData( ), lFromInstId = 0, vRemarkOfSender = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, strFromMobile = "", strFromName = "", vNickName = mutableListOf(), ) ), svrip = 1554, vSyncCookie = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, vUinPairMsg = mutableListOf(), mPreviews = mutableMapOf( ), ) } setBot(1230001) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230002, "user2", MemberPermission.OWNER) addMember(1230003, "user3", MemberPermission.MEMBER) botAsMember.apply { _muteTimestamp = currentTimeSeconds().toInt() + 600 } } runTest().run { assertEquals(1, size) val event = single() assertIs<BotUnmuteEvent>(event) assertEquals(1230002, event.operator.id) } setBot(1230001) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230002, "user2", MemberPermission.OWNER) addMember(1230003, "user3", MemberPermission.MEMBER) botAsMember.apply { _muteTimestamp = 0 } } runTest().run { assertEquals(0, size) } } @Test fun `member mute`() = runBlockingUnit { suspend fun MuteTest.runTest() = use { OnlinePushPack.SvcReqPushMsg( uin = 1230001, uMsgTime = 1629870209, vMsgInfos = mutableListOf( MsgInfo( lFromUin = 2230203, shMsgType = 732, shMsgSeq = 8159, strMsg = "", uRealMsgTime = 16298, vMsg = "00 22 07 BB 0C 01 00 12 C4 B2 61 25 D8 81 00 01 00 12 C4 B3 00 00 02 58".hexToBytes(), uAppShareID = 0, vMsgCookies = "08 DC 05 10 DC 85 E0 80 80 80 80 80 02 18 03 20 DE 86 03".hexToBytes(), lMsgUid = 1441151, lLastChangeTime = 1, vCPicInfo = mutableListOf(), stShareData = ShareData( ), lFromInstId = 0, vRemarkOfSender = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, strFromMobile = "", strFromName = "", vNickName = mutableListOf(), ) ), svrip = -176, vSyncCookie = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, vUinPairMsg = mutableListOf(), mPreviews = mutableMapOf( ), ) } setBot(1230001) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230002, "user2", MemberPermission.OWNER) addMember(1230003, "user3", MemberPermission.MEMBER) } runTest().run { assertEquals(1, size) val event = single() assertIs<MemberMuteEvent>(event) assertEquals(600, event.durationSeconds) assertEquals(1230002, event.operator?.id) } } @Test fun `member unmute`() = runBlockingUnit { suspend fun MuteTest.runTest() = use { OnlinePushPack.SvcReqPushMsg( uin = 1230001, uMsgTime = 16298, vMsgInfos = mutableListOf( MsgInfo( lFromUin = 2230203, shMsgType = 732, shMsgSeq = 16929, strMsg = "", uRealMsgTime = 16298, vMsg = "00 22 07 BB 0C 01 00 12 C4 B2 61 25 D7 02 00 01 00 12 C4 B3 00 00 00 00".hexToBytes(), uAppShareID = 0, vMsgCookies = "08 DC 05 10 DC 85 E0 80 80 80 80 80 02 18 03 20 DE 86 03".hexToBytes(), lMsgUid = 1441151, lLastChangeTime = 1, vCPicInfo = mutableListOf(), stShareData = ShareData( ), lFromInstId = 0, vRemarkOfSender = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, strFromMobile = "", strFromName = "", vNickName = mutableListOf(), ) ), svrip = 20406, vSyncCookie = net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY, vUinPairMsg = mutableListOf(), mPreviews = mutableMapOf( ), ) } setBot(1230001) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230002, "user2", MemberPermission.OWNER) addMember(1230003, "user3", MemberPermission.MEMBER).apply { _muteTimestamp = currentTimeSeconds().toInt() + 600 } } runTest().run { assertEquals(1, size) val event = single() assertIs<MemberUnmuteEvent>(event) assertEquals(1230002, event.operator?.id) } setBot(1230001) .addGroup(2230203, 1230002, name = "testtest").apply { addMember(1230002, "user2", MemberPermission.OWNER) addMember(1230003, "user3", MemberPermission.MEMBER).apply { _muteTimestamp = 0 } } runTest().run { assertEquals(0, size) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/notice/processors/RecallTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.notice.processors import io.ktor.utils.io.core.* import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.message.source.OnlineMessageSourceFromGroupImpl 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.network.protocol.packet.chat.PbMessageSvc import net.mamoe.mirai.internal.test.runBlockingUnit import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.utils.hexToBytes import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal class RecallTest : AbstractNoticeProcessorTest() { fun source(b: Bot, senderid: Long, groupid: Long, permision: MemberPermission): OnlineMessageSource = OnlineMessageSourceFromGroupImpl( b, listOf( MsgComm.Msg( msgHead = MsgComm.MsgHead( fromUin = senderid, toUin = b.id, msgType = 82, msgSeq = 1628, msgTime = 1629, msgUid = 1441, groupInfo = MsgComm.GroupInfo( groupCode = groupid, groupType = 1, groupInfoSeq = 624, groupCard = "user3", groupLevel = 1, groupCardType = 2, groupName = "testtest".toByteArray(), /* 74 65 73 74 74 65 73 74 */ ), fromAppid = 1, fromInstid = 1, userActive = 1, ), contentHead = MsgComm.ContentHead( pkgNum = 1, ), msgBody = ImMsgBody.MsgBody( richText = ImMsgBody.RichText( attr = ImMsgBody.Attr( codePage = 0, time = 162, random = -313, size = 9, effect = 0, charSet = 134, pitchAndFamily = 0, fontName = "微软雅黑", ), elems = mutableListOf( ImMsgBody.Elem( text = ImMsgBody.Text( str = "123123123", ), ), ImMsgBody.Elem( elemFlags2 = ImMsgBody.ElemFlags2( msgRptCnt = 1, ), ), ImMsgBody.Elem( generalFlags = ImMsgBody.GeneralFlags( pbReserve = "08 01 20 CB 50 80 01 01 C8 01 00 F0 01 00 F8 01 00 90 02 00 98 03 00 A0 03 00 B0 03 00 C0 03 00 D0 03 00 E8 03 00 8A 04 02 10 02 90 04 80 01 B8 04 00 C0 04 00 CA 04 00 F8 04 00 88 05 00".hexToBytes(), ), ), ImMsgBody.Elem( extraInfo = ImMsgBody.ExtraInfo( nick = "user3", level = permision.level, groupMask = 1, ), ) ), ), ), ) ) ) override fun setBot(id: Long): QQAndroidBot { return super.setBot(id).also { bot -> runBlockingUnit { bot.login() } network.addPacketReplier { assertEquals("PbMessageSvc.PbMsgWithDraw", it.commandName) reply(PbMessageSvc.PbMsgWithDraw.Response.Success) } } } @Test fun `recall member message without permission`() = runBlockingUnit { val bot = setBot(2) val group = bot.addGroup(5, 3, MemberPermission.MEMBER).apply { // owner addMember(3, permission = MemberPermission.OWNER) // sender addMember(1, permission = MemberPermission.MEMBER) } assertFailsWith<PermissionDeniedException> { Mirai.recallMessage(bot, source(bot, 1, group.id, group.botPermission)) } } @Test fun `recall member message`() = runBlockingUnit { val bot = setBot(2) val group = bot.addGroup(5, 3, MemberPermission.ADMINISTRATOR).apply { // owner addMember(3, permission = MemberPermission.OWNER) // sender addMember(1, permission = MemberPermission.MEMBER) } Mirai.recallMessage(bot, source(bot, 1, group.id, group.botPermission)) } @Test fun `recall administrator message`() = runBlockingUnit { val bot = setBot(2) val group = bot.addGroup(5, 3, MemberPermission.ADMINISTRATOR).apply { // owner addMember(3, permission = MemberPermission.OWNER) // sender addMember(1, permission = MemberPermission.ADMINISTRATOR) } assertFailsWith<PermissionDeniedException> { Mirai.recallMessage(bot, source(bot, 1, group.id, group.botPermission)) } } @Test fun `recall administrator message as owner`() = runBlockingUnit { val bot = setBot(2) val group = bot.addGroup(5, 2, MemberPermission.OWNER).apply { // sender addMember(1, permission = MemberPermission.ADMINISTRATOR) } Mirai.recallMessage(bot, source(bot, 1, group.id, group.botPermission)) } @Test fun `recall owner message`() = runBlockingUnit { val bot = setBot(2) val group = bot.addGroup(5, 1, MemberPermission.ADMINISTRATOR).apply { // sender addMember(1, permission = MemberPermission.OWNER) } assertFailsWith<PermissionDeniedException> { Mirai.recallMessage(bot, source(bot, 1, group.id, group.botPermission)) } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/samples/CustomMessageSamples.kt ================================================ /* * Copyright 2019-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("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE") package samples import kotlinx.serialization.Serializable import net.mamoe.mirai.message.data.CustomMessage import net.mamoe.mirai.message.data.CustomMessageMetadata /** * 定义一个自定义消息类型. * 在消息链中加入这个元素, 即可像普通元素一样发送和接收 (自动解析). */ @Serializable data class CustomMessageIdentifier( val identifier1: Long, val custom: String ) : CustomMessageMetadata() { // 可使用 JsonSerializerFactory 或 ProtoBufSerializerFactory companion object Factory : CustomMessage.ProtoBufSerializerFactory<CustomMessageIdentifier>( "myMessage.CustomMessageIdentifier" ) override fun getFactory(): Factory = Factory } ================================================ FILE: mirai-core/src/commonTest/kotlin/test/events.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.test import kotlinx.coroutines.ExperimentalCoroutinesApi import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.utils.ConcurrentLinkedQueue import net.mamoe.mirai.utils.cast import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.test.assertEquals internal inline fun <reified T : Event, R> assertEventBroadcasts(times: Int = 1, block: () -> R): R { assertEventBroadcasts<T>(times) { return block() } } internal inline fun <reified T : Event> assertEventBroadcasts(times: Int = 1, block: () -> Unit): List<T> { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } val receivedEvents = ConcurrentLinkedQueue<Event>() val listener = GlobalEventChannel.subscribeAlways<Event> { event -> receivedEvents.add(event) } try { block() } finally { listener.complete() } if (times < 0) return receivedEvents.filterIsInstance<T>().cast() val actual = receivedEvents.filterIsInstance<T>().count() assertEquals( times, actual, "Expected event ${T::class.simpleName} broadcast $times time(s). " + "But actual count is ${actual}. " + "\nAll received events: ${receivedEvents.joinToString(", ", "[", "]")}" ) return receivedEvents.filterIsInstance<T>().cast() } @OptIn(ExperimentalCoroutinesApi::class) internal inline fun <R> assertEventNotBroadcast(block: () -> R): R { return assertEventBroadcasts<Event, R>(0, block) } ================================================ FILE: mirai-core/src/commonTest/kotlin/test/initPlatform.common.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.test import kotlinx.coroutines.CoroutineDispatcher import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.utils.setSystemProp /** * All test classes should inherit from [AbstractTest] */ expect abstract class AbstractTest() { // public, can be used in other modules fun borrowSingleThreadDispatcher(): CoroutineDispatcher companion object } internal fun initializeTestCommon() { setSystemProp("mirai.network.packet.logger", "true") setSystemProp("mirai.network.state.observer.logging", "true") setSystemProp("mirai.network.show.all.components", "true") setSystemProp("mirai.network.show.components.creation.stacktrace", "true") setSystemProp("mirai.network.handler.selector.logging", "true") Exception() // creates an exception to load relevant classes to estimate invocation time of test cases more accurately. IMirai::class.simpleName // similarly, load classes. Mirai // load services } ================================================ FILE: mirai-core/src/commonTest/kotlin/test/printing.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") package test import io.ktor.utils.io.core.* import net.mamoe.mirai.IMirai import net.mamoe.mirai.utils.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract val DebugLogger: MiraiLogger = MiraiLogger.Factory.create(IMirai::class, "Packet Debug") internal inline fun ByteArray.debugPrintThis(name: String): ByteArray { DebugLogger.debug(name + "=" + this.toUHexString()) return this } internal inline fun <R> Input.debugIfFail( name: String = "", onFail: (ByteArray) -> ByteReadPacket = { it.toReadPacket() }, block: ByteReadPacket.() -> R ): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) callsInPlace(onFail, InvocationKind.UNKNOWN) } ByteArrayPool.useInstance { val count = this.readAvailable(it) try { return it.toReadPacket(0, count).withUse(block) } catch (e: Throwable) { onFail(it.take(count).toByteArray()).readAvailable(it) DebugLogger.debug("Error in ByteReadPacket $name=" + it.toUHexString(offset = 0, length = count)) throw e } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/test/utils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.test import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import kotlinx.coroutines.withTimeout import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.time.Duration fun runBlockingUnit( context: CoroutineContext = EmptyCoroutineContext, block: suspend TestScope.() -> Unit ) = runTest(context) { block() } fun runBlockingUnit( context: CoroutineContext = EmptyCoroutineContext, timeout: Duration, block: suspend TestScope.() -> Unit ) { runTest(context, dispatchTimeoutMs = timeout.inWholeMilliseconds) { withTimeout(timeout) { block() } } } ================================================ FILE: mirai-core/src/commonTest/kotlin/testFramework/DebugProbes.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job /** * Mirror of kotlinx-coroutines-debug to be used in common sources. */ @Suppress("RedundantSetter", "RedundantGetter") expect object DebugProbes { /** * Whether coroutine creation stack traces should be sanitized. * Sanitization removes all frames from `kotlinx.coroutines` package except * the first one and the last one to simplify diagnostic. */ var sanitizeStackTraces: Boolean get set /** * Whether coroutine creation stack traces should be captured. * When enabled, for each created coroutine a stack trace of the current * thread is captured and attached to the coroutine. * This option can be useful during local debug sessions, but is recommended * to be disabled in production environments to avoid stack trace dumping overhead. */ var enableCreationStackTraces: Boolean get set /** * Determines whether debug probes were [installed][DebugProbes.install]. */ val isInstalled: Boolean get /** * Installs a [DebugProbes] instead of no-op stdlib probes by redefining * debug probes class using the same class loader as one loaded [DebugProbes] class. */ fun install() /** * Uninstall debug probes. */ fun uninstall() /** * Invokes given block of code with installed debug probes and uninstall probes in the end. */ inline fun withDebugProbes(block: () -> Unit) /** * Returns string representation of the coroutines [job] hierarchy with additional debug information. * Hierarchy is printed from the [job] as a root transitively to all children. */ fun jobToString(job: Job): String /** * Returns string representation of all coroutines launched within the given [scope]. * Throws [IllegalStateException] if the scope has no a job in it. */ fun scopeToString(scope: CoroutineScope): String /** * Prints [job] hierarchy representation from [jobToString] to the given [out]. */ public fun printJob(job: Job): Unit /** * Prints all coroutines launched within the given [scope]. * Throws [IllegalStateException] if the scope has no a job in it. */ public fun printScope(scope: CoroutineScope): Unit /** * Returns all existing coroutines info. * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation. */ // public fun dumpCoroutinesInfo(): List<CoroutineInfo> /** * Dumps all active coroutines into the given output stream, providing a consistent snapshot of all existing coroutines at the moment of invocation. * The output of this method is similar to `jstack` or a full thread dump. It can be used as the replacement to * "Dump threads" action. * * Example of the output: * ``` * Coroutines dump 2018/11/12 19:45:14 * * Coroutine "coroutine#42":StandaloneCoroutine{Active}@58fdd99, state: SUSPENDED * at MyClass$awaitData.invokeSuspend(MyClass.kt:37) * (Coroutine creation stacktrace) * at MyClass.createIoRequest(MyClass.kt:142) * at MyClass.fetchData(MyClass.kt:154) * at MyClass.showData(MyClass.kt:31) * ... * ``` */ fun dumpCoroutines(): Unit } ================================================ FILE: mirai-core/src/commonTest/kotlin/testFramework/DynamicTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("DynamicTestKt_common") package net.mamoe.mirai.internal.testFramework import kotlin.jvm.JvmName import kotlin.test.Test /** * Annotates a function to be a [Test] factory that returns a [DynamicTestsResult]. * * On JVM, this delegates to JUnit's `TestFactory`. On Native, test functions are executed in-place. * * Tips: To run [TestFactory]s in IDEA, you can press `Ctrl + Shift + R`(for both macOS and Windows) with caret in the test function. */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) expect annotation class TestFactory() /** * @see dynamicTest */ expect open class DynamicTest // = junit.DynamicTest /** * @see runDynamicTests */ expect class DynamicTestsResult /** * Creates a dynamic test */ expect fun dynamicTest(displayName: String, action: () -> Unit): DynamicTest /** * The returned value must be returned from the function annotated with [TestFactory], otherwise the tests won't be executed on some platforms. */ expect fun runDynamicTests(dynamicTests: List<DynamicTest>): DynamicTestsResult /** * The returned value must be returned from the function annotated with [TestFactory], otherwise the tests won't be executed on some platforms. */ fun runDynamicTests(vararg dynamicTests: Iterable<DynamicTest>): DynamicTestsResult = runDynamicTests(dynamicTests = dynamicTests.flatMap { it }) /** * The returned value must be returned from the function annotated with [TestFactory], otherwise the tests won't be executed on some platforms. */ fun runDynamicTests(vararg dynamicTests: Sequence<DynamicTest>): DynamicTestsResult = runDynamicTests(dynamicTests = dynamicTests.flatMap { it }) /** * The returned value must be returned from the function annotated with [TestFactory], otherwise the tests won't be executed on some platforms. */ fun runDynamicTests(vararg dynamicTests: DynamicTest): DynamicTestsResult = runDynamicTests(dynamicTests.toList()) /** * The returned value must be returned from the function annotated with [TestFactory], otherwise the tests won't be executed on some platforms. */ fun runDynamicTests(dynamicTests: Sequence<DynamicTest>): DynamicTestsResult = runDynamicTests(dynamicTests.toList()) ================================================ FILE: mirai-core/src/commonTest/kotlin/testFramework/Platform.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework expect fun currentPlatform(): Platform enum class PlatformRuntime { JVM, DALVIK, } // see `@DisabledOnPlatform` sealed class Platform( val runtime: PlatformRuntime, ) { sealed class JvmLike(runtime: PlatformRuntime) : Platform(runtime) sealed class Android(runtime: PlatformRuntime) : JvmLike(runtime) sealed class AndroidUnitTest : Android(PlatformRuntime.JVM) object AndroidUnitTestWithJdk : AndroidUnitTest() object AndroidUnitTestWithAdk : AndroidUnitTest() object AndroidInstrumentedTest : Android(PlatformRuntime.DALVIK) object Jvm : JvmLike(PlatformRuntime.JVM) } ================================================ FILE: mirai-core/src/commonTest/kotlin/testFramework/message/TestMessageSourceSequenceIdAwaiter.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.message import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Deferred import net.mamoe.mirai.internal.message.source.MessageSourceSequenceIdAwaiter import net.mamoe.mirai.internal.message.source.OnlineMessageSourceToGroupImpl internal class TestMessageSourceSequenceIdAwaiter : MessageSourceSequenceIdAwaiter() { override fun getSequenceIdAsync( sourceToGroupImpl: OnlineMessageSourceToGroupImpl, coroutineScope: CoroutineScope ): Deferred<IntArray?> { return CompletableDeferred(value = null) // assuming server didn't provide response, just to be simpler } } ================================================ FILE: mirai-core/src/commonTest/kotlin/testFramework/package.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework ================================================ FILE: mirai-core/src/commonTest/kotlin/testFramework/rules/DisabledOnJvmLikePlatform.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.rules import net.mamoe.mirai.internal.testFramework.Platform import kotlin.reflect.KClass /** * 在 commonTest 使用, 以在目标平台忽略这个 test. * * 此注解只对 JVM 和 Android 平台生效. * 要在 native 平台忽略 test, 请手动写 `if (currentPlatform() is Native) return`. */ @OptIn(ExperimentalMultiplatform::class) @OptionalExpectation expect annotation class DisabledOnJvmLikePlatform( vararg val values: KClass<out Platform.JvmLike> // don't read this property ) ================================================ FILE: mirai-core/src/commonTest/kotlin/utils/FileSystemTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.internal.test.AbstractTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal class FileSystemTest : AbstractTest() { private val fs = FileSystem @Test fun testLegitimacy() { fs.checkLegitimacy("a") assertFailsWith<IllegalArgumentException> { fs.checkLegitimacy("a:") } assertFailsWith<IllegalArgumentException> { fs.checkLegitimacy("?a") } } @Test fun testNormalize() { assertEquals("/", fs.normalize("/")) assertEquals("/", fs.normalize("\\")) assertEquals("/foo", fs.normalize("/foo")) assertEquals("/foo", fs.normalize("\\foo")) assertEquals("foo", fs.normalize("foo")) assertEquals("foo/", fs.normalize("foo/")) assertEquals("/bar", fs.normalize("\\foo", "/bar")) assertEquals("/foo/bar", fs.normalize("\\foo", "bar")) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/utils/crypto/AESTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import net.mamoe.mirai.utils.currentTimeMillis import net.mamoe.mirai.utils.getRandomString import net.mamoe.mirai.utils.toUHexString import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals internal class AESTest { @Test fun `can do crypto`() { val random = Random(currentTimeMillis()) val key = getRandomString(16, random).encodeToByteArray() val iv = getRandomString(16, random).encodeToByteArray() val currentTime = currentTimeMillis() val plainText = buildString { append("Use of this source code is governed by the GNU AGPLv3 license ") append("that can be found through the following link. ") append(currentTime) } println("AES crypto test: key = ${key.toUHexString()}, iv = ${iv.toUHexString()}, currentTimeMillis = $currentTime") val encrypted = aesEncrypt(plainText.encodeToByteArray(), iv, key) val decrypted = aesDecrypt(encrypted, iv, key) assertEquals(plainText, decrypted.decodeToString()) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/utils/crypto/EcdhTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.toUHexString import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals internal class EcdhTest : AbstractTest() { @Test fun `can generate key pair`() { val alice = Ecdh.Instance.generateKeyPair() val bob = Ecdh.Instance.generateKeyPair() val aliceSecret = Ecdh.Instance.calculateShareKey(bob.public, alice.private) val bobSecret = Ecdh.Instance.calculateShareKey(alice.public, bob.private) println(aliceSecret.toUHexString()) assertContentEquals(aliceSecret, bobSecret) } @Test fun `can export and import public keys`() { val alice = Ecdh.Instance.generateKeyPair() println(alice) val publicKey = Ecdh.Instance.exportPublicKey(alice.public) println(publicKey.toUHexString()) assertEquals(0x04, publicKey.first()) val importedAlicePubKey = Ecdh.Instance.importPublicKey(publicKey) assertEquals(alice.public, importedAlicePubKey) } /* EC_KEY *alice = create_key(); EC_KEY *bob = create_key(); assert(alice != NULL && bob != NULL); const EC_POINT *alice_public = EC_KEY_get0_public_key(alice); const EC_POINT *bob_public = EC_KEY_get0_public_key(bob); size_t alice_secret_len; size_t bob_secret_len; unsigned char *alice_secret = get_secret(alice, bob_public, &alice_secret_len); unsigned char *bob_secret = get_secret(bob, alice_public, &bob_secret_len); assert(alice_secret != NULL && bob_secret != NULL && alice_secret_len == bob_secret_len); for (int i = 0; i < alice_secret_len; i++) assert(alice_secret[i] == bob_secret[i]); EC_KEY_free(alice); EC_KEY_free(bob); OPENSSL_free(alice_secret); OPENSSL_free(bob_secret); return 0; */ } ================================================ FILE: mirai-core/src/commonTest/kotlin/utils/crypto/RSATest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import net.mamoe.mirai.internal.testFramework.* import net.mamoe.mirai.internal.testFramework.rules.DisabledOnJvmLikePlatform import kotlin.math.pow import kotlin.test.Test import kotlin.test.assertEquals @DisabledOnJvmLikePlatform(Platform.AndroidUnitTest::class) class RSATest { @TestFactory fun `can generate key pair`(): DynamicTestsResult { return runDynamicTests(buildList { repeat(4) { exp -> val keySize = 2.0.pow(9 + exp).toInt() add(dynamicTest("RSAKeyGenLength$keySize") { val rsaKeyPair = generateRSAKeyPair(keySize) println("RSA keygen test #${exp + 1}: keySize = $keySize") println(rsaKeyPair.plainPubPemKey) println(rsaKeyPair.plainPrivPemKey) }) } }) } @Test fun `can do crypto with generated key`() { val keyPair = generateRSAKeyPair(2048) val plainText = buildString { append("Use of this source code is governed by the GNU AGPLv3 license ") append("that can be found through the following link. ") } println( "RSA crypto test: plainTextLength: ${plainText.length}, " + "pubKey = ${keyPair.plainPubPemKey}, " + "privKey = ${keyPair.plainPrivPemKey}" ) val enc = rsaEncryptWithX509PubKey(plainText.encodeToByteArray(), keyPair.plainPubPemKey, 0) println("rsa encrypt: data size=${enc.size}") val decrypted = rsaDecryptWithPKCS8PrivKey(enc, keyPair.plainPrivPemKey, 0) assertEquals(plainText, decrypted.decodeToString()) } @Test fun `can do crypto with specific key`() { val pubKey = """ -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr0KIpsWPW2JA7ShJI18o wPv3Ip3Y6a0OkJOozfVlQDOjjUG6niDcrPIm+OpL7pCAzwc+h8d9sFH5c/7/bY4i wKK6CpSaOYgQQ03P31KhzmXGJ4LVSxUIV0bhuDYQr+sU5Gu97onF8Ko8MELtWTPw KP1dfqZ3PrK8QBH6su0GlB8onYFtzDUckr2wCrrJ1cR4L1Dg5f2egE75l1cliAIM 4FH1WFU2musfdEuCo5oPgl8ZPPLrQwp8qm9w7xBvbgbmfPTjPBC0N4gcelVzvdfC eU8vpIlLP/9W5nkdqF6CWzjE3dIx2btOH4QDDyogDSLRAvcKN5/1EIZeu2FTbw9k 3QIDAQAB -----END PUBLIC KEY----- """.trimIndent() val privKey = """ -----BEGIN PRIVATE KEY----- MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCvQoimxY9bYkDt KEkjXyjA+/cindjprQ6Qk6jN9WVAM6ONQbqeINys8ib46kvukIDPBz6Hx32wUflz /v9tjiLAoroKlJo5iBBDTc/fUqHOZcYngtVLFQhXRuG4NhCv6xTka73uicXwqjww Qu1ZM/Ao/V1+pnc+srxAEfqy7QaUHyidgW3MNRySvbAKusnVxHgvUODl/Z6ATvmX VyWIAgzgUfVYVTaa6x90S4Kjmg+CXxk88utDCnyqb3DvEG9uBuZ89OM8ELQ3iBx6 VXO918J5Ty+kiUs//1bmeR2oXoJbOMTd0jHZu04fhAMPKiANItEC9wo3n/UQhl67 YVNvD2TdAgMBAAECggEAExREqRcfvJyNIeQ9Vg7xclTbuhaB+ypeSAnzGfzJeXxF pUaPCNDeBSvVZ0qmWoG7rA4HViO3AJ9j7ydG6kfLa7orU6SKx5GS56jMZOzrdXsp 37pD+wj+n/W08+da2LPYUeeSxSmVdVYq+DwI96mKTwQKDhQULiyqBrWOW7Um/q/Z JC8kJWKEmlNideDQHJxZViRyOdKvJtiwvoBLe1Jvbx7oMbpZnf20gV8C7UU7U38R e0BKT6HBUHXuOOp2tFFpX6dySkJqW7Jijv04B/KnDYaSWD8TtaQfPfAhkiEVA17E Ret04PnPMiYCkSVakO0MEeFpwb01vPca4Z64zgf7EQKBgQDyU6wsO3v8L1OlV7tx 7+T0PuOqeo7MWESSn18LeyOP2Y+fDtHKMUFULeYH1UZaGsZJvW+P+c8Mvyitbcvv SZPTR0Dg+1HueXWkNTejs8Z2BKpPmIPaVLz5FxhV7hV2hKgII/yhyRoiWrTiawLg ocOnYSostg+tt6kT8U2QPKhg9QKBgQC5Jho1nZ3pFPVu2rV/o9VvN7bGfn1M2o8k 9PQjLZQYiXJvPP0tNlvAOHk9cAqYecHJ3wDVacZWmLicU8xmNSFmmN6Vs9jj6km4 CWq0/wuTUO/fiH+oHZb6+JM63RXbASWyNK+WwmZtDryNBGRB5zbeCAK8tFsRCJDw 19WQUzljSQKBgCWKkuzTVlTuXA4MdmyjVpwENi8OB5tevVjdudLEg/DgKqDgod2q Hc3VwoJKJzkEVt3LrEHo2IvH/ZxIm0R56J3dtw5jwQCp7nC/EdyZmFBmTqBAJ4Um hZQtYMbHOKoAySthr9y8lADofodpPqvgQ7hllCwTFIC8KER/qJ2E2C0VAoGATLUM hsoWckrMpHDYYVlvQ/TBNNuS7hRe2eDihPCNOt03G/8YpXKv8KN1F48j1KgdMZXC sqhwE9CSK7JMLMw2WltbXIp2gXa/tA+yteo00YPm3aWfvfcEZlY2KV0PgPyosXxC gyNnbCd+1q3LG8K/aJ3JBIV0dUonQqEpSfIxBIECgYArO3Iw+LvoePjq4yHheyEM rz6d6RB+i1Q7ExBK7lbZxN17HmKiOewwI772zEo28IY9sIHugV7rW1vQVs3bnzgk ExDGjYWZSKHfs+3mvrLNRIx/IsVqqwlXt5oO9TspSh68ASvmXN51dmduxRrSuScq 8a49uOr675SyFCJTIdF/Ag== -----END PRIVATE KEY----- """.trimIndent() val plainText = buildString { append("Use of this source code is governed by the GNU AGPLv3 license ") append("that can be found through the following link. ") } val enc = rsaEncryptWithX509PubKey(plainText.encodeToByteArray(), pubKey, 0) val dec = rsaDecryptWithPKCS8PrivKey(enc, privKey, 0) assertEquals(plainText, dec.decodeToString()) } } ================================================ FILE: mirai-core/src/commonTest/kotlin/utils/io/serialization/ReadJceStructTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.io.serialization import net.mamoe.mirai.internal.network.protocol.data.jce.PushReq import net.mamoe.mirai.internal.network.protocol.data.jce.RequestPacket import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.hexToBytes import net.mamoe.mirai.utils.read import kotlin.test.Test internal class ReadJceStructTest : AbstractTest() { @Test fun `ByteReadPacket readJceStruct`() { "10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().read { readJceStruct(RequestPacket.serializer()) } } @Test fun `ByteReadPacket readJceStruct with rubbish length`() { // #1991 "00 00 03 E4 10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().read { readJceStruct(RequestPacket.serializer()) } } @Test fun `ByteReadPacket readUniPacket`() { "10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().read { readUniPacket(PushReq.serializer(), "PushReq") } } @Test fun `ByteReadPacket readUniPacket with rubbish length`() { // #1991 "00 00 03 E4 10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().read { readUniPacket(PushReq.serializer(), "PushReq") } } @Test fun `ByteArray readJceStruct`() { "10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().loadAs(RequestPacket.serializer()) } @Test fun `ByteArray readJceStruct with rubbish length`() { // #1991 "00 00 03 E4 10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().loadAs(RequestPacket.serializer()) } @Test fun `ByteArray readUniPacket`() { "10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().loadWithUniPacket(PushReq.serializer(), "PushReq") } @Test fun `ByteArray readUniPacket with rubbish length`() { // #1991 "00 00 03 E4 10 02 2C 3C 4C 56 23 51 51 53 65 72 76 69 63 65 2E 43 6F 6E 66 69 67 50 75 73 68 53 76 63 2E 4D 61 69 6E 53 65 72 76 61 6E 74 66 07 50 75 73 68 52 65 71 7D 00 01 03 A3 08 00 01 06 07 50 75 73 68 52 65 71 18 00 01 06 12 43 6F 6E 66 69 67 50 75 73 68 2E 50 75 73 68 52 65 71 1D 00 01 03 7B 0A 10 01 2D 00 01 03 6D 19 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 39 00 07 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 31 34 37 21 1F 90 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 34 39 2E 37 2E 32 35 33 2E 32 34 34 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0C 31 38 33 2E 34 37 2E 39 39 2E 32 34 21 36 B0 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 34 35 21 01 BB 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 38 33 2E 34 37 2E 31 30 32 2E 31 36 35 20 50 30 01 4C 5C 60 08 70 01 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 37 36 2E 32 31 31 20 50 30 01 4C 5C 60 08 70 01 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 11 6D 73 66 77 69 66 69 2E 33 67 2E 71 71 2E 63 6F 6D 21 1F 90 30 01 4C 5C 60 08 7C 86 06 6F 74 68 65 72 73 96 06 6F 74 68 65 72 73 AC 0B 4C 5C 6C 70 01 89 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 99 00 04 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0D 34 32 2E 38 31 2E 31 39 33 2E 32 34 32 20 50 30 01 4C 50 03 60 08 7C 86 02 73 7A 96 03 74 65 6C AC 0B 0A 16 0E 31 31 39 2E 31 34 37 2E 31 39 30 2E 33 37 20 50 30 01 4C 50 03 60 08 7C 86 02 74 6A 96 03 74 65 6C AC 0B A9 0C B9 0C C9 0C D9 0C EC FC 0F F6 10 00 FC 11 32 00 29 18 59 0B 8C 98 0C A8 0C" .hexToBytes().loadWithUniPacket(PushReq.serializer(), "PushReq") } } ================================================ FILE: mirai-core/src/commonTest/kotlin/utils/io/serialization/tars/internal/DebugLoggerTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.io.serialization.tars.internal import io.ktor.utils.io.core.* import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.utils.io.JceStruct import net.mamoe.mirai.internal.utils.io.serialization.readJceStruct import net.mamoe.mirai.internal.utils.io.serialization.tars.Tars import net.mamoe.mirai.internal.utils.io.serialization.tars.TarsId import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.utils.toReadPacket import net.mamoe.mirai.utils.toUHexString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFails internal class DebugLoggerTest : AbstractTest() { fun String.uniteLine(): String = replace("\r\n", "\n").replace("\r", "\n") @Serializable data class Struct( @TarsId(1) val str: String, @TarsId(2) val int: Int, ) : JceStruct @Test fun `can log`() { val out = BytePacketBuilder() val logger = DebugLogger(out) val original = Struct("string", 1) val bytes = original.toByteArray(Struct.serializer()) val value = bytes.toReadPacket().use { Tars.UTF_8.load(Struct.serializer(), it, logger) } assertEquals(original, value) assertEquals( """ beginStructure: net.mamoe.mirai.internal.utils.io.serialization.tars.internal.DebugLoggerTest.Struct, CLASS decodeElementIndex: TarsHead(tag=1, type=6(String1)) name=str decodeElementIndex: TarsHead(tag=2, type=0(Byte)) name=int decodeElementIndex: currentHead == null endStructure: net.mamoe.mirai.internal.utils.io.serialization.tars.internal.DebugLoggerTest.Struct, null, null """.trimIndent(), out.build().readBytes().decodeToString().trim().uniteLine() ) } @Test fun `can auto log`() { val original = Struct("string", 1) val bytes = original.toByteArray(Struct.serializer()) println(bytes.toUHexString()) // 16 06 73 74 72 69 6E 67 20 01 bytes[bytes.lastIndex - 1] = 0x30.toByte() // change tag val exception = assertFails { bytes.toReadPacket().use { it.readJceStruct(Struct.serializer()) } } assertEquals( """ 在 解析 net.mamoe.mirai.internal.utils.io.serialization.tars.internal.DebugLoggerTest.Struct 时遇到了意料之中的问题. 请完整复制此日志提交给 mirai: https://github.com/mamoe/mirai/issues/new/choose 调试信息: Data: 16 06 73 74 72 69 6E 67 30 01 Trace: beginStructure: net.mamoe.mirai.internal.utils.io.serialization.tars.internal.DebugLoggerTest.Struct, CLASS decodeElementIndex: TarsHead(tag=1, type=6(String1)) name=str decodeElementIndex: TarsHead(tag=3, type=0(Byte)) skipping Byte decodeElementIndex EOF endStructure: net.mamoe.mirai.internal.utils.io.serialization.tars.internal.DebugLoggerTest.Struct, null, null """.trimIndent().trim(), exception.message!!.trim().uniteLine() ) } } ================================================ FILE: mirai-core/src/commonTest/resources/META-INF/services/net.mamoe.mirai.internal.message.source.MessageSourceSequenceIdAwaiter ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.mirai.internal.testFramework.message.TestMessageSourceSequenceIdAwaiter ================================================ FILE: mirai-core/src/commonTest/resources/recording/configs/desensitization.yml ================================================ # Template for Desensitization in recordings # # Format: # ``` # <sensitive value>: <replacement> # ``` # # If key is a number, its group uin counterpart will also be processed, with calculated replacer. # WARNING: Ensure the <replacement> is not longer than <sensitive value>. # # For example, if your account id is 147258369, you may add: # ``` # 147258369: 123456 # ``` # Then your id will be replaced with 123456. # # # To use desensitization, duplicate this file into name "local.desensitization.yml". 123456789: 111 ================================================ FILE: mirai-core/src/commonTest/resources/recording/configs/test.desensitization.yml ================================================ 123456789: 111 987654321: 222 ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/MiraiImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("MiraiImplKt") package net.mamoe.mirai.internal import io.ktor.client.* import io.ktor.client.engine.okhttp.* import io.ktor.client.plugins.* import kotlinx.atomicfu.atomic import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.utils.MiraiCoreServices private val initialized = atomic(false) @Suppress("FunctionName") internal actual fun _MiraiImpl_static_init() { if (!initialized.compareAndSet(expect = false, update = true)) return MiraiCoreServices.registerAll() MessageProtocolFacade.INSTANCE // register serializers } internal actual fun createDefaultHttpClient(): HttpClient { return HttpClient(OkHttp) { install(HttpTimeout) { this.requestTimeoutMillis = 30_0000 this.connectTimeoutMillis = 30_0000 this.socketTimeoutMillis = 30_0000 } } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/contact/GroupImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact import net.mamoe.mirai.contact.ContactList import net.mamoe.mirai.contact.Group import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.utils.RemoteFileImpl import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.coroutines.CoroutineContext internal actual class GroupImpl actual constructor( bot: QQAndroidBot, parentCoroutineContext: CoroutineContext, id: Long, groupInfo: GroupInfo, members: ContactList<NormalMemberImpl>, ) : Group, CommonGroupImpl(bot, parentCoroutineContext, id, groupInfo, members) { actual companion object; @Suppress("DEPRECATION_ERROR") @Deprecated("Please use files instead.", replaceWith = ReplaceWith("files.root"), level = DeprecationLevel.ERROR) @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.14") override val filesRoot: net.mamoe.mirai.utils.RemoteFile by lazy { RemoteFileImpl(this, "/") } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/contact/active/GroupActiveImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.active import kotlinx.coroutines.runBlocking import net.mamoe.mirai.contact.active.ActiveRecord import net.mamoe.mirai.data.GroupInfo import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.utils.JavaFriendlyAPI import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.stream import java.util.stream.Stream internal actual class GroupActiveImpl actual constructor( group: GroupImpl, logger: MiraiLogger, groupInfo: GroupInfo, ) : CommonGroupActiveImpl(group, logger, groupInfo) { @JavaFriendlyAPI override fun asStream(): Stream<ActiveRecord> { return stream { var page = 0 while (true) { val result = runBlocking { getGroupActiveData(page = page) } val most = result.info.mostAct ?: break for (active in most) yield(active.toActiveRecord(group)) if (result.info.isEnd == 1) break page++ } } } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/contact/file/AbsoluteFolderImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.contact.file import kotlinx.coroutines.runBlocking import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFileFolder import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.internal.network.protocol.data.proto.Oidb0x6d8 import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.internal.utils.FileSystem import net.mamoe.mirai.utils.JavaFriendlyAPI import java.util.stream.Stream import kotlin.streams.asStream internal actual class AbsoluteFolderImpl actual constructor( contact: FileSupported, parent: AbsoluteFolder?, id: String, name: String, uploadTime: Long, uploaderId: Long, lastModifiedTime: Long, contentsCount: Int, ) : CommonAbsoluteFolderImpl(contact, parent, id, name, uploadTime, uploaderId, lastModifiedTime, contentsCount) { @JavaFriendlyAPI override suspend fun foldersStream(): Stream<AbsoluteFolder> { return getItemsSequence().filter { it.folderInfo != null }.map { it.resolve() as AbsoluteFolder }.asStream() } @JavaFriendlyAPI override suspend fun filesStream(): Stream<AbsoluteFile> { return getItemsSequence().filter { it.fileInfo != null }.map { it.resolve() as AbsoluteFile }.asStream() } @JavaFriendlyAPI override suspend fun childrenStream(): Stream<AbsoluteFileFolder> { return getItemsSequence().mapNotNull { it.resolve() }.asStream() } @JavaFriendlyAPI private suspend fun getItemsSequence(): Sequence<Oidb0x6d8.GetFileListRspBody.Item> { return sequence { var index = 0 while (true) { val list = runBlocking { bot.network.sendAndExpect( FileManagement.GetFileList( client, groupCode = contact.id, folderId = id, startIndex = index ) ) }.toResult("AbsoluteFolderImpl.getFilesFlow").getOrThrow() index += list.itemList.size if (list.int32RetCode != 0) return@sequence if (list.itemList.isEmpty()) return@sequence yieldAll(list.itemList) } } } @OptIn(JavaFriendlyAPI::class) override suspend fun resolveFilesStream(path: String): Stream<AbsoluteFile> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") if (!FileSystem.isLegal(path)) return Stream.empty() if (path[0] == '/') { return root.resolveFilesStream(path.substring(1)) } if (!path.contains('/')) { return getItemsSequence() .filter { it.fileInfo?.fileName == path } .map { it.resolve() as AbsoluteFile } .asStream() } return resolveFolder(path.substringBefore('/'))?.resolveFilesStream(path.substringAfter('/')) ?: Stream.empty() } @JavaFriendlyAPI override suspend fun resolveAllStream(path: String): Stream<AbsoluteFileFolder> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") if (!FileSystem.isLegal(path)) return Stream.empty() if (path[0] == '/') { return root.resolveAllStream(path.substring(1)) } if (!path.contains('/')) { return getItemsSequence().mapNotNull { it.resolve() }.asStream() } return resolveFolder(path.substringBefore('/'))?.resolveAllStream(path.substringAfter('/')) ?: Stream.empty() } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/message/protocol/impl/TextProtocol.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message.protocol.impl internal actual fun getEmojiPatternResourceOrNull(): String? { return TextProtocol::class.java.classLoader?.getResourceAsStream("emoji-pattern.regex") ?.use { it.readBytes().decodeToString() } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/component/ComponentKey.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import kotlin.reflect.KTypeProjection import kotlin.reflect.full.allSupertypes internal actual fun ComponentKey<*>.getComponentTypeArgument(): KTypeProjection? { val thisType = this::class.allSupertypes.find { it.classifier == ComponentKey::class } return thisType?.arguments?.firstOrNull() } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/handler/NetworkHandlerFactory.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandlerFactory import java.net.InetAddress import java.net.InetSocketAddress /** * Factory for a specific [NetworkHandler] implementation. */ internal actual fun interface NetworkHandlerFactory<out H : NetworkHandler> { actual fun create(context: NetworkHandlerContext, host: String, port: Int): H = create(context, InetSocketAddress.createUnresolved(host, port)) fun create(context: NetworkHandlerContext, host: InetAddress, port: Int): H = create(context, InetSocketAddress(host, port)) /** * Create an instance of [H]. The returning [H] has [NetworkHandler.state] of [State.INITIALIZED] */ actual fun create(context: NetworkHandlerContext, address: SocketAddress): H actual companion object { actual fun getPlatformDefault(): NetworkHandlerFactory<*> = NettyNetworkHandlerFactory } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/handler/SocketAddress.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler import java.net.InetSocketAddress @Suppress("ACTUAL_WITHOUT_EXPECT") // visibility internal actual typealias SocketAddress = java.net.InetSocketAddress @Suppress("EXTENSION_SHADOWED_BY_MEMBER") internal actual fun SocketAddress.getHost(): String = hostString ?: error("Failed to get host from address '$this'.") @Suppress("EXTENSION_SHADOWED_BY_MEMBER") internal actual fun SocketAddress.getPort(): Int = this.port internal actual fun createSocketAddress(host: String, port: Int): SocketAddress { return InetSocketAddress.createUnresolved(host, port) } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/handler/selector/SelectorRequireReconnectException.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.handler.selector internal actual class SelectorRequireReconnectException( withStackTrace: Boolean ) : NetworkException(true) { actual constructor() : this(false) private companion object { val EMPTY = arrayOf<StackTraceElement>() } override fun fillInStackTrace(): Throwable { stackTrace = EMPTY return this } init { if (withStackTrace) super.fillInStackTrace() } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyChannelException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.netty import net.mamoe.mirai.internal.network.handler.selector.NetworkChannelException internal data class NettyChannelException( override val message: String? = null, override val cause: Throwable? = null, ) : NetworkChannelException() ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.netty import io.netty.bootstrap.Bootstrap import io.netty.buffer.ByteBuf import io.netty.channel.* import io.netty.channel.nio.NioEventLoopGroup import io.netty.channel.socket.SocketChannel import io.netty.channel.socket.nio.NioSocketChannel import io.netty.handler.codec.LengthFieldBasedFrameDecoder import io.netty.handler.codec.MessageToByteEncoder import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.job import net.mamoe.mirai.internal.network.handler.CommonNetworkHandler import net.mamoe.mirai.internal.network.handler.NetworkHandler.State import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.debug import java.net.SocketAddress import io.netty.channel.Channel as NettyChannel internal open class NettyNetworkHandler( context: NetworkHandlerContext, address: SocketAddress, ) : CommonNetworkHandler<NettyChannel>(context, address.cast()) { override fun toString(): String { return "NettyNetworkHandler(context=$context, address=$address)" } /////////////////////////////////////////////////////////////////////////// // exception handling /////////////////////////////////////////////////////////////////////////// protected open fun handlePipelineException(ctx: ChannelHandlerContext, error: Throwable) { setState { StateClosed(NettyChannelException(cause = error, message = "An unexpected exception was received from netty pipeline. (context=$context, address=$address)")) } } /////////////////////////////////////////////////////////////////////////// // netty conn. /////////////////////////////////////////////////////////////////////////// private inner class IncomingPacketDecoder( private val decodePipeline: PacketDecodePipeline, ) : SimpleChannelInboundHandler<ByteBuf>(ByteBuf::class.java) { override fun channelRead0(ctx: ChannelHandlerContext, msg: ByteBuf) { decodePipeline.send(msg.toReadPacket()) } } private inner class OutgoingPacketEncoder : MessageToByteEncoder<OutgoingPacket>(OutgoingPacket::class.java) { override fun encode(ctx: ChannelHandlerContext, msg: OutgoingPacket, out: ByteBuf) { out.writeBytes(msg.delegate) } } protected open fun setupChannelPipeline(pipeline: ChannelPipeline, decodePipeline: PacketDecodePipeline) { pipeline .addLast(object : ChannelInboundHandlerAdapter() { @Suppress("OVERRIDE_DEPRECATION") override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { handlePipelineException(ctx, cause) } }) .addLast("outgoing-packet-encoder", OutgoingPacketEncoder()) .addLast(LengthFieldBasedFrameDecoder(Int.MAX_VALUE, 0, 4, -4, 4)) .addLast(IncomingPacketDecoder(decodePipeline)) } protected open fun createDummyDecodePipeline() = PacketDecodePipeline(this@NettyNetworkHandler.coroutineContext) // can be overridden for tests override suspend fun createConnection(): NettyChannel { packetLogger.debug { "Connecting to $address" } val contextResult = CompletableDeferred<NettyChannel>() val eventLoopGroup = NioEventLoopGroup() val decodePipeline = PacketDecodePipeline( this@NettyNetworkHandler.coroutineContext .plus(eventLoopGroup.asCoroutineDispatcher()) ) val future = Bootstrap().group(eventLoopGroup) .channel(NioSocketChannel::class.java) .option(ChannelOption.SO_KEEPALIVE, true) .handler(object : ChannelInitializer<SocketChannel>() { override fun initChannel(ch: SocketChannel) { setupChannelPipeline(ch.pipeline(), decodePipeline) ch.pipeline() .addLast(object : ChannelInboundHandlerAdapter() { override fun channelInactive(ctx: ChannelHandlerContext?) { eventLoopGroup.shutdownGracefully() contextResult.cancel() } }) } }) .connect(address) .runCatching { awaitKt() }.onFailure { eventLoopGroup.shutdownGracefully() contextResult.cancel() }.getOrElse { error -> throw NettyChannelException(cause = error, message = "Failed to connect $address") } contextResult.complete(future.channel()) coroutineContext.job.invokeOnCompletion { future.channel().close() eventLoopGroup.shutdownGracefully() } future.channel().closeFuture().addListener { if (_state.correspondingState == State.CLOSED) return@addListener close(NettyChannelException(cause = it.cause())) } return contextResult.await() } @Suppress("EXTENSION_SHADOWED_BY_MEMBER") override fun io.netty.channel.Channel.close() { this.close() } override fun NettyChannel.writeAndFlushOrCloseAsync(packet: OutgoingPacket) { writeAndFlush(packet) .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE) .addListener(ChannelFutureListener.CLOSE_ON_FAILURE) } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/NettyNetworkHandlerFactory.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.netty import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.network.handler.SocketAddress internal object NettyNetworkHandlerFactory : NetworkHandlerFactory<NettyNetworkHandler> { override fun create(context: NetworkHandlerContext, address: SocketAddress): NettyNetworkHandler { return NettyNetworkHandler(context, address) } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/impl/netty/nettyUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.netty import io.ktor.utils.io.core.* import io.ktor.utils.io.streams.* import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBufInputStream import io.netty.channel.ChannelFuture import kotlinx.coroutines.suspendCancellableCoroutine import net.mamoe.mirai.utils.withUse internal suspend fun ChannelFuture.awaitKt(): ChannelFuture { suspendCancellableCoroutine<Unit> { cont -> cont.invokeOnCancellation { channel().close() } addListener { f -> if (f.isSuccess) { cont.resumeWith(Result.success(Unit)) } else { cont.resumeWith(Result.failure(f.cause())) } } } return this } internal fun ByteBuf.toReadPacket(): ByteReadPacket { val buf = this return buildPacket { ByteBufInputStream(buf).withUse { copyTo(outputStream()) } } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/impl/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/network/protocol/data/richstatus/RichStatus.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.protocol.data.richstatus import net.mamoe.mirai.utils.pos import net.mamoe.mirai.utils.toIntUnsigned import java.nio.ByteBuffer import java.nio.ByteOrder @Suppress("UsePropertyAccessSyntax") internal actual fun parseRichStatusImpl(rawData: ByteArray?) : RichStatus { val rsp = RichStatus() if (rawData == null || rawData.size <= 2) return rsp val byteBuffer = ByteBuffer.wrap(rawData).order(ByteOrder.BIG_ENDIAN) var lastPosition = 0 var lastStringData: String? = null while (byteBuffer.remaining() >= 2) { val dataType = byteBuffer.get().toIntUnsigned() val dataLength = byteBuffer.get().toIntUnsigned() if (byteBuffer.remaining() < dataLength) break val dataStartPosition = lastPosition + 2 // Origin: dataType > 0 && dataType < 128 if (dataType in 1..127) { val dataContent = String(rawData, dataStartPosition, dataLength) lastPosition = dataStartPosition + dataLength byteBuffer.pos = lastPosition when (dataType) { 1 -> rsp.actionText = dataContent 2 -> rsp.dataText = dataContent 4 -> { if (lastStringData != null) { rsp.addPlainText(lastStringData) lastStringData = null } if (rsp.plainText != null) { rsp.locationPosition = rsp.plainText!!.size } else { rsp.locationPosition = 0 } rsp.locationText = dataContent } else -> { if (lastStringData == null) { lastStringData = dataContent } else { lastStringData += dataContent } } } } else { run theSwitch@{ when (dataType) { 129 -> { if (byteBuffer.remaining() >= 8) { rsp.actionId = byteBuffer.getInt() rsp.dataId = byteBuffer.getInt() } } 130 -> { if (byteBuffer.remaining() >= 8) { rsp.lontitude = byteBuffer.getInt() rsp.latitude = byteBuffer.getInt() } } 144 -> rsp.feedsId = String(rawData, dataStartPosition, dataLength) 145 -> rsp.tplId = byteBuffer.getInt() 146 -> rsp.tplType = byteBuffer.getInt() 147 -> rsp.actId = byteBuffer.getInt() 148 -> { if (byteBuffer.remaining() >= 4) { lastPosition = byteBuffer.getInt() /* if (var1 > 4) { var19 = String(var0, var5+4, var1-4) if (var19.isNotEmpty()) { var9.topics.add(Pair(var2, var19)) } } */ } } 149 -> { if (byteBuffer.remaining() >= 5) { lastPosition = dataLength while (true) { if (lastPosition < 5) return@theSwitch byteBuffer.getInt() byteBuffer.get().toIntUnsigned() // var9.topicsPos.add(new Pair(var6, var3)); lastPosition -= 5 } } } 161 -> { /* val var11 = ByteArray(dataLength) byteBuffer.get(var11) */ byteBuffer.pos += dataLength // Parse richstatus_sticker$RichStatus_Sticker } 162 -> { rsp.fontId = byteBuffer.getInt() } 163 -> { rsp.fontType = byteBuffer.getInt() } } } lastPosition = dataStartPosition + dataLength byteBuffer.pos = lastPosition } } if (lastStringData != null) { rsp.addPlainText(lastStringData) } return rsp } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/BotConfigurationExt.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.BotConfiguration internal actual val BotConfiguration.workingDirPath: String get() = workingDir.absolutePath internal actual val BotConfiguration.cacheDirPath: String get() = cacheDir.path ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/ExternalResourceImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import io.ktor.utils.io.core.* import io.ktor.utils.io.streams.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.sha1 import java.io.InputStream import java.io.SequenceInputStream import java.util.Collections @Suppress("FunctionName") internal actual fun CombinedExternalResource(vararg resources: ExternalResource): ExternalResource { return CombinedExternalResource(resources.toList()) } /** * it is caller's responsibility to guarantee the immutability of the stream. */ internal class CombinedExternalResource( private val inputs: Collection<ExternalResource> ) : ExternalResource { override val isAutoClose: Boolean = true override val size: Long = inputs.sumOf { it.size } override val md5: ByteArray by lazy { combine().md5() } override val sha1: ByteArray by lazy { combine().sha1() } override val formatName: String = "" private val _closed = CompletableDeferred<Unit>() override val closed: Deferred<Unit> get() = _closed override fun close() { _closed.complete(Unit) } override fun inputStream(): InputStream = combine() @MiraiInternalApi override fun input(): Input = inputStream().asInput() private fun combine(): InputStream { return SequenceInputStream(Collections.enumeration(inputs.map { it.inputStream() })) } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/PlatformSocket.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import io.ktor.utils.io.core.* import io.ktor.utils.io.streams.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.suspendCancellableCoroutine import net.mamoe.mirai.internal.network.highway.HighwayProtocolChannel import java.io.BufferedInputStream import java.io.BufferedOutputStream import java.io.IOException import java.net.Socket import java.util.concurrent.Executors import kotlin.contracts.InvocationKind import kotlin.contracts.contract internal actual class PlatformSocket : Closeable, HighwayProtocolChannel { private lateinit var socket: Socket actual val isOpen: Boolean get() = if (::socket.isInitialized) socket.isConnected else false actual override fun close() { if (::socket.isInitialized) { socket.close() } thread.shutdownNow() kotlin.runCatching { writeChannel.close() } kotlin.runCatching { readChannel.close() } } @PublishedApi internal lateinit var writeChannel: BufferedOutputStream @PublishedApi internal lateinit var readChannel: BufferedInputStream actual suspend fun send(packet: ByteArray, offset: Int, length: Int) { runInterruptible(Dispatchers.IO) { writeChannel.write(packet, offset, length) writeChannel.flush() } } /** * @throws SendPacketInternalException */ actual override suspend fun send(packet: ByteReadPacket) { runInterruptible(Dispatchers.IO) { try { writeChannel.writePacket(packet) writeChannel.flush() } catch (e: IOException) { throw SendPacketInternalException(e) } } } private val thread = Executors.newSingleThreadExecutor() /** * @throws ReadPacketInternalException */ actual override suspend fun read(): ByteReadPacket = suspendCancellableCoroutine { cont -> val task = thread.submit { kotlin.runCatching { readChannel.readPacketAtMost(Long.MAX_VALUE) }.let { cont.resumeWith(it) } } cont.invokeOnCancellation { kotlin.runCatching { task.cancel(true) } } } suspend fun connect(serverHost: String, serverPort: Int) { runInterruptible(Dispatchers.IO) { socket = Socket(serverHost, serverPort) readChannel = socket.getInputStream().buffered() writeChannel = socket.getOutputStream().buffered() } } actual companion object { actual suspend fun connect( serverIp: String, serverPort: Int, ): PlatformSocket { val socket = PlatformSocket() socket.connect(serverIp, serverPort) return socket } actual suspend inline fun <R> withConnection( serverIp: String, serverPort: Int, block: PlatformSocket.() -> R, ): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return connect(serverIp, serverPort).use(block) } } } @Suppress("ACTUAL_WITHOUT_EXPECT") internal actual typealias SocketException = java.net.SocketException @Suppress("ACTUAL_WITHOUT_EXPECT") internal actual typealias NoRouteToHostException = java.net.NoRouteToHostException @Suppress("ACTUAL_WITHOUT_EXPECT") internal actual typealias UnknownHostException = java.net.UnknownHostException ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/RemoteFileImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("DEPRECATION", "DEPRECATION_ERROR") package net.mamoe.mirai.internal.utils import io.ktor.utils.io.core.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.runBlocking import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.contact.groupCode import net.mamoe.mirai.internal.message.data.FileMessageImpl import net.mamoe.mirai.internal.message.flags.AllowSendFileMessage import net.mamoe.mirai.internal.network.highway.Highway import net.mamoe.mirai.internal.network.highway.ResourceKind import net.mamoe.mirai.internal.network.protocol import net.mamoe.mirai.internal.network.protocol.data.proto.* import net.mamoe.mirai.internal.network.protocol.packet.chat.FileManagement import net.mamoe.mirai.internal.network.protocol.packet.chat.toResult import net.mamoe.mirai.internal.utils.io.serialization.toByteArray import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.RemoteFile.Companion.ROOT_PATH import java.io.File import kotlin.contracts.contract import kotlin.text.toByteArray private val fs = FileSystem internal class RemoteFileInfo( val id: String, // fileId or folderId val isFile: Boolean, val path: String, val name: String, val parentFolderId: String, val size: Long, val busId: Int, // for file only val creatorId: Long, //ownerUin, createUin val createTime: Long, // uploadTime, createTime val modifyTime: Long, val downloadTimes: Int, val sha: ByteArray, // for file only val md5: ByteArray, // for file only ) { companion object { val root = RemoteFileInfo( "/", false, "/", "/", "", 0, 0, 0, 0, 0, 0, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY ) } } internal fun RemoteFile.checkIsImpl(): CommonRemoteFileImpl { contract { returns() implies (this@checkIsImpl is RemoteFileImpl) } return this as? RemoteFileImpl ?: error("RemoteFile must not be implemented manually.") } internal expect class RemoteFileImpl( contact: Group, path: String, // absolute ) : CommonRemoteFileImpl { constructor(contact: Group, parent: String, name: String) } internal abstract class CommonRemoteFileImpl( override val contact: Group, override val path: String, // absolute ) : RemoteFile { override var id: String? = null override val name: String get() = path.substringAfterLast('/') private val bot get() = contact.bot.asQQAndroidBot() private val client get() = bot.client override val parent: CommonRemoteFileImpl? get() { if (path == ROOT_PATH) return null val s = path.substringBeforeLast('/') return RemoteFileImpl(contact, s.ifEmpty { ROOT_PATH }) } /** * Prefer id matching. */ private suspend fun Flow<Oidb0x6d8.GetFileListRspBody.Item>.findMatching(): Oidb0x6d8.GetFileListRspBody.Item? { var nameMatching: Oidb0x6d8.GetFileListRspBody.Item? = null val idMatching = firstOrNull { if (it.name == this@CommonRemoteFileImpl.name) { nameMatching = it } it.id == this@CommonRemoteFileImpl.id } return idMatching ?: nameMatching } private suspend fun getFileFolderInfo(): RemoteFileInfo? { val parent = parent ?: return RemoteFileInfo.root val info = parent.getFilesFlow() .filter { it.name == this.name } .findMatching() ?: return null return when { info.folderInfo != null -> info.folderInfo.run { RemoteFileInfo( id = folderId, isFile = false, path = path, name = folderName, parentFolderId = parentFolderId, size = 0, busId = 0, creatorId = createUin, createTime = createTime.toLongUnsigned(), modifyTime = modifyTime.toLongUnsigned(), downloadTimes = 0, sha = EMPTY_BYTE_ARRAY, md5 = EMPTY_BYTE_ARRAY, ) } info.fileInfo != null -> info.fileInfo.run { RemoteFileInfo( id = fileId, isFile = true, path = path, name = fileName, parentFolderId = parentFolderId, size = fileSize, busId = busId, creatorId = uploaderUin, createTime = uploadTime.toLongUnsigned(), modifyTime = modifyTime.toLongUnsigned(), downloadTimes = downloadTimes, sha = sha, md5 = md5, ) } else -> null } } private fun RemoteFileInfo?.checkExists(thisPath: String, kind: String = "Remote path"): RemoteFileInfo { if (this == null) throw IllegalStateException("$kind '$thisPath' does not exist.") return this } override suspend fun isFile(): Boolean = this.getFileFolderInfo().checkExists(this.path).isFile // compiler bug override suspend fun isDirectory(): Boolean = !isFile() override suspend fun length(): Long = this.getFileFolderInfo().checkExists(this.path).size override suspend fun exists(): Boolean = this.getFileFolderInfo() != null override suspend fun getInfo(): RemoteFile.FileInfo? { return getFileFolderInfo()?.run { RemoteFile.FileInfo( name = name, id = id, path = path, length = size, downloadTimes = downloadTimes, uploaderId = creatorId, uploadTime = createTime, lastModifyTime = modifyTime, sha1 = sha, md5 = md5, ) } } private suspend fun getFilesFlow(): Flow<Oidb0x6d8.GetFileListRspBody.Item> { val info = getFileFolderInfo() ?: return emptyFlow() return flow { var index = 0 while (true) { val list = bot.network.sendAndExpect( FileManagement.GetFileList( client, groupCode = contact.id, folderId = info.id, startIndex = index ) ).toResult("RemoteFile.listFiles").getOrThrow() index += list.itemList.size if (list.int32RetCode != 0) return@flow if (list.itemList.isEmpty()) return@flow emitAll(list.itemList.asFlow()) } } } private fun Oidb0x6d8.GetFileListRspBody.Item.resolveToFile(): RemoteFile? { val item = this return when { item.fileInfo != null -> { resolve(item.fileInfo.fileName) } item.folderInfo != null -> { resolve(item.folderInfo.folderName) } else -> null }?.also { it.id = item.id } } override suspend fun listFiles(): Flow<RemoteFile> { return getFilesFlow().mapNotNull { item -> item.resolveToFile() } } // compiler bug override suspend fun listFilesCollection(): List<RemoteFile> = listFiles().toList() @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @OptIn(JavaFriendlyAPI::class) override suspend fun listFilesIterator(lazy: Boolean): Iterator<RemoteFile> { if (!lazy) return listFiles().toList().iterator() return object : Iterator<RemoteFile> { private val queue = ArrayDeque<Oidb0x6d8.GetFileListRspBody.Item>(1) @Volatile private var index = 0 private var ended = false private suspend fun updateItems() { val list = bot.network.sendAndExpect( FileManagement.GetFileList( client, groupCode = contact.id, folderId = path, startIndex = index ) ).toResult("RemoteFile.listFiles").getOrThrow() if (list.int32RetCode != 0 || list.itemList.isEmpty()) { ended = true return } index += list.itemList.size for (item in list.itemList) { if (item.fileInfo != null || item.folderInfo != null) queue.add(item) } } override fun hasNext(): Boolean { if (queue.isEmpty() && !ended) runBlocking { updateItems() } return queue.isNotEmpty() } override fun next(): RemoteFile { return queue.removeFirst().resolveToFile()!! } } } override fun resolve(relative: String) = RemoteFileImpl(contact, this.path, relative) override fun resolve(relative: RemoteFile): RemoteFileImpl { if (relative.checkIsImpl().contact !== this.contact) error("`relative` must be obtained from the same Group as `this`.") return resolve(relative.path).also { it.id = relative.id } } override suspend fun resolveById(id: String, deep: Boolean): RemoteFile? { if (this.id == id) return this val dirs = mutableListOf<Oidb0x6d8.GetFileListRspBody.Item>() getFilesFlow().mapNotNull { item -> when { item.id == id -> item.resolveToFile() deep && item.folderInfo != null -> { dirs.add(item) null } else -> null } }.firstOrNull()?.let { return it } for (dir in dirs) { dir.resolveToFile()?.resolveById(id, deep)?.let { return it } } return null } // compiler bug override suspend fun resolveById(id: String): RemoteFile? = resolveById(id, deep = true) override fun resolveSibling(relative: String): RemoteFileImpl { val parent = this.parent if (parent == null) { if (fs.normalize(relative) == ROOT_PATH) error("Root path does not have sibling paths.") return RemoteFileImpl(contact, ROOT_PATH) } return RemoteFileImpl(contact, parent.path, relative) } override fun resolveSibling(relative: RemoteFile): RemoteFileImpl { if (relative.checkIsImpl().contact !== this.contact) error("`relative` must be obtained from the same Group as `this`.") return resolveSibling(relative.path).also { it.id = relative.id } } private fun RemoteFileInfo.isOperable(): Boolean = creatorId == bot.id || contact.botPermission.isOperator() private fun isBotOperator(): Boolean = contact.botPermission.isOperator() override suspend fun delete(): Boolean { val info = getFileFolderInfo() ?: return false if (!info.isOperable()) return false return when { info.isFile -> { bot.network.sendAndExpect( FileManagement.DeleteFile( client, groupCode = contact.id, busId = info.busId, fileId = info.id, parentFolderId = info.parentFolderId, ) ).toResult("RemoteFile.delete", checkResp = false).getOrThrow().int32RetCode == 0 } // recursively -> { // this.listFiles().collect { child -> // child.delete() // } // this.delete() // } else -> { // natively 'recursive' bot.network.sendAndExpect( FileManagement.DeleteFolder( client, contact.id, info.id ) ).toResult("RemoteFile.delete").getOrThrow().int32RetCode == 0 } } } override suspend fun renameTo(name: String): Boolean { if (path == ROOT_PATH && name != ROOT_PATH) return false val normalized = fs.normalize(name) if (normalized.contains('/')) throw IllegalArgumentException("'/' is not allowed in file or directory names. Given: '$name'.") val info = getFileFolderInfo() ?: return false if (!info.isOperable()) return false return bot.network.sendAndExpect( if (info.isFile) { FileManagement.RenameFile(client, contact.id, info.busId, info.id, info.parentFolderId, normalized) } else { FileManagement.RenameFolder(client, contact.id, info.id, normalized) } ).toResult("RemoteFile.renameTo", checkResp = false).getOrThrow().int32RetCode == 0 } /** * null means not exist */ private suspend fun getIdSmart(): String? { if (path == ROOT_PATH) return ROOT_PATH return this.id ?: this.getFileFolderInfo()?.id } override suspend fun moveTo(target: RemoteFile): Boolean { if (target.checkIsImpl().contact != this.contact) { // TODO: 2021/3/4 cross-group file move // target.mkdir() // val targetFolderId = target.getIdSmart() ?: return false // this.listFiles().mapNotNull { it.checkIsImpl().getFileFolderInfo() }.collect { // FileManagement.MoveFile(client, contact.id, it.busId, it.id, it.parentFolderId, targetFolderId) // .sendAndExpect(bot).toResult("RemoteFile.moveTo", checkResp = false).getOrThrow() // // // TODO: 2021/3/3 batch packets // } // this.delete() // it is now empty error("Cross-group file operation is not yet supported.") } if (target.path == this.path) return true if (target.parent?.path == this.path) return false val info = getFileFolderInfo() ?: return false if (!info.isOperable()) return false return if (info.isFile) { val newParentId = target.parent?.checkIsImpl()?.getIdSmart() ?: return false bot.network.sendAndExpect( FileManagement.MoveFile( client, contact.id, info.busId, info.id, info.parentFolderId, newParentId ) ).toResult("RemoteFile.moveTo", checkResp = false).getOrThrow().int32RetCode == 0 } else { return bot.network.sendAndExpect(FileManagement.RenameFolder(client, contact.id, info.id, target.name)) .toResult("RemoteFile.moveTo", checkResp = false).getOrThrow().int32RetCode == 0 } } override suspend fun mkdir(): Boolean { if (path == ROOT_PATH) return false if (!isBotOperator()) return false val parentFolderId: String = parent?.getIdSmart() ?: return false return bot.network.sendAndExpect(FileManagement.CreateFolder(client, contact.id, parentFolderId, this.name)) .toResult("RemoteFile.mkdir", checkResp = false).getOrThrow().int32RetCode == 0 } private suspend fun upload0( resource: ExternalResource, callback: RemoteFile.ProgressionCallback?, ): Oidb0x6d6.UploadFileRspBody? = resource.withAutoClose { val parent = parent ?: return null val parentInfo = parent.getFileFolderInfo() ?: return null val resp = bot.network.sendAndExpect( FileManagement.RequestUpload( client, groupCode = contact.id, folderId = parentInfo.id, resource = resource, filename = this.name ) ).toResult("RemoteFile.upload").getOrThrow() if (resp.boolFileExist) { return resp } val ext = GroupFileUploadExt( u1 = 100, u2 = 1, entry = GroupFileUploadEntry( business = ExcitingBusiInfo( busId = resp.busId, senderUin = bot.id, receiverUin = contact.groupCode, // TODO: 2021/3/1 code or uin? groupCode = contact.groupCode, ), fileEntry = ExcitingFileEntry( fileSize = resource.size, md5 = resource.md5, sha1 = resource.sha1, fileId = resp.fileId.toByteArray(), uploadKey = resp.checkKey, ), clientInfo = ExcitingClientInfo( clientType = 2, appId = client.protocol.id.toString(), terminalType = 2, clientVer = "9e9c09dc", unknown = 4, ), fileNameInfo = ExcitingFileNameInfo(this.name), host = ExcitingHostConfig( hosts = listOf( ExcitingHostInfo( url = ExcitingUrlInfo( unknown = 1, host = resp.uploadIpLanV4.firstOrNull() ?: resp.uploadIpLanV6.firstOrNull() ?: resp.uploadIp, ), port = resp.uploadPort, ), ), ), ), u3 = 0, ).toByteArray(GroupFileUploadExt.serializer()) callback?.onBegin(this, resource) kotlin.runCatching { Highway.uploadResourceBdh( bot = bot, resource = resource, kind = ResourceKind.GROUP_FILE, commandId = 71, extendInfo = ext, dataFlag = 0, callback = if (callback == null) null else fun(it: Long) { callback.onProgression(this, resource, it) } ) }.fold( onSuccess = { callback?.onSuccess(this, resource) }, onFailure = { callback?.onFailure(this, resource, it) } ) return resp } private suspend fun uploadInternal( resource: ExternalResource, callback: RemoteFile.ProgressionCallback?, ): FileMessage { val resp = upload0(resource, callback) ?: error("Failed to upload file.") return FileMessageImpl( resp.fileId, resp.busId, name, resource.size, allowSend = true ) } @Deprecated( "Use uploadAndSend instead.", replaceWith = ReplaceWith("this.uploadAndSend(resource, callback)"), level = DeprecationLevel.ERROR ) override suspend fun upload( resource: ExternalResource, callback: RemoteFile.ProgressionCallback?, ): FileMessage { val msg = uploadInternal(resource, callback) contact.sendMessage(msg + AllowSendFileMessage) return msg } // compiler bug @Deprecated( "Use uploadAndSend instead.", replaceWith = ReplaceWith("this.uploadAndSend(resource)"), level = DeprecationLevel.ERROR ) @Suppress("DEPRECATION_ERROR") override suspend fun upload(resource: ExternalResource): FileMessage { return upload(resource, null) } override suspend fun uploadAndSend(resource: ExternalResource): MessageReceipt<Contact> { @Suppress("DEPRECATION") return contact.sendMessage(AllowSendFileMessage + uploadInternal(resource, null)) } override suspend fun getDownloadInfo(): RemoteFile.DownloadInfo? { val info = getFileFolderInfo() ?: return null if (!info.isFile) return null val resp = bot.network.sendAndExpect( FileManagement.RequestDownload( client, groupCode = contact.id, busId = info.busId, fileId = info.id ) ).toResult("RemoteFile.getDownloadInfo").getOrThrow() return RemoteFile.DownloadInfo( filename = name, id = info.id, path = path, url = "http://${resp.downloadIp}/ftn_handler/${resp.downloadUrl.toUHexString("")}/?fname=" + info.id.toByteArray().toUHexString(""), sha1 = info.sha, md5 = info.md5 ) } override fun toString(): String = path override suspend fun toMessage(): FileMessage? { val info = getFileFolderInfo() ?: return null if (!info.isFile) return null return FileMessageImpl(info.id, info.busId, name, info.size) } } internal actual class RemoteFileImpl actual constructor( contact: Group, path: String ) : CommonRemoteFileImpl(contact, path), RemoteFile { actual constructor(contact: Group, parent: String, name: String) : this(contact, FileSystem.normalize(parent, name)) // compiler bug @Deprecated( "Use uploadAndSend instead.", replaceWith = ReplaceWith("this.uploadAndSend(file, callback)"), level = DeprecationLevel.ERROR ) @Suppress("DEPRECATION_ERROR") override suspend fun upload(file: File, callback: RemoteFile.ProgressionCallback?): FileMessage = file.toExternalResource().use { upload(it, callback) } //compiler bug @Deprecated( "Use sendFile instead.", replaceWith = ReplaceWith("this.uploadAndSend(file)"), level = DeprecationLevel.ERROR ) @Suppress("DEPRECATION_ERROR") override suspend fun upload(file: File): FileMessage { // Dear compiler: // // Please generate invokeinterface. // // Yours Sincerely // Him188 return file.toExternalResource().use { upload(it) } } // compiler bug override suspend fun uploadAndSend(file: File): MessageReceipt<Contact> = file.toExternalResource().use { uploadAndSend(it) } // override suspend fun writeSession(resource: ExternalResource): FileUploadSession { // } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/crypto/AES.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import javax.crypto.Cipher import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec internal actual fun aesEncrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray { return doAES(input, iv, key, Cipher.ENCRYPT_MODE) } internal actual fun aesDecrypt(input: ByteArray, iv: ByteArray, key: ByteArray): ByteArray { return doAES(input, iv, key, Cipher.DECRYPT_MODE) } private fun doAES(input: ByteArray, iv: ByteArray, key: ByteArray, opMode: Int): ByteArray { val keySpec = SecretKeySpec(key, "AES") val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(opMode, keySpec, IvParameterSpec(iv)) return cipher.doFinal(input) } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/crypto/JceEcdh.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import java.math.BigInteger import java.security.AlgorithmParameters import java.security.KeyFactory import java.security.KeyPairGenerator import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey import java.security.spec.ECGenParameterSpec import java.security.spec.ECParameterSpec import java.security.spec.ECPoint import java.security.spec.ECPublicKeySpec import javax.crypto.KeyAgreement internal open class JceEcdh : Ecdh<ECPublicKey, ECPrivateKey> { protected open fun newECKeyPairGenerator() = KeyPairGenerator.getInstance("EC") protected open fun newECKeyFactory() = KeyFactory.getInstance("EC") protected open fun newECAlgorithmParameters() = AlgorithmParameters.getInstance("EC") protected open fun newECDHKeyAgreement() = KeyAgreement.getInstance("ECDH") override fun generateKeyPair(): EcdhKeyPair<ECPublicKey, ECPrivateKey> { return newECKeyPairGenerator() .apply { // AKA. prime256v1 // But `secp256r1` is more common initialize(ECGenParameterSpec("secp256r1")) } .genKeyPair() .let { EcdhKeyPair(it.public as ECPublicKey, it.private as ECPrivateKey) } } override fun calculateShareKey(peerKey: ECPublicKey, privateKey: ECPrivateKey): ByteArray { return newECDHKeyAgreement().apply { init(privateKey) doPhase(peerKey, true) }.generateSecret() } override fun importPublicKey(encoded: ByteArray): ECPublicKey { val params: ECParameterSpec = newECAlgorithmParameters().apply { init(ECGenParameterSpec("secp256r1")) }.getParameterSpec(ECParameterSpec::class.java) require(encoded[0] == 0x04.toByte()) { "Only uncompressed format is supported" } val fieldSize = params.curve.field.fieldSize val elementSize = (fieldSize + 7) / 8 val affineXBytes = ByteArray(elementSize) val affineYBytes = ByteArray(elementSize) System.arraycopy(encoded, 1, affineXBytes, 0, elementSize) System.arraycopy(encoded, elementSize + 1, affineYBytes, 0, elementSize) val point = ECPoint(BigInteger(1, affineXBytes), BigInteger(1, affineYBytes)) val keySpec = ECPublicKeySpec(point, params) return newECKeyFactory().generatePublic(keySpec) as ECPublicKey } override fun exportPublicKey(key: ECPublicKey): ByteArray { val point = key.w val fieldSize = key.params.curve.field.fieldSize val elementSize = (fieldSize + 7) / 8 val x = point.affineX.toByteArray() val y = point.affineY.toByteArray() val startOfX = countLeadingZeros(x) val startOfY = countLeadingZeros(y) val encoded = ByteArray(elementSize * 2 + 1) encoded[0] = 0x04 // uncompressed System.arraycopy(x, startOfX, encoded, elementSize - x.size + startOfX + 1, x.size - startOfX) System.arraycopy(y, startOfY, encoded, encoded.size - y.size + startOfY, y.size - startOfY) return encoded } private fun countLeadingZeros(bytes: ByteArray): Int { for (i in bytes.indices) { if (bytes[i] != 0.toByte()) { return i } } return bytes.size } } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/crypto/JceEcdhWithProvider.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import java.security.AlgorithmParameters import java.security.KeyFactory import java.security.KeyPairGenerator import java.security.Provider import javax.crypto.KeyAgreement internal class JceEcdhWithProvider(val provider: Provider): JceEcdh() { override fun newECKeyPairGenerator() = KeyPairGenerator.getInstance("EC", provider) override fun newECKeyFactory() = KeyFactory.getInstance("EC", provider) override fun newECAlgorithmParameters() = AlgorithmParameters.getInstance("EC", provider) override fun newECDHKeyAgreement() = KeyAgreement.getInstance("ECDH", provider) } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/crypto/QQEcdhJvm.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import net.mamoe.mirai.utils.decodeBase64 import java.security.KeyFactory import java.security.Signature import java.security.spec.X509EncodedKeySpec internal val publicKeyForVerify by lazy { KeyFactory.getInstance("RSA") .generatePublic(X509EncodedKeySpec("MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuJTW4abQJXeVdAODw1CamZH4QJZChyT08ribet1Gp0wpSabIgyKFZAOxeArcCbknKyBrRY3FFI9HgY1AyItH8DOUe6ajDEb6c+vrgjgeCiOiCVyum4lI5Fmp38iHKH14xap6xGaXcBccdOZNzGT82sPDM2Oc6QYSZpfs8EO7TYT7KSB2gaHz99RQ4A/Lel1Vw0krk+DescN6TgRCaXjSGn268jD7lOO23x5JS1mavsUJtOZpXkK9GqCGSTCTbCwZhI33CpwdQ2EHLhiP5RaXZCio6lksu+d8sKTWU1eEiEb3cQ7nuZXLYH7leeYFoPtbFV4RicIWp0/YG+RP7rLPCwIDAQAB".decodeBase64())) } internal actual fun QQEcdhInitialPublicKey.verify(sign: String): Boolean { val arrayForVerify = "305$version$keyStr".toByteArray() val signInstance = Signature.getInstance("SHA256WithRSA").apply { initVerify(publicKeyForVerify) update(arrayForVerify) } return signInstance.verify(sign.decodeBase64()) } ================================================ FILE: mirai-core/src/jvmBaseMain/kotlin/utils/crypto/RSA.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import net.mamoe.mirai.utils.decodeBase64 import net.mamoe.mirai.utils.encodeBase64 import java.security.KeyFactory import java.security.KeyPairGenerator import java.security.SecureRandom import java.security.spec.PKCS8EncodedKeySpec import java.security.spec.X509EncodedKeySpec import javax.crypto.Cipher internal actual fun rsaEncryptWithX509PubKey(input: ByteArray, plainPubPemKey: String, seed: Long): ByteArray { val encodedKey = plainPubPemKey .replace("\n", "") .removePrefix("-----BEGIN PUBLIC KEY-----") .removeSuffix("-----END PUBLIC KEY-----") .trim() .decodeBase64() val keyFactory = KeyFactory.getInstance("RSA") val rsaPubKey = keyFactory.generatePublic(X509EncodedKeySpec(encodedKey)) val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") cipher.init(Cipher.ENCRYPT_MODE, rsaPubKey, SecureRandom().apply { setSeed(seed) }) return cipher.doFinal(input) } internal actual fun rsaDecryptWithPKCS8PrivKey(input: ByteArray, plainPrivPemKey: String, seed: Long): ByteArray { val encodedKey = plainPrivPemKey .replace("\n", "") .removePrefix("-----BEGIN PRIVATE KEY-----") .removeSuffix("-----END PRIVATE KEY-----") .trim() .decodeBase64() val keyFactory = KeyFactory.getInstance("RSA") val rsaPubKey = keyFactory.generatePrivate(PKCS8EncodedKeySpec(encodedKey)) val cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding") cipher.init(Cipher.DECRYPT_MODE, rsaPubKey, SecureRandom().apply { setSeed(seed) }) return cipher.doFinal(input) } internal actual fun generateRSAKeyPair(keySize: Int): RSAKeyPair { val keyGen = KeyPairGenerator.getInstance("RSA") keyGen.initialize(keySize) val keyPair = keyGen.generateKeyPair() return RSAKeyPair( plainPubPemKey = buildString { appendLine("-----BEGIN PUBLIC KEY-----") keyPair.public.encoded.encodeBase64().chunked(64).forEach(::appendLine) appendLine("-----END PUBLIC KEY-----") }, plainPrivPemKey = buildString { appendLine("-----BEGIN PRIVATE KEY-----") keyPair.private.encoded.encodeBase64().chunked(64).forEach(::appendLine) appendLine("-----END PRIVATE KEY-----") } ) } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/event/EventChannelJavaTest.java ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package event; import net.mamoe.mirai.event.GlobalEventChannel; import net.mamoe.mirai.event.events.GroupMessageEvent; import net.mamoe.mirai.event.events.MessageEvent; import java.util.Objects; /** * 仅测试可以使用, 不会被编译运行 */ public class EventChannelJavaTest { public static void main(String[] args) { GlobalEventChannel.INSTANCE .filter(event -> Objects.equals(event.toString(), "test")) .filterIsInstance(MessageEvent.class) .subscribeAlways(GroupMessageEvent.class, groupMessageEvent -> { }); } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/event/EventLaunchUndispatchedTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.EventPriority import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.globalEventChannel import net.mamoe.mirai.internal.network.components.EVENT_LAUNCH_UNDISPATCHED import net.mamoe.mirai.internal.test.runBlockingUnit import org.junit.jupiter.api.AfterEach import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.Executors import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertSame @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") internal class EventLaunchUndispatchedTest : AbstractEventTest() { internal class TestEvent : AbstractEvent() private var originalValue = EVENT_LAUNCH_UNDISPATCHED @Test fun `event runs undispatched`() = runBlockingUnit { originalValue = EVENT_LAUNCH_UNDISPATCHED EVENT_LAUNCH_UNDISPATCHED = true doTest() EVENT_LAUNCH_UNDISPATCHED = originalValue } @Test fun `event runs undispatched fail`() = runBlockingUnit { originalValue = EVENT_LAUNCH_UNDISPATCHED EVENT_LAUNCH_UNDISPATCHED = false assertFails { doTest() } EVENT_LAUNCH_UNDISPATCHED = originalValue } private val dispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() @AfterEach fun cleanup() { dispatcher.close() } private suspend fun doTest() = coroutineScope { val invoked = ConcurrentLinkedQueue<Int>() val thread = Thread.currentThread() val job = SupervisorJob() globalEventChannel(dispatcher).parentJob(job).exceptionHandler {} // printing exception to stdout is very slow .run { subscribeAlways<TestEvent>(dispatcher, priority = EventPriority.MONITOR) { assertSame(thread, Thread.currentThread()) invoked.add(1) awaitCancellation() } repeat(1000) { i -> subscribeAlways<TestEvent>(dispatcher, priority = EventPriority.MONITOR) { assertSame(thread, Thread.currentThread()) invoked.add(i + 2) awaitCancellation() } } } launch(dispatcher, start = CoroutineStart.UNDISPATCHED) { TestEvent().broadcast() } // `launch` returns on first suspension point of `broadcast` // if EVENT_LAUNCH_UNDISPATCHED is `true`, all listeners run to `awaitCancellation` when `broadcast` is suspended // otherwise, they are put into tasks queue to be scheduled. 10000 tasks wont complete very quickly, so the following `invoked.size` check works. assertSame(thread, Thread.currentThread()) assertEquals(invoked.toList(), invoked.sorted()) assertEquals(1000 + 1, invoked.size) job.cancel() } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/event/JvmMethodEventsTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("RedundantSuspendModifier", "unused", "UNUSED_PARAMETER") package net.mamoe.mirai.internal.event import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.isActive import kotlinx.coroutines.runBlocking import net.mamoe.mirai.event.* import org.jetbrains.annotations.NotNull import java.util.concurrent.atomic.AtomicInteger import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.test.* internal class JvmMethodEventsTest : AbstractEventTest() { @Test fun testMethodListener() { class TestClass : ListenerHost, CoroutineScope by CoroutineScope(EmptyCoroutineContext) { private var called = AtomicInteger(0) fun getCalled() = called.get() @Suppress("unused") @EventHandler suspend fun TestEvent.`suspend receiver param Unit`(event: TestEvent) { called.getAndIncrement() } @Suppress("unused") @EventHandler suspend fun TestEvent.`suspend receiver Unit`() { called.getAndIncrement() } @Suppress("unused") @EventHandler suspend fun `suspend param Unit`(event: TestEvent) { called.getAndIncrement() } @EventHandler @Suppress("unused") fun TestEvent.`receiver param Unit`(event: TestEvent) { called.getAndIncrement() } @EventHandler @Suppress("unused") suspend fun TestEvent.`suspend receiver param LS`(event: TestEvent): ListeningStatus { called.getAndIncrement() return ListeningStatus.STOPPED } @Suppress("unused") @EventHandler suspend fun TestEvent.`suspend receiver LS`(): ListeningStatus { called.getAndIncrement() return ListeningStatus.STOPPED } @EventHandler @Suppress("unused") suspend fun `suspend param LS`(event: TestEvent): ListeningStatus { called.getAndIncrement() return ListeningStatus.STOPPED } @EventHandler @Suppress("unused") private fun TestEvent.`test annotations`(@NotNull event: TestEvent): ListeningStatus { called.getAndIncrement() return ListeningStatus.STOPPED } @EventHandler @Suppress("unused") fun TestEvent.`receiver param LS`(event: TestEvent): ListeningStatus { called.getAndIncrement() return ListeningStatus.STOPPED } } TestClass().run { this.globalEventChannel().registerListenerHost(this) runBlocking { TestEvent().broadcast() } assertEquals(9, this.getCalled()) cancel() // reset listeners } } @Test fun testExceptionHandle() { class MyException : RuntimeException() class TestClass : SimpleListenerHost() { var exceptionHandled: Boolean = false override fun handleException(context: CoroutineContext, exception: Throwable) { assert(exception is ExceptionInEventHandlerException) assert(exception.event is TestEvent) assert(exception.rootCause is MyException) exceptionHandled = true } @Suppress("unused") @EventHandler private suspend fun TestEvent.test() { throw MyException() } } TestClass().run { this.globalEventChannel().registerListenerHost(this) runBlocking { TestEvent().broadcast() } cancel() // reset listeners if (!exceptionHandled) { fail("SimpleListenerHost.handleException not invoked") } } TestClass().run { globalEventChannel().registerListenerHost(this) runBlocking { TestEvent().broadcast() } cancel() // reset listeners if (!exceptionHandled) { fail("SimpleListenerHost.handleException not invoked") } } TestClass().run { val scope = CoroutineScope(EmptyCoroutineContext) scope.globalEventChannel().registerListenerHost(this) runBlocking { TestEvent().broadcast() } cancel() // reset listeners scope.cancel() if (!exceptionHandled) { fail("SimpleListenerHost.handleException not invoked") } } } @Test fun testIntercept() { class TestClass : ListenerHost, CoroutineScope by CoroutineScope(EmptyCoroutineContext) { private var called = AtomicInteger(0) fun getCalled() = called.get() @Suppress("unused") @EventHandler(EventPriority.HIGHEST) private suspend fun TestEvent.`suspend receiver param Unit`(event: TestEvent) { intercept() called.getAndIncrement() } @EventHandler(EventPriority.MONITOR) @Suppress("unused") private fun TestEvent.`receiver param LS`(event: TestEvent): ListeningStatus { called.getAndIncrement() return ListeningStatus.STOPPED } } TestClass().run { this.globalEventChannel().registerListenerHost(this) runBlocking { TestEvent().broadcast() } cancel() // reset listeners assertEquals(1, this.getCalled()) } } @Test fun testCancellation() { class TestingListener : SimpleListenerHost() { var handled: Boolean = false @EventHandler fun handle(event: TestEvent) { handled = true } } runBlocking { TestingListener().runTesting(this.globalEventChannel()) { TestEvent().broadcast() assertTrue { handled } } // registered listeners cancelled when parent scope cancelled CoroutineScope(EmptyCoroutineContext).let { scope -> TestingListener().runTesting(scope.globalEventChannel()) { listener -> scope.cancel() TestEvent().broadcast() assertFalse { handled } assertTrue { listener.isActive } } } // registered listeners cancelled when ListenerHost cancelled CoroutineScope(EmptyCoroutineContext).let { scope -> val listener = TestingListener() listener.runTesting(scope.globalEventChannel()) { } assertFalse { listener.isActive } assertTrue { scope.isActive } TestEvent().broadcast() assertFalse { listener.handled } scope.cancel() } } } private inline fun <T> T.runTesting( channel: EventChannel<*>, block: T.(T) -> Unit ) where T : SimpleListenerHost { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } channel.registerListenerHost(this) block(this, this) cancel() } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/event/JvmMethodEventsTestJava.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.cancel import kotlinx.coroutines.test.runTest import net.mamoe.mirai.event.* import net.mamoe.mirai.utils.childScope import java.util.concurrent.atomic.AtomicInteger import kotlin.test.Test import kotlin.test.assertEquals internal class JvmMethodEventsTestJava : AbstractEventTest() { private val called = AtomicInteger(0) @Test fun test() = runTest { val host = TestHost(called) val scope = this.childScope() scope.globalEventChannel().registerListenerHost(host) TestEvent().broadcast() assertEquals(3, called.get(), null) host.cancel() // reset listeners scope.cancel() } } @Suppress( "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RedundantVisibilityModifier", "UNUSED_PARAMETER", "unused", "RedundantNullableReturnType" ) @net.mamoe.mirai.utils.EventListenerLikeJava internal class TestHost( private val called: AtomicInteger ) : SimpleListenerHost() { @EventHandler public fun ev(event: TestEvent?) { called.incrementAndGet() } @EventHandler public fun ev2(event: TestEvent?): Void? { called.incrementAndGet() return null } @EventHandler public fun ev3(event: TestEvent?): ListeningStatus? { called.incrementAndGet() return ListeningStatus.LISTENING } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/event/SimpleListenerHostTestJava.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking import net.mamoe.mirai.event.* import net.mamoe.mirai.utils.AtomicBoolean import net.mamoe.mirai.utils.JavaFriendlyAPI import kotlin.coroutines.EmptyCoroutineContext import kotlin.test.Test @JavaFriendlyAPI @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @net.mamoe.mirai.utils.EventListenerLikeJava internal class SimpleListenerHostTestJava : AbstractEventTest() { @Test fun testJavaSimpleListenerHostWork() { val called = AtomicBoolean(false) val host: SimpleListenerHost = object : SimpleListenerHost() { @EventHandler @net.mamoe.mirai.utils.EventListenerLikeJava @Suppress("unused") fun testListen( event: AbstractEvent? ) { println(event) called.value = true } } val scope = CoroutineScope(EmptyCoroutineContext) scope.globalEventChannel().registerListenerHost(host) runBlocking { object : AbstractEvent() {}.broadcast() } if (!called.value) { throw AssertionError("JavaTest: SimpleListenerHost Failed.") } } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/network/component/ComponentKeyTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import net.mamoe.mirai.internal.network.component.ComponentKey.Companion.componentName import net.mamoe.mirai.internal.network.component.ComponentKey.Companion.smartToString import net.mamoe.mirai.internal.test.AbstractTest import kotlin.test.Test import kotlin.test.assertEquals private open class TestComponent { companion object : ComponentKey<TestComponent> } private interface MyInterface private open class TestComponentExt : TestComponent(), MyInterface private open class Key<R> : ComponentKey<R> where R : TestComponent, R : MyInterface private class KeyActual : Key<TestComponentExt>() internal class ComponentKeyTest : AbstractTest() { @Test fun testComponentName() { assertEquals("TestComponent", TestComponent.componentName(false)) assertEquals(TestComponent::class.qualifiedName!!, TestComponent.componentName(true)) } @Test fun `componentName with erased type argument`() { assertEquals( "TestComponent & MyInterface", Key<TestComponentExt>().componentName(false) ) assertEquals( "${TestComponent::class.qualifiedName!!} & ${MyInterface::class.qualifiedName!!}", Key<TestComponentExt>().componentName(true) ) } @Test fun `componentName with actual type argument`() { assertEquals( "TestComponentExt", KeyActual().componentName(false) ) assertEquals( TestComponentExt::class.qualifiedName!!, KeyActual().componentName(true) ) } @Test fun `test smartToString`() { assertEquals("ComponentKey<TestComponent>", TestComponent.smartToString(false)) assertEquals("ComponentKey<${TestComponent::class.qualifiedName!!}>", TestComponent.smartToString(true)) assertEquals( "ComponentKey<${TestComponent::class.qualifiedName!!} & ${MyInterface::class.qualifiedName!!}>", Key<TestComponentExt>().smartToString(true) ) assertEquals("ComponentKey<${TestComponentExt::class.qualifiedName!!}>", KeyActual().smartToString(true)) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/network/component/ConcurrentComponentStorageToStringTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.component import kotlin.test.Test import kotlin.test.assertEquals internal class ConcurrentComponentStorageTest : AbstractMutableComponentStorageTest() { internal data class TestComponent2( val value: Int ) { companion object : ComponentKey<TestComponent2> } internal data class TestComponent3( val value: Int ) { companion object : ComponentKey<TestComponent3> } override fun createStorage(): MutableComponentStorage = ConcurrentComponentStorage(showAllComponents = true) @Test fun `test toString non debug`() { val storage = ConcurrentComponentStorage(showAllComponents = false).apply { set(TestComponent2, TestComponent2(1)) } assertEquals("ConcurrentComponentStorage(size=1)", storage.toString()) } @Test fun `test toString debugging`() { val storage = ConcurrentComponentStorage(showAllComponents = true).apply { set(TestComponent2, TestComponent2(1)) } assertEquals( """ConcurrentComponentStorage(size=1) { | TestComponent2: TestComponent2(value=1) |}""".trimMargin(), storage.toString() ) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/network/framework/AbstractCommonNHTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import kotlinx.coroutines.ExecutorCoroutineDispatcher import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import kotlin.test.AfterTest /** * Without selector. When network is closed, it will not reconnect, so that you can check for its states. * * @see AbstractCommonNHTestWithSelector */ internal actual abstract class AbstractCommonNHTest actual constructor() : AbstractRealNetworkHandlerTest<TestCommonNetworkHandler>() { private val startedDispatchers = mutableListOf<ExecutorCoroutineDispatcher>() @AfterTest fun cleanupDispatchers() { startedDispatchers.forEach { it.close() } } actual override val factory: NetworkHandlerFactory<TestCommonNetworkHandler> = NetworkHandlerFactory<TestCommonNetworkHandler> { context, address -> object : TestCommonNetworkHandler(bot, context, address) { override suspend fun createConnection(): PlatformConn { return conn.apply { doRegister() // restart channel // setupChannelPipeline( // pipeline(), PacketDecodePipeline( // coroutineContext.plus( // NioEventLoopGroup().asCoroutineDispatcher().also { startedDispatchers.add(it) }) // ) // ) } } } } protected actual fun removeOutgoingPacketEncoder() { kotlin.runCatching { conn.pipeline().remove("outgoing-packet-encoder") } } actual val conn: PlatformConn = NettyNHTestChannel() } internal actual typealias PlatformConn = NettyNHTestChannel ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/network/framework/AbstractNettyNHTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework internal abstract class AbstractNettyNHTest : AbstractCommonNHTest() { } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/network/framework/NettyNHTestChannel.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.framework import io.ktor.utils.io.core.* import io.netty.channel.embedded.EmbeddedChannel import io.netty.util.ReferenceCountUtil import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.serializer import net.mamoe.mirai.internal.network.components.RawIncomingPacket import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacket import net.mamoe.mirai.internal.utils.io.ProtoBuf import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.error internal class NettyNHTestChannel( var fakeServer: (NettyNHTestChannel.(msg: Any?) -> Unit)?, ) : EmbeddedChannel() { constructor() : this(null) @OptIn(InternalSerializationApi::class) fun listen(listener: (OutgoingPacket) -> Any?) { fakeServer = { packet -> if (packet is OutgoingPacket) { val rsp0 = when (val rsp = listener(packet)) { null -> null is Unit -> null is ByteArray -> { RawIncomingPacket( commandName = packet.commandName, sequenceId = packet.sequenceId, body = rsp ) } is RawIncomingPacket -> rsp is ProtoBuf -> { RawIncomingPacket( commandName = packet.commandName, sequenceId = packet.sequenceId, body = buildPacket { writeProtoBuf( rsp::class.serializer().cast<KSerializer<ProtoBuf>>(), rsp ) }.readBytes() ) } else -> { logger.error { "Failed to respond $rsp" } null } } if (rsp0 != null) { pipeline().fireChannelRead(rsp0) } } ReferenceCountUtil.release(packet) } } public /*internal*/ override fun doRegister() { super.doRegister() // Set channel state to ACTIVE // Drop old handlers pipeline().let { p -> while (p.first() != null) { p.removeFirst() } } } override fun handleInboundMessage(msg: Any?) { ReferenceCountUtil.release(msg) // Not handled, Drop } override fun handleOutboundMessage(msg: Any?) { fakeServer?.invoke(this, msg) ?: ReferenceCountUtil.release(msg) } companion object { private val logger by lazy { MiraiLogger.Factory.create(NettyNHTestChannel::class) } } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/network/impl/netty/CommonNHUtilsTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.network.impl.netty import io.netty.channel.DefaultChannelPromise import io.netty.channel.embedded.EmbeddedChannel import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.launch import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.test.runBlockingUnit import org.junit.jupiter.api.AfterAll import kotlin.test.Test import kotlin.test.assertFailsWith import kotlin.test.assertTrue import kotlin.time.Duration.Companion.seconds /** * @see awaitKt */ internal class CommonNHUtilsTest : AbstractTest() { companion object { private val channel = EmbeddedChannel() @JvmStatic @AfterAll fun afterAll() { channel.close() } } @Test fun canAwait() = runBlockingUnit(timeout = 5.seconds) { val future = DefaultChannelPromise(channel) launch(start = CoroutineStart.UNDISPATCHED) { future.awaitKt() } launch { future.setSuccess() } } @Test fun returnsImmediatelyIfCompleted() = runBlockingUnit(timeout = 5.seconds) { val future = DefaultChannelPromise(channel) future.setSuccess() future.awaitKt() } @Test fun testAwait() { class MyError : AssertionError("My") // coroutine debugger will modify the exception if inside coroutine runBlockingUnit(timeout = 5.seconds) { val future = DefaultChannelPromise(channel) launch(start = CoroutineStart.UNDISPATCHED) { assertFailsWith<AssertionError> { future.awaitKt() }.let { actual -> assertTrue { actual is MyError } } } launch { future.setFailure(MyError()) } } } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/test/AbstractTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.test import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.debug.DebugProbes import kotlinx.coroutines.test.StandardTestDispatcher import net.mamoe.mirai.internal.testFramework.TestFactory import org.junit.jupiter.api.BeforeAll import org.junit.jupiter.api.TestInfo import org.junit.jupiter.api.Timeout import java.util.concurrent.TimeUnit import kotlin.jvm.optionals.getOrNull import kotlin.reflect.KClass import kotlin.reflect.full.functions import kotlin.reflect.full.hasAnnotation @Timeout(value = 7, unit = TimeUnit.MINUTES) actual abstract class AbstractTest actual constructor() { actual fun borrowSingleThreadDispatcher(): CoroutineDispatcher { return StandardTestDispatcher() } @OptIn(ExperimentalCoroutinesApi::class) actual companion object { @BeforeAll @JvmStatic fun checkTestFactories(testInfo: TestInfo) { val clazz = testInfo.testClass.getOrNull()?.kotlin ?: return for (function in clazz.functions) { if (function.hasAnnotation<TestFactory>()) { check(function.returnType.classifier == List::class) { "Illegal TestFactory function. A such function must return DynamicTestsResult." } check((function.returnType.classifier as? KClass<*>)?.qualifiedName == List::class.qualifiedName) { "Illegal TestFactory function. A such function must return DynamicTestsResult." } } } } init { initializeTestPlatformBeforeCommon() initializeTestCommon() runCatching { DebugProbes.install() } } } } internal expect fun initializeTestPlatformBeforeCommon() /* override MiraiLogger.Factory implementation on AndroidUnitTest */ ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/test/NativeTestWrapper.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.test ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/DebugProbes.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework import kotlinx.coroutines.ExperimentalCoroutinesApi @Suppress("ACTUAL_WITHOUT_EXPECT", "NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") @OptIn(ExperimentalCoroutinesApi::class) actual typealias DebugProbes = kotlinx.coroutines.debug.DebugProbes ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/DynamicTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework actual typealias TestFactory = org.junit.jupiter.api.TestFactory actual typealias DynamicTest = org.junit.jupiter.api.DynamicTest @Suppress("ACTUAL_TYPE_ALIAS_TO_CLASS_WITH_DECLARATION_SITE_VARIANCE", "ACTUAL_WITHOUT_EXPECT") actual typealias DynamicTestsResult = List<*> actual fun dynamicTest(displayName: String, action: () -> Unit): DynamicTest { return DynamicTest.dynamicTest(displayName, action) } actual fun runDynamicTests(dynamicTests: List<DynamicTest>): DynamicTestsResult = dynamicTests ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/RemoveDefaultValuesVisitor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc import net.mamoe.mirai.internal.testFramework.codegen.descriptors.accept import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitorUnit import net.mamoe.mirai.internal.testFramework.codegen.visitors.AnalyzeDefaultValuesMappingVisitor import net.mamoe.mirai.internal.testFramework.codegen.visitors.DefaultValuesMapping import kotlin.reflect.KParameter fun ValueDesc.removeDefaultValues(): ValueDesc { val def = AnalyzeDefaultValuesMappingVisitor() this.accept(def) this.accept(RemoveDefaultValuesVisitor(def.mappings)) return this } class RemoveDefaultValuesVisitor( private val mappings: MutableList<DefaultValuesMapping>, ) : ValueDescVisitorUnit { override fun visitValue(desc: ValueDesc, data: Nothing?) { desc.acceptChildren(this, data) } override fun <T : Any> visitClass(desc: ClassValueDesc<T>, data: Nothing?) { super.visitClass(desc, data) val mapping = mappings.find { it.forClass == desc.type }?.mapping ?: return // remove properties who have the same values as their default values, this would significantly reduce code size. mapping.forEach { (name, defaultValue) -> if (desc.properties.entries.removeIf { it.key.name == name && isDefaultOrEmpty(it.key, it.value, defaultValue) }) { return@forEach // by removing one property, there will not by any other matches } } } private fun isDefaultOrEmpty(key: KParameter, value: ValueDesc, defaultValue: Any?): Boolean { if (!key.isOptional) return false if (equals(value.origin, defaultValue)) return true if (value is ClassValueDesc<*> && value.properties.all { it.key.isOptional && isDefaultOrEmpty(it.key, it.value, defaultValue) } ) { return true } return false } private fun Any?.isNullOrZeroOrEmpty(): Boolean { when (this) { null, 0.toByte(), 0.toShort(), 0, 0L, 0.toFloat(), 0.toDouble(), 0.toChar(), "", listOf<Any>(), setOf<Any>(), mapOf<Any, Any>(), -> return true } check(this != null) when { this is Array<*> && this.isEmpty() -> return true this is IntArray && this.isEmpty() -> return true this is ByteArray && this.isEmpty() -> return true this is ShortArray && this.isEmpty() -> return true this is LongArray && this.isEmpty() -> return true this is CharArray && this.isEmpty() -> return true this is FloatArray && this.isEmpty() -> return true this is DoubleArray && this.isEmpty() -> return true this is BooleanArray && this.isEmpty() -> return true } return false } fun equals(a: Any?, b: Any?): Boolean { if (a.isNullOrZeroOrEmpty() && b.isNullOrZeroOrEmpty()) return true return when { a === b -> true a == b -> true a is Array<*>? && b is Array<*>? -> a.contentEquals(b) a is IntArray? && b is IntArray? -> a.contentEquals(b) a is ByteArray? && b is ByteArray? -> a.contentEquals(b) a is ShortArray? && b is ShortArray? -> a.contentEquals(b) a is LongArray? && b is LongArray? -> a.contentEquals(b) a is CharArray? && b is CharArray? -> a.contentEquals(b) a is FloatArray? && b is FloatArray? -> a.contentEquals(b) a is DoubleArray? && b is DoubleArray? -> a.contentEquals(b) a is BooleanArray? && b is BooleanArray? -> a.contentEquals(b) else -> false } } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/ValueDescAnalyzer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalStdlibApi::class) package net.mamoe.mirai.internal.testFramework.codegen import kotlinx.serialization.Serializable import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* import net.mamoe.mirai.utils.cast import kotlin.reflect.KParameter import kotlin.reflect.KProperty1 import kotlin.reflect.KType import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.hasAnnotation import kotlin.reflect.full.primaryConstructor import kotlin.reflect.full.valueParameters import kotlin.reflect.typeOf object ValueDescAnalyzer { private val anyType = typeOf<Any>() /** * Analyze [value] and give its correspondent [ValueDesc]. */ fun analyze(value: Any?, type: KType): ValueDesc { if (value == null) return PlainValueDesc(null, "null", null) val clazz = value::class if (clazz.isData || clazz.hasAnnotation<Serializable>()) { val primaryConstructor = clazz.primaryConstructor ?: error("$value does not have a primary constructor.") val properties = clazz.declaredMemberProperties val map = mutableMapOf<KParameter, ValueDesc>() for (valueParameter in primaryConstructor.valueParameters) { val prop = properties.find { it.name == valueParameter.name } ?: error("Could not find corresponding property for parameter ${clazz.qualifiedName}.${valueParameter.name}") prop.cast<KProperty1<Any, Any?>>() map[valueParameter] = analyze(prop.get(value), prop.returnType) } return ClassValueDesc(null, value, map) } CollectionLikeValueDesc.createOrNull(value, type, null)?.let { return it } if (value is Collection<*>) { return CollectionValueDesc( null, value, arrayType = type, elementType = type.arguments.firstOrNull()?.type ?: anyType ) } else if (value is Map<*, *>) { return MapValueDesc( null, value.cast(), value.cast(), type, type.arguments.firstOrNull()?.type ?: anyType, type.arguments.getOrNull(1)?.type ?: anyType ) } return when (value) { is CharSequence -> { PlainValueDesc(null, '"' + value.toString() + '"', value) } is Char -> { PlainValueDesc(null, "'$value'", value) } else -> PlainValueDesc(null, value.toString(), value) } } } inline fun <reified T> ValueDescAnalyzer.analyze(value: T): ValueDesc { return analyze(value, typeOf<T>()) } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/ClassValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor import kotlin.reflect.KClass import kotlin.reflect.KParameter class ClassValueDesc<T : Any>( override val parent: ValueDesc?, override val origin: T, val properties: MutableMap<KParameter, ValueDesc>, ) : ValueDesc { val type: KClass<out T> by lazy { origin::class } override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R { return visitor.visitClass(this, data) } override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) { properties.forEach { (_, u) -> u.accept(visitor, data) } } override fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) { val result = mutableMapOf<KParameter, ValueDesc>() for (entry in this.properties.entries) { entry.value.acceptChildren(visitor, data) val newValue = entry.value.accept(visitor, data) if (newValue != null) { result[entry.key] = newValue } } this.properties.clear() this.properties.putAll(result) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/CollectionLikeValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor import kotlin.reflect.KType import kotlin.reflect.typeOf sealed interface CollectionLikeValueDesc : ValueDesc { val value: Any val arrayType: KType val elementType: KType val elements: MutableList<ValueDesc> override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) { for (element in elements) { element.accept(visitor, data) } } override fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) { elements.transform { it.accept(visitor, data) } } companion object { @OptIn(ExperimentalStdlibApi::class) fun createOrNull(array: Any, type: KType, parent: ValueDesc?): CollectionLikeValueDesc? { if (array is Array<*>) return ObjectArrayValueDesc(parent, array, arrayType = type) return when (array) { is IntArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Int>()) is ByteArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Byte>()) is ShortArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Short>()) is CharArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Char>()) is LongArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Long>()) is FloatArray -> PrimitiveArrayValueDesc(parent, array, arrayType = type, elementType = typeOf<Float>()) is DoubleArray -> PrimitiveArrayValueDesc( parent, array, arrayType = type, elementType = typeOf<Double>() ) is BooleanArray -> PrimitiveArrayValueDesc( parent, array, arrayType = type, elementType = typeOf<Boolean>() ) else -> return null } } } } fun <E> MutableList<E>.transform(transformer: (E) -> E?) { val result = this.asSequence().mapNotNull(transformer).toMutableList() this.clear() this.addAll(result) } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/CollectionValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor import kotlin.reflect.KType import kotlin.reflect.full.createType class CollectionValueDesc( override val parent: ValueDesc?, value: Collection<*>, override val origin: Collection<*> = value, override val arrayType: KType, override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType() ) : CollectionLikeValueDesc { override var value: Collection<*> = value set(value) { field = value elements.clear() elements.addAll(initializeElements(value)) } override val elements: MutableList<ValueDesc> by lazy { initializeElements(value) } private fun initializeElements(value: Collection<*>) = value.mapTo(ArrayList(value.size)) { ValueDescAnalyzer.analyze(it, elementType) } override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R = visitor.visitCollection(this, data) } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/MapValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor import kotlin.reflect.KType import kotlin.reflect.full.createType class MapValueDesc( override val parent: ValueDesc?, var value: Map<Any?, Any?>, override val origin: Map<Any?, Any?> = value, val mapType: KType, val keyType: KType = mapType.arguments.first().type ?: Any::class.createType(), val valueType: KType = mapType.arguments[1].type ?: Any::class.createType(), ) : ValueDesc { val elements: MutableMap<ValueDesc, ValueDesc> by lazy { value.map { ValueDescAnalyzer.analyze(it.key, keyType) to ValueDescAnalyzer.analyze( it.value, valueType ) }.toMap(mutableMapOf()) } override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R = visitor.visitMap(this, data) override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) { for ((key, value) in elements.entries) { key.accept(visitor, data) value.accept(visitor, data) } } override fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) { val resultMap = mutableMapOf<ValueDesc, ValueDesc>() for (entry in this.elements.entries) { val newKey = entry.key.accept(visitor, data) val newValue = entry.value.accept(visitor, data) if (newKey != null && newValue != null) { resultMap[newKey] = newValue } } this.elements.clear() this.elements.putAll(resultMap) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/ObjectArrayValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor import kotlin.reflect.KType import kotlin.reflect.full.createType class ObjectArrayValueDesc( override val parent: ValueDesc?, value: Array<*>, override val origin: Array<*> = value, override val arrayType: KType, override val elementType: KType = arrayType.arguments.first().type ?: Any::class.createType(), ) : CollectionLikeValueDesc { override var value: Array<*> = value set(value) { field = value elements.clear() elements.addAll(initializeElements(value)) } override val elements: MutableList<ValueDesc> by lazy { initializeElements(value) } private fun initializeElements(value: Array<*>) = value.mapTo(ArrayList(value.size)) { ValueDescAnalyzer.analyze(it, elementType) } override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R = visitor.visitObjectArray(this, data) } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/PlainValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor class PlainValueDesc( override val parent: ValueDesc?, var value: String, override val origin: Any? ) : ValueDesc { init { require(value.isNotBlank()) } override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R { return visitor.visitPlain(this, data) } override fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) { } override fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) { } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/PrimitiveArrayValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor import kotlin.reflect.KType class PrimitiveArrayValueDesc( override val parent: ValueDesc?, value: Any, override val origin: Any = value, override val arrayType: KType, override val elementType: KType ) : CollectionLikeValueDesc { override var value: Any = value set(value) { field = value elements.clear() elements.addAll(initializeElements(value)) } override val elements: MutableList<ValueDesc> by lazy { initializeElements(value) } private fun initializeElements(value: Any): MutableList<ValueDesc> = when (value) { is IntArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } is ByteArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } is ShortArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } is CharArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } is LongArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } is FloatArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } is DoubleArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } is BooleanArray -> value.mapTo(mutableListOf()) { ValueDescAnalyzer.analyze(it, elementType) } else -> error("$value is not an array.") } override fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R { return visitor.visitPrimitiveArray(this, data) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/descriptors/ValueDesc.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.descriptors import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor sealed interface ValueDesc { val origin: Any? val parent: ValueDesc? fun <D, R> accept(visitor: ValueDescVisitor<D, R>, data: D): R fun <D> acceptChildren(visitor: ValueDescVisitor<D, *>, data: D) fun <D> transform(visitor: ValueDescTransformer<D>, data: D): ValueDesc? = this.accept(visitor, data) fun <D> transformChildren(visitor: ValueDescTransformer<D>, data: D) } fun <R> ValueDesc.accept(visitor: ValueDescVisitor<Nothing?, R>): R = accept(visitor, null) fun ValueDesc.transform(visitor: ValueDescTransformer<Nothing?>) = transform(visitor, null) fun ValueDesc.transform(visitor: ValueDescTransformerNotNull<Nothing?>) = transform(visitor, null)!! fun <R> ValueDesc.acceptChildren(visitor: ValueDescVisitor<Nothing?, R>) = acceptChildren(visitor, null) fun ValueDesc.transformChildren(visitor: ValueDescTransformer<Nothing?>) = transformChildren(visitor, null) val ValueDesc.parents get() = sequence { var parent = parent do { parent ?: return@sequence yield(parent) parent = parent.parent } while (true) } inline fun <reified T : ValueDesc> ValueDesc.findParent(): T? = parents.filterIsInstance<T>().firstOrNull() ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/test/IndenterTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.test import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.testFramework.codegen.visitors.WordingIndenter import kotlin.test.Test import kotlin.test.assertEquals internal class IndenterTest : AbstractTest() { @Test fun `can indentize`() { assertEquals(" test", WordingIndenter.spacing(4).indentize("test")) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/test/OptimizeByteArrayAsHexStringTransformerTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.test import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.analyze import net.mamoe.mirai.internal.testFramework.codegen.descriptors.transform import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitors.ValueDescToStringRenderer import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString import kotlin.test.Test import kotlin.test.assertEquals internal class OptimizeByteArrayAsHexStringTransformerTest : AbstractTest() { private inline fun <reified T> analyzeTransformAndRender( value: T, renderer: ValueDescToStringRenderer = ValueDescToStringRenderer() ): String { return ValueDescAnalyzer.analyze(value) .transform(OptimizeByteArrayAsHexStringTransformer()) .renderToString(renderer) } @Test fun `can optimize as string`() { assertEquals( """ "test".toByteArray() /* 74 65 73 74 */ """.trimIndent(), analyzeTransformAndRender("test".toByteArray()) ) } @Test fun `can optimize as hex`() { assertEquals( """ "4F 02".hexToBytes() """.trimIndent(), analyzeTransformAndRender(byteArrayOf(0x4f, 0x02)) ) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/test/visitors/ValueDescAnalyzerTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.test.visitors import net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg import net.mamoe.mirai.internal.testFramework.codegen.RemoveDefaultValuesVisitor import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.analyze import net.mamoe.mirai.internal.testFramework.codegen.descriptors.accept import net.mamoe.mirai.internal.testFramework.codegen.visitors.* import kotlin.test.Test import kotlin.test.assertEquals class ValueDescAnalyzerTest { private inline fun <reified T> analyzeAndRender( value: T ): String { return ValueDescAnalyzer.analyze(value).renderToString( rendererContext = RendererContext( indenter = Indenter.NoIndent, classFormatter = object : ClassFormatter() { override fun formatClassProperty( context: ClassFormatterContext, propertyString: String?, valueString: String ): String = "$propertyString=$valueString" } ), ) } @Test fun `test plain`() { assertEquals( "\"test\"", analyzeAndRender("test") ) assertEquals( "1", analyzeAndRender(1) ) assertEquals( "1.0", analyzeAndRender(1.0) ) } @Test fun `test array`() { assertEquals( "arrayOf(1, 2)", analyzeAndRender(arrayOf(1, 2)) ) assertEquals( "arrayOf(5.0)", analyzeAndRender(arrayOf(5.0)) ) assertEquals( "arrayOf(\"1\")", analyzeAndRender(arrayOf("1")) ) assertEquals( """ arrayOf( arrayOf(1), ) """.trimIndent(), analyzeAndRender(arrayOf(arrayOf(1))) ) } data class TestClass( val value: String ) data class TestClass2( val value: Any ) @Test fun `test class`() { assertEquals( """ ${TestClass::class.qualifiedName!!}( value="test", ) """.trimIndent(), analyzeAndRender(TestClass("test")) ) assertEquals( """ ${TestClass2::class.qualifiedName!!}( value="test", ) """.trimIndent(), analyzeAndRender(TestClass2("test")) ) assertEquals( """ ${TestClass2::class.qualifiedName!!}( value=1, ) """.trimIndent(), analyzeAndRender(TestClass2(1)) ) } data class TestNesting( val nested: Nested ) { data class Nested( val value: String ) } @Test fun `test nesting`() { assertEquals( """ ${TestNesting::class.qualifiedName}( nested=${TestNesting.Nested::class.qualifiedName}( value="test", ), ) """.trimIndent(), analyzeAndRender(TestNesting(TestNesting.Nested("test"))) ) } @Test fun `test complex nesting`() { assertEquals( """ net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.StructMsg( version = 1, msgType = 2, msgSeq = 1630, msgTime = 1630, reqUin = 1230, msg = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsg( subType = 1, msgTitle = "邀请加群", msgDescribe = "邀请你加入 %group_name%", actions = mutableListOf( net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgAction( name = "拒绝", result = "已拒绝", actionInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgActionInfo( type = 12, groupCode = 2230203, ), detailName = "拒绝", ), net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgAction( name = "同意", result = "已同意", actionInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgActionInfo( type = 11, groupCode = 2230203, ), detailName = "同意", ), net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgAction( name = "忽略", result = "已忽略", actionInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.SystemMsgActionInfo( type = 14, groupCode = 2230203, ), detailName = "忽略", ), ), groupCode = 2230203, actionUin = 1230001, groupMsgType = 2, groupInviterRole = 1, groupInfo = net.mamoe.mirai.internal.network.protocol.data.proto.Structmsg.GroupInfo( appPrivilegeFlag = 67698880, ), reqUinNick = "user3", groupName = "testtest", actionUinNick = "user1", groupExtFlag = 1075905600, actionUinQqNick = "user1", reqUinGender = 255, c2cInviteJoinGroupFlag = 1, ), ) """.trimIndent(), ValueDescAnalyzer.analyze( Structmsg.StructMsg( version = 1, msgType = 2, msgSeq = 1630, msgTime = 1630, reqUin = 1230, msg = Structmsg.SystemMsg( subType = 1, msgTitle = "邀请加群", msgDescribe = "邀请你加入 %group_name%", actions = mutableListOf( Structmsg.SystemMsgAction( name = "拒绝", result = "已拒绝", actionInfo = Structmsg.SystemMsgActionInfo( type = 12, groupCode = 2230203, ), detailName = "拒绝", ), Structmsg.SystemMsgAction( name = "同意", result = "已同意", actionInfo = Structmsg.SystemMsgActionInfo( type = 11, groupCode = 2230203, ), detailName = "同意", ), Structmsg.SystemMsgAction( name = "忽略", result = "已忽略", actionInfo = Structmsg.SystemMsgActionInfo( type = 14, groupCode = 2230203, ), detailName = "忽略", ), ), groupCode = 2230203, actionUin = 1230001, groupMsgType = 2, groupInviterRole = 1, groupInfo = Structmsg.GroupInfo( appPrivilegeFlag = 67698880, ), reqUinNick = "user3", groupName = "testtest", actionUinNick = "user1", groupExtFlag = 1075905600, actionUinQqNick = "user1", reqUinGender = 255, c2cInviteJoinGroupFlag = 1, ), ) ).apply { val def = AnalyzeDefaultValuesMappingVisitor() accept(def) accept(RemoveDefaultValuesVisitor(def.mappings)) }.renderToString(ValueDescToStringRenderer()) ) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/test/visitors/ValueDescToStringRendererTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalStdlibApi::class) package net.mamoe.mirai.internal.testFramework.codegen.test.visitors import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.analyze import net.mamoe.mirai.internal.testFramework.codegen.visitors.ValueDescToStringRenderer import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString import kotlin.test.Test import kotlin.test.assertEquals internal class ValueDescToStringRendererTest { private val renderer = ValueDescToStringRenderer() @Test fun `plain value`() { assertEquals("\"str\"", ValueDescAnalyzer.analyze("str").renderToString(renderer)) assertEquals("1", ValueDescAnalyzer.analyze(1).renderToString(renderer)) } @Test fun `object array value`() { assertEquals( "arrayOf(\"str\", \"obj\")", ValueDescAnalyzer.analyze(arrayOf("str", "obj")).renderToString(renderer) ) assertEquals("arrayOf(1, 2)", ValueDescAnalyzer.analyze(arrayOf(1, 2)).renderToString(renderer)) } @Test fun `object array value nested`() { assertEquals( """ arrayOf( arrayOf("str", "obj"), arrayOf("str", "obj"), ) """.trimIndent(), ValueDescAnalyzer.analyze(arrayOf(arrayOf("str", "obj"), arrayOf("str", "obj"))).renderToString(renderer) ) assertEquals( """ arrayOf( arrayOf(1, 2), arrayOf(1, 2), ) """.trimIndent(), ValueDescAnalyzer.analyze(arrayOf(arrayOf(1, 2), arrayOf(1, 2))).renderToString(renderer) ) } @Test fun `primitive array value`() { assertEquals("intArrayOf(1, 2)", ValueDescAnalyzer.analyze(intArrayOf(1, 2)).renderToString(renderer)) } @Test fun `collection value`() { assertEquals("mutableListOf(1, 2)", ValueDescAnalyzer.analyze(listOf(1, 2)).renderToString(renderer)) assertEquals("mutableSetOf(1, 2)", ValueDescAnalyzer.analyze(setOf(1, 2)).renderToString(renderer)) } @Test fun `collection value nested`() { assertEquals( """ mutableListOf( mutableListOf(1, 2), mutableListOf(1, 2), ) """.trimIndent(), ValueDescAnalyzer.analyze(listOf(listOf(1, 2), listOf(1, 2))).renderToString(renderer) ) assertEquals( """ mutableSetOf( mutableListOf(1, 2), mutableListOf(2, 2), ) """.trimIndent(), ValueDescAnalyzer.analyze(setOf(listOf(1, 2), listOf(2, 2))).renderToString(renderer) ) } @Test fun `map value`() { assertEquals( """ mutableMapOf( 1 to 2, 3 to 2, ) """.trimIndent(), ValueDescAnalyzer.analyze(mapOf(1 to 2, 3 to 2)).renderToString(renderer) ) } @Test fun `map value nested`() { assertEquals( """ |mutableMapOf( | 1 to 2, | 5 to mutableMapOf( | 1 to 2, | 3 to 2, | ), |) """.trimMargin(), ValueDescAnalyzer.analyze(mapOf(1 to 2, 5 to mapOf(1 to 2, 3 to 2))) .renderToString(renderer) ) } data class MyClass( val str: String, val int: Int, val self: MyClass?, ) @Suppress("ClassName") data class `MyClass$3`( val str: String, val int: Int, val self: `MyClass$3`?, ) @Test fun `class value`() { assertEquals( """ ${MyClass::class.qualifiedName}( str = "str", int = 1, self = null, ) """.trimIndent(), ValueDescAnalyzer.analyze(MyClass("str", 1, null)).renderToString(renderer) ) } @Test fun `local class`() { data class MyClass2( val str: String, val int: Int, val self: MyClass2?, ) assertEquals( """ ${MyClass2::class.simpleName}( str = "str", int = 1, self = null, ) """.trimIndent(), ValueDescAnalyzer.analyze(MyClass2("str", 1, null)).renderToString(renderer) ) } @Test fun `class with special name`() { assertEquals( """ `${`MyClass$3`::class.qualifiedName}`( str = "str", int = 1, self = null, ) """.trimIndent(), ValueDescAnalyzer.analyze(`MyClass$3`("str", 1, null)).renderToString(renderer) ) } @Test fun `class value nested`() { data class MyClass2( val str: String, val int: Int, val self: MyClass2?, ) assertEquals( """ ${MyClass::class.qualifiedName}( str = "str", int = 1, self = ${MyClass::class.qualifiedName}( str = "str", int = 1, self = null, ), ) """.trimIndent(), ValueDescAnalyzer.analyze(MyClass("str", 1, MyClass("str", 1, null))).renderToString(renderer) ) assertEquals( """ ${MyClass2::class.simpleName}( str = "str", int = 1, self = ${MyClass2::class.simpleName}( str = "str", int = 1, self = null, ), ) """.trimIndent(), ValueDescAnalyzer.analyze(MyClass2("str", 1, MyClass2("str", 1, null))).renderToString(renderer) ) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitor/ValueDescTransformer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalStdlibApi::class) package net.mamoe.mirai.internal.testFramework.codegen.visitor import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* import kotlin.reflect.KParameter abstract class ValueDescTransformer<D> : ValueDescVisitor<D, ValueDesc?> { override fun visitValue(desc: ValueDesc, data: D): ValueDesc? { return desc } override fun visitObjectArray(desc: ObjectArrayValueDesc, data: D): ValueDesc? { val newElements = desc.elements.mapNotNull { element -> element.acceptChildren(this, data) element.accept(this, data) } return ObjectArrayValueDesc(desc.parent, desc.value, desc.origin, desc.arrayType, desc.elementType).apply { elements.clear() elements.addAll(newElements) } } override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: D): ValueDesc? { val newElements = desc.elements.mapNotNull { element -> element.acceptChildren(this, data) element.accept(this, data) } return PrimitiveArrayValueDesc(desc.parent, desc.value, desc.origin, desc.arrayType, desc.elementType).apply { elements.clear() elements.addAll(newElements) } } override fun visitCollection(desc: CollectionValueDesc, data: D): ValueDesc? { val newElements = desc.elements.mapNotNull { element -> element.acceptChildren(this, data) element.accept(this, data) } return CollectionValueDesc(desc.parent, desc.value, desc.origin, desc.arrayType, desc.elementType).apply { elements.clear() elements.addAll(newElements) } } override fun <T : Any> visitClass(desc: ClassValueDesc<T>, data: D): ValueDesc? { val resultMap = mutableMapOf<KParameter, ValueDesc>() for ((param, value) in desc.properties) { value.accept(this, data)?.let { resultMap.put(param, it) } } return ClassValueDesc(desc.parent, desc.origin, resultMap) } } abstract class ValueDescTransformerNotNull<D> : ValueDescTransformer<D>() { private fun fail(): Nothing { throw IllegalStateException("ValueDescTransformerNotNull cannot return null from its 'visit' functions.") } override fun visitValue(desc: ValueDesc, data: D): ValueDesc { return super.visitValue(desc, data) ?: fail() } override fun visitObjectArray(desc: ObjectArrayValueDesc, data: D): ValueDesc { return super.visitObjectArray(desc, data) ?: fail() } override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: D): ValueDesc { return super.visitPrimitiveArray(desc, data) ?: fail() } override fun visitCollection(desc: CollectionValueDesc, data: D): ValueDesc { return super.visitCollection(desc, data) ?: fail() } override fun <T : Any> visitClass(desc: ClassValueDesc<T>, data: D): ValueDesc { return super.visitClass(desc, data) ?: fail() } override fun visitPlain(desc: PlainValueDesc, data: D): ValueDesc? { return super.visitPlain(desc, data) ?: fail() } override fun visitArray(desc: CollectionLikeValueDesc, data: D): ValueDesc { return super.visitArray(desc, data) ?: fail() } override fun visitMap(desc: MapValueDesc, data: D): ValueDesc { return super.visitMap(desc, data) ?: fail() } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitor/ValueDescVisitor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.visitor import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* interface ValueDescVisitor<D, R> { fun visitValue(desc: ValueDesc, data: D): R fun visitPlain(desc: PlainValueDesc, data: D): R { return visitValue(desc, data) } fun visitArray(desc: CollectionLikeValueDesc, data: D): R { return visitValue(desc, data) } fun visitObjectArray(desc: ObjectArrayValueDesc, data: D): R { return visitArray(desc, data) } fun visitCollection(desc: CollectionValueDesc, data: D): R { return visitArray(desc, data) } fun visitMap(desc: MapValueDesc, data: D): R { return visitValue(desc, data) } fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: D): R { return visitArray(desc, data) } fun <T : Any> visitClass(desc: ClassValueDesc<T>, data: D): R { return visitValue(desc, data) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitor/ValueDescVisitorUnit.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.visitor import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc interface ValueDescVisitorUnit : ValueDescVisitor<Nothing?, Unit> { override fun visitValue(desc: ValueDesc, data: Nothing?) { } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitors/AnalyzeDefaultValuesMappingVisitor.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.visitors import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ValueDesc import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitorUnit import net.mamoe.mirai.utils.cast import kotlin.reflect.KClass import kotlin.reflect.KParameter import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 import kotlin.reflect.full.memberProperties import kotlin.reflect.full.primaryConstructor class DefaultValuesMapping( val forClass: KClass<*>, val mapping: MutableMap<String, Any?> = mutableMapOf() ) { operator fun get(property: KProperty<*>): Any? = mapping[property.name] } class AnalyzeDefaultValuesMappingVisitor : ValueDescVisitorUnit { val mappings: MutableList<DefaultValuesMapping> = mutableListOf() override fun visitValue(desc: ValueDesc, data: Nothing?) { desc.acceptChildren(this, data) } override fun <T : Any> visitClass(desc: ClassValueDesc<T>, data: Nothing?) { super.visitClass(desc, data) if (mappings.any { it.forClass == desc.type }) return val defaultInstance = createInstanceWithMostDefaultValues(desc.type, desc.properties.mapValues { it.value.origin }) val optionalParameters = desc.type.primaryConstructor!!.parameters.filter { it.isOptional } mappings.add( DefaultValuesMapping( desc.type, optionalParameters.associateTo(mutableMapOf()) { param -> val value = findCorrespondingProperty(desc, param).get(defaultInstance) param.name!! to value } ) ) } private fun <T : Any> findCorrespondingProperty( desc: ClassValueDesc<T>, param: KParameter ) = desc.type.memberProperties.single { it.name == param.name }.cast<KProperty1<Any, Any>>() private fun <T : Any> createInstanceWithMostDefaultValues(clazz: KClass<T>, arguments: Map<KParameter, Any?>): T { val primaryConstructor = clazz.primaryConstructor ?: error("Type $clazz does not have primary constructor.") return primaryConstructor.callBy(arguments.filter { !it.key.isOptional }) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitors/ClassFormatter.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.visitors import net.mamoe.mirai.internal.testFramework.codegen.descriptors.ClassValueDesc data class ClassFormatterContext( val desc: ClassValueDesc<*>, val visitor: ValueDescToStringRenderer, val rendererContext: RendererContext, ) open class ClassFormatter { open fun formatClassName(context: ClassFormatterContext): String { val name = context.desc.type.qualifiedName ?: context.desc.type.simpleName ?: context.desc.type.toString() return wrapBacktickIfNecessary(name) } open fun formatClassProperty(context: ClassFormatterContext, propertyString: String?, valueString: String): String = "$propertyString = $valueString" companion object { protected fun wrapBacktickIfNecessary(name: String) = if (name.contains(' ') || name.contains('$')) { "`$name`" } else name } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitors/Indenter.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.visitors fun interface Indenter { fun indentize(string: String): String object NoIndent : Indenter { override fun indentize(string: String): String = string } } class WordingIndenter constructor( private val separator: String, ) : Indenter { override fun indentize(string: String): String = "$separator$string" companion object { fun spacing(count: Int = 4): Indenter = WordingIndenter(" ".repeat(count)) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitors/OptimizeByteArrayAsHexStringTransformer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalStdlibApi::class) package net.mamoe.mirai.internal.testFramework.codegen.visitors import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull import net.mamoe.mirai.utils.toUHexString import kotlin.reflect.typeOf /** * If the byte array was from [String.toByteArray], transform it to the [String]. * Otherwise, transform it as hex string. */ open class OptimizeByteArrayAsHexStringTransformer : ValueDescTransformerNotNull<Nothing?>() { open fun transform(desc: CollectionLikeValueDesc, value: ByteArray): ValueDesc { return if (isReadableString(value)) { // prefers to show readable string PlainValueDesc( desc.parent, value = "\"${ value.decodeToString().escapeQuotation() }\".toByteArray() /* ${value.toUHexString()} */", origin = desc.origin ) } else { PlainValueDesc( desc.parent, value = "\"${value.toUHexString()}\".hexToBytes()", origin = desc.origin ) } } protected fun isReadableString(value: ByteArray) = value.decodeToString().all { (it.isDefined() || it.isWhitespace()) && !it.isISOControl() } override fun visitValue(desc: ValueDesc, data: Nothing?): ValueDesc { desc.acceptChildren(this, data) return super.visitValue(desc, data) } override fun visitObjectArray(desc: ObjectArrayValueDesc, data: Nothing?): ValueDesc { if (desc.arrayType == arrayOfByteType) { val array = desc.elements.mapNotNull { (it as? PlainValueDesc)?.value?.toByteOrNull() }.toByteArray() if (array.size != desc.elements.size) return desc return transform(desc, array) } return super.visitObjectArray(desc, data) } override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: Nothing?): ValueDesc { if (desc.value is ByteArray) { return transform(desc, desc.value as ByteArray) } return super.visitPrimitiveArray(desc, data) } companion object { private val arrayOfByteType = typeOf<Array<Byte>>() private fun String.escapeQuotation(): String = buildString { this@escapeQuotation.escapeQuotationTo(this) } private fun String.escapeQuotationTo(out: StringBuilder) { for (element in this) { when (element) { '\\' -> out.append("\\\\") '\n' -> out.append("\\n") '\r' -> out.append("\\r") '\t' -> out.append("\\t") '\"' -> out.append("\\\"") else -> out.append(element) } } } } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/codegen/visitors/ValueDescToStringRenderer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.codegen.visitors import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescVisitor class RendererContext( val indenter: Indenter, val classFormatter: ClassFormatter = ClassFormatter() ) fun ValueDesc.renderToString( renderer: ValueDescToStringRenderer = ValueDescToStringRenderer(), rendererContext: RendererContext = RendererContext(WordingIndenter.spacing(4)) ): String { return accept( renderer, rendererContext ) } open class ValueDescToStringRenderer : ValueDescVisitor<RendererContext, String> { private inline val visitor get() = this private inline fun Appendable.withIndent(indenter: Indenter, block: Appendable.() -> Unit) { append( buildString(block) .lineSequence() .map { indenter.indentize(it) } .joinToString("\n") .trimEnd { it != '\n' && it.isWhitespace() } ) } override fun visitValue(desc: ValueDesc, data: RendererContext): String { return desc.toString() // tentative fallback } override fun visitPlain(desc: PlainValueDesc, data: RendererContext): String { return desc.value } override fun visitArray(desc: CollectionLikeValueDesc, data: RendererContext): String = buildString { val array = desc.value fun impl(funcName: String, elements: List<ValueDesc>) { if (elements.any { it !is PlainValueDesc }) { // complex types append(funcName) append('(') appendLine() withIndent(data.indenter) { val list = elements.toList() list.forEach { desc -> append(desc.accept(visitor, data)) appendLine(", ") } } append(')') } else { // primitive types append(funcName) append('(') val list = elements.toList() list.forEachIndexed { index, desc -> append(desc.accept(visitor, data)) if (index != list.lastIndex) append(", ") } append(')') } } when (array) { is Array<*> -> impl("arrayOf", desc.elements) is IntArray -> impl("intArrayOf", desc.elements) is ByteArray -> impl("byteArrayOf", desc.elements) is ShortArray -> impl("shortArrayOf", desc.elements) is CharArray -> impl("charArrayOf", desc.elements) is LongArray -> impl("longArrayOf", desc.elements) is FloatArray -> impl("floatArrayOf", desc.elements) is DoubleArray -> impl("doubleArrayOf", desc.elements) is BooleanArray -> impl("booleanArrayOf", desc.elements) is List<*> -> impl("mutableListOf", desc.elements) is Set<*> -> impl("mutableSetOf", desc.elements) else -> error("$array is not an array.") } } override fun visitMap(desc: MapValueDesc, data: RendererContext): String = buildString { appendLine("mutableMapOf(") for ((key, value) in desc.elements) { withIndent(data.indenter) { append(key.accept(visitor, data)) append(" to ") append(value.accept(visitor, data)) appendLine(",") } } append(")") } override fun <T : Any> visitClass(desc: ClassValueDesc<T>, data: RendererContext): String = buildString { val classFormatterContext = ClassFormatterContext(desc, visitor, data) appendLine("${data.classFormatter.formatClassName(classFormatterContext)}(") for ((param, valueDesc) in desc.properties) { withIndent(data.indenter) { append( data.classFormatter.formatClassProperty( classFormatterContext, param.name, valueDesc.accept(visitor, data) ) ) } appendLine(",") } append(")") } // open fun renderClassName(desc: ClassValueDesc<*>): String { // val name = desc.type.qualifiedName ?: desc.type.java.name // return wrapBacktickIfNecessary(name) // } companion object { protected fun wrapBacktickIfNecessary(name: String) = if (name.contains(' ') || name.contains('$')) { "`$name`" } else name } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/desensitizer/Desensitizer.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.desensitizer import io.ktor.utils.io.core.* import kotlinx.serialization.decodeFromString import net.mamoe.mirai.Mirai import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.analyze import net.mamoe.mirai.internal.testFramework.codegen.descriptors.* import net.mamoe.mirai.internal.testFramework.codegen.removeDefaultValues import net.mamoe.mirai.internal.testFramework.codegen.visitor.ValueDescTransformerNotNull import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString import net.mamoe.mirai.internal.utils.io.NestedStructure import net.mamoe.mirai.internal.utils.io.NestedStructureDesensitizer import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.* import net.mamoe.yamlkt.Yaml import net.mamoe.yamlkt.YamlBuilder import java.io.File import java.net.URL import kotlin.reflect.KType import kotlin.reflect.full.createInstance import kotlin.reflect.full.findAnnotation import kotlin.reflect.typeOf private val logger: MiraiLogger by lazy { MiraiLogger.Factory.create(Desensitizer::class) } internal class Desensitizer private constructor( val rules: Map<String, String>, ) { fun desensitize(value: String): String { return rules.entries.fold(value) { acc, entry -> acc.replace(entry.key, entry.value) } } fun desensitize(value: ByteArray): ByteArray { return desensitize(value.toUHexString()).hexToBytes() } fun desensitize(value: Array<Byte>): Array<Byte> { return desensitize(value.toByteArray().toUHexString()).hexToBytes().toTypedArray() } companion object { private val instance by lateinitMutableProperty { create( run<Map<String, String>> { val filename = systemProp("mirai.network.recording.desensitization.filepath", "local.desensitization.yml") val file: URL? = File(filename).takeIf { it.isFile }?.toURI()?.toURL() ?: Thread.currentThread().contextClassLoader?.getResource(filename) ?: Thread.currentThread().contextClassLoader?.getResource("recording/configs/$filename") file?.readText()?.let { format.decodeFromString(it) } ?: kotlin.run { logger.warning { "Couldn't find desensitization rules. You can set by system property 'mirai.network.recording.desensitization.filepath' to path to the desensitization configuration file, or use the 'local.desensitization.yml' by default." } mapOf() } }.also { logger.info { "Loaded ${it.size} desensitization rules." } } ) } /** * Loaded from local.desensitization.yml */ val local get() = instance fun desensitize(string: String): String = instance.desensitize(string) fun ValueDescAnalyzer.generateAndDesensitize( value: Any?, type: KType, desensitizer: Desensitizer = instance, ): String { return analyze(value, type) .transform(OptimizeByteArrayAsHexStringTransformer()) .removeDefaultValues() .transform(DesensitizationVisitor(desensitizer)) .renderToString() } inline fun <reified T> ValueDescAnalyzer.generateAndDesensitize( value: T, desensitizer: Desensitizer = instance, ): String = generateAndDesensitize(value, typeOf<T>(), desensitizer) fun create(rules: Map<String, String>): Desensitizer { val map = HashMap<String, String>() map.putAll(rules) fun addExtraRulesForString(value: String, replacement: String) { // in proto, strings have lengths field, we must ensure that their lengths are intact. when { value.length > replacement.length -> { map[value.toByteArray().toUHexString()] = (replacement + "0".repeat(value.length - replacement.length)).toByteArray() .toUHexString() // fix it to the same length } value.length < replacement.length -> { error("Replacement '$replacement' must not be longer than '$value'") } else -> { map.getOrPut(value.toByteArray().toUHexString()) { replacement.toByteArray().toUHexString() } } } } fun addExtraRulesForNumber(value: Long, replacement: Long) { map.getOrPut(value.toString()) { replacement.toString() } // 某些地方会 readLong, readInt, desensitizer visit 不到这些目标 map.getOrPut(value.toByteArray().toUHexString()) { replacement.toByteArray().toUHexString() } if (value in Int.MIN_VALUE.toLong()..UInt.MAX_VALUE.toLong() && replacement in Int.MIN_VALUE.toLong()..UInt.MAX_VALUE.toLong() ) { map.getOrPut( value.toInt().toByteArray().toUHexString() ) { replacement.toInt().toByteArray().toUHexString() } } // 不需要处理 proto, 所有 proto 都会被反序列化为结构类型由 desensitizer 处理 } rules.forEach { (t, u) -> if (t.toLongOrNull() != null && u.toLongOrNull() != null) { addExtraRulesForNumber(t.toLong(), u.toLong()) @Suppress("DEPRECATION") addExtraRulesForNumber( Mirai.calculateGroupUinByGroupCode(t.toLong()), Mirai.calculateGroupUinByGroupCode(u.toLong()) ) // putIfAbsent, code prevails } addExtraRulesForString(t, u) } return Desensitizer(map) } } } private val format = Yaml { // one-line classSerialization = YamlBuilder.MapSerialization.FLOW_MAP mapSerialization = YamlBuilder.MapSerialization.FLOW_MAP listSerialization = YamlBuilder.ListSerialization.FLOW_SEQUENCE stringSerialization = YamlBuilder.StringSerialization.DOUBLE_QUOTATION encodeDefaultValues = false } internal class DesensitizationVisitor( private val desensitizer: Desensitizer, ) : ValueDescTransformerNotNull<Nothing?>() { override fun visitValue(desc: ValueDesc, data: Nothing?): ValueDesc { desc.acceptChildren(this, data) return super.visitValue(desc, data) } override fun visitPlain(desc: PlainValueDesc, data: Nothing?): ValueDesc { return PlainValueDesc(desc.parent, desensitizer.desensitize(desc.value), desc.origin) } override fun visitObjectArray(desc: ObjectArrayValueDesc, data: Nothing?): ValueDesc { return if ( desc.arrayType.arguments.firstOrNull()?.type?.classifier == Byte::class || (desc.value as? Array<*>)?.getOrNull(0) is Byte ) { @Suppress("UNCHECKED_CAST") ObjectArrayValueDesc( desc.parent, desensitizer.desensitize(desc.value as Array<Byte>), desc.origin, desc.arrayType, desc.elementType ) } else { super.visitObjectArray(desc, data) } } override fun visitPrimitiveArray(desc: PrimitiveArrayValueDesc, data: Nothing?): ValueDesc { return if (desc.value is ByteArray) { PrimitiveArrayValueDesc( desc.parent, desensitizer.desensitize(desc.value as ByteArray), desc.origin, desc.arrayType, desc.elementType ) } else super.visitPrimitiveArray(desc, data) } override fun <T : Any> visitClass(desc: ClassValueDesc<T>, data: Nothing?): ValueDesc { ClassValueDesc(desc.parent, desc.origin, desc.properties.toMutableMap().apply { replaceAll() { key, value -> val annotation = key.findAnnotation<NestedStructure>() if (annotation != null && value.origin is ByteArray) { val instance = annotation.serializer.objectInstance ?: annotation.serializer.createInstance() val result = instance.cast<NestedStructureDesensitizer<ProtocolStruct, ProtocolStruct>>() .deserialize(desc.origin as ProtocolStruct, value.origin as ByteArray) ?.let { ValueDescAnalyzer.analyze(it) } if (result == null) { value } else { val generate = ValueDescAnalyzer.analyze(result) .transform(OptimizeByteArrayAsHexStringTransformer()) .transform(DesensitizationVisitor(desensitizer)) .renderToString() PlainValueDesc( desc, "$generate.toByteArray(${result::class.qualifiedName}.serializer())", value.origin ) } } else value } }).let { return super.visitClass(it, data) } } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/message/protocol/MessageDecodingRecorder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.message.protocol import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer.Companion.generateAndDesensitize import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.debug internal class MessageDecodingRecorder( private val logger: MiraiLogger = MiraiLogger.Factory.create(MessageDecodingRecorder::class) ) : MessageDecoder { override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) { logger.debug { "\n" + ValueDescAnalyzer.generateAndDesensitize(data) } } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/notice/RecordingNoticeHandler.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.notice import kotlinx.atomicfu.atomic import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import net.mamoe.mirai.internal.network.components.NoticePipelineContext import net.mamoe.mirai.internal.network.components.SimpleNoticeProcessor import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer.Companion.generateAndDesensitize import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.info /** * ### How to use recorder? * * 0. Configure desensitization. See mirai-core/src/commonTest/recording/configs/desensitization.yml * 1. Inject the recorder as follows: * * ``` * bot.components[NoticeProcessorPipeline].registerProcessor(recorder) * ``` * * 2. Do something * 3. Recorded values are shown in logs. Check 'decoded' to ensure that all sensitive values are replaced. */ internal class RecordingNoticeProcessor : SimpleNoticeProcessor<ProtocolStruct>(type()) { private val id = atomic(0) private val lock = Mutex() override suspend fun NoticePipelineContext.processImpl(data: ProtocolStruct) { lock.withLock { id.getAndDecrement() logger.info { "Recorded #${id.value} ${data::class.simpleName}" } logger.info { "Desensitized: \n\n\u001B[0m" + ValueDescAnalyzer.generateAndDesensitize(data) + "\n\n" } } } } private val logger: MiraiLogger by lazy { MiraiLogger.Factory.create(RecordingNoticeProcessor::class) } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/rules/DisabledOnPlatform.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.rules import net.mamoe.mirai.internal.testFramework.Platform import net.mamoe.mirai.internal.testFramework.currentPlatform import org.junit.jupiter.api.extension.ConditionEvaluationResult import org.junit.jupiter.api.extension.ExecutionCondition import org.junit.jupiter.api.extension.ExtendWith import org.junit.jupiter.api.extension.ExtensionContext import org.junit.platform.commons.util.AnnotationUtils import kotlin.jvm.optionals.getOrNull import kotlin.reflect.KClass import kotlin.reflect.full.isSuperclassOf @Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // magic actual typealias DisabledOnJvmLikePlatform = DisabledOnPlatform @Target( AnnotationTarget.CLASS, AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER ) @Retention(AnnotationRetention.RUNTIME) @MustBeDocumented @ExtendWith(DisabledOnPlatformCondition::class) annotation class DisabledOnPlatform( vararg val values: KClass<out Platform> ) internal object DisabledOnPlatformCondition : ExecutionCondition { private val ENABLED_BY_DEFAULT = ConditionEvaluationResult.enabled("@DisabledOnPlatform is not present") override fun evaluateExecutionCondition(context: ExtensionContext?): ConditionEvaluationResult { val annotation = AnnotationUtils.findAnnotation( context!!.element, DisabledOnPlatform::class.java ).getOrNull() ?: return ENABLED_BY_DEFAULT for (value in annotation.values) { check(value != Platform::class) { "@DisabledOnPlatform(Platform::class) is invalid. Use `@Disabled` to disable on all platforms" } } val currentPlatform = currentPlatform() val currentPlatformClass = currentPlatform::class annotation.values.find { it.isSuperclassOf(currentPlatformClass) } ?.let { return ConditionEvaluationResult.disabled("@DisabledOnPlatform(${it.simpleName}), current = $currentPlatform") } return ENABLED_BY_DEFAULT } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/testFramework/test/RecordingNoticeProcessorTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework.test import kotlinx.serialization.Serializable import kotlinx.serialization.decodeFromString import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.testFramework.Platform import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer import net.mamoe.mirai.internal.testFramework.rules.DisabledOnPlatform import net.mamoe.mirai.internal.utils.io.ProtocolStruct import net.mamoe.yamlkt.Yaml import net.mamoe.yamlkt.YamlBuilder import kotlin.test.Test import kotlin.test.assertEquals internal class RecordingNoticeProcessorTest : AbstractTest() { @Serializable data class MyProtocolStruct( val value: String ) : ProtocolStruct @Test @DisabledOnPlatform(Platform.Android::class) // resources not available fun `test plain desensitization`() { val text = loadDesensitizationRules() val desensitizer = Desensitizer.create(Yaml.decodeFromString(text)) assertEquals( """ "111": s1av12sad3 "222": s1av12sad3 """.trim(), desensitizer.desensitize( """ "123456789": s1av12sad3 "987654321": s1av12sad3 """.trim() ) ) } @Serializable data class TestProto( @ProtoNumber(1) val proto: Proto ) : ProtocolStruct { @Serializable data class Proto( @ProtoNumber(1) val int: Int ) } @Serializable data class ByteArrayWrapper( val value: ByteArray ) val format = Yaml { // one-line classSerialization = YamlBuilder.MapSerialization.FLOW_MAP mapSerialization = YamlBuilder.MapSerialization.FLOW_MAP listSerialization = YamlBuilder.ListSerialization.FLOW_SEQUENCE stringSerialization = YamlBuilder.StringSerialization.DOUBLE_QUOTATION encodeDefaultValues = false } @Test @DisabledOnPlatform(Platform.Android::class) fun `test long as byte array desensitization`() { val text = loadDesensitizationRules() val desensitizer = Desensitizer.create(Yaml.decodeFromString(text)) val proto = TestProto(TestProto.Proto(123456789)) assertEquals( TestProto(TestProto.Proto(111)), format.decodeFromString( TestProto.serializer(), desensitizer.desensitize(format.encodeToString(TestProto.serializer(), proto)) ) ) } private fun loadDesensitizationRules() = ((Thread.currentThread().contextClassLoader ?: this::class.java.classLoader) .getResource("recording/configs/test.desensitization.yml")!!).readText() } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/utils/CombinedExternalResourceTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import io.ktor.utils.io.core.* import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.text.toByteArray class CombinedExternalResourceTest : AbstractTest() { @Test fun `work`() { val res1 = STRING_1.toByteArray().toExternalResource() val res2 = STRING_2.toByteArray().toExternalResource() val combined1 = buildPacket { res1.input().use { it.copyTo(this) } res2.input().use { it.copyTo(this) } }.readBytes().toExternalResource() val combined2 = CombinedExternalResource(res1, res2) assertEquals(combined1.size, combined2.size) assertTrue { combined1.md5.contentEquals(combined2.md5) } assertTrue { combined1.sha1.contentEquals(combined2.sha1) } } private val STRING_1 = """ b4FNDvv49gMInP29t82fPJuWQ4ArG1k1YVeCN3UReWXplm4H2S4Rp7zTpt8WXRQEtTL7VemlTIytPbwUkus7qgPVsyUCFreRR1vB3QhRznXqcT06fDkXJQJKyyBGEdwddNWZAkqZcdrOk679sG14kKK5GexaQUmdfTivT5VPO8w1yoWPcUHPfpjB0shCEzjkHI84LJbWNRCVjoZhy0jZAKZxLrsi1sGhl30QcXCFnHpPhWbED8Er9c8gVbjYsG8ejaUlbeNNdKW3GoOpgjFLbwZoQI4QZZgvP5jhBWUPiMG3MCcPlYRSgTf70JpDVTE0YOLhXdJJxz87S8MR4M7rU0WO7ZRkoFOQpFHdmfMmJxbiATHHkOyHVhu1mvA0L72MNtDQP5GcKlDbDcdJL7om4FmekAVVnh7R """.trimIndent() private val STRING_2 = """ FdDoAZt2hJkKAfEWBNWO44R0tJRmApqIwHDD05oW0jyLVVPOdcPaFjY1muYM1qa6jbhZppWYm1oOmgbpFgdPZRYDgzznR0kSapdqXeSSevV4ww4E1U71ELDMsq4f0a1Y8K6UxIOpQl1n20eoe80fHuXKkfN6kbhROBXcwGbiFRpPg5k8G5hCerQQunQyNoeEZrbKacq2OYkOEJV57LuSbBTF4FMZYxCEp1a8omnK1EUHC1Go5pGy0dovz78KpCshPr7MHNMnRu0FiuJ1WYT8ri8iXWsTx3AMxHRjCYfJgrtqc86L3HW0V6Wr8FqFMJLtFl4PgXj5etfRSaaqRJFIZ3nWiRqW48JMRqdGRvLTUWs1Zoa8H11bych18MVypUQJOyxghLLJw0ZP4CvSNUeJOEMitxFxyzjC """.trimIndent() } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/utils/StructureToStringTransformerNew.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.internal.testFramework.codegen.ValueDescAnalyzer import net.mamoe.mirai.internal.testFramework.codegen.analyze import net.mamoe.mirai.internal.testFramework.codegen.descriptors.transform import net.mamoe.mirai.internal.testFramework.codegen.removeDefaultValues import net.mamoe.mirai.internal.testFramework.codegen.visitors.OptimizeByteArrayAsHexStringTransformer import net.mamoe.mirai.internal.testFramework.codegen.visitors.renderToString import net.mamoe.mirai.internal.testFramework.desensitizer.DesensitizationVisitor import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer import net.mamoe.mirai.utils.StructureToStringTransformer import net.mamoe.mirai.utils.StructureToStringTransformerLegacy internal class StructureToStringTransformerNew : StructureToStringTransformer { private val legacy = StructureToStringTransformerLegacy() override fun transform(any: Any?): String = kotlin.runCatching { ValueDescAnalyzer.analyze(any) .transform(OptimizeByteArrayAsHexStringTransformer()) .removeDefaultValues() .renderToString() }.getOrNull() ?: legacy.transform(any) override fun transformAndDesensitize(any: Any?): String { val desensitizer = Desensitizer.local return kotlin.runCatching { ValueDescAnalyzer.analyze(any) .transform(OptimizeByteArrayAsHexStringTransformer()) .removeDefaultValues() .transform(DesensitizationVisitor(desensitizer)) .renderToString() }.getOrNull() ?: legacy.transform(any) } } ================================================ FILE: mirai-core/src/jvmBaseTest/kotlin/utils/test/StructureToStringTransformerNewTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.test import net.mamoe.mirai.internal.test.AbstractTest import net.mamoe.mirai.internal.utils.StructureToStringTransformerNew import net.mamoe.mirai.utils.StructureToStringTransformer import net.mamoe.mirai.utils.structureToString import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class StructureToStringTransformerNewTest : AbstractTest() { data class MyClass( val value: String ) @Test fun `can load service`() { assertIs<StructureToStringTransformerNew>(StructureToStringTransformer.instance) } @Test fun `can use`() { assertEquals( """ ${MyClass::class.qualifiedName}( value = "1", ) """.trimIndent(), MyClass("1").structureToString() ) } } ================================================ FILE: mirai-core/src/jvmBaseTest/resources/META-INF/services/net.mamoe.mirai.utils.StructureToStringTransformer ================================================ # # Copyright 2019-2022 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/dev/LICENSE # net.mamoe.mirai.internal.utils.StructureToStringTransformerNew ================================================ FILE: mirai-core/src/jvmMain/kotlin/package.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal ================================================ FILE: mirai-core/src/jvmMain/kotlin/utils/crypto/EcdhJvmDesktop.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils.crypto import org.bouncycastle.jce.provider.BouncyCastleProvider internal actual fun Ecdh.Companion.create(): Ecdh<*, *> = kotlin.runCatching { // try platform default EC/ECDH implementations first, which may have better performance // note that they may not work properly but being created successfully JceEcdh().apply { val keyPair = generateKeyPair() calculateShareKey(keyPair.public, keyPair.private) val encoded = exportPublicKey(keyPair.public) importPublicKey(encoded) } }.getOrElse { // fallback to BouncyCastle JceEcdhWithProvider(BouncyCastleProvider()) } ================================================ FILE: mirai-core/src/jvmTest/README.md ================================================ # mirai-core - jvm test - debug run ------------- !! IMPORTANT !! 在 `jvmTest` 直接启动 `mirai-core` 前, 您必须先阅读此篇的内容 否则你可能会遇到一些问题 ----------------------------------- ## 测试启动点配置 在直接启动前, 首先需要手动创建一个专属于自己本地测试的测试启动点 > mirai-core 自带 `jvmTest/kotlin/local` 的忽略, 您只需要在 `jvmTest/kotlin` 手动创建此文件夹即可 在 `jvmTest/kotlin/local` 创建一个新的 kotlin 文件, 并写入以下内容 ```kotlin fun main() { prepareEnvironmentForDebugRun() // ..... val bot = DebugRunHelper.newBot(/* ..... */) { } bot as QQAndroidBot runBlocking { bot.login() bot.eventChannel.subscribeAlways<MessageEvent> { //...... } bot.join() } } ``` --------------- ## 测试数据存储 Intellij IDEA 直接以默认配置运行程序时, 程序的工作目录都是顶层根目录 (`$rootProject/`) 即在测试代码中的文件相关的操作都是相对 `$rootProject` 而言的 `$rootProject/test` 在 `$rootProject/.gitignore` 中被声明完全忽略 所以 `$rootProject/test` 可以用于存储测试数据, 如果目前本地环境中没有 `test` 文件夹, 可以手动创建 ================================================ FILE: mirai-core/src/jvmTest/kotlin/AtomicResizeCacheListTest.kt ================================================ /* * Copyright 2019-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 /* internal class AtomicResizeCacheListTest { @Test fun testDuplication() { val list = AtomicResizeCacheList<Int>(1000) assertTrue { list.ensureNoDuplication(1) } assertFalse { list.ensureNoDuplication(1) } assertTrue { list.ensureNoDuplication(1) } } @Test fun testRetention() { val list = AtomicResizeCacheList<Int>(1000) assertTrue { list.ensureNoDuplication(1) } runBlocking { delay(1010) // because no concurrency guaranteed on same elements assertFalse { list.ensureNoDuplication(1) } assertTrue { list.ensureNoDuplication(2) } // outdated elements are now cleaned assertTrue { list.ensureNoDuplication(1) } } } }*/ ================================================ FILE: mirai-core/src/jvmTest/kotlin/JavaApiTests.java ================================================ /* * Copyright 2019-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 */ import kotlin.coroutines.CoroutineContext; import net.mamoe.mirai.Bot; import net.mamoe.mirai.BotFactory; import net.mamoe.mirai.contact.Contact; import net.mamoe.mirai.contact.Group; import net.mamoe.mirai.event.EventHandler; import net.mamoe.mirai.event.ListeningStatus; import net.mamoe.mirai.event.SimpleListenerHost; import net.mamoe.mirai.event.events.GroupMessageEvent; import net.mamoe.mirai.event.events.MessageEvent; import net.mamoe.mirai.message.data.Image; import net.mamoe.mirai.message.data.MessageChain; import net.mamoe.mirai.message.data.MessageSource; import net.mamoe.mirai.message.data.MessageUtils; import net.mamoe.mirai.utils.BotConfiguration; import net.mamoe.mirai.utils.ExternalResource; import org.jetbrains.annotations.NotNull; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.RandomAccessFile; /** * 仅用来测试调用,不会被单元测试运行 */ @SuppressWarnings({"unused", "UnusedAssignment"}) public class JavaApiTests { @NotNull Bot bot; public JavaApiTests(@NotNull Bot bot) { this.bot = bot; } public void generalCalls() { bot.login(); bot.getAsFriend().sendMessage("test"); // blocking bridge bot.getOtherClients().getOrFail(1).getBot(); } public void events() { bot.getEventChannel().subscribe(MessageEvent.class, event -> ListeningStatus.LISTENING); bot.getEventChannel().subscribeAlways(GroupMessageEvent.class, event -> { Bot b = event.getBot(); }); SimpleListenerHost slh = new SimpleListenerHost() { @Override public void handleException(@NotNull CoroutineContext context, @NotNull Throwable exception) { super.handleException(context, exception); } @EventHandler public void onMsg(GroupMessageEvent e) { } }; bot.getEventChannel().registerListenerHost(slh); } @NotNull private <T> T magic() { throw new RuntimeException(); } @NotNull MessageChain chain = magic(); @NotNull Contact contact = magic(); public void messages() { Image image = (Image) chain.stream().filter(Image.class::isInstance).findFirst().orElse(null); assert image != null; String url = Image.queryUrl(image); Image.fromId("123"); MessageSource source = magic(); MessageUtils.getBot(source); MessageUtils.calculateImageMd5(image); MessageUtils.isContentEmpty(image); MessageSource.quote(source); MessageSource.quote(chain); MessageSource.recall(source); } ExternalResource resource = magic(); public void externalResource() throws IOException { resource.inputStream(); contact.uploadImage(resource); // base method ExternalResource r; r = ExternalResource.create((InputStream) magic()); // throws IOException r = ExternalResource.create((File) magic()); r = ExternalResource.create((RandomAccessFile) magic()); ExternalResource.uploadAsImage(r, contact); // returns Image ExternalResource.sendAsImage(r, contact); // returns MessageReceipt ExternalResource.uploadAsImage((ExternalResource) magic(), contact); // returns Image ExternalResource.uploadAsImage((File) magic(), contact); // returns Image ExternalResource.uploadAsImage((InputStream) magic(), contact); // returns Image ExternalResource.sendAsImage((ExternalResource) magic(), contact); // returns MessageReceipt ExternalResource.sendAsImage((File) magic(), contact); // returns MessageReceipt ExternalResource.sendAsImage((InputStream) magic(), contact); // returns MessageReceipt Contact.uploadImage(contact, (ExternalResource) magic()); // returns Image Contact.uploadImage(contact, (File) magic()); // returns Image Contact.uploadImage(contact, (InputStream) magic()); // returns Image Contact.sendImage(contact, (ExternalResource) magic()); // returns MessageReceipt Contact.sendImage(contact, (File) magic()); // returns MessageReceipt Contact.sendImage(contact, (InputStream) magic()); // returns MessageReceipt // experimental ExternalResource.uploadAsVoice(magic(), (Group) contact); } public static void main(String[] args) { Bot bot = BotFactory.INSTANCE.newBot(11, "", configuration -> { configuration.fileBasedDeviceInfo(); configuration.setProtocol(BotConfiguration.MiraiProtocol.ANDROID_PHONE); }); new JavaApiTests(bot); } } ================================================ FILE: mirai-core/src/jvmTest/kotlin/bootstrap/RunMessageDecodingRecorder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.bootstrap import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacade import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderProcessor import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer import net.mamoe.mirai.internal.testFramework.message.protocol.MessageDecodingRecorder import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.readResource import net.mamoe.yamlkt.Yaml import kotlin.concurrent.thread internal suspend fun main() { Runtime.getRuntime().addShutdownHook(thread(start = false) { Bot.instances.forEach { it.close() } }) Desensitizer.local.desensitize("") // verify rules val account = Yaml.decodeFromString(LocalAccount.serializer(), readResource("local.account.yml")) val bot = BotFactory.newBot(account.id, account.password) { enableContactCache() fileBasedDeviceInfo("local.device.json") protocol = BotConfiguration.MiraiProtocol.ANDROID_PHONE }.asQQAndroidBot() MessageProtocolFacade.decoderPipeline.registerBefore(MessageDecoderProcessor(MessageDecodingRecorder())) bot.login() bot.join() } ================================================ FILE: mirai-core/src/jvmTest/kotlin/bootstrap/RunNoticeRecorder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.bootstrap import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.components.NoticeProcessorPipeline import net.mamoe.mirai.internal.testFramework.desensitizer.Desensitizer import net.mamoe.mirai.internal.testFramework.notice.RecordingNoticeProcessor import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.readResource import net.mamoe.yamlkt.Yaml import kotlin.concurrent.thread @Serializable internal data class LocalAccount( val id: Long, val password: String ) internal suspend fun main() { Runtime.getRuntime().addShutdownHook(thread(start = false) { Bot.instances.forEach { it.close() } }) Desensitizer.local.desensitize("") // verify rules val account = Yaml.decodeFromString(LocalAccount.serializer(), readResource("local.account.yml")) val bot = BotFactory.newBot(account.id, account.password) { enableContactCache() fileBasedDeviceInfo("local.device.json") protocol = BotConfiguration.MiraiProtocol.ANDROID_PAD }.asQQAndroidBot() bot.components[NoticeProcessorPipeline].registerProcessor(RecordingNoticeProcessor()) bot.login() bot.join() } ================================================ FILE: mirai-core/src/jvmTest/kotlin/directboot/DebugRunHelper.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.directboot import kotlinx.coroutines.Dispatchers import net.mamoe.mirai.BotFactory import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.utils.BotConfiguration import java.io.File internal object DebugRunHelper { fun newBot(id: Long, pwd: String, conf: BotConfiguration.(botid: Long) -> Unit): QQAndroidBot { return newBot(id, BotAuthorization.byPassword(pwd), conf) } fun newBot(id: Long, authorization: BotAuthorization, conf: BotConfiguration.(botid: Long) -> Unit): QQAndroidBot { val bot = BotFactory.newBot(id, authorization) { parentCoroutineContext = Dispatchers.IO workingDir = File("test/session/$id").also { it.mkdirs() }.absoluteFile cacheDir = workingDir.resolve("cache").absoluteFile this.fileBasedDeviceInfo(File("test/session/$id/device.json").absolutePath) conf(id) } return bot as QQAndroidBot } } ================================================ FILE: mirai-core/src/jvmTest/kotlin/directboot/envprepare.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.directboot import net.mamoe.mirai.internal.message.source.MessageSourceSequenceIdAwaiter internal fun prepareEnvironmentForDebugRun() { System.setProperty("mirai.network.packet.logger", "true") System.setProperty("mirai.event.show.verbose.events", "true") System.setProperty("mirai.network.state.observer.logging", "full") System.setProperty("mirai.network.handle.selector.logging", "true") System.setProperty("mirai.network.handler.selector.logging", "true") System.setProperty("mirai.resource.creation.stack.enabled", "true") MessageSourceSequenceIdAwaiter.setInstance(MessageSourceSequenceIdAwaiter()) } ================================================ FILE: mirai-core/src/jvmTest/kotlin/netinternalkit/Ansi.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.netinternalkit // Copied from mirai-console/backend/mirai-console/src/util/AnsiMessageBuilder.kt @Suppress("RegExpRedundantEscape") private val DROP_CSI_PATTERN = """\u001b\[([\u0030-\u003F])*?([\u0020-\u002F])*?[\u0040-\u007E]""".toRegex() private val DROP_ANSI_PATTERN = """\u001b[\u0040–\u005F]""".toRegex() internal fun String.dropAnsi(): String = this .replace(DROP_CSI_PATTERN, "") // 先进行 CSI 剔除后进行 ANSI 剔除 .replace(DROP_ANSI_PATTERN, "") ================================================ FILE: mirai-core/src/jvmTest/kotlin/netinternalkit/LogCapture.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") package net.mamoe.mirai.internal.netinternalkit import kotlinx.atomicfu.locks.withLock import net.mamoe.mirai.utils.* import java.io.File internal object LogCapture { lateinit var logCache: SizedCache<String> private val output: (String) -> Unit = { log -> println(log) logCache.emit(log.dropAnsi()) } var outputDir = File("test/capture") fun setupCapture(maxLine: Int = 200) { logCache = SizedCache(maxLine) @Suppress("INVISIBLE_MEMBER") MiraiLoggerFactoryImplementationBridge.wrapCurrent { object : MiraiLogger.Factory { override fun create(requester: Class<*>, identity: String?): MiraiLogger { return PlatformLogger( identity ?: requester.kotlin.simpleName ?: requester.simpleName, output ) } } } NetReplayHelperSettings.logger_console = PlatformLogger( identity = "NetReplayHelper", output = ::println ) NetReplayHelperSettings.logger_file = PlatformLogger( identity = "NetReplayHelper", output = { log -> logCache.emit(log.dropAnsi()) } ) } fun saveCapture(type: String = "capture") { val output = outputDir.resolve("$type-${currentTimeMillis()}.txt") logCache.lock.withLock { output.also { it.parentFile.mkdirs() }.bufferedWriter().use { writer -> logCache.forEach { line -> writer.write(line) writer.write(10) } } } } } ================================================ FILE: mirai-core/src/jvmTest/kotlin/netinternalkit/NetReplayHelper.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("NetReplayHelper") @file:Suppress("TestFunctionName") package net.mamoe.mirai.internal.netinternalkit import io.netty.channel.* import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.internal.asQQAndroidBot import net.mamoe.mirai.internal.network.components.PacketLoggingStrategyImpl import net.mamoe.mirai.internal.network.components.RawIncomingPacket import net.mamoe.mirai.internal.network.components.ServerList import net.mamoe.mirai.internal.network.handler.NetworkHandlerContext import net.mamoe.mirai.internal.network.handler.NetworkHandlerContextImpl import net.mamoe.mirai.internal.network.handler.NetworkHandlerFactory import net.mamoe.mirai.internal.network.handler.SocketAddress import net.mamoe.mirai.internal.network.handler.selector.KeepAliveNetworkHandlerSelector import net.mamoe.mirai.internal.network.handler.selector.SelectorNetworkHandler import net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler import net.mamoe.mirai.internal.utils.subLogger import net.mamoe.mirai.utils.* import java.awt.Component import java.awt.event.MouseAdapter import java.awt.event.MouseEvent import java.lang.invoke.MethodHandles import javax.swing.* import kotlin.reflect.KProperty import kotlin.reflect.full.declaredMembers import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaField internal object NetReplayHelperSettings { var commands_hide_hideAll: Collection<String> by lateinitMutableProperty { listOf( "Heartbeat.Alive", "wtlogin.exchange_emp", "StatSvc.register", "StatSvc.GetDevLoginInfo", "MessageSvc.PbGetMsg", "friendlist.getFriendGroupList", "friendlist.GetTroopListReqV2", "friendlist.GetTroopMemberListReq", ) } var commands_hide_hideInConsole: Collection<String> by lateinitMutableProperty { listOf( "ConfigPushSvc.PushReq", *PacketLoggingStrategyImpl.getDefaultBlacklist().toTypedArray(), ) } var logger_console: MiraiLogger by lateinitMutableProperty { MiraiLogger.Factory.create(NetReplayHelperClass()) } var logger_file: MiraiLogger = SilentLogger.withSwitch(false) @JvmField val NetReplyHelper: Class<*> = NetReplayHelperClass() } private fun NetReplayHelperClass(): Class<*> { return MethodHandles.lookup().lookupClass() } private fun attachNetReplayHelper(channel: Channel) { channel.pipeline() // TODO: 2022/6/2 will not work since "raw-packet-collector" has been removed .addBefore("raw-packet-collector", "raw-packet-dumper", newRawPacketDumper()) attachNetReplayWView(channel) } private fun newRawPacketDumper(): ChannelHandler = object : ChannelInboundHandlerAdapter() { override fun channelRead(ctx: ChannelHandlerContext?, msg: Any?) { if (msg is RawIncomingPacket) { if (msg.commandName in NetReplayHelperSettings.commands_hide_hideAll) { NetReplayHelperSettings.logger_console.debug { "sid=${msg.sequenceId}, cmd=${msg.commandName}, body=<DROPPED>" } NetReplayHelperSettings.logger_file.debug { "sid=${msg.sequenceId}, cmd=${msg.commandName}, body=<DROPPED>" } super.channelRead(ctx, msg) return } if (msg.commandName in NetReplayHelperSettings.commands_hide_hideInConsole) { NetReplayHelperSettings.logger_console.debug { "sid=${msg.sequenceId}, cmd=${msg.commandName}, body=<DROPPED>" } } else { NetReplayHelperSettings.logger_console.debug { "sid=${msg.sequenceId}, cmd=${msg.commandName}, body=${msg.body.toUHexString()}" } } NetReplayHelperSettings.logger_file.debug { "sid=${msg.sequenceId}, cmd=${msg.commandName}, body=${msg.body.toUHexString()}" } } super.channelRead(ctx, msg) } } private fun attachNetReplayWView(channel: Channel) { val frame = JFrame("Net Replay Helper") val panel = JPanel() val layout = GroupLayout(panel) panel.layout = layout frame.add(panel) val cmd = JTextField() val sid = JTextField() val bdy = JTextField() val log = JTextField() val cmdLabel = JLabel("cmd").also { it.labelFor = cmd } val sidLabel = JLabel("seq").also { it.labelFor = sid } val bdyLabel = JLabel("body").also { it.labelFor = bdy } val logLabel = JLabel("log").also { it.labelFor = log } val fireCustom = JButton("Fire Cus") val fireLog = JButton("Fire Log") // region layout.setHorizontalGroup( layout.createParallelGroup().addGroup( layout.createSequentialGroup().addGroup( layout.createParallelGroup() .addComponent(cmdLabel) .addComponent(sidLabel) .addComponent(bdyLabel) .addComponent(logLabel) ).addGroup( layout.createParallelGroup() .addComponent(cmd) .addComponent(sid) .addComponent(bdy) .addComponent(log) ) ).addGroup( layout.createSequentialGroup() .addComponent(fireCustom) .addComponent(fireLog) ) ) layout.setVerticalGroup( layout.createSequentialGroup() .addGroup( layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(cmdLabel) .addComponent(cmd) ) .addGroup( layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(sidLabel) .addComponent(sid) ) .addGroup( layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(bdyLabel) .addComponent(bdy) ) .addGroup( layout.createParallelGroup(GroupLayout.Alignment.BASELINE) .addComponent(logLabel) .addComponent(log) ) .addGroup( layout.createParallelGroup() .addComponent(fireCustom) .addComponent(fireLog) ) ) // endregion fun Component.onClick(handle: () -> Unit) { addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent?) { runCatching(handle).onFailure { err -> NetReplayHelperSettings.logger_console.error(err) NetReplayHelperSettings.logger_file.error(err) } } }) } fireCustom.onClick { val rp = RawIncomingPacket( commandName = cmd.text.trim(), sequenceId = sid.text.toInt(), body = bdy.text.hexToBytes(), ) channel.pipeline().fireChannelRead(rp) } @Suppress("LocalVariableName") fireLog.onClick { var line = log.text.substringAfter("sid=") // 2021-11-07 11:49:38 D/NetReplayHelper: sid=123, cmd=HelloWorld!, body=00 val sid_ = line.substringBefore(",").toInt() line = line.substringAfter("cmd=") val cmd_ = line.substringBeforeLast("body=").trim().removeSuffix(",").trim() val bdy_ = line.substringAfterLast("body=").trim().hexToBytes() val rp = RawIncomingPacket( commandName = cmd_, sequenceId = sid_, body = bdy_, ) channel.pipeline().fireChannelRead(rp) } frame.pack() frame.defaultCloseOperation = JFrame.DO_NOTHING_ON_CLOSE frame.setLocationRelativeTo(null) frame.isVisible = true channel.closeFuture().addListener { SwingUtilities.invokeLater { frame.dispose() } } } private object NRHNettyNetworkHandlerFactory : NetworkHandlerFactory<NettyNetworkHandler> { override fun create(context: NetworkHandlerContext, address: SocketAddress): NettyNetworkHandler { return object : NettyNetworkHandler(context, address) { override fun setupChannelPipeline(pipeline: ChannelPipeline, decodePipeline: PacketDecodePipeline) { super.setupChannelPipeline(pipeline, decodePipeline) attachNetReplayHelper(pipeline.channel()) } } } } // Call before bot.login() fun Bot.attachNetReplayHelper() { asQQAndroidBot() val networkLogger = this::class.declaredMembers.first { it.name == "networkLogger" }.let { property -> property as KProperty<*> property.isAccessible = true property.getter.call(this@attachNetReplayHelper) } as MiraiLogger val snh = network.cast<SelectorNetworkHandler<*>>() val field = snh::selector.javaField!! field.isAccessible = true field.set( snh, KeepAliveNetworkHandlerSelector( maxAttempts = configuration.reconnectionRetryTimes.coerceIn(1, Int.MAX_VALUE), logger = networkLogger.subLogger("Selector") ) { val context = NetworkHandlerContextImpl( bot, networkLogger, createNetworkLevelComponents(), ) NRHNettyNetworkHandlerFactory.create( context, context[ServerList].pollAny().toSocketAddress(), ) }, ) } fun main() { val bot = BotFactory.newBot(0, "") bot.attachNetReplayHelper() // // TODO: 2022/6/2 will not work since "raw-packet-collector" has been removed, see net.mamoe.mirai.internal.netinternalkit.NetReplayHelper.attachNetReplayHelper(io.netty.channel.Channel) } ================================================ FILE: mirai-core/src/jvmTest/kotlin/package.kt ================================================ /* * Copyright 2019-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 ================================================ FILE: mirai-core/src/jvmTest/kotlin/test/initializeTestJvm.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.test internal actual fun initializeTestPlatformBeforeCommon() { // nop } ================================================ FILE: mirai-core/src/jvmTest/kotlin/testFramework/currentPlatform.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.testFramework actual fun currentPlatform(): Platform = Platform.Jvm ================================================ FILE: mirai-core/src/jvmTest/resources/account.yml ================================================ id: 123 password: "" ================================================ FILE: mirai-core-all/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UnusedImport") import shadow.configureRelocatedShadowJarForJvmProject import shadow.relocateImplementation plugins { kotlin("jvm") kotlin("plugin.serialization") `maven-publish` id("me.him188.kotlin-jvm-blocking-bridge") } version = Versions.project description = "Mirai core shadowed" dependencies { api(project(":mirai-core")) api(project(":mirai-core-api")) api(project(":mirai-core-utils")) implementation(`slf4j-api`) // Required by mirai-console relocateImplementation(project, `kt-bignum_relocated`) relocateImplementation(project, `ktor-client-core_relocated`) relocateImplementation(project, `ktor-client-okhttp_relocated`) relocateImplementation(project, `ktor-io_relocated`) } val shadow = configureRelocatedShadowJarForJvmProject(kotlin) if (System.getenv("MIRAI_IS_SNAPSHOTS_PUBLISHING")?.toBoolean() != true) { // Do not publish `-all` jars to snapshot server since they are too large. configurePublishing("mirai-core-all", addShadowJar = false) publications { getByName("mavenJava", MavenPublication::class) { artifact(shadow) } } tasks.getByName("publishMavenJavaPublicationToMavenLocal").dependsOn(shadow) tasks.findByName("publishMavenJavaPublicationToMavenCentralRepository")?.dependsOn(shadow) } // //// WARNING: You must also consider relocating transitive dependencies. //// Otherwise, user will get NoClassDefFound error when using mirai as a classpath dependency. See #2263. // //val includeInRuntime = true //relocateAllFromGroupId("io.ktor", includeInRuntime, "io.ktor") //relocateAllFromGroupId("com.squareup.okhttp3", includeInRuntime, listOf("okhttp3")) //relocateAllFromGroupId("com.squareup.okio", includeInRuntime, listOf("okio")) ================================================ FILE: mirai-core-api/README.md ================================================ # mirai-core-api mirai 核心 API 模块。本文档帮助读者了解该模块的主要架构。 > mirai 为多平台设计。支持 Android 和 JVM 双平台,拥有多个源集。 > > - `commonMain`:平台无关的通用代码。绝大部分代码都存在与这个源集。 > - `jvmMain`:桌面 JVM 平台的特别实现。 > - `androidMain`:Android 平台的特别实现。 > > 阅读源码通常阅读 `src/commonMain` ## 架构 | 包名 | 描述 | |:------------------|:----------------------------------------------------| | `net.mamoe.mirai` | mirai 核心 API | | `.contact` | 联系人类型。如群 `Group`,好友 `Friend` | | `.event` | 事件框架。提供事件对象的基类以及监听事件的方法 | | `.event.events` | 事件的定义。包含许多事件的具体类, 如消息事件 `MessageEvent` | | `.message` | 消息系统 | | `.message.data` | 提供对富文本聊天消息及其元素多样性的抽象 | | `.message.code` | 提供一个易于阅读的消息字符串表示方式 | | `.message.action` | 提供与消息有关的动作的抽象,如戳一戳 | | `.utils` | 一些工具类 | | `.internal` | 内部实现 | | `.internal.event` | 事件框架的实现 | ## `net.mamoe.mirai` ### `IMirai` [IMirai.kt](src/commonMain/kotlin/IMirai.kt#L33) **API 模块与协议实现模块的对接接口。** - 单例 - 通过 `ServiceLoader` 寻找[协议实现](../mirai-core/README.md)。 - 若 `ServiceLoader` 在特定环境下不可用,外部可在 Kotlin **在调用任何 Mirai API 之前**覆盖实例: ```kotlin @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // 必要 net.mamoe.mirai._MiraiInstance.set(net.mamoe.mirai.internal.MiraiImpl()) ``` ### `Bot` [BotFactory]: src/commonMain/kotlin/BotFactory.kt [Bot.kt](src/commonMain/kotlin/IMirai.kt#L29) 表示一个机器人对象(账户)。 - 通过 [BotFactory] 构造 - 是功能的入口点----大部分操作都直接或间接经过 `Bot` - 持有联系人(好友和群)对象列表 - 可获得事件通道 ## `net.mamoe.mirai.contact` 联系人系统。[docs/Contacts](../docs/Contacts.md) ## `net.mamoe.mirai.event` 事件系统。[docs/Contacts](../docs/Contacts.md) ## `net.mamoe.mirai.event.events` 事件列表。[README](src/commonMain/kotlin/event/events/EventList.md#事件) ## `net.mamoe.mirai.message` 消息系统。 ### `MessageReceipt` [MessageReceipt.kt](src/commonMain/kotlin/message/MessageReceipt.kt#L25) 在发送消息(`Contact.sendMessage`)后收到的回执。 ### `MessageSerializers` [MessageSerializers.kt](src/commonMain/kotlin/message/MessageSerializers.kt#L27) [kotlinx.serialization](https://github.com/kotlin/kotlinx.serialization) 序列化支持。 ## `net.mamoe.mirai.message.data` 对富文本聊天消息及其元素多样性的抽象。 一个消息元素最基本的接口为 [Message](src/commonMain/kotlin/message/data/Message.kt#L30). ================================================ FILE: mirai-core-api/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UNUSED_VARIABLE") import BinaryCompatibilityConfigurator.configureBinaryValidators import shadow.relocateCompileOnly plugins { kotlin("multiplatform") kotlin("plugin.serialization") id("kotlinx-atomicfu") id("signing") id("me.him188.kotlin-jvm-blocking-bridge") id("me.him188.kotlin-dynamic-delegation") // id("me.him188.maven-central-publish") `maven-publish` } description = "Mirai API module" kotlin { explicitApi() apply(plugin = "explicit-api") configureJvmTargetsHierarchical("net.mamoe.mirai") sourceSets { val commonMain by getting { dependencies { api(kotlin("reflect")) api(`kotlinx-serialization-core`) api(`kotlinx-serialization-json`) api(`kotlinx-coroutines-core`) // don't remove it, otherwise IDE will complain implementation(project(":mirai-core-utils")) implementation(project(":mirai-console-compiler-annotations")) implementation(`kotlinx-serialization-protobuf`) implementation(`kotlinx-atomicfu`) implementation(`jetbrains-annotations`) // runtime from mirai-core-utils relocateCompileOnly(`ktor-io_relocated`) implementation(`kotlin-jvm-blocking-bridge`) implementation(`kotlin-dynamic-delegation`) implementation(`log4j-api`) compileOnly(`slf4j-api`) } } commonTest { dependencies { runtimeOnly(`log4j-core`) implementation(`kotlinx-coroutines-test`) api(`junit-jupiter-api`) } } afterEvaluate { findByName("androidUnitTest")?.apply { dependencies { runtimeOnly(`slf4j-api`) } } } findByName("jvmMain")?.apply { } findByName("jvmTest")?.apply { dependencies { runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE } } } } atomicfu { transformJvm = false } if (tasks.findByName("androidMainClasses") != null) { tasks.register("checkAndroidApiLevel") { doFirst { analyzes.AndroidApiLevelCheck.check( buildDir.resolve("classes/kotlin/android/main"), project.property("mirai.android.target.api.level")!!.toString().toInt(), project ) } group = "verification" this.mustRunAfter("androidMainClasses") } tasks.findByName("androidTest")?.dependsOn("checkAndroidApiLevel") } configureMppPublishing() configureBinaryValidators(setOf("jvm", "android").filterTargets()) //mavenCentralPublish { // artifactId = "mirai-core-api" // githubProject("mamoe", "mirai") // developer("Mamoe Technologies", email = "support@mamoe.net", url = "https://github.com/mamoe") // licenseFromGitHubProject("AGPLv3", "dev") // publishPlatformArtifactsInRootModule = "jvm" //} ================================================ FILE: mirai-core-api/compatibility-validation/android/api/android.api ================================================ public abstract interface class net/mamoe/mirai/Bot : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/ContactOrBot, net/mamoe/mirai/contact/UserOrBot { public static final field Companion Lnet/mamoe/mirai/Bot$Companion; public fun close ()V public abstract fun close (Ljava/lang/Throwable;)V public static synthetic fun close$default (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)V public fun closeAndJoin (Ljava/lang/Throwable;)V public fun closeAndJoin (Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun closeAndJoin$default (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)V public static synthetic fun closeAndJoin$default (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun closeAndJoin$suspendImpl (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun findInstance (J)Lnet/mamoe/mirai/Bot; public abstract fun getAsFriend ()Lnet/mamoe/mirai/contact/Friend; public abstract fun getAsStranger ()Lnet/mamoe/mirai/contact/Stranger; public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getConfiguration ()Lnet/mamoe/mirai/utils/BotConfiguration; public abstract fun getEventChannel ()Lnet/mamoe/mirai/event/EventChannel; public fun getFriend (J)Lnet/mamoe/mirai/contact/Friend; public abstract fun getFriendGroups ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroups; public fun getFriendOrFail (J)Lnet/mamoe/mirai/contact/Friend; public abstract fun getFriends ()Lnet/mamoe/mirai/contact/ContactList; public fun getGroup (J)Lnet/mamoe/mirai/contact/Group; public fun getGroupOrFail (J)Lnet/mamoe/mirai/contact/Group; public abstract fun getGroups ()Lnet/mamoe/mirai/contact/ContactList; public static fun getInstance (J)Lnet/mamoe/mirai/Bot; public static fun getInstanceOrNull (J)Lnet/mamoe/mirai/Bot; public static fun getInstances ()Ljava/util/List; public static fun getInstancesSequence ()Lkotlin/sequences/Sequence; public abstract fun getLogger ()Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun getOtherClients ()Lnet/mamoe/mirai/contact/ContactList; public fun getStranger (J)Lnet/mamoe/mirai/contact/Stranger; public fun getStrangerOrFail (J)Lnet/mamoe/mirai/contact/Stranger; public abstract fun getStrangers ()Lnet/mamoe/mirai/contact/ContactList; public abstract fun isOnline ()Z public fun join ()V public fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun join$suspendImpl (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun login ()V public abstract fun login (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun nudge ()Lnet/mamoe/mirai/message/action/BotNudge; public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; } public final class net/mamoe/mirai/Bot$Companion { public final fun findInstance (J)Lnet/mamoe/mirai/Bot; public final fun getInstance (J)Lnet/mamoe/mirai/Bot; public final fun getInstanceOrNull (J)Lnet/mamoe/mirai/Bot; public final fun getInstances ()Ljava/util/List; public final fun getInstancesSequence ()Lkotlin/sequences/Sequence; } public abstract interface class net/mamoe/mirai/BotFactory { public static final field INSTANCE Lnet/mamoe/mirai/BotFactory$INSTANCE; public fun newBot (JLjava/lang/String;)Lnet/mamoe/mirai/Bot; public fun newBot (JLjava/lang/String;Lnet/mamoe/mirai/BotFactory$BotConfigurationLambda;)Lnet/mamoe/mirai/Bot; public abstract fun newBot (JLjava/lang/String;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;)Lnet/mamoe/mirai/Bot; public fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;Lnet/mamoe/mirai/BotFactory$BotConfigurationLambda;)Lnet/mamoe/mirai/Bot; public abstract fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public fun newBot (J[B)Lnet/mamoe/mirai/Bot; public fun newBot (J[BLnet/mamoe/mirai/BotFactory$BotConfigurationLambda;)Lnet/mamoe/mirai/Bot; public abstract fun newBot (J[BLnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; } public abstract interface class net/mamoe/mirai/BotFactory$BotConfigurationLambda { public abstract fun invoke (Lnet/mamoe/mirai/utils/BotConfiguration;)V } public final class net/mamoe/mirai/BotFactory$INSTANCE : net/mamoe/mirai/BotFactory { public final synthetic fun newBot (JLjava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/Bot; public fun newBot (JLjava/lang/String;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public final synthetic fun newBot (J[BLkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/Bot; public fun newBot (J[BLnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; } public final class net/mamoe/mirai/BotKt { public static final synthetic fun alsoLogin (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun containsFriend (Lnet/mamoe/mirai/Bot;J)Z public static final synthetic fun containsGroup (Lnet/mamoe/mirai/Bot;J)Z public static final synthetic fun getSupervisorJob (Lnet/mamoe/mirai/Bot;)Lkotlinx/coroutines/CompletableJob; } public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLevelApiAccessor { public fun acceptInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)V public abstract fun acceptInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun acceptMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;)V public abstract fun acceptMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun acceptNewFriendRequest (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;)V public abstract fun acceptNewFriendRequest (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun broadcastEvent (Lnet/mamoe/mirai/event/Event;)V public abstract fun broadcastEvent (Lnet/mamoe/mirai/event/Event;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun calculateGroupCodeByGroupUin (J)J 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 createFileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; public fun createImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public abstract fun createUnsupportedMessage ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; 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 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;ZILjava/lang/Object;)Ljava/util/List; 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 getUin (Lnet/mamoe/mirai/contact/ContactOrBot;)J public fun ignoreInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)V public abstract fun ignoreInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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;ZILjava/lang/Object;)V 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 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 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; public fun rejectMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;)V public abstract fun rejectMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun rejectMemberJoinRequest$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;ILjava/lang/Object;)V public static synthetic fun rejectMemberJoinRequest$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 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;ZILjava/lang/Object;)V 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 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 interface annotation class net/mamoe/mirai/LowLevelApi : java/lang/annotation/Annotation { } public abstract interface class net/mamoe/mirai/LowLevelApiAccessor { public fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJ)Ljava/lang/String; public abstract fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupList (Lnet/mamoe/mirai/Bot;)Lkotlin/sequences/Sequence; public abstract fun getRawGroupList (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupMemberList (Lnet/mamoe/mirai/Bot;JJJ)Lkotlin/sequences/Sequence; public abstract fun getRawGroupMemberList (Lnet/mamoe/mirai/Bot;JJJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun muteAnonymousMember (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;JI)V public abstract fun muteAnonymousMember (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;JILkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun newFriend (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/data/FriendInfo;)Lnet/mamoe/mirai/contact/Friend; public abstract fun newStranger (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/data/StrangerInfo;)Lnet/mamoe/mirai/contact/Stranger; public fun recallFriendMessageRaw (Lnet/mamoe/mirai/Bot;J[I[II)Z public abstract fun recallFriendMessageRaw (Lnet/mamoe/mirai/Bot;J[I[IILkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun recallGroupMessageRaw (Lnet/mamoe/mirai/Bot;J[I[I)Z 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 fun solveBotInvitedJoinGroupRequestEvent (Lnet/mamoe/mirai/Bot;JJJZ)V public abstract fun solveBotInvitedJoinGroupRequestEvent (Lnet/mamoe/mirai/Bot;JJJZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun solveMemberJoinRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;)V public abstract fun solveMemberJoinRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun solveMemberJoinRequestEvent$default (Lnet/mamoe/mirai/LowLevelApiAccessor;Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;ILjava/lang/Object;)V public static synthetic fun solveMemberJoinRequestEvent$default (Lnet/mamoe/mirai/LowLevelApiAccessor;Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun solveNewFriendRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;ZZ)V public abstract fun solveNewFriendRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/Mirai { public static final fun getInstance ()Lnet/mamoe/mirai/IMirai; public static final synthetic fun recallMessage (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/_MiraiInstance { public static final field INSTANCE Lnet/mamoe/mirai/_MiraiInstance; public static final fun get ()Lnet/mamoe/mirai/IMirai; public static final fun set (Lnet/mamoe/mirai/IMirai;)V } public abstract class net/mamoe/mirai/auth/AuthReason { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$FastLoginError : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$ForceOffline : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$FreshLogin : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$MsfOffline : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$NetworkError : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$Unknown : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getCause ()Ljava/lang/Throwable; public fun getMessage ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/auth/BotAuthInfo { public abstract fun getConfiguration ()Lnet/mamoe/mirai/utils/BotConfiguration; public abstract fun getDeviceInfo ()Lnet/mamoe/mirai/utils/DeviceInfo; public abstract fun getId ()J public abstract fun getReason ()Lnet/mamoe/mirai/auth/AuthReason; public abstract fun isFirstLogin ()Z } public abstract interface class net/mamoe/mirai/auth/BotAuthResult { } public abstract interface class net/mamoe/mirai/auth/BotAuthSession { public abstract fun authByPassword (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun authByPassword ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun authByQRCode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/auth/BotAuthorization { public static final field Companion Lnet/mamoe/mirai/auth/BotAuthorization$Companion; public abstract fun authorize (Lnet/mamoe/mirai/auth/BotAuthSession;Lnet/mamoe/mirai/auth/BotAuthInfo;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun byPassword (Ljava/lang/String;)Lnet/mamoe/mirai/auth/BotAuthorization; public static fun byPassword ([B)Lnet/mamoe/mirai/auth/BotAuthorization; public static fun byQRCode ()Lnet/mamoe/mirai/auth/BotAuthorization; public fun calculateSecretsKey (Lnet/mamoe/mirai/auth/BotAuthInfo;)[B } public final class net/mamoe/mirai/auth/BotAuthorization$Companion { public final fun byPassword (Ljava/lang/String;)Lnet/mamoe/mirai/auth/BotAuthorization; public final fun byPassword ([B)Lnet/mamoe/mirai/auth/BotAuthorization; public final fun byQRCode ()Lnet/mamoe/mirai/auth/BotAuthorization; public final fun invoke (Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/auth/BotAuthorization; } public abstract interface class net/mamoe/mirai/auth/QRCodeLoginListener { public fun getQrCodeEcLevel ()I public fun getQrCodeMargin ()I public fun getQrCodeSize ()I public fun getQrCodeStateUpdateInterval ()J public fun onCompleted ()V public abstract fun onFetchQRCode (Lnet/mamoe/mirai/Bot;[B)V public fun onIntervalLoop ()V public abstract fun onStateChanged (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/auth/QRCodeLoginListener$State;)V } public final class net/mamoe/mirai/auth/QRCodeLoginListener$State : java/lang/Enum { public static final field CANCELLED Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field CONFIRMED Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field DEFAULT Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field TIMEOUT Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field WAITING_FOR_CONFIRM Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field WAITING_FOR_SCAN Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static fun values ()[Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; } public abstract interface class net/mamoe/mirai/contact/AnonymousMember : net/mamoe/mirai/contact/Member { public abstract fun getAnonymousId ()Ljava/lang/String; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/AnonymousMember;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/AnonymousMember;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; public static synthetic fun uploadImage$suspendImpl (Lnet/mamoe/mirai/contact/AnonymousMember;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/AudioSupported : net/mamoe/mirai/contact/Contact { public fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/OfflineAudio; public abstract fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/AvatarSpec : java/lang/Enum, java/lang/Comparable { public static final field LARGE Lnet/mamoe/mirai/contact/AvatarSpec; public static final field LARGEST Lnet/mamoe/mirai/contact/AvatarSpec; public static final field MEDIUM Lnet/mamoe/mirai/contact/AvatarSpec; public static final field ORIGINAL Lnet/mamoe/mirai/contact/AvatarSpec; public static final field SMALL Lnet/mamoe/mirai/contact/AvatarSpec; public static final field SMALLEST Lnet/mamoe/mirai/contact/AvatarSpec; public final fun getSize ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/AvatarSpec; public static fun values ()[Lnet/mamoe/mirai/contact/AvatarSpec; } public final class net/mamoe/mirai/contact/BotIsBeingMutedException : net/mamoe/mirai/contact/SendMessageFailedException { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Group;)V public fun getMessage ()Ljava/lang/String; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Group; } public final class net/mamoe/mirai/contact/ClientKind : java/lang/Enum { public static final field ANDROID_PAD Lnet/mamoe/mirai/contact/ClientKind; public static final field AOL_CHAOJIHUIYUAN Lnet/mamoe/mirai/contact/ClientKind; public static final field AOL_HUIYUAN Lnet/mamoe/mirai/contact/ClientKind; public static final field AOL_SQQ Lnet/mamoe/mirai/contact/ClientKind; public static final field CAR Lnet/mamoe/mirai/contact/ClientKind; public static final field Companion Lnet/mamoe/mirai/contact/ClientKind$Companion; public static final field HRTX_IPHONE Lnet/mamoe/mirai/contact/ClientKind; public static final field HRTX_PC Lnet/mamoe/mirai/contact/ClientKind; public static final field MC_3G Lnet/mamoe/mirai/contact/ClientKind; public static final field MISRO_MSG Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_ANDROID Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_ANDROID_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_HD Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_HD_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_IPAD Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_IPAD_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_IPHONE Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_OTHER Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_PC_QQ Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_PC_TIM Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_WINPHONE_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field QQ_FORELDER Lnet/mamoe/mirai/contact/ClientKind; public static final field QQ_SERVICE Lnet/mamoe/mirai/contact/ClientKind; public static final field TV_QQ Lnet/mamoe/mirai/contact/ClientKind; public static final field WIN8 Lnet/mamoe/mirai/contact/ClientKind; public static final field WINPHONE Lnet/mamoe/mirai/contact/ClientKind; public final fun getId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/ClientKind; public static fun values ()[Lnet/mamoe/mirai/contact/ClientKind; } public final class net/mamoe/mirai/contact/ClientKind$Companion { } public abstract interface class net/mamoe/mirai/contact/Contact : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/ContactOrBot { public static final field Companion Lnet/mamoe/mirai/contact/Contact$Companion; public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getId ()J public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image; public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ShortVideo; public abstract fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ShortVideo; public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/Contact$Companion { public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/ContactKt { public static final synthetic fun recallMessage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun recallMessage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/ContactList : java/util/Collection, kotlin/jvm/internal/markers/KMappedMarker { public final field delegate Ljava/util/Collection; public synthetic fun add (Ljava/lang/Object;)Z public fun add (Lnet/mamoe/mirai/contact/Contact;)Z public fun addAll (Ljava/util/Collection;)Z public fun clear ()V public final fun contains (J)Z public final fun contains (Ljava/lang/Object;)Z public fun contains (Lnet/mamoe/mirai/contact/Contact;)Z public fun containsAll (Ljava/util/Collection;)Z public fun equals (Ljava/lang/Object;)Z public final fun get (J)Lnet/mamoe/mirai/contact/Contact; public final fun getOrFail (J)Lnet/mamoe/mirai/contact/Contact; public fun getSize ()I public fun hashCode ()I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public final fun remove (J)Z public fun remove (Ljava/lang/Object;)Z public fun removeAll (Ljava/util/Collection;)Z public fun removeIf (Ljava/util/function/Predicate;)Z public fun retainAll (Ljava/util/Collection;)Z public final fun size ()I public fun toArray ()[Ljava/lang/Object; public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/ContactOrBot : kotlinx/coroutines/CoroutineScope { public fun getAvatarUrl ()Ljava/lang/String; public fun getAvatarUrl (Lnet/mamoe/mirai/contact/AvatarSpec;)Ljava/lang/String; public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getId ()J } public final class net/mamoe/mirai/contact/ExceptionsKt { public static final fun getBotMuteRemaining (Lnet/mamoe/mirai/contact/BotIsBeingMutedException;)I } public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamoe/mirai/contact/Contact { public abstract fun getFiles ()Lnet/mamoe/mirai/contact/file/RemoteFiles; public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile; } public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User, net/mamoe/mirai/contact/roaming/RoamingSupported { public fun delete ()V public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getFriendGroup ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public abstract fun getRemark ()Ljava/lang/String; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun setRemark (Ljava/lang/String;)V } public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/FileSupported, net/mamoe/mirai/contact/roaming/RoamingSupported { public static final field Companion Lnet/mamoe/mirai/contact/Group$Companion; public fun avatarUrl (Lnet/mamoe/mirai/contact/AvatarSpec;)Ljava/lang/String; public abstract fun contains (J)Z public fun contains (Lnet/mamoe/mirai/contact/NormalMember;)Z public abstract fun get (J)Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getActive ()Lnet/mamoe/mirai/contact/active/GroupActive; public abstract fun getAnnouncements ()Lnet/mamoe/mirai/contact/announcement/Announcements; public fun getAvatarUrl ()Ljava/lang/String; public synthetic fun getAvatarUrl (Lnet/mamoe/mirai/contact/AvatarSpec;)Ljava/lang/String; public abstract fun getBotAsMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun getBotMuteRemaining ()I public fun getBotPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public abstract fun getEssences ()Lnet/mamoe/mirai/contact/essence/Essences; public abstract fun getId ()J public abstract fun getMembers ()Lnet/mamoe/mirai/contact/ContactList; public abstract fun getName ()Ljava/lang/String; public fun getOrFail (J)Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getOwner ()Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getSettings ()Lnet/mamoe/mirai/contact/GroupSettings; public fun quit ()Z public abstract fun quit (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;)Z public static fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 synthetic fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Voice; public abstract synthetic fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/Group$Companion { public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;)Z public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/GroupKt { public static final synthetic fun getMember (Lnet/mamoe/mirai/contact/Group;J)Lnet/mamoe/mirai/contact/NormalMember; public static final synthetic fun getMemberOrFail (Lnet/mamoe/mirai/contact/Group;J)Lnet/mamoe/mirai/contact/NormalMember; public static final fun isBotMuted (Lnet/mamoe/mirai/contact/Group;)Z } public abstract interface class net/mamoe/mirai/contact/GroupSettings { public abstract synthetic fun getEntranceAnnouncement ()Ljava/lang/String; public abstract fun isAllowMemberInvite ()Z public abstract fun isAnonymousChatEnabled ()Z public abstract fun isAutoApproveEnabled ()Z public abstract fun isMuteAll ()Z public abstract fun setAllowMemberInvite (Z)V public abstract fun setAnonymousChatEnabled (Z)V public abstract synthetic fun setEntranceAnnouncement (Ljava/lang/String;)V public abstract fun setMuteAll (Z)V } public abstract interface class net/mamoe/mirai/contact/Member : net/mamoe/mirai/contact/User { public abstract fun getActive ()Lnet/mamoe/mirai/contact/active/MemberActive; public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; public abstract fun getNameCard ()Ljava/lang/String; public abstract fun getPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public fun getRankTitle ()Ljava/lang/String; public abstract fun getSpecialTitle ()Ljava/lang/String; public fun getTemperatureTitle ()Ljava/lang/String; 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 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 final class net/mamoe/mirai/contact/MemberKt { public static final fun asFriend (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Friend; public static final fun asFriendOrNull (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Friend; public static final fun asStranger (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Stranger; public static final fun asStrangerOrNull (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Stranger; public static final fun getNameCardOrNick (Lnet/mamoe/mirai/contact/Member;)Ljava/lang/String; public static final fun isFriend (Lnet/mamoe/mirai/contact/Member;)Z public static final fun isStranger (Lnet/mamoe/mirai/contact/Member;)Z } public final class net/mamoe/mirai/contact/MemberPermission : java/lang/Enum, java/lang/Comparable { public static final field ADMINISTRATOR Lnet/mamoe/mirai/contact/MemberPermission; public static final field MEMBER Lnet/mamoe/mirai/contact/MemberPermission; public static final field OWNER Lnet/mamoe/mirai/contact/MemberPermission; public final fun getLevel ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/MemberPermission; public static fun values ()[Lnet/mamoe/mirai/contact/MemberPermission; } public final class net/mamoe/mirai/contact/MemberPermissionKt { public static final fun checkBotPermission (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lkotlin/jvm/functions/Function0;)V public static synthetic fun checkBotPermission$default (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V public static final fun isAdministrator (Lnet/mamoe/mirai/contact/Member;)Z public static final fun isOperator (Lnet/mamoe/mirai/contact/Member;)Z public static final fun isOwner (Lnet/mamoe/mirai/contact/Member;)Z } public final class net/mamoe/mirai/contact/MessageTooLargeException : net/mamoe/mirai/contact/SendMessageFailedException { public fun <init> (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;)V public fun getMessage ()Ljava/lang/String; public final fun getMessageAfterEvent ()Lnet/mamoe/mirai/message/data/Message; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe/mirai/contact/Member { public abstract fun getJoinTimestamp ()I public abstract fun getLastSpeakTimestamp ()I public abstract fun getMuteTimeRemaining ()I public abstract fun getNameCard ()Ljava/lang/String; public abstract fun getSpecialTitle ()Ljava/lang/String; public fun isMuted ()Z public fun kick (Ljava/lang/String;)V public fun kick (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun kick (Ljava/lang/String;Z)V public abstract fun kick (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun kick$suspendImpl (Lnet/mamoe/mirai/contact/NormalMember;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun modifyAdmin (Z)V public abstract fun modifyAdmin (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/NormalMember;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun setNameCard (Ljava/lang/String;)V public abstract fun setSpecialTitle (Ljava/lang/String;)V public fun unmute ()V public abstract fun unmute (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/NormalMemberKt { public static final fun getNameCardOrNick (Lnet/mamoe/mirai/contact/User;)Ljava/lang/String; public static final fun getNameCardOrNick (Lnet/mamoe/mirai/contact/UserOrBot;)Ljava/lang/String; public static final fun isMuted (Lnet/mamoe/mirai/contact/NormalMember;)Z public static final fun mute-8Mi8wO0 (Lnet/mamoe/mirai/contact/NormalMember;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun mute-fcu0wV4 (Lnet/mamoe/mirai/contact/NormalMember;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/OtherClient : net/mamoe/mirai/contact/Contact { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public fun getId ()J public abstract fun getInfo ()Lnet/mamoe/mirai/contact/OtherClientInfo; public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/OtherClient;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; public static synthetic fun uploadImage$suspendImpl (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/OtherClientInfo { public final fun component1 ()I public final fun component2 ()Lnet/mamoe/mirai/contact/Platform; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun copy (ILnet/mamoe/mirai/contact/Platform;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/contact/OtherClientInfo; public static synthetic fun copy$default (Lnet/mamoe/mirai/contact/OtherClientInfo;ILnet/mamoe/mirai/contact/Platform;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/OtherClientInfo; public fun equals (Ljava/lang/Object;)Z public final fun getAppId ()I public final fun getDeviceKind ()Ljava/lang/String; public final fun getDeviceName ()Ljava/lang/String; public final fun getPlatform ()Lnet/mamoe/mirai/contact/Platform; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/contact/OtherClientKt { public static final fun getDeviceKind (Lnet/mamoe/mirai/contact/OtherClient;)Ljava/lang/String; public static final fun getDeviceName (Lnet/mamoe/mirai/contact/OtherClient;)Ljava/lang/String; public static final fun getPlatform (Lnet/mamoe/mirai/contact/OtherClient;)Lnet/mamoe/mirai/contact/Platform; } public final class net/mamoe/mirai/contact/PermissionDeniedException : java/lang/IllegalStateException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/contact/Platform : java/lang/Enum { public static final field Companion Lnet/mamoe/mirai/contact/Platform$Companion; public static final field IOS Lnet/mamoe/mirai/contact/Platform; public static final field MOBILE Lnet/mamoe/mirai/contact/Platform; public static final field WINDOWS Lnet/mamoe/mirai/contact/Platform; public final fun getPlatformId ()I public final fun getTerminalId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/Platform; public static fun values ()[Lnet/mamoe/mirai/contact/Platform; } public final class net/mamoe/mirai/contact/Platform$Companion { } public class net/mamoe/mirai/contact/SendMessageFailedException : java/lang/RuntimeException { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/Message; public final fun getReason ()Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public final class net/mamoe/mirai/contact/SendMessageFailedException$Reason : java/lang/Enum { public static final field AT_ALL_LIMITED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field BOT_MUTED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field GROUP_CHAT_LIMITED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field LIMITED_MESSAGING Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field MESSAGE_TOO_LARGE Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static fun values ()[Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; } public abstract interface class net/mamoe/mirai/contact/Stranger : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/User { public fun delete ()V public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Stranger;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/StrangerKt { public static final fun asFriend (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/contact/Friend; public static final fun asFriendOrNull (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/contact/Friend; } public abstract interface class net/mamoe/mirai/contact/User : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/UserOrBot { public abstract fun getId ()J public abstract fun getRemark ()Ljava/lang/String; public abstract fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge; public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile; public fun queryProfile (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun queryProfile$suspendImpl (Lnet/mamoe/mirai/contact/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/User;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/UserKt { public static final fun getRemarkOrNameCard (Lnet/mamoe/mirai/contact/Member;)Ljava/lang/String; public static final fun getRemarkOrNameCardOrNick (Lnet/mamoe/mirai/contact/Member;)Ljava/lang/String; public static final fun getRemarkOrNick (Lnet/mamoe/mirai/contact/User;)Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/UserOrBot : net/mamoe/mirai/contact/ContactOrBot { public abstract fun getNick ()Ljava/lang/String; public abstract fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; } public final class net/mamoe/mirai/contact/active/ActiveChart { public final fun getActives ()Ljava/util/Map; public final fun getExit ()Ljava/util/Map; public final fun getJoin ()Ljava/util/Map; public final fun getMembers ()Ljava/util/Map; public final fun getSentences ()Ljava/util/Map; } public final class net/mamoe/mirai/contact/active/ActiveHonorInfo { public final fun getAvatar ()Ljava/lang/String; public final fun getHistoryDays ()I public final fun getMaxTermDays ()I public final fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getMemberId ()J public final fun getMemberName ()Ljava/lang/String; public final fun getTermDays ()I } public final class net/mamoe/mirai/contact/active/ActiveHonorList { public final fun getCurrent ()Lnet/mamoe/mirai/contact/active/ActiveHonorInfo; public final fun getRecords ()Ljava/util/List; public final fun getType ()I } public final class net/mamoe/mirai/contact/active/ActiveRankRecord { public final fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getMemberId ()J public final fun getMemberName ()Ljava/lang/String; public final fun getScore ()I public final fun getTemperature ()I } public final class net/mamoe/mirai/contact/active/ActiveRecord { public final fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getMemberId ()J public final fun getMemberName ()Ljava/lang/String; public final fun getMessagesCount ()I public final fun getPeriodDays ()I } public abstract interface class net/mamoe/mirai/contact/active/GroupActive : net/mamoe/mirai/utils/Streamable { public abstract fun getRankTitles ()Ljava/util/Map; public abstract fun getTemperatureTitles ()Ljava/util/Map; public abstract fun isHonorVisible ()Z public abstract fun isTemperatureVisible ()Z public abstract fun isTitleVisible ()Z public fun queryActiveRank ()Ljava/util/List; public abstract fun queryActiveRank (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun queryChart ()Lnet/mamoe/mirai/contact/active/ActiveChart; public abstract fun queryChart (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun queryHonorHistory (ILkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun refresh ()V public abstract fun refresh (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setHonorVisible (Z)V public abstract fun setHonorVisible (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setRankTitles (Ljava/util/Map;)V public abstract fun setRankTitles (Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setTemperatureTitles (Ljava/util/Map;)V public abstract fun setTemperatureTitles (Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setTemperatureVisible (Z)V public abstract fun setTemperatureVisible (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setTitleVisible (Z)V public abstract fun setTitleVisible (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/active/MemberActive { public abstract fun getHonors ()Ljava/util/Set; public abstract fun getPoint ()I public abstract fun getRank ()I public abstract fun getTemperature ()I public fun queryMedal ()Lnet/mamoe/mirai/contact/active/MemberMedalInfo; public abstract fun queryMedal (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/active/MemberMedalInfo { public final fun getColor ()Ljava/lang/String; public final fun getMedals ()Ljava/util/Set; public final fun getTitle ()Ljava/lang/String; public final fun getWearing ()Lnet/mamoe/mirai/contact/active/MemberMedalType; } public final class net/mamoe/mirai/contact/active/MemberMedalType : java/lang/Enum { public static final field ACTIVE Lnet/mamoe/mirai/contact/active/MemberMedalType; public static final field ADMIN Lnet/mamoe/mirai/contact/active/MemberMedalType; public static final field OWNER Lnet/mamoe/mirai/contact/active/MemberMedalType; public static final field SPECIAL Lnet/mamoe/mirai/contact/active/MemberMedalType; public final fun getMask ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/active/MemberMedalType; public static fun values ()[Lnet/mamoe/mirai/contact/active/MemberMedalType; } public abstract interface class net/mamoe/mirai/contact/announcement/Announcement { public static final field Companion Lnet/mamoe/mirai/contact/announcement/Announcement$Companion; public abstract fun getContent ()Ljava/lang/String; public abstract fun getParameters ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun publishTo (Lnet/mamoe/mirai/contact/Group;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public fun publishTo (Lnet/mamoe/mirai/contact/Group;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun publishTo$suspendImpl (Lnet/mamoe/mirai/contact/announcement/Announcement;Lnet/mamoe/mirai/contact/Group;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/Announcement$Companion { public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun publishAnnouncement$default (Lnet/mamoe/mirai/contact/announcement/Announcement$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public static synthetic fun publishAnnouncement$default (Lnet/mamoe/mirai/contact/announcement/Announcement$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/AnnouncementImage { public static final field Companion Lnet/mamoe/mirai/contact/announcement/AnnouncementImage$Companion; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;IILkotlinx/serialization/internal/SerializationConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun create (Ljava/lang/String;II)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public fun equals (Ljava/lang/Object;)Z public final fun getHeight ()I public final fun getId ()Ljava/lang/String; public final fun getUrl ()Ljava/lang/String; public final fun getWidth ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/contact/announcement/AnnouncementImage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/contact/announcement/AnnouncementImage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/contact/announcement/AnnouncementImage$$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/contact/announcement/AnnouncementImage; 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/contact/announcement/AnnouncementImage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementImage$Companion { public final fun create (Ljava/lang/String;II)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementKt { public static final fun toOffline (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParameters { public static final field Companion Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters$Companion; public static final field SERIAL_NAME Ljava/lang/String; public fun <init> ()V public synthetic fun <init> (ILnet/mamoe/mirai/contact/announcement/AnnouncementImage;ZZZZZLkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun builder ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public fun equals (Ljava/lang/Object;)Z public static final fun getDefault ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public final fun getImage ()Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public final fun getRequireConfirmation ()Z public final fun getSendToNewMember ()Z public final fun getShowEditCard ()Z public final fun getShowPopup ()Z public fun hashCode ()I public final fun isPinned ()Z public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/contact/announcement/AnnouncementParameters$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters$$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/contact/announcement/AnnouncementParameters; 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/contact/announcement/AnnouncementParameters;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParameters$Companion { public final fun getDefault ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder { public fun <init> ()V public fun <init> (Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)V public synthetic fun <init> (Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public final fun image ()Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public final fun image (Lnet/mamoe/mirai/contact/announcement/AnnouncementImage;)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun isPinned ()Z public final fun isPinned (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun requireConfirmation ()Z public final fun requireConfirmation (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun sendToNewMember ()Z public final fun sendToNewMember (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final synthetic fun setImage (Lnet/mamoe/mirai/contact/announcement/AnnouncementImage;)V public final synthetic fun setPinned (Z)V public final synthetic fun setRequireConfirmation (Z)V public final synthetic fun setSendToNewMember (Z)V public final synthetic fun setShowEditCard (Z)V public final synthetic fun setShowPopup (Z)V public final fun showEditCard ()Z public final fun showEditCard (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun showPopup ()Z public final fun showPopup (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParametersBuilderKt { public static final fun buildAnnouncementParameters (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; } public abstract interface class net/mamoe/mirai/contact/announcement/Announcements : net/mamoe/mirai/utils/Streamable { public synthetic fun asFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun asFlow (Lnet/mamoe/mirai/contact/announcement/Announcements;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun delete (Ljava/lang/String;)Z public abstract fun delete (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun get (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun members (Ljava/lang/String;Z)Ljava/util/List; public abstract fun members (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remind (Ljava/lang/String;)V public abstract fun remind (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/announcement/OfflineAnnouncement : net/mamoe/mirai/contact/announcement/Announcement { public static final field Companion Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement$Companion; public static final field SERIAL_NAME Ljava/lang/String; public static fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static fun create (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static fun from (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; } public final class net/mamoe/mirai/contact/announcement/OfflineAnnouncement$Companion { public static final field SERIAL_NAME Ljava/lang/String; public final fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final synthetic fun create (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final fun create (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static synthetic fun create$default (Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement$Companion;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final fun from (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/OfflineAnnouncementKt { public static final fun OfflineAnnouncement (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static final fun OfflineAnnouncement (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static final fun OfflineAnnouncement (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static synthetic fun OfflineAnnouncement$default (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; } public abstract interface class net/mamoe/mirai/contact/announcement/OnlineAnnouncement : net/mamoe/mirai/contact/announcement/Announcement { public fun delete ()Z public fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun delete$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getAllConfirmed ()Z public abstract fun getConfirmedMembersCount ()I public abstract fun getFid ()Ljava/lang/String; public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; public abstract fun getPublicationTime ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getSenderId ()J public fun members (Z)Ljava/util/List; public fun members (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun members$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remind ()V public fun remind (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun remind$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt { public static final fun getBot (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;)Lnet/mamoe/mirai/Bot; } public final class net/mamoe/mirai/contact/essence/EssenceMessageRecord { public final fun getFullSource ()Lnet/mamoe/mirai/message/data/MessageSource; public final fun getFullSource (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getOperatorId ()J public final fun getOperatorNick ()Ljava/lang/String; public final fun getOperatorTime ()I public final fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getSenderId ()J public final fun getSenderNick ()Ljava/lang/String; public final fun getSenderTime ()I public final fun getSource ()Lnet/mamoe/mirai/message/data/MessageSource; public final fun getSource (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/essence/Essences : net/mamoe/mirai/utils/Streamable { public fun getPage (II)Ljava/util/List; public abstract fun getPage (IILkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove (Lnet/mamoe/mirai/message/data/MessageSource;)V public abstract fun remove (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun share (Lnet/mamoe/mirai/message/data/MessageSource;)Ljava/lang/String; public abstract fun share (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFile : net/mamoe/mirai/contact/file/AbsoluteFileFolder { public abstract fun getExpiryTime ()J public abstract fun getMd5 ()[B public abstract fun getSha1 ()[B public abstract fun getSize ()J public fun getUrl ()Ljava/lang/String; public abstract fun getUrl (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;)Z public abstract fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun toMessage ()Lnet/mamoe/mirai/message/data/FileMessage; } public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFileFolder { public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion; public fun delete ()Z public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun exists ()Z public abstract fun exists (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getAbsolutePath ()Ljava/lang/String; public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported; public static fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; public abstract fun getId ()Ljava/lang/String; public abstract fun getLastModifiedTime ()J public abstract fun getName ()Ljava/lang/String; public static fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; public abstract fun getParent ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun getUploadTime ()J public abstract fun getUploaderId ()J public abstract fun isFile ()Z public abstract fun isFolder ()Z public fun refresh ()Z public abstract fun refresh (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder; public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun renameTo (Ljava/lang/String;)Z public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion { public final fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; public final fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFolder : net/mamoe/mirai/contact/file/AbsoluteFileFolder { public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFolder$Companion; public static final field ROOT_FOLDER_ID Ljava/lang/String; public fun children ()Lkotlinx/coroutines/flow/Flow; public abstract fun children (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun childrenStream ()Ljava/util/stream/Stream; public abstract fun childrenStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun createFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun createFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun files ()Lkotlinx/coroutines/flow/Flow; public abstract fun files (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun filesStream ()Ljava/util/stream/Stream; public abstract fun filesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun folders ()Lkotlinx/coroutines/flow/Flow; public abstract fun folders (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun foldersStream ()Ljava/util/stream/Stream; public abstract fun foldersStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getContentsCount ()I public fun isEmpty ()Z public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveAll (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; public abstract fun resolveAll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveAllStream (Ljava/lang/String;)Ljava/util/stream/Stream; public abstract fun resolveAllStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFileById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun resolveFileById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFileById (Ljava/lang/String;Z)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun resolveFileById (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun resolveFiles (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; public abstract fun resolveFiles (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFilesStream (Ljava/lang/String;)Ljava/util/stream/Stream; public abstract fun resolveFilesStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun resolveFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFolderById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun resolveFolderById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/file/AbsoluteFolder$Companion { public static final field ROOT_FOLDER_ID Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/file/RemoteFiles { public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported; public abstract fun getRoot ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadNewFile$suspendImpl (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroup { public fun delete ()Z public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getCount ()I public abstract fun getFriends ()Ljava/util/Collection; public abstract fun getId ()I public abstract fun getName ()Ljava/lang/String; public fun moveIn (Lnet/mamoe/mirai/contact/Friend;)Z public abstract fun moveIn (Lnet/mamoe/mirai/contact/Friend;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun renameTo (Ljava/lang/String;)Z public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroups { public abstract fun asCollection ()Ljava/util/Collection; public fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public abstract fun create (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun get (I)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public fun getDefault ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessage { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getContact ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getIds ()[I public abstract fun getInternalIds ()[I public abstract fun getSender ()J public abstract fun getTarget ()J public abstract fun getTime ()J } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessageFilter { public static final field ANY Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public static final field Companion Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion; public static final field RECEIVED Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public static final field SENT Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public fun and (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public abstract fun invoke (Lnet/mamoe/mirai/contact/roaming/RoamingMessage;)Z public fun not ()Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public fun or (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; } public final class net/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion { } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessages { public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun getAllMessages$suspendImpl (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getAllMessagesStream ()Ljava/util/stream/Stream; public fun getAllMessagesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun getAllMessagesStream$suspendImpl (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; public abstract fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun getMessagesStream (JJ)Ljava/util/stream/Stream; public fun getMessagesStream (JJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; public fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun getMessagesStream$suspendImpl (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingSupported : net/mamoe/mirai/contact/Contact { public abstract fun getRoamingMessages ()Lnet/mamoe/mirai/contact/roaming/RoamingMessages; } public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getFriendGroupId ()I 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 { public fun <init> (JLjava/lang/String;Ljava/lang/String;)V public fun getFriendGroupId ()I public fun getNick ()Ljava/lang/String; public fun getRemark ()Ljava/lang/String; public fun getUin ()J public fun setFriendGroupId (I)V public fun setNick (Ljava/lang/String;)V public fun setRemark (Ljava/lang/String;)V } public final class net/mamoe/mirai/data/GroupHonorType { public static final field BRONZE_ID I public static final field Companion Lnet/mamoe/mirai/data/GroupHonorType$Companion; public static final field EMOTION_ID I public static final field GOLDEN_ID I public static final field LEGEND_ID I public static final field PERFORMER_ID I public static final field RED_PACKET_ID I public static final field RICHER_ID I public static final field SILVER_ID I public static final field STRONG_NEWBIE_ID I public static final field TALKATIVE_ID I public static final field WHIRLWIND_ID I public static final synthetic fun box-impl (I)Lnet/mamoe/mirai/data/GroupHonorType; public static fun constructor-impl (I)I public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (ILjava/lang/Object;)Z public static final fun equals-impl0 (II)Z public static final fun getBRONZE-AVr_HNQ ()I public static final fun getEMOTION-AVr_HNQ ()I public static final fun getGOLDEN-AVr_HNQ ()I public final fun getId ()I public static final fun getLEGEND-AVr_HNQ ()I public static final fun getPERFORMER-AVr_HNQ ()I public static final fun getRED_PACKET-AVr_HNQ ()I public static final fun getRICHER-AVr_HNQ ()I public static final fun getSILVER-AVr_HNQ ()I public static final fun getSTRONG_NEWBIE-AVr_HNQ ()I public static final fun getTALKATIVE-AVr_HNQ ()I public static final fun getWHIRLWIND-AVr_HNQ ()I public fun hashCode ()I public static fun hashCode-impl (I)I public fun toString ()Ljava/lang/String; public static fun toString-impl (I)Ljava/lang/String; public final synthetic fun unbox-impl ()I } public final class net/mamoe/mirai/data/GroupHonorType$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/GroupHonorType$$serializer; public fun childSerializers ()[Lkotlinx/serialization/KSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize-NYH6FXw (Lkotlinx/serialization/encoding/Decoder;)I public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V public fun serialize-aLnpm_Q (Lkotlinx/serialization/encoding/Encoder;I)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/GroupHonorType$Companion { public final fun getBRONZE-AVr_HNQ ()I public final fun getEMOTION-AVr_HNQ ()I public final fun getGOLDEN-AVr_HNQ ()I public final fun getLEGEND-AVr_HNQ ()I public final fun getPERFORMER-AVr_HNQ ()I public final fun getRED_PACKET-AVr_HNQ ()I public final fun getRICHER-AVr_HNQ ()I public final fun getSILVER-AVr_HNQ ()I public final fun getSTRONG_NEWBIE-AVr_HNQ ()I public final fun getTALKATIVE-AVr_HNQ ()I public final fun getWHIRLWIND-AVr_HNQ ()I public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/data/GroupInfo { public abstract fun getAllowAnonymousChat ()Z public abstract fun getAllowMemberInvite ()Z public abstract fun getAutoApprove ()Z public abstract fun getBotMuteTimestamp ()I public abstract fun getConfessTalk ()Z public abstract fun getGroupCode ()J public abstract fun getMemo ()Ljava/lang/String; public abstract fun getMuteAll ()Z public abstract fun getName ()Ljava/lang/String; public abstract fun getOwner ()J public abstract fun getRankTitles ()Ljava/util/Map; public abstract fun getTemperatureTitles ()Ljava/util/Map; public abstract fun getUin ()J public abstract fun isHonorVisible ()Z public abstract fun isTemperatureVisible ()Z public abstract fun isTitleVisible ()Z } public abstract interface class net/mamoe/mirai/data/MemberInfo : net/mamoe/mirai/data/UserInfo { public fun getAnonymousId ()Ljava/lang/String; public abstract fun getHonors ()Ljava/util/Set; public abstract fun getJoinTimestamp ()I public abstract fun getLastSpeakTimestamp ()I public abstract fun getMuteTimestamp ()I public abstract fun getNameCard ()Ljava/lang/String; public abstract fun getPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public abstract fun getPoint ()I public abstract fun getRank ()I public abstract fun getSpecialTitle ()Ljava/lang/String; public abstract fun getTemperature ()I public abstract fun isOfficialBot ()Z } public final class net/mamoe/mirai/data/OnlineStatus : java/lang/Enum { public static final field AWAY Lnet/mamoe/mirai/data/OnlineStatus; public static final field BUSY Lnet/mamoe/mirai/data/OnlineStatus; public static final field Companion Lnet/mamoe/mirai/data/OnlineStatus$Companion; public static final field DND Lnet/mamoe/mirai/data/OnlineStatus; public static final field INVISIBLE Lnet/mamoe/mirai/data/OnlineStatus; public static final field OFFLINE Lnet/mamoe/mirai/data/OnlineStatus; public static final field ONLINE Lnet/mamoe/mirai/data/OnlineStatus; public static final field Q_ME Lnet/mamoe/mirai/data/OnlineStatus; public static final field RECEIVE_OFFLINE_MESSAGE Lnet/mamoe/mirai/data/OnlineStatus; public static final field UNKNOWN Lnet/mamoe/mirai/data/OnlineStatus; public final fun getId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/data/OnlineStatus; public static fun values ()[Lnet/mamoe/mirai/data/OnlineStatus; } public final class net/mamoe/mirai/data/OnlineStatus$Companion { public final fun ofId (I)Lnet/mamoe/mirai/data/OnlineStatus; public final fun ofIdOrNull (I)Lnet/mamoe/mirai/data/OnlineStatus; } public abstract class net/mamoe/mirai/data/RequestEventData { public static final field Factory Lnet/mamoe/mirai/data/RequestEventData$Factory; public synthetic fun <init> (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;)V public abstract fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun from (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest; public static final fun from (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest; public static final fun from (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest; public abstract fun getEventId ()J public fun reject (Lnet/mamoe/mirai/Bot;)V public abstract fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest : net/mamoe/mirai/data/RequestEventData { public static final field Companion Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$Companion; public synthetic fun <init> (IJJLjava/lang/String;JLjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getEventId ()J public final fun getGroupId ()J public final fun getGroupName ()Ljava/lang/String; public final fun getInvitor ()J public final fun getInvitorNick ()Ljava/lang/String; public fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$$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/data/RequestEventData$BotInvitedJoinGroupRequest; 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/data/RequestEventData$BotInvitedJoinGroupRequest;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$Factory { public final fun from (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest; public final fun from (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest; public final fun from (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$MemberJoinRequest : net/mamoe/mirai/data/RequestEventData { public static final field Companion Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest$Companion; public synthetic fun <init> (IJJLjava/lang/String;JLjava/lang/String;JLjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public synthetic fun <init> (JJLjava/lang/String;JLjava/lang/String;JLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getEventId ()J public final fun getGroupId ()J public final fun getGroupName ()Ljava/lang/String; public final fun getInvitor ()J public final fun getMessage ()Ljava/lang/String; public final fun getRequester ()J public final fun getRequesterNick ()Ljava/lang/String; public final fun reject (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Lnet/mamoe/mirai/Bot;ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun reject$default (Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest;Lnet/mamoe/mirai/Bot;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$MemberJoinRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest$$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/data/RequestEventData$MemberJoinRequest; 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/data/RequestEventData$MemberJoinRequest;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$MemberJoinRequest$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$NewFriendRequest : net/mamoe/mirai/data/RequestEventData { public static final field Companion Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest$Companion; public synthetic fun <init> (IJJLjava/lang/String;JLjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getEventId ()J public final fun getFromGroupId ()J public final fun getMessage ()Ljava/lang/String; public final fun getRequester ()J public final fun getRequesterNick ()Ljava/lang/String; public fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$NewFriendRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest$$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/data/RequestEventData$NewFriendRequest; 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/data/RequestEventData$NewFriendRequest;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$NewFriendRequest$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/data/StrangerInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getFromGroup ()J public abstract fun getNick ()Ljava/lang/String; public abstract fun getUin ()J } public abstract interface class net/mamoe/mirai/data/UserInfo { public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; public abstract fun getUin ()J } public abstract interface class net/mamoe/mirai/data/UserProfile { public abstract fun getAge ()I public abstract fun getEmail ()Ljava/lang/String; public abstract fun getFriendGroupId ()I public abstract fun getNickname ()Ljava/lang/String; public abstract fun getQLevel ()I public abstract fun getSex ()Lnet/mamoe/mirai/data/UserProfile$Sex; public abstract fun getSign ()Ljava/lang/String; } public final class net/mamoe/mirai/data/UserProfile$Sex : java/lang/Enum { public static final field FEMALE Lnet/mamoe/mirai/data/UserProfile$Sex; public static final field MALE Lnet/mamoe/mirai/data/UserProfile$Sex; public static final field UNKNOWN Lnet/mamoe/mirai/data/UserProfile$Sex; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/data/UserProfile$Sex; public static fun values ()[Lnet/mamoe/mirai/data/UserProfile$Sex; } public abstract class net/mamoe/mirai/event/AbstractEvent : net/mamoe/mirai/event/Event { public field _intercepted Z public final field broadCastLock Lkotlinx/coroutines/sync/Mutex; public fun <init> ()V public final fun cancel ()V public fun intercept ()V public final fun isCancelled ()Z public fun isIntercepted ()Z } public abstract interface class net/mamoe/mirai/event/BroadcastControllable : net/mamoe/mirai/event/Event { public fun getShouldBroadcast ()Z } public abstract interface class net/mamoe/mirai/event/CancellableEvent : net/mamoe/mirai/event/Event { public abstract fun cancel ()V public abstract fun isCancelled ()Z } public final class net/mamoe/mirai/event/ConcurrencyKind : java/lang/Enum { public static final field CONCURRENT Lnet/mamoe/mirai/event/ConcurrencyKind; public static final field LOCKED Lnet/mamoe/mirai/event/ConcurrencyKind; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/event/ConcurrencyKind; public static fun values ()[Lnet/mamoe/mirai/event/ConcurrencyKind; } public abstract interface class net/mamoe/mirai/event/Event { public abstract fun intercept ()V public abstract fun isIntercepted ()Z } public abstract class net/mamoe/mirai/event/EventChannel { public static synthetic fun asChannel$default (Lnet/mamoe/mirai/event/EventChannel;ILkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;ILjava/lang/Object;)Lkotlinx/coroutines/channels/Channel; public abstract fun asFlow ()Lkotlinx/coroutines/flow/Flow; public abstract fun context ([Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/event/EventChannel; public final fun exceptionHandler (Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/EventChannel; public final fun exceptionHandler (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/event/EventChannel; public final fun exceptionHandler (Lkotlinx/coroutines/CoroutineExceptionHandler;)Lnet/mamoe/mirai/event/EventChannel; public final fun filter (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/event/EventChannel; public final synthetic fun filter (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/event/EventChannel; public final fun filterIsInstance (Ljava/lang/Class;)Lnet/mamoe/mirai/event/EventChannel; public final fun filterIsInstance (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/event/EventChannel; public final fun forwardToChannel (Lkotlinx/coroutines/channels/SendChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun forwardToChannel$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public final fun getBaseEventClass ()Lkotlin/reflect/KClass; public final fun getDefaultCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun parentJob (Lkotlinx/coroutines/Job;)Lnet/mamoe/mirai/event/EventChannel; public final fun parentScope (Lkotlinx/coroutines/CoroutineScope;)Lnet/mamoe/mirai/event/EventChannel; public final fun registerListenerHost (Lnet/mamoe/mirai/event/ListenerHost;)V public final fun registerListenerHost (Lnet/mamoe/mirai/event/ListenerHost;Lkotlin/coroutines/CoroutineContext;)V public static synthetic fun registerListenerHost$default (Lnet/mamoe/mirai/event/EventChannel;Lnet/mamoe/mirai/event/ListenerHost;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)V public final fun subscribe (Ljava/lang/Class;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final fun subscribe (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final fun subscribe (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final fun subscribe (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final synthetic fun subscribe (Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribe$default (Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Function;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribe$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final synthetic fun subscribeAlways (Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeAlways$default (Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeAlways$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeOnce$default (Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeOnce$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; } public final class net/mamoe/mirai/event/EventChannelKt { public static final synthetic fun globalEventChannel (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/event/EventChannel; public static synthetic fun globalEventChannel$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lnet/mamoe/mirai/event/EventChannel; } public abstract interface annotation class net/mamoe/mirai/event/EventHandler : java/lang/annotation/Annotation { public abstract fun concurrency ()Lnet/mamoe/mirai/event/ConcurrencyKind; public abstract fun ignoreCancelled ()Z public abstract fun priority ()Lnet/mamoe/mirai/event/EventPriority; } public final class net/mamoe/mirai/event/EventKt { public static final fun broadcast (Lnet/mamoe/mirai/event/Event;)Lnet/mamoe/mirai/event/Event; public static final fun broadcast (Lnet/mamoe/mirai/event/Event;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/EventPriority : java/lang/Enum { public static final field HIGH Lnet/mamoe/mirai/event/EventPriority; public static final field HIGHEST Lnet/mamoe/mirai/event/EventPriority; public static final field LOW Lnet/mamoe/mirai/event/EventPriority; public static final field LOWEST Lnet/mamoe/mirai/event/EventPriority; public static final field MONITOR Lnet/mamoe/mirai/event/EventPriority; public static final field NORMAL Lnet/mamoe/mirai/event/EventPriority; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/event/EventPriority; public static fun values ()[Lnet/mamoe/mirai/event/EventPriority; } public final class net/mamoe/mirai/event/Events { public static final synthetic fun registerTo (Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventChannel;)V } public final class net/mamoe/mirai/event/ExceptionInEventChannelFilterException : java/lang/IllegalStateException { public fun <init> (Lnet/mamoe/mirai/event/Event;Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Lnet/mamoe/mirai/event/Event;Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; public final fun getEvent ()Lnet/mamoe/mirai/event/Event; public final fun getEventChannel ()Lnet/mamoe/mirai/event/EventChannel; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/event/ExceptionInEventHandlerException : java/lang/IllegalStateException { public fun <init> (Lnet/mamoe/mirai/event/Event;Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Lnet/mamoe/mirai/event/Event;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; public final fun getEvent ()Lnet/mamoe/mirai/event/Event; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/event/ExtensionsKt { public static final fun nextEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun syncFromEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/GlobalEventChannel : net/mamoe/mirai/event/EventChannel { public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel; public fun asFlow ()Lkotlinx/coroutines/flow/Flow; public fun context ([Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/event/EventChannel; } public abstract interface class net/mamoe/mirai/event/Listener : kotlinx/coroutines/CompletableJob { public abstract fun getConcurrencyKind ()Lnet/mamoe/mirai/event/ConcurrencyKind; public fun getPriority ()Lnet/mamoe/mirai/event/EventPriority; public abstract fun onEvent (Lnet/mamoe/mirai/event/Event;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/Listener$DefaultImpls { public static synthetic fun cancel (Lnet/mamoe/mirai/event/Listener;)V public static fun fold (Lnet/mamoe/mirai/event/Listener;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static fun get (Lnet/mamoe/mirai/event/Listener;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; public static fun minusKey (Lnet/mamoe/mirai/event/Listener;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; public static fun plus (Lnet/mamoe/mirai/event/Listener;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; public static fun plus (Lnet/mamoe/mirai/event/Listener;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; } public abstract interface class net/mamoe/mirai/event/ListenerHost { } public final class net/mamoe/mirai/event/ListeningStatus : java/lang/Enum { public static final field LISTENING Lnet/mamoe/mirai/event/ListeningStatus; public static final field STOPPED Lnet/mamoe/mirai/event/ListeningStatus; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/event/ListeningStatus; public static fun values ()[Lnet/mamoe/mirai/event/ListeningStatus; } public abstract interface annotation class net/mamoe/mirai/event/MessageDsl : java/lang/annotation/Annotation { } public abstract class net/mamoe/mirai/event/MessageSelectBuilder : net/mamoe/mirai/event/MessageSelectBuilderUnit { public fun <init> (Lnet/mamoe/mirai/event/events/MessageEvent;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V public synthetic fun always (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun always (Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun containsReply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun containsReply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun containsReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun containsReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun endsWithReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun endsWithReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun findingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun findingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun mapping (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun mapping (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun matchingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun matchingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLjava/lang/String;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLjava/lang/String;)V public synthetic fun quoteReply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)V public synthetic fun quoteReply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)V public synthetic fun reply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun reply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun reply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun reply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun reply (Ljava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public synthetic fun reply (Ljava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLjava/lang/String;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLjava/lang/String;)V public synthetic fun reply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)V public synthetic fun reply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)V } public abstract class net/mamoe/mirai/event/MessageSelectBuilderUnit : net/mamoe/mirai/event/CommonMessageSelectBuilderUnit { public fun <init> (Lnet/mamoe/mirai/event/events/MessageEvent;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V public final synthetic fun invoke-RNyhSv4 (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public final synthetic fun invoke-RNyhSv4 (JLkotlin/jvm/functions/Function1;)V public final synthetic fun quoteReply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public final synthetic fun quoteReply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)V public final synthetic fun quoteReply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public final synthetic fun quoteReply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)V public final synthetic fun quoteReply-sCZ5gAI (JLjava/lang/String;)Ljava/lang/Void; public final synthetic fun quoteReply-sCZ5gAI (JLjava/lang/String;)V public final synthetic fun reply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public final synthetic fun reply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)V public final synthetic fun reply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public final synthetic fun reply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)V public final synthetic fun reply-sCZ5gAI (JLjava/lang/String;)Ljava/lang/Void; public final synthetic fun reply-sCZ5gAI (JLjava/lang/String;)V public final synthetic fun timeout-ncvN2qU (J)J } public final class net/mamoe/mirai/event/MessageSelectionTimeoutChecker { public static final synthetic fun box-impl (J)Lnet/mamoe/mirai/event/MessageSelectionTimeoutChecker; public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (JLjava/lang/Object;)Z public static final fun equals-impl0 (JJ)Z public final fun getTimeoutMillis ()J public fun hashCode ()I public static fun hashCode-impl (J)I public fun toString ()Ljava/lang/String; public static fun toString-impl (J)Ljava/lang/String; public final synthetic fun unbox-impl ()J } public final class net/mamoe/mirai/event/MessageSelectionTimeoutException : java/lang/RuntimeException { public fun <init> ()V } public class net/mamoe/mirai/event/MessageSubscribersBuilder { public fun always (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun at (J)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun at (Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun atAll ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun atBot ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun atBot (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun case (Ljava/lang/String;ZZ)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun case (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun case$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun case$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; public final fun contains (Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun contains (Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun contains (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun contains$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun contains$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; public final fun containsAll ([Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAll ([Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAll ([Ljava/lang/String;ZZ)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun containsAll$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;[Ljava/lang/String;ZZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAny ([Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAny ([Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAny ([Ljava/lang/String;ZZ)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun containsAny$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;[Ljava/lang/String;ZZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public fun containsReply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public fun containsReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun content (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun endsWith (Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun endsWith (Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun endsWith (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun endsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun endsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; public fun endsWithReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun finding (Lkotlin/text/Regex;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun finding (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final synthetic fun findingExtension (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun findingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun getSubscriber ()Lkotlin/jvm/functions/Function2; public final fun invoke (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun mapping (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun matching (Lkotlin/text/Regex;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun matching (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final synthetic fun matchingExtension (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun matchingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun newListeningFilter (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public fun reply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public fun reply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun reply (Ljava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public final fun sentBy (J)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentBy (JLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun sentBy (Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentBy (Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByAdministrator ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByFriend ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByFriend (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun sentByGroupTemp ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByOperator ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByOwner ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByStranger ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByStranger (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final synthetic fun sentByTemp ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentFrom (J)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentFrom (Lnet/mamoe/mirai/contact/Group;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun startsWith (Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun startsWith (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun startsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun startsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter { public final fun and (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun getFilter ()Lkotlin/jvm/functions/Function2; public final fun invoke (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun nand (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun not ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun or (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun xor (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; } public final class net/mamoe/mirai/event/NextEventKt { public static final synthetic fun nextBotEventImpl (Lnet/mamoe/mirai/Bot;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun nextEventImpl (Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun parentJob (Lnet/mamoe/mirai/event/EventChannel;Lkotlinx/coroutines/Job;)Lnet/mamoe/mirai/event/EventChannel; public static final synthetic fun withTimeoutOrCoroutineScope (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun withTimeoutOrCoroutineScope (JLkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun withTimeoutOrCoroutineScope$default (JLkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/SelectKt { public static synthetic fun selectMessagesImpl$default (Lnet/mamoe/mirai/event/events/MessageEvent;JZZLnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static final synthetic fun withSilentTimeoutOrCoroutineScope (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract class net/mamoe/mirai/event/SimpleListenerHost : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/event/ListenerHost { protected static final field Companion Lnet/mamoe/mirai/event/SimpleListenerHost$Companion; public fun <init> ()V public fun <init> (Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun cancelAll ()V public final fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; protected static final fun getEvent (Ljava/lang/Throwable;)Lnet/mamoe/mirai/event/Event; protected static final fun getRootCause (Ljava/lang/Throwable;)Ljava/lang/Throwable; public fun handleException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V } protected final class net/mamoe/mirai/event/SimpleListenerHost$Companion { } public final class net/mamoe/mirai/event/SubscribeMessagesKt { public static final fun subscribeFriendMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeFriendMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeGroupMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeGroupMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeGroupTempMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeGroupTempMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeOtherClientMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeOtherClientMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeStrangerMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeStrangerMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final synthetic fun subscribeTempMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeTempMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeUserMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeUserMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/SyncFromEventKt { public static final synthetic fun syncFromEventImpl (Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/events/BeforeImageUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Contact; public final fun component2 ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun copy (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/event/events/BeforeImageUploadEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BeforeImageUploadEvent;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BeforeImageUploadEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/BeforeShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public final fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; } public abstract interface class net/mamoe/mirai/event/events/BotActiveEvent : net/mamoe/mirai/event/events/BotEvent { } public final class net/mamoe/mirai/event/events/BotAvatarChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, 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/BotAvatarChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotAvatarChangedEvent;Lnet/mamoe/mirai/Bot;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotAvatarChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/BotEvent : net/mamoe/mirai/event/Event { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; } public final class net/mamoe/mirai/event/events/BotEventsKt { public static final synthetic fun getOperatorOrBot (Lnet/mamoe/mirai/event/events/GroupOperableEvent;)Lnet/mamoe/mirai/contact/Member; public static final fun getResult (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Ljava/lang/Object; public static final synthetic fun getSource (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Lnet/mamoe/mirai/message/data/MessageSource; public static final synthetic fun getSourceResult (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Ljava/lang/Object; public static final synthetic fun isByBot (Lnet/mamoe/mirai/event/events/GroupOperableEvent;)Z public static final fun isByBot (Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall;)Z public static final fun isByBot (Lnet/mamoe/mirai/event/events/MessageRecallEvent;)Z public static final synthetic fun isFailure (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Z 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun component3 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;)Lnet/mamoe/mirai/event/events/BotGroupPermissionChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotGroupPermissionChangeEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotGroupPermissionChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getNew ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun getOrigin ()Lnet/mamoe/mirai/contact/MemberPermission; public fun hashCode ()I 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/BaseGroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public final fun accept ()V public final fun accept (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()J public final fun component4 ()J public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/Bot;JJJLjava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;Lnet/mamoe/mirai/Bot;JJJLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/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 fun getGroupId ()J public final fun getGroupName ()Ljava/lang/String; public final fun getInvitor ()Lnet/mamoe/mirai/contact/Friend; public final fun getInvitorId ()J public final fun getInvitorNick ()Ljava/lang/String; public fun hashCode ()I public final fun ignore ()V public final fun ignore (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; } 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; } 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 final fun component1 ()I public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ILnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/BotMuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotMuteEvent;ILnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotMuteEvent; public fun equals (Ljava/lang/Object;)Z public final fun getDurationSeconds ()I public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/BotNickChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, 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; public final fun copy (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/BotNickChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotNickChangedEvent;Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotNickChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getFrom ()Ljava/lang/String; public final fun getTo ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/event/events/BotOfflineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent { public fun getReconnect ()Z } public final class net/mamoe/mirai/event/events/BotOfflineEvent$Active : net/mamoe/mirai/event/events/BotOfflineEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotOfflineEvent$CauseAware { 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$Active; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOfflineEvent$Active;Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Active; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getCause ()Ljava/lang/Throwable; public fun getReconnect ()Z public fun hashCode ()I public fun toString ()Ljava/lang/String; } 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 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; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOfflineEvent$Dropped;Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Dropped; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getCause ()Ljava/lang/Throwable; public fun getReconnect ()Z public fun hashCode ()I public fun setReconnect (Z)V public fun toString ()Ljava/lang/String; } 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 final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Force; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOfflineEvent$Force;Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Force; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getMessage ()Ljava/lang/String; public fun getReconnect ()Z public final fun getTitle ()Ljava/lang/String; public fun hashCode ()I public fun setReconnect (Z)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/BotOnlineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent { 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; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/BotPassiveEvent : net/mamoe/mirai/event/events/BotEvent { } 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 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; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotReloginEvent;Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotReloginEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getCause ()Ljava/lang/Throwable; public fun hashCode ()I 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/BotUnmuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotUnmuteEvent;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotUnmuteEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/EventCancelledException : java/lang/RuntimeException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V 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/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/FriendAddEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendAddEvent;Lnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendAddEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendAvatarChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, 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/FriendAvatarChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendAvatarChangedEvent;Lnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendAvatarChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I 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/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; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/FriendEvent : net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/event/events/UserEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getUser ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/event/events/FriendInputStatusChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/internal/event/VerboseEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Friend; public final fun component2 ()Z public final fun copy (Lnet/mamoe/mirai/contact/Friend;Z)Lnet/mamoe/mirai/event/events/FriendInputStatusChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendInputStatusChangedEvent;Lnet/mamoe/mirai/contact/Friend;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendInputStatusChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public final fun getInputting ()Z public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/UserMessageEvent { public fun <init> (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessagePostSendEvent : net/mamoe/mirai/event/events/UserMessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Friend; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/FriendMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendMessagePostSendEvent;Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessagePreSendEvent : net/mamoe/mirai/event/events/UserMessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Friend; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/FriendMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendMessagePreSendEvent;Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/MessageSyncEvent { public fun <init> (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Friend; 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/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; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/FriendNickChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendNickChangedEvent;Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendNickChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public final fun getFrom ()Ljava/lang/String; public final fun getTo ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendRemarkChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/BroadcastControllable, 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; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/FriendRemarkChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendRemarkChangeEvent;Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendRemarkChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public final fun getNewRemark ()Ljava/lang/String; public final fun getOldRemark ()Ljava/lang/String; public fun getShouldBroadcast ()Z public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent;ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Z public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Z)Lnet/mamoe/mirai/event/events/GroupAllowConfessTalkEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupAllowConfessTalkEvent;ZZLnet/mamoe/mirai/contact/Group;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupAllowConfessTalkEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I public final fun isByBot ()Z 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupAllowMemberInviteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupAllowMemberInviteEvent;ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupAllowMemberInviteEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/GroupAwareMessageEvent : net/mamoe/mirai/event/events/MessageEvent { 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public synthetic fun getNew ()Ljava/lang/Object; public fun getNew ()Ljava/lang/String; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getOrigin ()Ljava/lang/Object; public fun getOrigin ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/GroupEvent : net/mamoe/mirai/event/events/BotEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; } public abstract interface class net/mamoe/mirai/event/events/GroupMemberEvent : net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/UserEvent { public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public abstract fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getUser ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/event/events/GroupMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/MessageEvent { public fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun getPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public fun getSender ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Group; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupMessagePostSendEvent : net/mamoe/mirai/event/events/MessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/GroupMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupMessagePostSendEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Group; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupMessagePreSendEvent : net/mamoe/mirai/event/events/MessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/GroupMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupMessagePreSendEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Group; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/MessageSyncEvent { public fun <init> (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/contact/Member;Ljava/lang/String;I)V public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/contact/Member;Ljava/lang/String;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Group; public fun getTime ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupMuteAllEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupMuteAllEvent;ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupMuteAllEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupNameChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupNameChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupNameChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public synthetic fun getNew ()Ljava/lang/Object; public fun getNew ()Ljava/lang/String; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getOrigin ()Ljava/lang/Object; public fun getOrigin ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/GroupOperableEvent : net/mamoe/mirai/event/events/GroupEvent { public abstract fun getOperator ()Lnet/mamoe/mirai/contact/Member; } public abstract interface class net/mamoe/mirai/event/events/GroupSettingChangeEvent : net/mamoe/mirai/event/BroadcastControllable, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent { public abstract fun getNew ()Ljava/lang/Object; public abstract fun getOrigin ()Ljava/lang/Object; public fun getShouldBroadcast ()Z } public final class net/mamoe/mirai/event/events/GroupTalkativeChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/internal/network/Packet { public fun <init> (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)V public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component3 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupTalkativeChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupTalkativeChangeEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupTalkativeChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getNow ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getPrevious ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessageEvent : net/mamoe/mirai/event/events/TempMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/UserMessageEvent { public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessagePostSendEvent : net/mamoe/mirai/event/events/TempMessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/GroupTempMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupTempMessagePostSendEvent;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupTempMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Member; public fun getTarget ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessagePreSendEvent : net/mamoe/mirai/event/events/TempMessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/GroupTempMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupTempMessagePreSendEvent;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupTempMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Member; public fun getTarget ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/MessageSyncEvent { public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/NormalMember; public fun getTime ()I } public abstract class net/mamoe/mirai/event/events/ImageUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public final class net/mamoe/mirai/event/events/ImageUploadEvent$Failed : net/mamoe/mirai/event/events/ImageUploadEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Contact; public final fun component2 ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun component3 ()I public final fun component4 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;ILjava/lang/String;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Failed; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/ImageUploadEvent$Failed;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;ILjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Failed; public fun equals (Ljava/lang/Object;)Z public final fun getErrno ()I public final fun getMessage ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/ImageUploadEvent$Succeed : net/mamoe/mirai/event/events/ImageUploadEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Contact; public final fun component2 ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun component3 ()Lnet/mamoe/mirai/message/data/Image; public final fun copy (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Succeed; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/ImageUploadEvent$Succeed;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/message/data/Image;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Succeed; public fun equals (Ljava/lang/Object;)Z public final fun getImage ()Lnet/mamoe/mirai/message/data/Image; public fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun hashCode ()I 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberCardChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberCardChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberCardChangeEvent; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getNew ()Ljava/lang/String; public final fun getOrigin ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve : net/mamoe/mirai/event/events/MemberHonorChangeEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2-AVr_HNQ ()I public final fun copy-aLnpm_Q (Lnet/mamoe/mirai/contact/NormalMember;I)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve; public static synthetic fun copy-aLnpm_Q$default (Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve;Lnet/mamoe/mirai/contact/NormalMember;IILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve; public fun equals (Ljava/lang/Object;)Z public fun getHonorType ()I public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose : net/mamoe/mirai/event/events/MemberHonorChangeEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2-AVr_HNQ ()I public final fun copy-aLnpm_Q (Lnet/mamoe/mirai/contact/NormalMember;I)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose; public static synthetic fun copy-aLnpm_Q$default (Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose;Lnet/mamoe/mirai/contact/NormalMember;IILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose; public fun equals (Ljava/lang/Object;)Z public fun getHonorType ()I public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I 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/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; } public final class net/mamoe/mirai/event/events/MemberJoinEvent$Active : net/mamoe/mirai/event/events/MemberJoinEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Active; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinEvent$Active;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Active; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberJoinEvent$Invite : net/mamoe/mirai/event/events/MemberJoinEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Invite; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinEvent$Invite;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Invite; public fun equals (Ljava/lang/Object;)Z public final fun getInvitor ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberJoinEvent$Retrieve : net/mamoe/mirai/event/events/MemberJoinEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Retrieve; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinEvent$Retrieve;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Retrieve; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I 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/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 synthetic fun <init> (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun accept ()V public final fun accept (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()Ljava/lang/String; public final fun component4 ()J public final fun component5 ()J public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/Long; public final synthetic fun copy (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public final fun copy (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/Long;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public static final synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/Long;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getEventId ()J public final fun getFromId ()J public final fun getFromNick ()Ljava/lang/String; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; 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; public final fun getMessage ()Ljava/lang/String; public fun hashCode ()I public final fun ignore (Z)V public final fun ignore (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun ignore$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZILjava/lang/Object;)V public static synthetic fun ignore$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun reject ()V public final fun reject (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Z)V public final fun reject (ZLjava/lang/String;)V public final fun reject (ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;ILjava/lang/Object;)V public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 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, 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 { public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)V public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Kick; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Kick;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Kick; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberLeaveEvent$Quit : net/mamoe/mirai/event/events/MemberLeaveEvent, 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/MemberLeaveEvent$Quit; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Quit;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Quit; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Member; public final fun component2 ()I public final fun component3 ()Lnet/mamoe/mirai/contact/Member; public final fun copy (Lnet/mamoe/mirai/contact/Member;ILnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/event/events/MemberMuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberMuteEvent;Lnet/mamoe/mirai/contact/Member;ILnet/mamoe/mirai/contact/Member;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberMuteEvent; public fun equals (Ljava/lang/Object;)Z public final fun getDurationSeconds ()I public fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun hashCode ()I 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun component3 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;)Lnet/mamoe/mirai/event/events/MemberPermissionChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberPermissionChangeEvent;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberPermissionChangeEvent; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getNew ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun getOrigin ()Lnet/mamoe/mirai/contact/MemberPermission; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getNew ()Ljava/lang/String; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getOrigin ()Ljava/lang/String; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Member; public final fun component2 ()Lnet/mamoe/mirai/contact/Member; public final fun copy (Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/event/events/MemberUnmuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberUnmuteEvent;Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Member;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberUnmuteEvent; public fun equals (Ljava/lang/Object;)Z public fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/MessageEvent : net/mamoe/mirai/event/Event, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/internal/network/Packet { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun getSender ()Lnet/mamoe/mirai/contact/User; public abstract fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getTime ()I } public abstract class net/mamoe/mirai/event/events/MessagePostSendEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public final fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getException ()Ljava/lang/Throwable; public abstract fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public abstract class net/mamoe/mirai/event/events/MessagePreSendEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public final fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public abstract fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V } public abstract class net/mamoe/mirai/event/events/MessageRecallEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent { public abstract fun getAuthor ()Lnet/mamoe/mirai/contact/UserOrBot; public abstract fun getAuthorId ()J public abstract fun getMessageIds ()[I public abstract fun getMessageInternalIds ()[I public abstract fun getMessageTime ()I } public final class net/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall : net/mamoe/mirai/event/events/MessageRecallEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()[I public final fun component3 ()[I public final fun component4 ()I public final fun component5 ()J public final fun component6 ()Lnet/mamoe/mirai/contact/Friend; public final fun copy (Lnet/mamoe/mirai/Bot;[I[IIJLnet/mamoe/mirai/contact/Friend;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall;Lnet/mamoe/mirai/Bot;[I[IIJLnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall; public fun equals (Ljava/lang/Object;)Z public fun getAuthor ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getAuthor ()Lnet/mamoe/mirai/contact/UserOrBot; public fun getAuthorId ()J public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessageIds ()[I public fun getMessageInternalIds ()[I public fun getMessageTime ()I public final fun getOperator ()Lnet/mamoe/mirai/contact/Friend; public final fun getOperatorId ()J public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall : net/mamoe/mirai/event/events/MessageRecallEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()[I public final fun component4 ()[I public final fun component5 ()I public final fun component6 ()Lnet/mamoe/mirai/contact/Member; public final fun component7 ()Lnet/mamoe/mirai/contact/Group; public final fun component8 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/Bot;J[I[IILnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall;Lnet/mamoe/mirai/Bot;J[I[IILnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall; public fun equals (Ljava/lang/Object;)Z public fun getAuthor ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getAuthor ()Lnet/mamoe/mirai/contact/UserOrBot; public fun getAuthorId ()J public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessageIds ()[I public fun getMessageInternalIds ()[I public fun getMessageTime ()I public fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/MessageSyncEvent : net/mamoe/mirai/event/events/MessageEvent, net/mamoe/mirai/event/events/OtherClientEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; } 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 fun accept ()V public final fun accept (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()Ljava/lang/String; public final fun component4 ()J public final fun component5 ()J public final fun component6 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;)Lnet/mamoe/mirai/event/events/NewFriendRequestEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/NewFriendRequestEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getEventId ()J public final fun getFromGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getFromGroupId ()J public final fun getFromId ()J public final fun getFromNick ()Ljava/lang/String; public final fun getMessage ()Ljava/lang/String; public fun hashCode ()I public final fun reject (Z)V public final fun reject (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;ZILjava/lang/Object;)V public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/NudgeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun component2 ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun component3 ()Lnet/mamoe/mirai/contact/Contact; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/NudgeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/NudgeEvent;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/NudgeEvent; public fun equals (Ljava/lang/Object;)Z public final fun getAction ()Ljava/lang/String; public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getFrom ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSuffix ()Ljava/lang/String; public final fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/OtherClientEvent : net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; } public final class net/mamoe/mirai/event/events/OtherClientMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/MessageEvent, net/mamoe/mirai/event/events/OtherClientEvent { public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/OtherClient; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/OtherClientOfflineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/OtherClientEvent { public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;)V public final fun component1 ()Lnet/mamoe/mirai/contact/OtherClient; public final fun copy (Lnet/mamoe/mirai/contact/OtherClient;)Lnet/mamoe/mirai/event/events/OtherClientOfflineEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/OtherClientOfflineEvent;Lnet/mamoe/mirai/contact/OtherClient;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/OtherClientOfflineEvent; public fun equals (Ljava/lang/Object;)Z public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/OtherClientOnlineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/OtherClientEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/OtherClient; public final fun component2 ()Lnet/mamoe/mirai/contact/ClientKind; public final fun copy (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/ClientKind;)Lnet/mamoe/mirai/event/events/OtherClientOnlineEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/OtherClientOnlineEvent;Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/ClientKind;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/OtherClientOnlineEvent; public fun equals (Ljava/lang/Object;)Z public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public final fun getKind ()Lnet/mamoe/mirai/contact/ClientKind; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/event/events/ShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; } public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Failed : net/mamoe/mirai/event/events/ShortVideoUploadEvent { public final fun getErrno ()I public final fun getMessage ()Ljava/lang/String; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Succeed : net/mamoe/mirai/event/events/ShortVideoUploadEvent { public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun getVideo ()Lnet/mamoe/mirai/message/data/ShortVideo; public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/SignEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getRank ()Ljava/lang/Integer; public final fun getSign ()Ljava/lang/String; public final fun getUser ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun hasRank ()Z public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerAddEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/StrangerEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/event/events/StrangerAddEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/StrangerAddEvent;Lnet/mamoe/mirai/contact/Stranger;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/StrangerAddEvent; public fun equals (Ljava/lang/Object;)Z public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/StrangerEvent : net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/event/events/UserEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public fun getUser ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/event/events/StrangerMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/StrangerEvent, net/mamoe/mirai/event/events/UserMessageEvent { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerMessagePostSendEvent : net/mamoe/mirai/event/events/UserMessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/StrangerMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/StrangerMessagePostSendEvent;Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/StrangerMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerMessagePreSendEvent : net/mamoe/mirai/event/events/UserMessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/StrangerMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/StrangerMessagePreSendEvent;Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/StrangerMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/MessageSyncEvent, net/mamoe/mirai/event/events/StrangerEvent { public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public fun getTime ()I } public abstract class net/mamoe/mirai/event/events/StrangerRelationChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/StrangerEvent, net/mamoe/mirai/internal/network/Packet { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; } public final class net/mamoe/mirai/event/events/StrangerRelationChangeEvent$Deleted : net/mamoe/mirai/event/events/StrangerRelationChangeEvent { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;)V } public final class net/mamoe/mirai/event/events/StrangerRelationChangeEvent$Friended : net/mamoe/mirai/event/events/StrangerRelationChangeEvent { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/contact/Friend;)V public final fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; } public abstract class net/mamoe/mirai/event/events/TempMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/UserMessageEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I } public abstract class net/mamoe/mirai/event/events/TempMessagePostSendEvent : net/mamoe/mirai/event/events/UserMessagePostSendEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getException ()Ljava/lang/Throwable; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Member; } public abstract class net/mamoe/mirai/event/events/TempMessagePreSendEvent : net/mamoe/mirai/event/events/UserMessagePreSendEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/message/data/Message;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V } public abstract interface class net/mamoe/mirai/event/events/UserEvent : net/mamoe/mirai/event/events/BotEvent { public abstract fun getUser ()Lnet/mamoe/mirai/contact/User; } public abstract interface class net/mamoe/mirai/event/events/UserMessageEvent : net/mamoe/mirai/event/events/MessageEvent { public abstract fun getSubject ()Lnet/mamoe/mirai/contact/User; } public abstract class net/mamoe/mirai/event/events/UserMessagePostSendEvent : net/mamoe/mirai/event/events/MessagePostSendEvent { } public abstract class net/mamoe/mirai/event/events/UserMessagePreSendEvent : net/mamoe/mirai/event/events/MessagePreSendEvent { public abstract fun getTarget ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/message/MessageEventKt { public static final fun isContextIdenticalWith (Lnet/mamoe/mirai/event/events/MessageEvent;Lnet/mamoe/mirai/event/events/MessageEvent;)Z } public class net/mamoe/mirai/message/MessageReceipt { public static final field Companion Lnet/mamoe/mirai/message/MessageReceipt$Companion; public final fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing; public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public final fun isToGroup ()Z public final fun quote ()Lnet/mamoe/mirai/message/data/QuoteReply; public fun quoteReply (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun quoteReply (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun quoteReply (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun quoteReply (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun recall ()V public final fun recall (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun recallIn (J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; } public final class net/mamoe/mirai/message/MessageReceipt$Companion { } public final class net/mamoe/mirai/message/MessageReceiptKt { public static final fun getBot (Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/Bot; public static final fun getSourceIds (Lnet/mamoe/mirai/message/MessageReceipt;)[I public static final fun getSourceInternalIds (Lnet/mamoe/mirai/message/MessageReceipt;)[I public static final fun getSourceMessage (Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun getSourceTime (Lnet/mamoe/mirai/message/MessageReceipt;)I } public abstract interface class net/mamoe/mirai/message/MessageSerializers { public static final field INSTANCE Lnet/mamoe/mirai/message/MessageSerializers$INSTANCE; public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; } public final class net/mamoe/mirai/message/MessageSerializers$INSTANCE : net/mamoe/mirai/message/MessageSerializers { public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; } public final class net/mamoe/mirai/message/action/AsyncRecallResult { public final fun awaitException ()Ljava/lang/Throwable; public final fun awaitException (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun awaitIsSuccess ()Z public final fun awaitIsSuccess (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getException ()Lkotlinx/coroutines/Deferred; public final fun getExceptionFuture ()Ljava/util/concurrent/CompletableFuture; public final fun isSuccess ()Lkotlinx/coroutines/Deferred; public final fun isSuccessFuture ()Ljava/util/concurrent/CompletableFuture; } public final class net/mamoe/mirai/message/action/BotNudge : net/mamoe/mirai/message/action/Nudge { 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/message/action/BotNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/BotNudge;Lnet/mamoe/mirai/Bot;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/BotNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/Bot; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/action/FriendNudge : net/mamoe/mirai/message/action/UserNudge { 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/message/action/FriendNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/FriendNudge;Lnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/FriendNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/action/MemberNudge : net/mamoe/mirai/message/action/UserNudge { 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/message/action/MemberNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/MemberNudge;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/MemberNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/message/action/Nudge { public static final field Companion Lnet/mamoe/mirai/message/action/Nudge$Companion; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public static final synthetic fun sendNudge (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/action/Nudge;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sendTo (Lnet/mamoe/mirai/contact/Contact;)Z public final fun sendTo (Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/action/Nudge$Companion { public final synthetic fun sendNudge (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/action/Nudge;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/action/StrangerNudge : net/mamoe/mirai/message/action/UserNudge { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;)V public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/message/action/StrangerNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/StrangerNudge;Lnet/mamoe/mirai/contact/Stranger;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/StrangerNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/message/action/UserNudge : net/mamoe/mirai/message/action/Nudge { public abstract fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; } public abstract interface class net/mamoe/mirai/message/code/CodableMessage : net/mamoe/mirai/message/data/Message { public fun serializeToMiraiCode ()Ljava/lang/String; } public final class net/mamoe/mirai/message/code/MiraiCode { public static final field INSTANCE Lnet/mamoe/mirai/message/code/MiraiCode; public static final fun deserializeMiraiCode (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun deserializeMiraiCode (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeMiraiCode$default (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public final synthetic fun parseMiraiCode1 (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun parseMiraiCode1$default (Lnet/mamoe/mirai/message/code/MiraiCode;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun serializeToMiraiCode (Ljava/lang/Iterable;)Ljava/lang/String; public static final fun serializeToMiraiCode (Ljava/util/Iterator;)Ljava/lang/String; public static final fun serializeToMiraiCode (Lkotlin/sequences/Sequence;)Ljava/lang/String; public static final fun serializeToMiraiCode (Lnet/mamoe/mirai/message/code/CodableMessage;)Ljava/lang/String; public static final fun serializeToMiraiCode ([Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/String; } public abstract class net/mamoe/mirai/message/data/AbstractMessageKey : net/mamoe/mirai/message/data/MessageKey { public fun <init> (Lkotlin/jvm/functions/Function1;)V public fun getSafeCast ()Lkotlin/jvm/functions/Function1; } public abstract class net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey : net/mamoe/mirai/message/data/AbstractMessageKey, net/mamoe/mirai/message/data/MessageKey { public fun <init> (Lnet/mamoe/mirai/message/data/MessageKey;Lkotlin/jvm/functions/Function1;)V } public final class net/mamoe/mirai/message/data/AbstractServiceMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/At : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field Companion Lnet/mamoe/mirai/message/data/At$Companion; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (IJLkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (J)V public final fun component1 ()J public fun contentToString ()Ljava/lang/String; public final fun copy (J)Lnet/mamoe/mirai/message/data/At; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/At;JILjava/lang/Object;)Lnet/mamoe/mirai/message/data/At; public fun equals (Ljava/lang/Object;)Z public fun followedBy (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun getDisplay (Lnet/mamoe/mirai/contact/Group;)Ljava/lang/String; public final fun getTarget ()J public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/At;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/At$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/At$$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/At; 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/At;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/At$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/AtAll : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field INSTANCE Lnet/mamoe/mirai/message/data/AtAll; public static final field SERIAL_NAME Ljava/lang/String; public static final field display Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public fun equals (Ljava/lang/Object;)Z public synthetic fun followedBy (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun hashCode ()I public fun serializeToMiraiCode ()Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/Audio : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/Audio$Key; public fun contentToString ()Ljava/lang/String; public abstract fun getCodec ()Lnet/mamoe/mirai/message/data/AudioCodec; public abstract fun getExtraData ()[B public abstract fun getFileMd5 ()[B public abstract fun getFileSize ()J public abstract fun getFilename ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/Audio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/AudioCodec : java/lang/Enum { public static final field AMR Lnet/mamoe/mirai/message/data/AudioCodec; public static final field Companion Lnet/mamoe/mirai/message/data/AudioCodec$Companion; public static final field SILK Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun getFormatName ()Ljava/lang/String; public final fun getId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public static fun values ()[Lnet/mamoe/mirai/message/data/AudioCodec; } public final class net/mamoe/mirai/message/data/AudioCodec$Companion { public final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/ConstrainSingle : net/mamoe/mirai/message/data/SingleMessage { public abstract fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; } public final class net/mamoe/mirai/message/data/CustomMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/CustomMessage$Companion$CustomMessageFullDataDeserializeInternalException : java/lang/RuntimeException { public fun <init> (Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/message/data/CustomMessage$Companion$CustomMessageFullDataDeserializeUserException : java/lang/RuntimeException { public fun <init> ([BLjava/lang/Throwable;)V public final fun getBody ()[B } public abstract class net/mamoe/mirai/message/data/CustomMessage$JsonSerializerFactory : net/mamoe/mirai/message/data/CustomMessage$Factory { public fun <init> (Ljava/lang/String;)V public fun dump (Lnet/mamoe/mirai/message/data/CustomMessage;)[B public fun getJson ()Lkotlinx/serialization/json/Json; public fun load ([B)Lnet/mamoe/mirai/message/data/CustomMessage; public abstract fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class net/mamoe/mirai/message/data/CustomMessage$ProtoBufSerializerFactory : net/mamoe/mirai/message/data/CustomMessage$Factory { public fun <init> (Ljava/lang/String;)V public fun dump (Lnet/mamoe/mirai/message/data/CustomMessage;)[B public fun load ([B)Lnet/mamoe/mirai/message/data/CustomMessage; public abstract fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/CustomMessageKt { } 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 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/DirectSizeAccess, net/mamoe/mirai/message/data/DirectToStringAccess, net/mamoe/mirai/message/data/MessageChain { public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain; public fun contains (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun containsAll (Ljava/util/Collection;)Z public fun contentToString ()Ljava/lang/String; public synthetic fun get (I)Ljava/lang/Object; public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun getHasConstrainSingle ()Z public fun getSize ()I public fun indexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public fun lastIndexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun listIterator ()Ljava/util/ListIterator; public fun listIterator (I)Ljava/util/ListIterator; public fun serializeToMiraiCode ()Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; public fun subList (II)Ljava/util/List; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/Face : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field AI_NI I public static final field AI_QING I public static final field AI_XIN I public static final field AN_ZHONG_GUAN_CHA I public static final field AO_MAN I public static final field BAI_TUO I public static final field BAI_XIE I public static final field BAI_YAN I public static final field BANG_BANG_TANG I public static final field BAN_ZHUAN_ZHONG I public static final field BAO_BAO I public static final field BAO_FU I public static final field BAO_JI I public static final field BAO_JIN I public static final field BAO_QUAN I public static final field BIAN_BIAN I public static final field BIAN_PAO I public static final field BIAN_XING I public static final field BIAO_LEI I public static final field BI_SHI I public static final field BI_XIN I public static final field BI_ZUI I public static final field BO_BO I public static final field BU I public static final field BU_KAI_XIN I public static final field CAI I public static final field CAI_DAO I public static final field CAI_GOU I public static final field CANG_SANG I public static final field CA_HAN I public static final field CENG_YI_CENG I public static final field CHA I public static final field CHAN_DOU I public static final field CHAO_FENG I public static final field CHAO_PIAO I public static final field CHA_JIN I public static final field CHE_YI_CHE I public static final field CHI I public static final field CHI_GUA I public static final field CHI_TANG I public static final field CHONG_BAI I public static final field CUO_HAO I public static final field Companion Lnet/mamoe/mirai/message/data/Face$Companion; public static final field DAN I public static final field DAN_GAO I public static final field DAO I public static final field DA_CALL I public static final field DA_KU I public static final field DA_LIAN I public static final field DA_XIAO I public static final field DA_YUAN_ZHONG I public static final field DA_ZHAN_HONG_TU I public static final field DA_ZHAO_HU I public static final field DENG_LONG I public static final field DE_YI I public static final field DIAN_ZAN I public static final field DIAO_XIE I public static final field DING_GUA_GUA I public static final field DUI_HAO I public static final field E I public static final field FAN I public static final field FA_DAI I public static final field FA_DOU I public static final field FA_NU I public static final field FEI_JI I public static final field FEI_WEN I public static final field FEN_DOU I public static final field FO_XI I public static final field FU_LUO_BO I public static final field GAN_BEI I public static final field GAN_GA I public static final field GONG_XI I public static final field GOU_YIN I public static final field GU_ZHANG I public static final field HAI_PA I public static final field HAI_XIU I public static final field HAN I public static final field HAN_XIAO I public static final field HAO I public static final field HAO_BANG I public static final field HAO_SHAN I public static final field HA_QIAN I public static final field HENG I public static final field HE_CAI I public static final field HE_HE_DA I public static final field HE_NAI I public static final field HE_XIE I public static final field HONG_BAO I public static final field HONG_BAO_BAO I public static final field HONG_BAO_DUO_DUO I public static final field HUAI_XIAO I public static final field HUA_CHI I public static final field HUA_DUO_LIAN I public static final field HUI_SHOU I public static final field HUI_TOU I public static final field HU_HU_SHENG_WEI I public static final field HU_LIAN I public static final field JIA_YI I public static final field JIA_YOU I public static final field JIA_YOU_BAO_BAO I public static final field JIA_YOU_BI_SHENG I public static final field JIE_WU I public static final field JING_DAI I public static final field JING_KONG I public static final field JING_LI I public static final field JING_XI I public static final field JING_XIA I public static final field JING_YA I public static final field JI_DONG I public static final field JI_E I public static final field JI_ZHANG I public static final field JU_HUA I public static final field JU_JUE I public static final field JU_PAI_PAI I public static final field KAI_QIANG I public static final field KA_FEI I public static final field KEN_TOU I public static final field KE_AI I public static final field KE_DAO_LE I public static final field KE_LIAN I public static final field KE_TOU I public static final field KOU_BI I public static final field KOU_ZHAO_HU_TI I public static final field KU I public static final field KUAI_KU_LE I public static final field KUANG_XIAO I public static final field KUN I public static final field KU_LOU I public static final field K_GE I public static final field K歌 I public static final field LAN_QIU I public static final field LAO_SE_PI I public static final field LA_JIAO_JIANG I public static final field LA_YAN_JING I public static final field LEI_BEN I public static final field LENG_HAN I public static final field LENG_MO I public static final field LIAO_YI_LIAO I public static final field LIU_HAN I public static final field LIU_LEI I public static final field LI_WU I public static final field MAI_MENG I public static final field MANG_DAO_FEI_QI I public static final field MEI_GUI I public static final field MIAN_WU_BIAO_QING I public static final field MIAO_MIAO I public static final field MING_BAI I public static final field MO_GUI_XIAO I public static final field MO_JIN_LI I public static final field MO_YU I public static final field NAN_GUO I public static final field NAO_KUO_TENG I public static final field NA_DAO_HONG_BAO I public static final field NIU_A I public static final field NIU_QI_CHONG_TIAN I public static final field NI_ZHEN_BANG_BANG I public static final field NO I public static final field O I public static final field OK I public static final field OU_HUO I public static final field O_YO I public static final field PAI_SHOU I public static final field PAI_TOU I public static final field PAI_ZHUO I public static final field PANG_SAN_JIN I public static final field PEN_LIAN I public static final field PEN_XIE I public static final field PIAO_CHONG I public static final field PIE_ZUI I public static final field PING_PANG I public static final field PI_JIU I public static final field QIAO_DA I public static final field QIAO_KAI_XIN I public static final field QIA_YI_QIA I public static final field QING I public static final field QING_ZHU I public static final field QIN_QIN I public static final field QIU_DA_LE I public static final field QIU_HONG_BAO I public static final field QI_DAI I public static final field QI_DAO I public static final field QUAN_TOU I public static final field RANG_WO_KANG_KANG I public static final field RENG_GOU I public static final field RE_HUA_LE I public static final field SAO_RAO I public static final field SE I public static final field SERIAL_NAME Ljava/lang/String; public static final field SHAN_DIAN I public static final field SHAN_LIAN I public static final field SHENG_LI I public static final field SHENG_QI I public static final field SHENG_RI_KUAI_LE I public static final field SHI_AI I public static final field SHOU_QIANG I public static final field SHUAI I public static final field SHUAI_TOU I public static final field SHUANG_XI I public static final field SHUI I public static final field SONG_HUA I public static final field SUAN_Q I public static final field TAI_NAN_LE I public static final field TAI_YANG I public static final field TIAN_PING I public static final field TIAN_YI_TIAN I public static final field TIAO_PI I public static final field TIAO_SHENG I public static final field TIAO_TIAO I public static final field TOU_KAN I public static final field TOU_TU I public static final field TOU_XIAO I public static final field TOU_ZHUANG_JI I public static final field TU I public static final field TUO_LIAN I public static final field TUO_SAI I public static final field WANG_WANG I public static final field WAN_CHENG I public static final field WEI_QU I public static final field WEI_XIAO I public static final field WEN_HAO_LIAN I public static final field WO_BU_KAN I public static final field WO_FANG_LE I public static final field WO_MEI_SHI I public static final field WO_SHOU I public static final field WO_SUAN_LE I public static final field WO_XIANG_KAI_LE I public static final field WO_ZUI_MEI I public static final field WU_LIAN I public static final field WU_LIAO I public static final field WU_NAI I public static final field WU_YAN_XIAO I public static final field XIA I public static final field XIAN_QI I public static final field XIAN_WEN I public static final field XIAO_JIU_JIE I public static final field XIAO_KU I public static final field XIAO_YANG_ER I public static final field XIE_HONG_BAO I public static final field XIE_YAN_XIAO I public static final field XIN_NIAN_YAN_HUA I public static final field XIN_SUI I public static final field XI_GUA I public static final field XU I public static final field YANG_TUO I public static final field YAN_HUA I public static final field YAO I public static final field YIN_XIAN I public static final field YI_WEN I public static final field YONG_BAO I public static final field YOU_BAI_NIAN I public static final field YOU_HENG_HENG I public static final field YOU_LING I public static final field YOU_QIN_QIN I public static final field YOU_TAI_JI I public static final field YOU_XIAN I public static final field YUAN_BAO I public static final field YUAN_LIANG I public static final field YUE_LIANG I public static final field YUN I public static final field ZAI_JIAN I public static final field ZAN I public static final field ZHA_DAN I public static final field ZHA_YAN_JING I public static final field ZHENG_YAN I public static final field ZHEN_HAO I public static final field ZHEN_JING I public static final field ZHE_MO I public static final field ZHOU_MA I public static final field ZHUAI_ZHA_TIAN I public static final field ZHUAN_QUAN I public static final field ZHUA_KUANG I public static final field ZHU_TOU I public static final field ZI_XI_FEN_XI I public static final field ZI_YA I public static final field ZUO_BAI_NIAN I public static final field ZUO_HENG_HENG I public static final field ZUO_QIN_QIN I public static final field ZUO_TAI_JI I public static final field ZU_QIU I public static final field doge I public static final field emm I public static final field names [Ljava/lang/String; public static final field 不 I public static final field 不开心 I public static final field 举牌牌 I public static final field 乒乓 I public static final field 亲亲 I public static final field 仔细分析 I public static final field 佛系 I public static final field 你真棒棒 I public static final field 便便 I public static final field 偷看 I public static final field 偷笑 I public static final field 傲慢 I public static final field 元宝 I public static final field 再见 I public static final field 冷汗 I public static final field 冷漠 I public static final field 凋谢 I public static final field 击掌 I public static final field 刀 I public static final field 加一 I public static final field 加油 I public static final field 加油必胜 I public static final field 加油抱抱 I public static final field 勾引 I public static final field 卖萌 I public static final field 原谅 I public static final field 双喜 I public static final field 发呆 I public static final field 发怒 I public static final field 发抖 I public static final field 变形 I public static final field 口罩护体 I public static final field 可怜 I public static final field 可爱 I public static final field 右亲亲 I public static final field 右哼哼 I public static final field 右太极 I public static final field 右拜年 I public static final field 吃 I public static final field 吃瓜 I public static final field 吃糖 I public static final field 吐 I public static final field 吓 I public static final field 呃 I public static final field 呲牙 I public static final field 呵呵哒 I public static final field 咒骂 I public static final field 咖啡 I public static final field 哈欠 I public static final field 哦 I public static final field 哦哟 I public static final field 哼 I public static final field 啃头 I public static final field 啤酒 I public static final field 啵啵 I public static final field 喝奶 I public static final field 喝彩 I public static final field 喵喵 I public static final field 喷脸 I public static final field 喷血 I public static final field 嗑到了 I public static final field 嘘 I public static final field 嘲讽 I public static final field 回头 I public static final field 困 I public static final field 坏笑 I public static final field 大哭 I public static final field 大展宏兔 I public static final field 大怨种 I public static final field 大笑 I public static final field 太南了 I public static final field 太阳 I public static final field 头撞击 I public static final field 头秃 I public static final field 奋斗 I public static final field 好 I public static final field 好棒 I public static final field 好闪 I public static final field 委屈 I public static final field 嫌弃 I public static final field 完成 I public static final field 害怕 I public static final field 害羞 I public static final field 对号 I public static final field 小样儿 I public static final field 小纠结 I public static final field 尴尬 I public static final field 崇拜 I public static final field 左亲亲 I public static final field 左哼哼 I public static final field 左太极 I public static final field 左拜年 I public static final field 差劲 I public static final field 干杯 I public static final field 幽灵 I public static final field 庆祝 I public static final field 开枪 I public static final field 得意 I public static final field 微笑 I public static final field 心碎 I public static final field 忙到飞起 I public static final field 快哭了 I public static final field 怄火 I public static final field 恭喜 I public static final field 悠闲 I public static final field 惊吓 I public static final field 惊呆 I public static final field 惊喜 I public static final field 惊恐 I public static final field 惊讶 I public static final field 憨笑 I public static final field 我不看 I public static final field 我想开了 I public static final field 我方了 I public static final field 我最美 I public static final field 我没事 I public static final field 我酸了 I public static final field 扇脸 I public static final field 手枪 I public static final field 打call I public static final field 打招呼 I public static final field 打脸 I public static final field 扔狗 I public static final field 托脸 I public static final field 托腮 I public static final field 扯一扯 I public static final field 抓狂 I public static final field 折磨 I public static final field 抠鼻 I public static final field 抱抱 I public static final field 抱拳 I public static final field 拍头 I public static final field 拍手 I public static final field 拍桌 I public static final field 拒绝 I public static final field 拜托 I public static final field 拜谢 I public static final field 拥抱 I public static final field 拳头 I public static final field 拽炸天 I public static final field 拿到红包 I public static final field 挥手 I public static final field 捂脸 I public static final field 掐一掐 I public static final field 握手 I public static final field 搬砖中 I public static final field 摸锦鲤 I public static final field 摸鱼 I public static final field 撇嘴 I public static final field 撩一撩 I public static final field 擦汗 I public static final field 敬礼 I public static final field 敲开心 I public static final field 敲打 I public static final field 斜眼笑 I public static final field 新年烟花 I public static final field 无奈 I public static final field 无眼笑 I public static final field 无聊 I public static final field 明白 I public static final field 晕 I public static final field 暗中观察 I public static final field 暴击 I public static final field 月亮 I public static final field 期待 I public static final field 棒棒糖 I public static final field 比心 I public static final field 求红包 I public static final field 汗 I public static final field 汪汪 I public static final field 沧桑 I public static final field 河蟹 I public static final field 泪奔 I public static final field 流汗 I public static final field 流泪 I public static final field 激动 I public static final field 灯笼 I public static final field 炸弹 I public static final field 点赞 I public static final field 烟花 I public static final field 热化了 I public static final field 爆筋 I public static final field 爱你 I public static final field 爱心 I public static final field 爱情 I public static final field 牛啊 I public static final field 牛气冲天 I public static final field 狂笑 I public static final field 猪头 I public static final field 献吻 I public static final field 玫瑰 I public static final field 瓢虫 I public static final field 生日快乐 I public static final field 生气 I public static final field 甩头 I public static final field 疑问 I public static final field 白眼 I public static final field 真好 I public static final field 眨眼睛 I public static final field 睁眼 I public static final field 睡 I public static final field 磕头 I public static final field 示爱 I public static final field 礼物 I public static final field 祈祷 I public static final field 福萝卜 I public static final field 笑哭 I public static final field 篮球 I public static final field 糊脸 I public static final field 糗大了 I public static final field 红包 I public static final field 红包包 I public static final field 红包多多 I public static final field 羊驼 I public static final field 老色痞 I public static final field 胖三斤 I public static final field 胜利 I public static final field 脑阔疼 I public static final field 舔一舔 I public static final field 舔屏 I public static final field 色 I public static final field 花朵脸 I public static final field 花痴 I public static final field 茶 I public static final field 药 I public static final field 菊花 I public static final field 菜刀 I public static final field 菜狗 I public static final field 虎虎生威 I public static final field 蛋 I public static final field 蛋糕 I public static final field 街舞 I public static final field 衰 I public static final field 西瓜 I public static final field 让我康康 I public static final field 请 I public static final field 调皮 I public static final field 谢红包 I public static final field 豹富 I public static final field 赞 I public static final field 足球 I public static final field 跳绳 I public static final field 跳跳 I public static final field 踩 I public static final field 蹭一蹭 I public static final field 转圈 I public static final field 辣椒酱 I public static final field 辣眼睛 I public static final field 送花 I public static final field 鄙视 I public static final field 酷 I public static final field 酸Q I public static final field 钞票 I public static final field 错号 I public static final field 闪电 I public static final field 闭嘴 I public static final field 问号脸 I public static final field 阴险 I public static final field 难过 I public static final field 震惊 I public static final field 面无表情 I public static final field 鞭炮 I public static final field 顶呱呱 I public static final field 颤抖 I public static final field 飙泪 I public static final field 飞吻 I public static final field 飞机 I public static final field 饥饿 I public static final field 饭 I public static final field 骚扰 I public static final field 骷髅 I public static final field 魔鬼笑 I public static final field 鼓掌 I public fun <init> (I)V public synthetic fun <init> (IILkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun component1 ()I public fun contentToString ()Ljava/lang/String; public final fun copy (I)Lnet/mamoe/mirai/message/data/Face; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/Face;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Face; public fun equals (Ljava/lang/Object;)Z public final fun getId ()I public final fun getName ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/Face;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/Face$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Face$$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/Face; 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/Face;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/Face$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/FileMessage : 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/FileMessage$Key; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public static fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; public abstract fun getId ()Ljava/lang/String; public abstract fun getInternalId ()I public fun getKey ()Lnet/mamoe/mirai/message/data/FileMessage$Key; public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getName ()Ljava/lang/String; public abstract fun getSize ()J public fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/utils/RemoteFile; public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun toRemoteFile$suspendImpl (Lnet/mamoe/mirai/message/data/FileMessage;Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/FileMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; public final fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; } public final class net/mamoe/mirai/message/data/FileMessage$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/FileMessage$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/FileMessage; 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/FileMessage;)V } public final class net/mamoe/mirai/message/data/FlashImage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/HummerMessage, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/FlashImage$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILnet/mamoe/mirai/message/data/Image;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/message/data/Image;)V public final fun component1 ()Lnet/mamoe/mirai/message/data/Image; public fun contentToString ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/FlashImage;Lnet/mamoe/mirai/message/data/Image;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FlashImage; public fun equals (Ljava/lang/Object;)Z public static final fun from (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FlashImage; public static final fun from (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; public final fun getImage ()Lnet/mamoe/mirai/message/data/Image; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public fun hashCode ()I public fun serializeToMiraiCode ()Ljava/lang/String; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/FlashImage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/FlashImage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/FlashImage$$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/FlashImage; 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/FlashImage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/FlashImage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun from (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FlashImage; public final fun from (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/FlashImageKt { public static final synthetic fun FlashImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FlashImage; public static final synthetic fun flash (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; } public final class net/mamoe/mirai/message/data/ForwardMessage : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/ForwardMessage$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/util/List; public fun contentToString ()Ljava/lang/String; public final fun copy (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/ForwardMessage;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public fun equals (Ljava/lang/Object;)Z public final fun getBrief ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getNodeList ()Ljava/util/List; public final fun getPreview ()Ljava/util/List; public final fun getSource ()Ljava/lang/String; public final fun getSummary ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/ForwardMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/ForwardMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/ForwardMessage$$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/ForwardMessage; 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/ForwardMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy { public static final field Default Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy$Default; public fun generateBrief (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; public fun generatePreview (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/util/List; public fun generateSource (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; public fun generateSummary (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; public fun generateTitle (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; } public final class net/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy$Default : net/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy { } public final class net/mamoe/mirai/message/data/ForwardMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/ForwardMessage$Node : net/mamoe/mirai/message/data/ForwardMessage$INode { public static final field Companion Lnet/mamoe/mirai/message/data/ForwardMessage$Node$Companion; public synthetic fun <init> (IJILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (JILjava/lang/String;Lnet/mamoe/mirai/message/data/Message;)V public fun <init> (JILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;)V public final fun component1 ()J public final fun component2 ()I public final fun component3 ()Ljava/lang/String; public final fun component4 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun copy (JILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/ForwardMessage$Node; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/ForwardMessage$Node;JILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage$Node; public fun equals (Ljava/lang/Object;)Z public fun getMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSenderId ()J public fun getSenderName ()Ljava/lang/String; public fun getTime ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/ForwardMessage$Node;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/ForwardMessage$Node$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/ForwardMessage$Node$$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/ForwardMessage$Node; 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/ForwardMessage$Node;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/ForwardMessage$Node$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/ForwardMessageBuilder : java/util/List, kotlin/jvm/internal/markers/KMutableList { public fun <init> (Lnet/mamoe/mirai/contact/Contact;)V public fun <init> (Lnet/mamoe/mirai/contact/Contact;I)V public synthetic fun add (ILjava/lang/Object;)V public fun add (ILnet/mamoe/mirai/message/data/ForwardMessage$INode;)V public final fun add (JLjava/lang/String;ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (JLjava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (JLjava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (JLjava/lang/String;Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public synthetic fun add (Ljava/lang/Object;)Z public final fun add (Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/event/events/MessageEvent;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public fun add (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)Z public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;JLjava/lang/String;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;JLjava/lang/String;Lnet/mamoe/mirai/message/data/Message;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;Lnet/mamoe/mirai/contact/UserOrBot;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public fun addAll (ILjava/util/Collection;)Z public fun addAll (Ljava/util/Collection;)Z public final fun at (II)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun at (JI)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun at (Lnet/mamoe/mirai/contact/User;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun at (Lnet/mamoe/mirai/contact/UserOrBot;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun build ()Lnet/mamoe/mirai/message/data/ForwardMessage; public fun clear ()V public final fun contains (Ljava/lang/Object;)Z public fun contains (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)Z public fun containsAll (Ljava/util/Collection;)Z public synthetic fun get (I)Ljava/lang/Object; public fun get (I)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public final fun getContext ()Lnet/mamoe/mirai/contact/Contact; public final fun getCurrentTime ()I public final fun getDisplayStrategy ()Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy; public fun getSize ()I public final fun indexOf (Ljava/lang/Object;)I public fun indexOf (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public final fun lastIndexOf (Ljava/lang/Object;)I public fun lastIndexOf (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)I public fun listIterator ()Ljava/util/ListIterator; public fun listIterator (I)Ljava/util/ListIterator; public final fun named (ILjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (JLjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (Lnet/mamoe/mirai/contact/User;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (Lnet/mamoe/mirai/contact/UserOrBot;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public synthetic fun remove (I)Ljava/lang/Object; public final fun remove (I)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public final fun remove (Ljava/lang/Object;)Z public fun remove (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)Z public fun removeAll (Ljava/util/Collection;)Z public fun removeAt (I)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public fun retainAll (Ljava/util/Collection;)Z public final fun says (ILjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (ILnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (JLjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (JLkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (JLnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/Bot;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/User;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/User;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/UserOrBot;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object; public fun set (ILnet/mamoe/mirai/message/data/ForwardMessage$INode;)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public final fun setCurrentTime (I)V public final fun setDisplayStrategy (Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)V public final fun size ()I public fun subList (II)Ljava/util/List; public fun toArray ()[Ljava/lang/Object; public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; public final fun toRawForwardMessage ()Lnet/mamoe/mirai/message/data/RawForwardMessage; } public final class net/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode : net/mamoe/mirai/message/data/ForwardMessage$INode { public field messageChain Lnet/mamoe/mirai/message/data/MessageChain; public final fun at (I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public fun getMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSenderId ()J public fun getSenderName ()Ljava/lang/String; public fun getTime ()I public final fun message (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun message (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun says (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun sender (Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun sender (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun senderId (I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun senderId (J)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun senderName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public fun setMessageChain (Lnet/mamoe/mirai/message/data/MessageChain;)V public fun setSenderId (J)V public fun setSenderName (Ljava/lang/String;)V public fun setTime (I)V public final fun time (I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public fun toString ()Ljava/lang/String; } public abstract interface annotation class net/mamoe/mirai/message/data/ForwardMessageDsl : java/lang/annotation/Annotation { } public final class net/mamoe/mirai/message/data/ForwardMessageKt { public static final synthetic fun buildForwardMessage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final synthetic fun buildForwardMessage (Lnet/mamoe/mirai/event/events/MessageEvent;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun buildForwardMessage$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun buildForwardMessage$default (Lnet/mamoe/mirai/event/events/MessageEvent;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Ljava/lang/Iterable;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;I)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;I)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun toForwardMessage$default (Ljava/lang/Iterable;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun toForwardMessage$default (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun toForwardMessage$default (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; } public final class net/mamoe/mirai/message/data/HummerMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/Image : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/Message, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/Image$Key; public static final field SERIAL_NAME Ljava/lang/String; public static fun fromId (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public abstract fun getHeight ()I public abstract fun getImageId ()Ljava/lang/String; public static fun getImageIdRegex ()Lkotlin/text/Regex; public static fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public static fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; public abstract fun getImageType ()Lnet/mamoe/mirai/message/data/ImageType; public fun getMd5 ()[B public abstract fun getSize ()J public abstract fun getWidth ()I public fun isEmoji ()Z public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/Image$AsStringSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Image$AsStringSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Image; 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/Image;)V } public final class net/mamoe/mirai/message/data/Image$Builder { public static final field Companion Lnet/mamoe/mirai/message/data/Image$Builder$Companion; public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lnet/mamoe/mirai/message/data/Image; public final fun getHeight ()I public final fun getImageId ()Ljava/lang/String; public final fun getSize ()J public final fun getType ()Lnet/mamoe/mirai/message/data/ImageType; public final fun getWidth ()I public final fun isEmoji ()Z public static final fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; public final fun setEmoji (Z)V public final fun setHeight (I)V public final fun setImageId (Ljava/lang/String;)V public final fun setSize (J)V public final fun setType (Lnet/mamoe/mirai/message/data/ImageType;)V public final fun setWidth (I)V } public final class net/mamoe/mirai/message/data/Image$Builder$Companion { public final fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; } public final class net/mamoe/mirai/message/data/Image$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public static final field SERIAL_NAME Ljava/lang/String; public final fun calculateImageMd5ByImageId (Ljava/lang/String;)[B public final fun fromId (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun getImageIdRegex ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/Image$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Image$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Image; 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/Image;)V } public final class net/mamoe/mirai/message/data/ImageType : java/lang/Enum { public static final field APNG Lnet/mamoe/mirai/message/data/ImageType; public static final field BMP Lnet/mamoe/mirai/message/data/ImageType; public static final field Companion Lnet/mamoe/mirai/message/data/ImageType$Companion; public static final field GIF Lnet/mamoe/mirai/message/data/ImageType; public static final field JPG Lnet/mamoe/mirai/message/data/ImageType; public static final field PNG Lnet/mamoe/mirai/message/data/ImageType; public static final field UNKNOWN Lnet/mamoe/mirai/message/data/ImageType; public final fun getFormatName ()Ljava/lang/String; public static final fun match (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static final fun matchOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static fun values ()[Lnet/mamoe/mirai/message/data/ImageType; } public final class net/mamoe/mirai/message/data/ImageType$Companion { public final fun match (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public final fun matchOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/LightApp : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/RichMessage { public static final field Key Lnet/mamoe/mirai/message/data/LightApp$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun copy (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/LightApp; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/LightApp;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/LightApp; public fun equals (Ljava/lang/Object;)Z public fun getContent ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/LightApp;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/LightApp$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/LightApp$$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/LightApp; 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/LightApp;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/LightApp$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/MarketFace : net/mamoe/mirai/message/data/HummerMessage { public static final field Key Lnet/mamoe/mirai/message/data/MarketFace$Key; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public abstract fun getId ()I public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getName ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MarketFace$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/Message { public static final field Companion Lnet/mamoe/mirai/message/data/Message$Companion; public fun contentEquals (Ljava/lang/String;Z)Z public fun contentEquals (Lnet/mamoe/mirai/message/data/Message;Z)Z public fun contentEquals (Lnet/mamoe/mirai/message/data/Message;ZZ)Z public static synthetic fun contentEquals$default (Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;ZILjava/lang/Object;)Z public static synthetic fun contentEquals$default (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;ZILjava/lang/Object;)Z public static synthetic fun contentEquals$default (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;ZZILjava/lang/Object;)Z public abstract fun contentToString ()Ljava/lang/String; public synthetic fun followedBy (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Ljava/lang/CharSequence;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lkotlin/sequences/Sequence;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lnet/mamoe/mirai/message/data/SingleMessage;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plusIterableString (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/Message$Companion { } public abstract interface class net/mamoe/mirai/message/data/MessageChain : java/util/List, java/util/RandomAccess, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/Message { public static final field Companion Lnet/mamoe/mirai/message/data/MessageChain$Companion; public fun contains (Lnet/mamoe/mirai/message/data/MessageKey;)Z public static fun deserializeFromJsonString (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public static fun deserializeFromJsonString (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public static fun deserializeFromMiraiCode (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static fun deserializeFromMiraiCode (Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public fun get (Lnet/mamoe/mirai/message/data/MessageKey;)Lnet/mamoe/mirai/message/data/SingleMessage; public static fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/lang/String; public static fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/json/Json;)Ljava/lang/String; public static fun serializeToString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/StringFormat;)Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MessageChain$Companion { public final fun deserializeFromJsonString (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun deserializeFromJsonString (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeFromJsonString$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Ljava/lang/String;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun deserializeFromMiraiCode (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun deserializeFromMiraiCode (Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeFromMiraiCode$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeFromMiraiCode$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public final synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public final synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/lang/String; public final fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/json/Json;)Ljava/lang/String; public static synthetic fun serializeToJsonString$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Ljava/lang/String; public final fun serializeToString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/StringFormat;)Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageChain$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageChain$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/MessageChain; 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/MessageChain;)V } public final class net/mamoe/mirai/message/data/MessageChainBuilder : java/lang/Appendable, java/util/List, kotlin/jvm/internal/markers/KMutableList { public fun <init> ()V public fun <init> (I)V public synthetic fun add (ILjava/lang/Object;)V public fun add (ILnet/mamoe/mirai/message/data/SingleMessage;)V public synthetic fun add (Ljava/lang/Object;)Z public final fun add (Ljava/lang/String;)V public final fun add (Lnet/mamoe/mirai/message/data/Message;)Z public fun add (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun addAll (ILjava/util/Collection;)Z public final fun addAll (Ljava/lang/Iterable;)Z public fun addAll (Ljava/util/Collection;)Z public final fun addAllFlatten (Ljava/lang/Iterable;)Z public synthetic fun append (C)Ljava/lang/Appendable; public fun append (C)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public synthetic fun append (Ljava/lang/CharSequence;)Ljava/lang/Appendable; public fun append (Ljava/lang/CharSequence;)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public synthetic fun append (Ljava/lang/CharSequence;II)Ljava/lang/Appendable; public fun append (Ljava/lang/CharSequence;II)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public final fun append (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public final fun append (Lnet/mamoe/mirai/message/data/SingleMessage;)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public final fun asMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun build ()Lnet/mamoe/mirai/message/data/MessageChain; public fun clear ()V public final fun contains (Ljava/lang/Object;)Z public fun contains (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun containsAll (Ljava/util/Collection;)Z public final fun copy ()Lnet/mamoe/mirai/message/data/MessageChainBuilder; public synthetic fun get (I)Ljava/lang/Object; public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun getSize ()I public final fun indexOf (Ljava/lang/Object;)I public fun indexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public final fun lastIndexOf (Ljava/lang/Object;)I public fun lastIndexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun listIterator ()Ljava/util/ListIterator; public fun listIterator (I)Ljava/util/ListIterator; public final synthetic fun plusAssign (Ljava/lang/CharSequence;)V public final synthetic fun plusAssign (Ljava/lang/String;)V public final synthetic fun plusAssign (Lnet/mamoe/mirai/message/data/Message;)V public final synthetic fun plusAssign (Lnet/mamoe/mirai/message/data/SingleMessage;)V public synthetic fun remove (I)Ljava/lang/Object; public final fun remove (I)Lnet/mamoe/mirai/message/data/SingleMessage; public final fun remove (Ljava/lang/Object;)Z public fun remove (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun removeAll (Ljava/util/Collection;)Z public fun removeAt (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun retainAll (Ljava/util/Collection;)Z public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object; public fun set (ILnet/mamoe/mirai/message/data/SingleMessage;)Lnet/mamoe/mirai/message/data/SingleMessage; public final fun size ()I public fun subList (II)Ljava/util/List; public fun toArray ()[Ljava/lang/Object; public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; public final synthetic fun unaryPlus (Ljava/lang/String;)V public final synthetic fun unaryPlus (Lnet/mamoe/mirai/message/data/Message;)V } public abstract interface class net/mamoe/mirai/message/data/MessageContent : net/mamoe/mirai/message/data/SingleMessage { public static final field Key Lnet/mamoe/mirai/message/data/MessageContent$Key; } public final class net/mamoe/mirai/message/data/MessageContent$Key : net/mamoe/mirai/message/data/AbstractMessageKey { } public abstract interface class net/mamoe/mirai/message/data/MessageKey { public abstract fun getSafeCast ()Lkotlin/jvm/functions/Function1; } public final class net/mamoe/mirai/message/data/MessageKeyKt { public static final fun getTopmostKey (Lnet/mamoe/mirai/message/data/MessageKey;)Lnet/mamoe/mirai/message/data/MessageKey; public static final fun isInstance (Lnet/mamoe/mirai/message/data/MessageKey;Lnet/mamoe/mirai/message/data/SingleMessage;)Z } public abstract interface class net/mamoe/mirai/message/data/MessageMetadata : net/mamoe/mirai/message/data/SingleMessage { public fun contentToString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MessageOrigin$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageOrigin$$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/MessageOrigin; 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/MessageOrigin;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageOrigin$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageOriginKind : java/lang/Enum { public static final field Companion Lnet/mamoe/mirai/message/data/MessageOriginKind$Companion; public static final field FORWARD Lnet/mamoe/mirai/message/data/MessageOriginKind; public static final field LONG Lnet/mamoe/mirai/message/data/MessageOriginKind; public static final field MUSIC_SHARE Lnet/mamoe/mirai/message/data/MessageOriginKind; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageOriginKind; public static fun values ()[Lnet/mamoe/mirai/message/data/MessageOriginKind; } public final class net/mamoe/mirai/message/data/MessageOriginKind$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class net/mamoe/mirai/message/data/MessageSource : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/Message, net/mamoe/mirai/message/data/MessageMetadata { public static final field Key Lnet/mamoe/mirai/message/data/MessageSource$Key; public static final field SERIAL_NAME Ljava/lang/String; public abstract fun getBotId ()J public abstract fun getFromId ()J public abstract fun getIds ()[I public abstract fun getInternalIds ()[I public final fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun getTargetId ()J public abstract fun getTime ()I public abstract fun isOriginalMessageInitialized ()Z public static final fun quote (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/QuoteReply; public static final fun quote (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/QuoteReply; public static final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;)V public static final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;)V public static final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun recallIn (Lnet/mamoe/mirai/message/data/MessageChain;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public static final fun recallIn (Lnet/mamoe/mirai/message/data/MessageSource;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MessageSource$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun quote (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/QuoteReply; public final fun quote (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/QuoteReply; public final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;)V public final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;)V public final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun recallIn (Lnet/mamoe/mirai/message/data/MessageChain;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public final fun recallIn (Lnet/mamoe/mirai/message/data/MessageSource;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageSource$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageSource$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/MessageSource; 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/MessageSource;)V } public final class net/mamoe/mirai/message/data/MessageSourceAmender : net/mamoe/mirai/message/data/MessageSourceBuilder { public fun <init> (Lnet/mamoe/mirai/message/data/MessageSource;)V public fun getFromId ()J public fun getIds ()[I public fun getInternalIds ()[I public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getTargetId ()J public fun getTime ()I public fun setFromId (J)V public fun setIds ([I)V public fun setInternalIds ([I)V public final fun setKind (Lnet/mamoe/mirai/message/data/MessageSourceKind;)V public final fun setOriginalMessage (Lnet/mamoe/mirai/message/data/MessageChain;)V public fun setTargetId (J)V public fun setTime (I)V } public class net/mamoe/mirai/message/data/MessageSourceBuilder { public fun <init> ()V public final fun allFrom (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun build (JLnet/mamoe/mirai/message/data/MessageSourceKind;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public final fun clearMessages ()Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public fun getFromId ()J public fun getIds ()[I public fun getInternalIds ()[I public fun getTargetId ()J public fun getTime ()I public final fun id (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun id ([I)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun internalId (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun internalId ([I)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun messages (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final synthetic fun messages (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun messages ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun messagesFrom (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun metadata (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun sender (J)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun sender (Lnet/mamoe/mirai/contact/ContactOrBot;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public fun setFromId (J)V public fun setIds ([I)V public fun setInternalIds ([I)V public final fun setSenderAndTarget (Lnet/mamoe/mirai/contact/ContactOrBot;Lnet/mamoe/mirai/contact/ContactOrBot;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public fun setTargetId (J)V public fun setTime (I)V public final fun target (J)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun target (Lnet/mamoe/mirai/contact/ContactOrBot;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun time (I)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun time (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; } public final class net/mamoe/mirai/message/data/MessageSourceKind : java/lang/Enum { public static final field Companion Lnet/mamoe/mirai/message/data/MessageSourceKind$Companion; public static final field FRIEND Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final field GROUP Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final field STRANGER Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final field TEMP Lnet/mamoe/mirai/message/data/MessageSourceKind; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageSourceKind; public static fun values ()[Lnet/mamoe/mirai/message/data/MessageSourceKind; } public final class net/mamoe/mirai/message/data/MessageSourceKind$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageUtils { public static final synthetic fun At (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/At; public static final synthetic fun FileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; public static final synthetic fun Image (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static final synthetic fun Image (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun Image$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static final synthetic fun OfflineAudio (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio; public static final synthetic fun OfflineAudio (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio; public static final synthetic fun UnsupportedMessage ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; public static final synthetic fun at (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/message/data/At; public static final fun buildMessageChain (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun buildMessageChain (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/IMirai;JLnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun calculateImageMd5 (Lnet/mamoe/mirai/message/data/Image;)[B public static final fun contentsList (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/util/List; public static final synthetic fun contentsSequence (Lnet/mamoe/mirai/message/data/MessageChain;)Lkotlin/sequences/Sequence; public static final fun copySource (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final fun emptyMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun getBot (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/Bot; public static final fun getBot (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/Bot; public static final fun getBotOrNull (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/Bot; public static final synthetic fun getContent (Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/String; public static final synthetic fun getIds (Lnet/mamoe/mirai/message/data/MessageChain;)[I public static final synthetic fun getInternalId (Lnet/mamoe/mirai/message/data/MessageChain;)[I public static final synthetic fun getKind (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final synthetic fun getKind (Lnet/mamoe/mirai/message/data/OnlineMessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final synthetic fun getLengthDuration (Lnet/mamoe/mirai/message/data/OnlineAudio;)J public static final synthetic fun getOrFail (Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/message/data/MessageKey;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/SingleMessage; public static synthetic fun getOrFail$default (Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/message/data/MessageKey;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/SingleMessage; public static final synthetic fun getSource (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/MessageSource; public static final synthetic fun getSourceOrNull (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/MessageSource; public static final synthetic fun getTime (Lnet/mamoe/mirai/message/data/MessageChain;)I public static final fun isContentBlank (Lnet/mamoe/mirai/message/data/Message;)Z public static final fun isContentEmpty (Lnet/mamoe/mirai/message/data/Message;)Z public static final fun metadataList (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/util/List; public static final synthetic fun metadataSequence (Lnet/mamoe/mirai/message/data/MessageChain;)Lkotlin/sequences/Sequence; public static final fun newChain (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Ljava/util/Iterator;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Ljava/util/stream/Stream;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Lkotlin/sequences/Sequence;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun newChain (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun plus (Lnet/mamoe/mirai/message/data/Message;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun recallSource (Lnet/mamoe/mirai/message/data/QuoteReply;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun repeat (Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun sendTo (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun times (Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun toMessageChain ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun toOfflineMessageSource (Lnet/mamoe/mirai/message/data/OnlineMessageSource;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun toPlainText (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/PlainText; public static final synthetic fun toVoice (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice; } public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum { public static final field KugouMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field KuwoMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field MiguMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field NeteaseCloudMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field QQMusic Lnet/mamoe/mirai/message/data/MusicKind; public final fun getAppId ()J public final fun getPackageName ()Ljava/lang/String; public final fun getPlatform ()I public final fun getSdkVersion ()Ljava/lang/String; public final fun getSignature ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MusicKind; public static fun values ()[Lnet/mamoe/mirai/message/data/MusicKind; } 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 final fun component1 ()Lnet/mamoe/mirai/message/data/MusicKind; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MusicShare; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/MusicShare;Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MusicShare; public fun equals (Ljava/lang/Object;)Z public final fun getBrief ()Ljava/lang/String; public final fun getJumpUrl ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getKind ()Lnet/mamoe/mirai/message/data/MusicKind; public final fun getMusicUrl ()Ljava/lang/String; public final fun getPictureUrl ()Ljava/lang/String; public final fun getSummary ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/MusicShare;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/MusicShare$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MusicShare$$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/MusicShare; 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/MusicShare;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MusicShare$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/OfflineAudio : net/mamoe/mirai/message/data/Audio { public static final field Key Lnet/mamoe/mirai/message/data/OfflineAudio$Key; public static final field SERIAL_NAME Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/OfflineAudio$Factory { public static final field INSTANCE Lnet/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE; public abstract fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio; public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio; } public final class net/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE : net/mamoe/mirai/message/data/OfflineAudio$Factory { public fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio; public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio; } public final class net/mamoe/mirai/message/data/OfflineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/mamoe/mirai/message/data/MessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OfflineMessageSource$Key; public fun <init> ()V public abstract fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OfflineMessageSource$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/OfflineShortVideo : net/mamoe/mirai/message/data/ShortVideo { public static final field Key Lnet/mamoe/mirai/message/data/OfflineShortVideo$Key; public static final field SERIAL_NAME Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder { public static final field Companion Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion; public final fun build ()Lnet/mamoe/mirai/message/data/OfflineShortVideo; public final fun getFileFormat ()Ljava/lang/String; public final fun getFileMd5 ()[B public final fun getFileName ()Ljava/lang/String; public final fun getFileSize ()J public final fun getThumbnailMd5 ()[B public final fun getThumbnailSize ()J public final fun getVideoId ()Ljava/lang/String; public static final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder; public final fun setFileFormat (Ljava/lang/String;)V public final fun setFileMd5 ([B)V public final fun setFileName (Ljava/lang/String;)V public final fun setFileSize (J)V public final fun setThumbnailMd5 ([B)V public final fun setThumbnailSize (J)V public final fun setVideoId (Ljava/lang/String;)V } public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion { public final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder; } public final class net/mamoe/mirai/message/data/OfflineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/OnlineAudio : net/mamoe/mirai/message/data/Audio { public static final field Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key; public static final field SERIAL_NAME Ljava/lang/String; public abstract fun getLength ()J public abstract fun getUrlForDownload ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource : net/mamoe/mirai/message/data/MessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Key; public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getBotId ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/ContactOrBot; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming : net/mamoe/mirai/message/data/OnlineMessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$Key; public fun getFromId ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getTargetId ()J } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Friend; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Friend; public final synthetic fun getTarget ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup$Key; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Group; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun getTarget ()Lnet/mamoe/mirai/contact/Group; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Stranger; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public final synthetic fun getTarget ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp$Key; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Member; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Member; public final synthetic fun getTarget ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Key : net/mamoe/mirai/message/data/AbstractMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing : net/mamoe/mirai/message/data/OnlineMessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$Key; public final fun getFromId ()J public abstract fun getSender ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public final fun getTargetId ()J } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToFriend : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToFriend$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Friend; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToFriend$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToGroup : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToGroup$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Group; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Group; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToGroup$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToStranger : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToStranger$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToStranger$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp$Key; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Member; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Member; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/OnlineShortVideo : net/mamoe/mirai/message/data/ShortVideo { public static final field Key Lnet/mamoe/mirai/message/data/OnlineShortVideo$Key; public static final field SERIAL_NAME Ljava/lang/String; public abstract fun getUrlForDownload ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OrNullDelegate { public static final synthetic fun box-impl (Ljava/lang/Object;)Lnet/mamoe/mirai/message/data/OrNullDelegate; public static fun constructor-impl (Ljava/lang/Object;)Ljava/lang/Object; public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z public static final fun getValue-impl (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; public fun hashCode ()I public static fun hashCode-impl (Ljava/lang/Object;)I public fun toString ()Ljava/lang/String; public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String; public final synthetic fun unbox-impl ()Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/PlainText : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field Companion Lnet/mamoe/mirai/message/data/PlainText$Companion; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/lang/CharSequence;)V public fun <init> (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public final fun copy (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/PlainText; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/PlainText;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/PlainText; public fun equals (Ljava/lang/Object;)Z public final fun getContent ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/PlainText;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/PlainText$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/PlainText$$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/PlainText; 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/PlainText;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PlainText$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PokeMessage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/HummerMessage { public static final field BaoBeiQiu Lnet/mamoe/mirai/message/data/PokeMessage; public static final field BiXin Lnet/mamoe/mirai/message/data/PokeMessage; public static final field ChuoYiChuo Lnet/mamoe/mirai/message/data/PokeMessage; public static final field DianZan Lnet/mamoe/mirai/message/data/PokeMessage; public static final field FangDaZhao Lnet/mamoe/mirai/message/data/PokeMessage; public static final field GouYin Lnet/mamoe/mirai/message/data/PokeMessage; public static final field JieYin Lnet/mamoe/mirai/message/data/PokeMessage; public static final field Key Lnet/mamoe/mirai/message/data/PokeMessage$Key; public static final field LiuLiuLiu Lnet/mamoe/mirai/message/data/PokeMessage; public static final field QiaoMen Lnet/mamoe/mirai/message/data/PokeMessage; public static final field RangNiPi Lnet/mamoe/mirai/message/data/PokeMessage; public static final field Rose Lnet/mamoe/mirai/message/data/PokeMessage; public static final field SERIAL_NAME Ljava/lang/String; public static final field ShouLei Lnet/mamoe/mirai/message/data/PokeMessage; public static final field SuiPing Lnet/mamoe/mirai/message/data/PokeMessage; public static final field XinSui Lnet/mamoe/mirai/message/data/PokeMessage; public static final field ZhaoHuanShu Lnet/mamoe/mirai/message/data/PokeMessage; public static final field ZhuaYiXia Lnet/mamoe/mirai/message/data/PokeMessage; public static final field values [Lnet/mamoe/mirai/message/data/PokeMessage; public synthetic fun <init> (ILjava/lang/String;IILkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()I public final fun component3 ()I public fun contentToString ()Ljava/lang/String; public final fun copy (Ljava/lang/String;II)Lnet/mamoe/mirai/message/data/PokeMessage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/PokeMessage;Ljava/lang/String;IIILjava/lang/Object;)Lnet/mamoe/mirai/message/data/PokeMessage; public fun equals (Ljava/lang/Object;)Z public final fun getId ()I public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getName ()Ljava/lang/String; public final fun getPokeType ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/PokeMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/PokeMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/PokeMessage$$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/PokeMessage; 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/PokeMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PokeMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PttMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/QuoteReply : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/Message, net/mamoe/mirai/message/data/MessageMetadata { public static final field Key Lnet/mamoe/mirai/message/data/QuoteReply$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILnet/mamoe/mirai/message/data/MessageSource;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/message/data/MessageChain;)V public fun <init> (Lnet/mamoe/mirai/message/data/MessageSource;)V public final fun component1 ()Lnet/mamoe/mirai/message/data/MessageSource; public final fun copy (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/QuoteReply; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/QuoteReply;Lnet/mamoe/mirai/message/data/MessageSource;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/QuoteReply; public fun equals (Ljava/lang/Object;)Z public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getSource ()Lnet/mamoe/mirai/message/data/MessageSource; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/QuoteReply;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/QuoteReply$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/QuoteReply$$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/QuoteReply; 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/QuoteReply;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/QuoteReply$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/RawForwardMessage { public static final field Companion Lnet/mamoe/mirai/message/data/RawForwardMessage$Companion; public synthetic fun <init> (ILjava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun copy (Ljava/util/List;)Lnet/mamoe/mirai/message/data/RawForwardMessage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/RawForwardMessage;Ljava/util/List;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/RawForwardMessage; public fun equals (Ljava/lang/Object;)Z public final fun getNodeList ()Ljava/util/List; public fun hashCode ()I public final fun render (Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/RawForwardMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/RawForwardMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/RawForwardMessage$$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/RawForwardMessage; 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/RawForwardMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/RawForwardMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/RichMessage : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/RichMessage$Key; public fun contentToString ()Ljava/lang/String; public abstract fun getContent ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; } 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 synthetic 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 final class net/mamoe/mirai/message/data/RockPaperScissors : java/lang/Enum, net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MarketFace { public static final field Key Lnet/mamoe/mirai/message/data/RockPaperScissors$Key; public static final field PAPER Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final field ROCK Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final field SCISSORS Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public final fun eliminates (Lnet/mamoe/mirai/message/data/RockPaperScissors;)Ljava/lang/Boolean; public final fun getContent ()Ljava/lang/String; public fun getId ()I public final fun getInternalId ()B public synthetic fun getName ()Ljava/lang/String; public static final fun random ()Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/message/data/RockPaperScissors; public fun toString ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/RockPaperScissors; public static fun values ()[Lnet/mamoe/mirai/message/data/RockPaperScissors; } public final class net/mamoe/mirai/message/data/RockPaperScissors$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun random ()Lnet/mamoe/mirai/message/data/RockPaperScissors; public final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/message/data/RockPaperScissors; public static synthetic fun random$default (Lnet/mamoe/mirai/message/data/RockPaperScissors$Key;Lkotlin/random/Random;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/RockPaperScissors; 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 abstract fun getServiceId ()I } public final class net/mamoe/mirai/message/data/ServiceMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/ShortVideo : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/ShortVideo$Key; public abstract fun getFileFormat ()Ljava/lang/String; public abstract fun getFileMd5 ()[B public abstract fun getFileSize ()J public abstract fun getFilename ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getVideoId ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/ShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/ShortVideoKt { public static final synthetic fun OfflineShortVideo (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo; public static synthetic fun OfflineShortVideo$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJILjava/lang/Object;)Lnet/mamoe/mirai/message/data/OfflineShortVideo; } 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 final fun serializer ()Lkotlinx/serialization/KSerializer; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/SimpleServiceMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/SimpleServiceMessage$$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/SimpleServiceMessage; 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/SimpleServiceMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/SimpleServiceMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/SingleMessage : net/mamoe/mirai/message/data/Message { } public final class net/mamoe/mirai/message/data/SingleMessage$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/SingleMessage$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/SingleMessage; 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/SingleMessage;)V } public final class net/mamoe/mirai/message/data/SuperFace : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/HummerMessage { public static final field Key Lnet/mamoe/mirai/message/data/SuperFace$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (IILjava/lang/String;ILkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun contentToString ()Ljava/lang/String; public fun equals (Ljava/lang/Object;)Z public static final fun from (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public static final fun fromOrNull (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public final fun getFace ()I public final fun getId ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getName ()Ljava/lang/String; public final fun getType ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/SuperFace;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/SuperFace$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/SuperFace$$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/SuperFace; 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/SuperFace;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/SuperFace$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun from (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public final fun fromOrNull (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/SuperFaceKt { public static final synthetic fun toFace (Lnet/mamoe/mirai/message/data/SuperFace;)Lnet/mamoe/mirai/message/data/Face; public static final synthetic fun toSuperFace (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public static final synthetic fun toSuperFaceOrNull (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; } public abstract interface class net/mamoe/mirai/message/data/UnsupportedMessage : net/mamoe/mirai/message/data/MessageContent { public static final field Companion Lnet/mamoe/mirai/message/data/UnsupportedMessage$Companion; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public static fun create ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; public abstract fun getStruct ()[B } public final class net/mamoe/mirai/message/data/UnsupportedMessage$Companion { public static final field SERIAL_NAME Ljava/lang/String; public final fun create ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; } public final class net/mamoe/mirai/message/data/UnsupportedMessage$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/UnsupportedMessage$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/UnsupportedMessage; 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/UnsupportedMessage;)V } public final class net/mamoe/mirai/message/data/VipFace : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/HummerMessage { public static final field AiXin Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field BianBian Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field ChaoPiao Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field DianZan Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field HaHa Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field Key Lnet/mamoe/mirai/message/data/VipFace$Key; public static final field LiuLian Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field LueLueLue Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field PingDiGuo Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field QinQin Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field SERIAL_NAME Ljava/lang/String; public static final field YaoWan Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field ZhaDan Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field ZhuTou Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field values [Lnet/mamoe/mirai/message/data/VipFace$Kind; public synthetic fun <init> (ILnet/mamoe/mirai/message/data/VipFace$Kind;ILkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun component1 ()Lnet/mamoe/mirai/message/data/VipFace$Kind; public final fun component2 ()I public fun contentToString ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/message/data/VipFace$Kind;I)Lnet/mamoe/mirai/message/data/VipFace; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/VipFace;Lnet/mamoe/mirai/message/data/VipFace$Kind;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/VipFace; public fun equals (Ljava/lang/Object;)Z public final fun getCount ()I public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getKind ()Lnet/mamoe/mirai/message/data/VipFace$Kind; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/VipFace;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/VipFace$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/VipFace$$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/VipFace; 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/VipFace;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/VipFace$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/VipFace$Kind { public static final field Companion Lnet/mamoe/mirai/message/data/VipFace$Kind$Companion; public synthetic fun <init> (IILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (ILjava/lang/String;)V public final fun component1 ()I public final fun component2 ()Ljava/lang/String; public final fun copy (ILjava/lang/String;)Lnet/mamoe/mirai/message/data/VipFace$Kind; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/VipFace$Kind;ILjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/VipFace$Kind; public fun equals (Ljava/lang/Object;)Z public final fun getId ()I public final fun getName ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/VipFace$Kind;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/VipFace$Kind$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/VipFace$Kind$$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/VipFace$Kind; 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/VipFace$Kind;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/VipFace$Kind$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage { public static final field Key Lnet/mamoe/mirai/message/data/Voice$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;[BJILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;[BJILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun contentToString ()Ljava/lang/String; public static final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice; public fun getFileName ()Ljava/lang/String; public fun getFileSize ()J public fun getMd5 ()[B public fun getUrl ()Ljava/lang/String; public final fun get_codec ()I public final fun toAudio ()Lnet/mamoe/mirai/message/data/Audio; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/Voice;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/Voice$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$$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/Voice; 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/Voice;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder { public fun <init> ()V public fun <init> (II)V public final fun getBg ()I public final fun getLayout ()I public final fun getText ()Ljava/lang/String; public final fun picture (Ljava/lang/String;)V public final fun setBg (I)V public final fun setLayout (I)V public final fun summary (Ljava/lang/String;Ljava/lang/String;)V public static synthetic fun summary$default (Lnet/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V public final fun title (Ljava/lang/String;ILjava/lang/String;)V public static synthetic fun title$default (Lnet/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder;Ljava/lang/String;ILjava/lang/String;ILjava/lang/Object;)V } public final class net/mamoe/mirai/message/data/visitor/MessageVisitorKt { } public final class net/mamoe/mirai/network/BotAuthorizationException : net/mamoe/mirai/network/LoginFailedException { public final fun getAuthorization ()Lnet/mamoe/mirai/auth/BotAuthorization; } public abstract class net/mamoe/mirai/network/CustomLoginFailedException : net/mamoe/mirai/network/LoginFailedException { public fun <init> (Z)V public fun <init> (ZLjava/lang/String;)V public fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;)V public fun <init> (ZLjava/lang/Throwable;)V } public final class net/mamoe/mirai/network/ForceOfflineException : java/util/concurrent/CancellationException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/network/InconsistentBotIdException : net/mamoe/mirai/network/LoginFailedException { public synthetic fun <init> (JJLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getActual ()J public final fun getExpected ()J } public abstract class net/mamoe/mirai/network/LoginFailedException : java/lang/RuntimeException { public synthetic fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getKillBot ()Z } public final class net/mamoe/mirai/network/NoServerAvailableException : net/mamoe/mirai/network/LoginFailedException { public fun getCause ()Ljava/lang/Throwable; } public final class net/mamoe/mirai/network/NoStandardInputForCaptchaException : net/mamoe/mirai/network/LoginFailedException { public synthetic fun <init> (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; } public final class net/mamoe/mirai/network/RetryLaterException : net/mamoe/mirai/network/LoginFailedException { public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class net/mamoe/mirai/network/UnsupportedCaptchaMethodException : net/mamoe/mirai/network/LoginFailedException { public fun <init> (Z)V public fun <init> (ZLjava/lang/String;)V public fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;)V public fun <init> (ZLjava/lang/Throwable;)V } public final class net/mamoe/mirai/network/UnsupportedQRCodeCaptchaException : net/mamoe/mirai/network/UnsupportedCaptchaMethodException { public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/network/UnsupportedSliderCaptchaException : net/mamoe/mirai/network/UnsupportedCaptchaMethodException { public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/network/UnsupportedSmsLoginException : net/mamoe/mirai/network/UnsupportedCaptchaMethodException { public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException { } public abstract interface class net/mamoe/mirai/spi/AudioToSilkService : net/mamoe/mirai/spi/BaseService { public static final field Companion Lnet/mamoe/mirai/spi/AudioToSilkService$Companion; public abstract fun convert (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun getInstance ()Lnet/mamoe/mirai/spi/AudioToSilkService; } public final class net/mamoe/mirai/spi/AudioToSilkService$Companion { public final fun getInstance ()Lnet/mamoe/mirai/spi/AudioToSilkService; } public abstract interface class net/mamoe/mirai/spi/BaseService { public fun getPriority ()I } public abstract class net/mamoe/mirai/utils/AbstractBotConfiguration { public fun <init> ()V public final fun fileBasedDeviceInfo ()V public final fun fileBasedDeviceInfo (Ljava/lang/String;)V public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V protected abstract fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1; public final fun getCacheDir ()Ljava/io/File; protected abstract fun getDeviceInfo ()Lkotlin/jvm/functions/Function1; protected abstract fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1; public final fun getWorkingDir ()Ljava/io/File; public final fun redirectBotLogToDirectory ()V public final fun redirectBotLogToDirectory (Ljava/io/File;)V public final fun redirectBotLogToDirectory (Ljava/io/File;J)V public final fun redirectBotLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V public static synthetic fun redirectBotLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun redirectBotLogToFile ()V public final fun redirectBotLogToFile (Ljava/io/File;)V public final fun redirectBotLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V public static synthetic fun redirectBotLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun redirectNetworkLogToDirectory ()V public final fun redirectNetworkLogToDirectory (Ljava/io/File;)V public final fun redirectNetworkLogToDirectory (Ljava/io/File;J)V public final fun redirectNetworkLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V public static synthetic fun redirectNetworkLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun redirectNetworkLogToFile ()V public final fun redirectNetworkLogToFile (Ljava/io/File;)V public final fun redirectNetworkLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V protected abstract fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V public final fun setCacheDir (Ljava/io/File;)V protected abstract fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V protected abstract fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V public final fun setWorkingDir (Ljava/io/File;)V } public abstract class net/mamoe/mirai/utils/AbstractExternalResource : net/mamoe/mirai/utils/ExternalResource { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V public synthetic fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V public synthetic fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun close ()V protected final fun dontRegisterLeakObserver ()V public final fun getClosed ()Lkotlinx/coroutines/Deferred; public fun getFormatName ()Ljava/lang/String; public fun getMd5 ()[B public fun getSha1 ()[B public final fun inputStream ()Ljava/io/InputStream; protected abstract fun inputStream0 ()Ljava/io/InputStream; protected final fun registerToLeakObserver ()V protected final fun setResourceCleanCallback (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V } public abstract interface class net/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback { public abstract fun cleanup ()V } public class net/mamoe/mirai/utils/BotConfiguration : net/mamoe/mirai/utils/AbstractBotConfiguration { 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 disableAccountSecretes ()V public final fun disableContactCache ()V public final fun enableContactCache ()V public final fun getAutoReconnectOnForceOffline ()Z public final fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1; 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 synthetic fun getFirstReconnectDelayMillis ()J public final fun getHeartbeatPeriodMillis ()J public final fun getHeartbeatStrategy ()Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public final fun getHeartbeatTimeoutMillis ()J public final fun getHighwayUploadCoroutineCount ()I public final fun getLoginCacheEnabled ()Z public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver; public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1; public final fun getParentCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun getProtocol ()Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public final synthetic fun getReconnectPeriodMillis ()J public final fun getReconnectionRetryTimes ()I public final fun getStatHeartbeatPeriodMillis ()J public final synthetic fun inheritCoroutineContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun isConvertLineSeparator ()Z public final fun isShowingVerboseEventLog ()Z public final fun loadDeviceInfoJson (Ljava/lang/String;)V public final fun noBotLog ()V public final fun noNetworkLog ()V public final fun randomDeviceInfo ()V public final fun setAutoReconnectOnForceOffline (Z)V public final fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)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 synthetic fun setFirstReconnectDelayMillis (J)V public final fun setHeartbeatPeriodMillis (J)V public final fun setHeartbeatStrategy (Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy;)V public final fun setHeartbeatTimeoutMillis (J)V public final fun setHighwayUploadCoroutineCount (I)V public final fun setLoginCacheEnabled (Z)V public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V public final fun setParentCoroutineContext (Lkotlin/coroutines/CoroutineContext;)V public final fun setProtocol (Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;)V public final synthetic fun setReconnectPeriodMillis (J)V public final fun setReconnectionRetryTimes (I)V public final fun setShowingVerboseEventLog (Z)V public final fun setStatHeartbeatPeriodMillis (J)V } public final class net/mamoe/mirai/utils/BotConfiguration$Companion { public final fun getDefault ()Lnet/mamoe/mirai/utils/BotConfiguration; } 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 ()J public final fun getSaveIntervalMillis ()J public final fun setFriendListCacheEnabled (Z)V public final fun setGroupMemberListCacheEnabled (Z)V public final synthetic fun setSaveInterval-LRDsOJo (J)V public final fun setSaveIntervalMillis (J)V } public final class net/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy : java/lang/Enum { public static final field NONE Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static final field REGISTER Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static final field STAT_HB Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static fun values ()[Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; } 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; public static final field ANDROID_WATCH Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public static final field IPAD Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public static final field MACOS Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public final fun isNudgeSupported ()Z public final fun isQRLoginSupported ()Z public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public static fun values ()[Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; } public final class net/mamoe/mirai/utils/DeviceInfo { public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Companion; public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B)V public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B[B)V public static final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo; public fun equals (Ljava/lang/Object;)Z public static final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo; public static final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun getAndroidId ()[B public final fun getApn ()[B public final fun getBaseBand ()[B public final fun getBoard ()[B public final fun getBootId ()[B public final fun getBootloader ()[B public final fun getBrand ()[B public final fun getDevice ()[B public final fun getDisplay ()[B public final fun getFingerprint ()[B public final fun getGuid ()[B public final fun getImei ()Ljava/lang/String; public final fun getImsiMd5 ()[B public final fun getIpAddress ()[B public final fun getMacAddress ()[B public final fun getModel ()[B public final fun getOsType ()[B public final fun getProcVersion ()[B public final fun getProduct ()[B public final fun getSimInfo ()[B public final fun getVersion ()Lnet/mamoe/mirai/utils/DeviceInfo$Version; public final fun getWifiBSSID ()[B public final fun getWifiSSID ()[B public fun hashCode ()I public static final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo; public static final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo; public static final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String; } public final class net/mamoe/mirai/utils/DeviceInfo$$serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/utils/DeviceInfo$$serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo; 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/utils/DeviceInfo;)V } public final class net/mamoe/mirai/utils/DeviceInfo$Companion { public final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo; public static synthetic fun from$default (Lnet/mamoe/mirai/utils/DeviceInfo$Companion;Ljava/io/File;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo; public final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/utils/DeviceInfo$Version { public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Version$Companion; public fun <init> ()V public synthetic fun <init> (I[B[B[BILkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> ([B[B[BI)V public synthetic fun <init> ([B[B[BIILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public final fun getCodename ()[B public final fun getIncremental ()[B public final fun getRelease ()[B public final fun getSdk ()I public fun hashCode ()I public static final fun write$Self (Lnet/mamoe/mirai/utils/DeviceInfo$Version;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/utils/DeviceInfo$Version$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/utils/DeviceInfo$Version$$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/utils/DeviceInfo$Version; 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/utils/DeviceInfo$Version;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/utils/DeviceInfo$Version$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/utils/DeviceInfoBuilder { public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfoBuilder$Companion; public fun <init> ()V public final fun androidId (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun androidId ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun apn (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun apn ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun baseBand (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun baseBand ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun board (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun board ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootId (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootId ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootloader (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootloader ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun brand (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun brand ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun build ()Lnet/mamoe/mirai/utils/DeviceInfo; public final fun device (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun device ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun display (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun display ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fingerprint (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fingerprint ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static final fun fromPrototype (Lnet/mamoe/mirai/utils/DeviceInfo;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static final fun fromRandom ()Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static final fun fromRandom (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun imei (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun imsiMd5 (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun imsiMd5 ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun macAddress (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun macAddress ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun model (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun model ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun osType (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun osType ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun procVersion (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun procVersion ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun product (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun product ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun simInfo (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun simInfo ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun version (Lnet/mamoe/mirai/utils/DeviceInfo$Version;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiBSSID (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiBSSID ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiSSID (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiSSID ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; } public final class net/mamoe/mirai/utils/DeviceInfoBuilder$Companion { public final fun fromPrototype (Lnet/mamoe/mirai/utils/DeviceInfo;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fromRandom ()Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fromRandom (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static synthetic fun fromRandom$default (Lnet/mamoe/mirai/utils/DeviceInfoBuilder$Companion;Lkotlin/random/Random;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; } public final class net/mamoe/mirai/utils/DeviceInfoKt { public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B public static final synthetic fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests { public abstract fun getFallback ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest; public abstract fun getPreferSms ()Z public abstract fun getSms ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest { public abstract fun getUrl ()Ljava/lang/String; public abstract fun solved ()Lnet/mamoe/mirai/utils/DeviceVerificationResult; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest { public abstract fun getCountryCode ()Ljava/lang/String; public abstract fun getPhoneNumber ()Ljava/lang/String; public abstract fun requestSms (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun solved (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceVerificationResult; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationResult { } public final class net/mamoe/mirai/utils/DirectoryLogger : net/mamoe/mirai/utils/SimpleLogger { public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/io/File;)V public fun <init> (Ljava/lang/String;Ljava/io/File;J)V public synthetic fun <init> (Ljava/lang/String;Ljava/io/File;JILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io/Closeable { public static final field Companion Lnet/mamoe/mirai/utils/ExternalResource$Companion; public static final field DEFAULT_FORMAT_NAME Ljava/lang/String; public static fun create (Ljava/io/File;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/InputStream;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/RandomAccessFile;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([B)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([BLjava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getClosed ()Lkotlinx/coroutines/Deferred; public abstract fun getFormatName ()Ljava/lang/String; public abstract fun getMd5 ()[B public fun getOrigin ()Ljava/lang/Object; public fun getSha1 ()[B public abstract fun getSize ()J public abstract fun inputStream ()Ljava/io/InputStream; public fun isAutoClose ()Z public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toAutoCloseable ()Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice; public static synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/ExternalResource$Companion { public static final field DEFAULT_FORMAT_NAME Ljava/lang/String; public final fun create (Ljava/io/File;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/InputStream;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/RandomAccessFile;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create ([B)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create ([BLjava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/RandomAccessFile;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;[BLjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public final synthetic fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice; public final synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/ExternalResourceKt { public static final fun runAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun useAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/utils/FileCacheStrategy { public static final field Companion Lnet/mamoe/mirai/utils/FileCacheStrategy$Companion; public static fun getPlatformDefault ()Lnet/mamoe/mirai/utils/FileCacheStrategy; public fun newCache (Ljava/io/InputStream;)Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun newCache (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun newCache$default (Lnet/mamoe/mirai/utils/FileCacheStrategy;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; } public final class net/mamoe/mirai/utils/FileCacheStrategy$Companion { public final fun getPlatformDefault ()Lnet/mamoe/mirai/utils/FileCacheStrategy; } public final class net/mamoe/mirai/utils/FileCacheStrategy$MemoryCache : net/mamoe/mirai/utils/FileCacheStrategy { public static final field INSTANCE Lnet/mamoe/mirai/utils/FileCacheStrategy$MemoryCache; public fun newCache (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; } public final class net/mamoe/mirai/utils/FileCacheStrategy$TempCache : net/mamoe/mirai/utils/FileCacheStrategy { public fun <init> ()V public fun <init> (Ljava/io/File;)V public synthetic fun <init> (Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getDirectory ()Ljava/io/File; public fun newCache (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; } public abstract interface annotation class net/mamoe/mirai/utils/JavaFriendlyAPI : java/lang/annotation/Annotation { } public final class net/mamoe/mirai/utils/LoggerAdapters { public static final field INSTANCE Lnet/mamoe/mirai/utils/LoggerAdapters; public static final fun asMiraiLogger (Ljava/util/logging/Logger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun asMiraiLogger (Lorg/apache/logging/log4j/Logger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun asMiraiLogger (Lorg/apache/logging/log4j/Logger;Lorg/apache/logging/log4j/Marker;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun asMiraiLogger (Lorg/slf4j/Logger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun useLog4j2 ()V } public abstract class net/mamoe/mirai/utils/LoginSolver { public static final field Companion Lnet/mamoe/mirai/utils/LoginSolver$Companion; public static final field Default Lnet/mamoe/mirai/utils/LoginSolver; public fun <init> ()V public fun createQRCodeLoginListener (Lnet/mamoe/mirai/Bot;)Lnet/mamoe/mirai/auth/QRCodeLoginListener; public fun isSliderCaptchaSupported ()Z public fun onSolveDeviceVerification (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/utils/DeviceVerificationRequests;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun onSolvePicCaptcha (Lnet/mamoe/mirai/Bot;[BLkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun onSolveSliderCaptcha (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onSolveUnsafeDeviceLoginVerify (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/LoginSolver$Companion { public final synthetic fun getDefault ()Lnet/mamoe/mirai/utils/LoginSolver; } public abstract interface annotation class net/mamoe/mirai/utils/MiraiExperimentalApi : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public abstract interface annotation class net/mamoe/mirai/utils/MiraiInternalApi : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public abstract interface annotation class net/mamoe/mirai/utils/MiraiInternalFile : java/lang/annotation/Annotation { } public abstract interface class net/mamoe/mirai/utils/MiraiLogger { public static final field Companion Lnet/mamoe/mirai/utils/MiraiLogger$Companion; public fun call (Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;Ljava/lang/String;Ljava/lang/Throwable;)V public static synthetic fun call$default (Lnet/mamoe/mirai/utils/MiraiLogger;Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)V public static synthetic fun create (Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun debug (Ljava/lang/String;)V public abstract fun debug (Ljava/lang/String;Ljava/lang/Throwable;)V public fun debug (Ljava/lang/Throwable;)V public abstract fun error (Ljava/lang/String;)V public abstract fun error (Ljava/lang/String;Ljava/lang/Throwable;)V public fun error (Ljava/lang/Throwable;)V public synthetic fun getFollower ()Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun getIdentity ()Ljava/lang/String; public abstract fun info (Ljava/lang/String;)V public abstract fun info (Ljava/lang/String;Ljava/lang/Throwable;)V public fun info (Ljava/lang/Throwable;)V public fun isDebugEnabled ()Z public abstract fun isEnabled ()Z public fun isErrorEnabled ()Z public fun isInfoEnabled ()Z public fun isVerboseEnabled ()Z public fun isWarningEnabled ()Z public synthetic fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static synthetic fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V public synthetic fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V public abstract fun verbose (Ljava/lang/String;)V public abstract fun verbose (Ljava/lang/String;Ljava/lang/Throwable;)V public fun verbose (Ljava/lang/Throwable;)V public abstract fun warning (Ljava/lang/String;)V public abstract fun warning (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning (Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/utils/MiraiLogger$Companion { public final synthetic fun create (Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public final synthetic fun getTopLevel ()Lnet/mamoe/mirai/utils/MiraiLogger; public final synthetic fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V } public abstract interface class net/mamoe/mirai/utils/MiraiLogger$Factory { public static final field INSTANCE Lnet/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE; public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Ljava/lang/Class;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Lkotlin/reflect/KClass;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger; } public final class net/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE : net/mamoe/mirai/utils/MiraiLogger$Factory { public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; } public abstract class net/mamoe/mirai/utils/MiraiLoggerPlatformBase : net/mamoe/mirai/utils/MiraiLogger { public fun <init> ()V public final fun debug (Ljava/lang/String;)V public final fun debug (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun debug0 (Ljava/lang/String;)V protected abstract fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun error (Ljava/lang/String;)V public final fun error (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun error0 (Ljava/lang/String;)V protected abstract fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun info (Ljava/lang/String;)V public final fun info (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun info0 (Ljava/lang/String;)V protected abstract fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun isEnabled ()Z public final fun verbose (Ljava/lang/String;)V public final fun verbose (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun verbose0 (Ljava/lang/String;)V protected abstract fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun warning (Ljava/lang/String;)V public final fun warning (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun warning0 (Ljava/lang/String;)V protected abstract fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/utils/MiraiLoggerWithSwitch : net/mamoe/mirai/utils/MiraiLoggerPlatformBase { public fun debug0 (Ljava/lang/String;)V public fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun disable ()V public final fun enable ()V public fun error0 (Ljava/lang/String;)V public fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun getIdentity ()Ljava/lang/String; public fun info0 (Ljava/lang/String;)V public fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun isEnabled ()Z public fun verbose0 (Ljava/lang/String;)V public fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning0 (Ljava/lang/String;)V public fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public abstract interface annotation class net/mamoe/mirai/utils/NotStableForInheritance : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public final class net/mamoe/mirai/utils/OverFileSizeMaxException : java/lang/IllegalStateException { public fun <init> ()V } public abstract interface class net/mamoe/mirai/utils/ProgressionCallback { public static final field Companion Lnet/mamoe/mirai/utils/ProgressionCallback$Companion; public static fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback; public fun onBegin (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;)V public fun onFailure (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Throwable;)V public fun onFinished (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V public fun onProgression (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V public fun onSuccess (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V } public final class net/mamoe/mirai/utils/ProgressionCallback$Companion { public final fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback; public static synthetic fun asProgressionCallback$default (Lnet/mamoe/mirai/utils/ProgressionCallback$Companion;Lkotlinx/coroutines/channels/SendChannel;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ProgressionCallback; } public abstract interface class net/mamoe/mirai/utils/RemoteFile { public static final field Companion Lnet/mamoe/mirai/utils/RemoteFile$Companion; public static final field ROOT_PATH Ljava/lang/String; public fun delete ()Z public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun exists ()Z public abstract fun exists (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported; public fun getDownloadInfo ()Lnet/mamoe/mirai/utils/RemoteFile$DownloadInfo; public abstract fun getDownloadInfo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getId ()Ljava/lang/String; public fun getInfo ()Lnet/mamoe/mirai/utils/RemoteFile$FileInfo; public abstract fun getInfo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getName ()Ljava/lang/String; public abstract fun getParent ()Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun getPath ()Ljava/lang/String; public fun isDirectory ()Z public fun isDirectory (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun isDirectory$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun isFile ()Z public abstract fun isFile (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun length ()J public abstract fun length (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun listFiles ()Lkotlinx/coroutines/flow/Flow; public abstract fun listFiles (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun listFilesCollection ()Ljava/util/List; public fun listFilesCollection (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun listFilesCollection$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun listFilesIterator (Z)Ljava/util/Iterator; public abstract fun listFilesIterator (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun mkdir ()Z public abstract fun mkdir (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun moveTo (Ljava/lang/String;)Z public fun moveTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun moveTo (Lnet/mamoe/mirai/utils/RemoteFile;)Z public abstract fun moveTo (Lnet/mamoe/mirai/utils/RemoteFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun moveTo$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun renameTo (Ljava/lang/String;)Z public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolve (Ljava/lang/String;)Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun resolve (Lnet/mamoe/mirai/utils/RemoteFile;)Lnet/mamoe/mirai/utils/RemoteFile; public fun resolveById (Ljava/lang/String;)Lnet/mamoe/mirai/utils/RemoteFile; public fun resolveById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveById (Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun resolveById (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun resolveById$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/RemoteFile; public static synthetic fun resolveById$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun resolveById$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolveSibling (Ljava/lang/String;)Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun resolveSibling (Lnet/mamoe/mirai/utils/RemoteFile;)Lnet/mamoe/mirai/utils/RemoteFile; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toMessage ()Lnet/mamoe/mirai/message/data/FileMessage; public abstract fun toMessage (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun toString ()Ljava/lang/String; public fun upload (Ljava/io/File;)Lnet/mamoe/mirai/message/data/FileMessage; public fun upload (Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun upload (Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public fun upload (Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun upload (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/FileMessage; public fun upload (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun upload (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public abstract fun upload (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun upload$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun upload$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun upload$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/RemoteFile$Companion { public static final field ROOT_PATH Ljava/lang/String; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/RemoteFile$DownloadInfo { public final fun getFilename ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; public final fun getMd5 ()[B public final fun getPath ()Ljava/lang/String; public final fun getSha1 ()[B public final fun getUrl ()Ljava/lang/String; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/utils/RemoteFile$FileInfo { public final fun getDownloadTimes ()I public final fun getId ()Ljava/lang/String; public final fun getLastModifyTime ()J public final fun getLength ()J public final fun getMd5 ()[B public final fun getName ()Ljava/lang/String; public final fun getPath ()Ljava/lang/String; public final fun getSha1 ()[B public final fun getUploadTime ()J public final fun getUploaderId ()J public final fun resolveToFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/utils/RemoteFile$ProgressionCallback { public static final field Companion Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback$Companion; public static fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback; public fun onBegin (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;)V public fun onFailure (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Throwable;)V public fun onProgression (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;J)V public fun onSuccess (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;)V } public final class net/mamoe/mirai/utils/RemoteFile$ProgressionCallback$Companion { public final fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback; public static synthetic fun asProgressionCallback$default (Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback$Companion;Lkotlinx/coroutines/channels/SendChannel;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback; } public final class net/mamoe/mirai/utils/SilentLogger : net/mamoe/mirai/utils/PlatformLogger { public static final field INSTANCE Lnet/mamoe/mirai/utils/SilentLogger; public fun debug0 (Ljava/lang/String;)V public fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun error0 (Ljava/lang/String;)V public fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun getIdentity ()Ljava/lang/String; public fun info0 (Ljava/lang/String;)V public fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun isDebugEnabled ()Z public fun isEnabled ()Z public fun isErrorEnabled ()Z public fun isInfoEnabled ()Z public fun isVerboseEnabled ()Z public fun isWarningEnabled ()Z public fun verbose0 (Ljava/lang/String;)V public fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning0 (Ljava/lang/String;)V public fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public class net/mamoe/mirai/utils/SimpleLogger : net/mamoe/mirai/utils/MiraiLoggerPlatformBase { public static final field Companion Lnet/mamoe/mirai/utils/SimpleLogger$Companion; public fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)V public fun debug0 (Ljava/lang/String;)V public fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun error0 (Ljava/lang/String;)V public fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun getIdentity ()Ljava/lang/String; protected fun getLogger ()Lkotlin/jvm/functions/Function3; public fun info0 (Ljava/lang/String;)V public fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun verbose0 (Ljava/lang/String;)V public fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning0 (Ljava/lang/String;)V public fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/utils/SimpleLogger$Companion { public final fun invoke (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/utils/SimpleLogger; public final fun invoke (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/utils/SimpleLogger; public final fun invoke (Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/utils/SimpleLogger; } public final class net/mamoe/mirai/utils/SimpleLogger$LogPriority : java/lang/Enum { public static final field DEBUG Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field ERROR Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field INFO Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field VERBOSE Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field WARNING Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public final fun getCorrespondingFunction ()Lkotlin/jvm/functions/Function3; public final fun getNameAligned ()Ljava/lang/String; public final fun getSimpleName ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static fun values ()[Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; } public final class net/mamoe/mirai/utils/SingleFileLogger : net/mamoe/mirai/utils/MiraiLogger { public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/io/File;)V public synthetic fun <init> (Ljava/lang/String;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun call (Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;Ljava/lang/String;Ljava/lang/Throwable;)V public fun debug (Ljava/lang/String;)V public fun debug (Ljava/lang/String;Ljava/lang/Throwable;)V public fun debug (Ljava/lang/Throwable;)V public fun error (Ljava/lang/String;)V public fun error (Ljava/lang/String;Ljava/lang/Throwable;)V public fun error (Ljava/lang/Throwable;)V public fun getFollower ()Lnet/mamoe/mirai/utils/MiraiLogger; public fun getIdentity ()Ljava/lang/String; public fun info (Ljava/lang/String;)V public fun info (Ljava/lang/String;Ljava/lang/Throwable;)V public fun info (Ljava/lang/Throwable;)V public fun isDebugEnabled ()Z public fun isEnabled ()Z public fun isErrorEnabled ()Z public fun isInfoEnabled ()Z public fun isVerboseEnabled ()Z public fun isWarningEnabled ()Z public synthetic fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V public fun verbose (Ljava/lang/String;)V public fun verbose (Ljava/lang/String;Ljava/lang/Throwable;)V public fun verbose (Ljava/lang/Throwable;)V public fun warning (Ljava/lang/String;)V public fun warning (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning (Ljava/lang/Throwable;)V } public abstract interface class net/mamoe/mirai/utils/Streamable { public abstract fun asFlow ()Lkotlinx/coroutines/flow/Flow; public fun asStream ()Ljava/util/stream/Stream; public fun toList ()Ljava/util/List; public fun toList (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun toList$suspendImpl (Lnet/mamoe/mirai/utils/Streamable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/Utils { public static final synthetic fun BotConfiguration (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/utils/BotConfiguration; public static final fun debug (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun debug (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun error (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun error (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun info (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun info (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun verbose (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun verbose (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun warning (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun warning (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun withSwitch (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLoggerWithSwitch; public static final fun withSwitch (Lnet/mamoe/mirai/utils/MiraiLogger;Z)Lnet/mamoe/mirai/utils/MiraiLoggerWithSwitch; public static synthetic fun withSwitch$default (Lnet/mamoe/mirai/utils/MiraiLogger;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLoggerWithSwitch; } ================================================ FILE: mirai-core-api/compatibility-validation/jvm/api/jvm.api ================================================ public abstract interface class net/mamoe/mirai/Bot : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/ContactOrBot, net/mamoe/mirai/contact/UserOrBot { public static final field Companion Lnet/mamoe/mirai/Bot$Companion; public fun close ()V public abstract fun close (Ljava/lang/Throwable;)V public static synthetic fun close$default (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)V public fun closeAndJoin (Ljava/lang/Throwable;)V public fun closeAndJoin (Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun closeAndJoin$default (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)V public static synthetic fun closeAndJoin$default (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun closeAndJoin$suspendImpl (Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun findInstance (J)Lnet/mamoe/mirai/Bot; public abstract fun getAsFriend ()Lnet/mamoe/mirai/contact/Friend; public abstract fun getAsStranger ()Lnet/mamoe/mirai/contact/Stranger; public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getConfiguration ()Lnet/mamoe/mirai/utils/BotConfiguration; public abstract fun getEventChannel ()Lnet/mamoe/mirai/event/EventChannel; public fun getFriend (J)Lnet/mamoe/mirai/contact/Friend; public abstract fun getFriendGroups ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroups; public fun getFriendOrFail (J)Lnet/mamoe/mirai/contact/Friend; public abstract fun getFriends ()Lnet/mamoe/mirai/contact/ContactList; public fun getGroup (J)Lnet/mamoe/mirai/contact/Group; public fun getGroupOrFail (J)Lnet/mamoe/mirai/contact/Group; public abstract fun getGroups ()Lnet/mamoe/mirai/contact/ContactList; public static fun getInstance (J)Lnet/mamoe/mirai/Bot; public static fun getInstanceOrNull (J)Lnet/mamoe/mirai/Bot; public static fun getInstances ()Ljava/util/List; public static fun getInstancesSequence ()Lkotlin/sequences/Sequence; public abstract fun getLogger ()Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun getOtherClients ()Lnet/mamoe/mirai/contact/ContactList; public fun getStranger (J)Lnet/mamoe/mirai/contact/Stranger; public fun getStrangerOrFail (J)Lnet/mamoe/mirai/contact/Stranger; public abstract fun getStrangers ()Lnet/mamoe/mirai/contact/ContactList; public abstract fun isOnline ()Z public fun join ()V public fun join (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun join$suspendImpl (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun login ()V public abstract fun login (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun nudge ()Lnet/mamoe/mirai/message/action/BotNudge; public synthetic fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; } public final class net/mamoe/mirai/Bot$Companion { public final fun findInstance (J)Lnet/mamoe/mirai/Bot; public final fun getInstance (J)Lnet/mamoe/mirai/Bot; public final fun getInstanceOrNull (J)Lnet/mamoe/mirai/Bot; public final fun getInstances ()Ljava/util/List; public final fun getInstancesSequence ()Lkotlin/sequences/Sequence; } public abstract interface class net/mamoe/mirai/BotFactory { public static final field INSTANCE Lnet/mamoe/mirai/BotFactory$INSTANCE; public fun newBot (JLjava/lang/String;)Lnet/mamoe/mirai/Bot; public fun newBot (JLjava/lang/String;Lnet/mamoe/mirai/BotFactory$BotConfigurationLambda;)Lnet/mamoe/mirai/Bot; public abstract fun newBot (JLjava/lang/String;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;)Lnet/mamoe/mirai/Bot; public fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;Lnet/mamoe/mirai/BotFactory$BotConfigurationLambda;)Lnet/mamoe/mirai/Bot; public abstract fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public fun newBot (J[B)Lnet/mamoe/mirai/Bot; public fun newBot (J[BLnet/mamoe/mirai/BotFactory$BotConfigurationLambda;)Lnet/mamoe/mirai/Bot; public abstract fun newBot (J[BLnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; } public abstract interface class net/mamoe/mirai/BotFactory$BotConfigurationLambda { public abstract fun invoke (Lnet/mamoe/mirai/utils/BotConfiguration;)V } public final class net/mamoe/mirai/BotFactory$INSTANCE : net/mamoe/mirai/BotFactory { public final synthetic fun newBot (JLjava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/Bot; public fun newBot (JLjava/lang/String;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public fun newBot (JLnet/mamoe/mirai/auth/BotAuthorization;Lnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; public final synthetic fun newBot (J[BLkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/Bot; public fun newBot (J[BLnet/mamoe/mirai/utils/BotConfiguration;)Lnet/mamoe/mirai/Bot; } public final class net/mamoe/mirai/BotKt { public static final synthetic fun alsoLogin (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun containsFriend (Lnet/mamoe/mirai/Bot;J)Z public static final synthetic fun containsGroup (Lnet/mamoe/mirai/Bot;J)Z public static final synthetic fun getSupervisorJob (Lnet/mamoe/mirai/Bot;)Lkotlinx/coroutines/CompletableJob; } public abstract interface class net/mamoe/mirai/IMirai : net/mamoe/mirai/LowLevelApiAccessor { public fun acceptInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)V public abstract fun acceptInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun acceptMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;)V public abstract fun acceptMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun acceptNewFriendRequest (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;)V public abstract fun acceptNewFriendRequest (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun broadcastEvent (Lnet/mamoe/mirai/event/Event;)V public abstract fun broadcastEvent (Lnet/mamoe/mirai/event/Event;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun calculateGroupCodeByGroupUin (J)J 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 createFileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; public fun createImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public abstract fun createUnsupportedMessage ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; 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 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;ZILjava/lang/Object;)Ljava/util/List; 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 getUin (Lnet/mamoe/mirai/contact/ContactOrBot;)J public fun ignoreInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)V public abstract fun ignoreInvitedJoinGroupRequest (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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;ZILjava/lang/Object;)V 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 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 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; public fun rejectMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;)V public abstract fun rejectMemberJoinRequest (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun rejectMemberJoinRequest$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;ILjava/lang/Object;)V public static synthetic fun rejectMemberJoinRequest$default (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 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;ZILjava/lang/Object;)V 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 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 interface annotation class net/mamoe/mirai/LowLevelApi : java/lang/annotation/Annotation { } public abstract interface class net/mamoe/mirai/LowLevelApiAccessor { public fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJ)Ljava/lang/String; public abstract fun getGroupVoiceDownloadUrl (Lnet/mamoe/mirai/Bot;[BJJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupList (Lnet/mamoe/mirai/Bot;)Lkotlin/sequences/Sequence; public abstract fun getRawGroupList (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getRawGroupMemberList (Lnet/mamoe/mirai/Bot;JJJ)Lkotlin/sequences/Sequence; public abstract fun getRawGroupMemberList (Lnet/mamoe/mirai/Bot;JJJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun muteAnonymousMember (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;JI)V public abstract fun muteAnonymousMember (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;JILkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun newFriend (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/data/FriendInfo;)Lnet/mamoe/mirai/contact/Friend; public abstract fun newStranger (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/data/StrangerInfo;)Lnet/mamoe/mirai/contact/Stranger; public fun recallFriendMessageRaw (Lnet/mamoe/mirai/Bot;J[I[II)Z public abstract fun recallFriendMessageRaw (Lnet/mamoe/mirai/Bot;J[I[IILkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun recallGroupMessageRaw (Lnet/mamoe/mirai/Bot;J[I[I)Z 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 fun solveBotInvitedJoinGroupRequestEvent (Lnet/mamoe/mirai/Bot;JJJZ)V public abstract fun solveBotInvitedJoinGroupRequestEvent (Lnet/mamoe/mirai/Bot;JJJZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun solveMemberJoinRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;)V public abstract fun solveMemberJoinRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun solveMemberJoinRequestEvent$default (Lnet/mamoe/mirai/LowLevelApiAccessor;Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;ILjava/lang/Object;)V public static synthetic fun solveMemberJoinRequestEvent$default (Lnet/mamoe/mirai/LowLevelApiAccessor;Lnet/mamoe/mirai/Bot;JJLjava/lang/String;JLjava/lang/Boolean;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun solveNewFriendRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;ZZ)V public abstract fun solveNewFriendRequestEvent (Lnet/mamoe/mirai/Bot;JJLjava/lang/String;ZZLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/Mirai { public static final fun getInstance ()Lnet/mamoe/mirai/IMirai; public static final synthetic fun recallMessage (Lnet/mamoe/mirai/IMirai;Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/_MiraiInstance { public static final field INSTANCE Lnet/mamoe/mirai/_MiraiInstance; public static final fun get ()Lnet/mamoe/mirai/IMirai; public static final fun set (Lnet/mamoe/mirai/IMirai;)V } public abstract class net/mamoe/mirai/auth/AuthReason { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$FastLoginError : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$ForceOffline : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$FreshLogin : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$MsfOffline : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$NetworkError : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/auth/AuthReason$Unknown : net/mamoe/mirai/auth/AuthReason { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getCause ()Ljava/lang/Throwable; public fun getMessage ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/auth/BotAuthInfo { public abstract fun getConfiguration ()Lnet/mamoe/mirai/utils/BotConfiguration; public abstract fun getDeviceInfo ()Lnet/mamoe/mirai/utils/DeviceInfo; public abstract fun getId ()J public abstract fun getReason ()Lnet/mamoe/mirai/auth/AuthReason; public abstract fun isFirstLogin ()Z } public abstract interface class net/mamoe/mirai/auth/BotAuthResult { } public abstract interface class net/mamoe/mirai/auth/BotAuthSession { public abstract fun authByPassword (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun authByPassword ([BLkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun authByQRCode (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/auth/BotAuthorization { public static final field Companion Lnet/mamoe/mirai/auth/BotAuthorization$Companion; public abstract fun authorize (Lnet/mamoe/mirai/auth/BotAuthSession;Lnet/mamoe/mirai/auth/BotAuthInfo;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun byPassword (Ljava/lang/String;)Lnet/mamoe/mirai/auth/BotAuthorization; public static fun byPassword ([B)Lnet/mamoe/mirai/auth/BotAuthorization; public static fun byQRCode ()Lnet/mamoe/mirai/auth/BotAuthorization; public fun calculateSecretsKey (Lnet/mamoe/mirai/auth/BotAuthInfo;)[B } public final class net/mamoe/mirai/auth/BotAuthorization$Companion { public final fun byPassword (Ljava/lang/String;)Lnet/mamoe/mirai/auth/BotAuthorization; public final fun byPassword ([B)Lnet/mamoe/mirai/auth/BotAuthorization; public final fun byQRCode ()Lnet/mamoe/mirai/auth/BotAuthorization; public final fun invoke (Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/auth/BotAuthorization; } public abstract interface class net/mamoe/mirai/auth/QRCodeLoginListener { public fun getQrCodeEcLevel ()I public fun getQrCodeMargin ()I public fun getQrCodeSize ()I public fun getQrCodeStateUpdateInterval ()J public fun onCompleted ()V public abstract fun onFetchQRCode (Lnet/mamoe/mirai/Bot;[B)V public fun onIntervalLoop ()V public abstract fun onStateChanged (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/auth/QRCodeLoginListener$State;)V } public final class net/mamoe/mirai/auth/QRCodeLoginListener$State : java/lang/Enum { public static final field CANCELLED Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field CONFIRMED Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field DEFAULT Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field TIMEOUT Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field WAITING_FOR_CONFIRM Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static final field WAITING_FOR_SCAN Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; public static fun values ()[Lnet/mamoe/mirai/auth/QRCodeLoginListener$State; } public abstract interface class net/mamoe/mirai/contact/AnonymousMember : net/mamoe/mirai/contact/Member { public abstract fun getAnonymousId ()Ljava/lang/String; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/AnonymousMember;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/AnonymousMember;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; public static synthetic fun uploadImage$suspendImpl (Lnet/mamoe/mirai/contact/AnonymousMember;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/AudioSupported : net/mamoe/mirai/contact/Contact { public fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/OfflineAudio; public abstract fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/AvatarSpec : java/lang/Enum, java/lang/Comparable { public static final field LARGE Lnet/mamoe/mirai/contact/AvatarSpec; public static final field LARGEST Lnet/mamoe/mirai/contact/AvatarSpec; public static final field MEDIUM Lnet/mamoe/mirai/contact/AvatarSpec; public static final field ORIGINAL Lnet/mamoe/mirai/contact/AvatarSpec; public static final field SMALL Lnet/mamoe/mirai/contact/AvatarSpec; public static final field SMALLEST Lnet/mamoe/mirai/contact/AvatarSpec; public final fun getSize ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/AvatarSpec; public static fun values ()[Lnet/mamoe/mirai/contact/AvatarSpec; } public final class net/mamoe/mirai/contact/BotIsBeingMutedException : net/mamoe/mirai/contact/SendMessageFailedException { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Group;)V public fun getMessage ()Ljava/lang/String; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Group; } public final class net/mamoe/mirai/contact/ClientKind : java/lang/Enum { public static final field ANDROID_PAD Lnet/mamoe/mirai/contact/ClientKind; public static final field AOL_CHAOJIHUIYUAN Lnet/mamoe/mirai/contact/ClientKind; public static final field AOL_HUIYUAN Lnet/mamoe/mirai/contact/ClientKind; public static final field AOL_SQQ Lnet/mamoe/mirai/contact/ClientKind; public static final field CAR Lnet/mamoe/mirai/contact/ClientKind; public static final field Companion Lnet/mamoe/mirai/contact/ClientKind$Companion; public static final field HRTX_IPHONE Lnet/mamoe/mirai/contact/ClientKind; public static final field HRTX_PC Lnet/mamoe/mirai/contact/ClientKind; public static final field MC_3G Lnet/mamoe/mirai/contact/ClientKind; public static final field MISRO_MSG Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_ANDROID Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_ANDROID_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_HD Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_HD_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_IPAD Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_IPAD_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_IPHONE Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_OTHER Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_PC_QQ Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_PC_TIM Lnet/mamoe/mirai/contact/ClientKind; public static final field MOBILE_WINPHONE_NEW Lnet/mamoe/mirai/contact/ClientKind; public static final field QQ_FORELDER Lnet/mamoe/mirai/contact/ClientKind; public static final field QQ_SERVICE Lnet/mamoe/mirai/contact/ClientKind; public static final field TV_QQ Lnet/mamoe/mirai/contact/ClientKind; public static final field WIN8 Lnet/mamoe/mirai/contact/ClientKind; public static final field WINPHONE Lnet/mamoe/mirai/contact/ClientKind; public final fun getId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/ClientKind; public static fun values ()[Lnet/mamoe/mirai/contact/ClientKind; } public final class net/mamoe/mirai/contact/ClientKind$Companion { } public abstract interface class net/mamoe/mirai/contact/Contact : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/ContactOrBot { public static final field Companion Lnet/mamoe/mirai/contact/Contact$Companion; public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getId ()J public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image; public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ShortVideo; public abstract fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ShortVideo; public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/Contact$Companion { public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/File;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadImage$default (Lnet/mamoe/mirai/contact/Contact$Companion;Lnet/mamoe/mirai/contact/Contact;Ljava/io/InputStream;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/ContactKt { public static final synthetic fun recallMessage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun recallMessage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/ContactList : java/util/Collection, kotlin/jvm/internal/markers/KMappedMarker { public final field delegate Ljava/util/Collection; public synthetic fun add (Ljava/lang/Object;)Z public fun add (Lnet/mamoe/mirai/contact/Contact;)Z public fun addAll (Ljava/util/Collection;)Z public fun clear ()V public final fun contains (J)Z public final fun contains (Ljava/lang/Object;)Z public fun contains (Lnet/mamoe/mirai/contact/Contact;)Z public fun containsAll (Ljava/util/Collection;)Z public fun equals (Ljava/lang/Object;)Z public final fun get (J)Lnet/mamoe/mirai/contact/Contact; public final fun getOrFail (J)Lnet/mamoe/mirai/contact/Contact; public fun getSize ()I public fun hashCode ()I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public final fun remove (J)Z public fun remove (Ljava/lang/Object;)Z public fun removeAll (Ljava/util/Collection;)Z public fun removeIf (Ljava/util/function/Predicate;)Z public fun retainAll (Ljava/util/Collection;)Z public final fun size ()I public fun toArray ()[Ljava/lang/Object; public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/ContactOrBot : kotlinx/coroutines/CoroutineScope { public fun getAvatarUrl ()Ljava/lang/String; public fun getAvatarUrl (Lnet/mamoe/mirai/contact/AvatarSpec;)Ljava/lang/String; public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getId ()J } public final class net/mamoe/mirai/contact/ExceptionsKt { public static final fun getBotMuteRemaining (Lnet/mamoe/mirai/contact/BotIsBeingMutedException;)I } public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamoe/mirai/contact/Contact { public abstract fun getFiles ()Lnet/mamoe/mirai/contact/file/RemoteFiles; public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile; } public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User, net/mamoe/mirai/contact/roaming/RoamingSupported { public fun delete ()V public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getFriendGroup ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public abstract fun getRemark ()Ljava/lang/String; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun setRemark (Ljava/lang/String;)V } public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/FileSupported, net/mamoe/mirai/contact/roaming/RoamingSupported { public static final field Companion Lnet/mamoe/mirai/contact/Group$Companion; public fun avatarUrl (Lnet/mamoe/mirai/contact/AvatarSpec;)Ljava/lang/String; public abstract fun contains (J)Z public fun contains (Lnet/mamoe/mirai/contact/NormalMember;)Z public abstract fun get (J)Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getActive ()Lnet/mamoe/mirai/contact/active/GroupActive; public abstract fun getAnnouncements ()Lnet/mamoe/mirai/contact/announcement/Announcements; public fun getAvatarUrl ()Ljava/lang/String; public synthetic fun getAvatarUrl (Lnet/mamoe/mirai/contact/AvatarSpec;)Ljava/lang/String; public abstract fun getBotAsMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun getBotMuteRemaining ()I public fun getBotPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public abstract fun getEssences ()Lnet/mamoe/mirai/contact/essence/Essences; public abstract fun getId ()J public abstract fun getMembers ()Lnet/mamoe/mirai/contact/ContactList; public abstract fun getName ()Ljava/lang/String; public fun getOrFail (J)Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getOwner ()Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getSettings ()Lnet/mamoe/mirai/contact/GroupSettings; public fun quit ()Z public abstract fun quit (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;)Z public static fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 synthetic fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Voice; public abstract synthetic fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/Group$Companion { public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;)Z public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/GroupKt { public static final synthetic fun getMember (Lnet/mamoe/mirai/contact/Group;J)Lnet/mamoe/mirai/contact/NormalMember; public static final synthetic fun getMemberOrFail (Lnet/mamoe/mirai/contact/Group;J)Lnet/mamoe/mirai/contact/NormalMember; public static final fun isBotMuted (Lnet/mamoe/mirai/contact/Group;)Z } public abstract interface class net/mamoe/mirai/contact/GroupSettings { public abstract synthetic fun getEntranceAnnouncement ()Ljava/lang/String; public abstract fun isAllowMemberInvite ()Z public abstract fun isAnonymousChatEnabled ()Z public abstract fun isAutoApproveEnabled ()Z public abstract fun isMuteAll ()Z public abstract fun setAllowMemberInvite (Z)V public abstract fun setAnonymousChatEnabled (Z)V public abstract synthetic fun setEntranceAnnouncement (Ljava/lang/String;)V public abstract fun setMuteAll (Z)V } public abstract interface class net/mamoe/mirai/contact/Member : net/mamoe/mirai/contact/User { public abstract fun getActive ()Lnet/mamoe/mirai/contact/active/MemberActive; public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; public abstract fun getNameCard ()Ljava/lang/String; public abstract fun getPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public fun getRankTitle ()Ljava/lang/String; public abstract fun getSpecialTitle ()Ljava/lang/String; public fun getTemperatureTitle ()Ljava/lang/String; 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 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 final class net/mamoe/mirai/contact/MemberKt { public static final fun asFriend (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Friend; public static final fun asFriendOrNull (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Friend; public static final fun asStranger (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Stranger; public static final fun asStrangerOrNull (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/contact/Stranger; public static final fun getNameCardOrNick (Lnet/mamoe/mirai/contact/Member;)Ljava/lang/String; public static final fun isFriend (Lnet/mamoe/mirai/contact/Member;)Z public static final fun isStranger (Lnet/mamoe/mirai/contact/Member;)Z } public final class net/mamoe/mirai/contact/MemberPermission : java/lang/Enum, java/lang/Comparable { public static final field ADMINISTRATOR Lnet/mamoe/mirai/contact/MemberPermission; public static final field MEMBER Lnet/mamoe/mirai/contact/MemberPermission; public static final field OWNER Lnet/mamoe/mirai/contact/MemberPermission; public final fun getLevel ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/MemberPermission; public static fun values ()[Lnet/mamoe/mirai/contact/MemberPermission; } public final class net/mamoe/mirai/contact/MemberPermissionKt { public static final fun checkBotPermission (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lkotlin/jvm/functions/Function0;)V public static synthetic fun checkBotPermission$default (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V public static final fun isAdministrator (Lnet/mamoe/mirai/contact/Member;)Z public static final fun isOperator (Lnet/mamoe/mirai/contact/Member;)Z public static final fun isOwner (Lnet/mamoe/mirai/contact/Member;)Z } public final class net/mamoe/mirai/contact/MessageTooLargeException : net/mamoe/mirai/contact/SendMessageFailedException { public fun <init> (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;)V public fun getMessage ()Ljava/lang/String; public final fun getMessageAfterEvent ()Lnet/mamoe/mirai/message/data/Message; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public abstract interface class net/mamoe/mirai/contact/NormalMember : net/mamoe/mirai/contact/Member { public abstract fun getJoinTimestamp ()I public abstract fun getLastSpeakTimestamp ()I public abstract fun getMuteTimeRemaining ()I public abstract fun getNameCard ()Ljava/lang/String; public abstract fun getSpecialTitle ()Ljava/lang/String; public fun isMuted ()Z public fun kick (Ljava/lang/String;)V public fun kick (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun kick (Ljava/lang/String;Z)V public abstract fun kick (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun kick$suspendImpl (Lnet/mamoe/mirai/contact/NormalMember;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun modifyAdmin (Z)V public abstract fun modifyAdmin (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/NormalMember;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun setNameCard (Ljava/lang/String;)V public abstract fun setSpecialTitle (Ljava/lang/String;)V public fun unmute ()V public abstract fun unmute (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/NormalMemberKt { public static final fun getNameCardOrNick (Lnet/mamoe/mirai/contact/User;)Ljava/lang/String; public static final fun getNameCardOrNick (Lnet/mamoe/mirai/contact/UserOrBot;)Ljava/lang/String; public static final fun isMuted (Lnet/mamoe/mirai/contact/NormalMember;)Z public static final fun mute-8Mi8wO0 (Lnet/mamoe/mirai/contact/NormalMember;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun mute-fcu0wV4 (Lnet/mamoe/mirai/contact/NormalMember;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/OtherClient : net/mamoe/mirai/contact/Contact { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public fun getId ()J public abstract fun getInfo ()Lnet/mamoe/mirai/contact/OtherClientInfo; public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/OtherClient;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; public static synthetic fun uploadImage$suspendImpl (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/OtherClientInfo { public final fun component1 ()I public final fun component2 ()Lnet/mamoe/mirai/contact/Platform; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun copy (ILnet/mamoe/mirai/contact/Platform;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/contact/OtherClientInfo; public static synthetic fun copy$default (Lnet/mamoe/mirai/contact/OtherClientInfo;ILnet/mamoe/mirai/contact/Platform;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/OtherClientInfo; public fun equals (Ljava/lang/Object;)Z public final fun getAppId ()I public final fun getDeviceKind ()Ljava/lang/String; public final fun getDeviceName ()Ljava/lang/String; public final fun getPlatform ()Lnet/mamoe/mirai/contact/Platform; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/contact/OtherClientKt { public static final fun getDeviceKind (Lnet/mamoe/mirai/contact/OtherClient;)Ljava/lang/String; public static final fun getDeviceName (Lnet/mamoe/mirai/contact/OtherClient;)Ljava/lang/String; public static final fun getPlatform (Lnet/mamoe/mirai/contact/OtherClient;)Lnet/mamoe/mirai/contact/Platform; } public final class net/mamoe/mirai/contact/PermissionDeniedException : java/lang/IllegalStateException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/contact/Platform : java/lang/Enum { public static final field Companion Lnet/mamoe/mirai/contact/Platform$Companion; public static final field IOS Lnet/mamoe/mirai/contact/Platform; public static final field MOBILE Lnet/mamoe/mirai/contact/Platform; public static final field WINDOWS Lnet/mamoe/mirai/contact/Platform; public final fun getPlatformId ()I public final fun getTerminalId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/Platform; public static fun values ()[Lnet/mamoe/mirai/contact/Platform; } public final class net/mamoe/mirai/contact/Platform$Companion { } public class net/mamoe/mirai/contact/SendMessageFailedException : java/lang/RuntimeException { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason;Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/Message; public final fun getReason ()Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public final class net/mamoe/mirai/contact/SendMessageFailedException$Reason : java/lang/Enum { public static final field AT_ALL_LIMITED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field BOT_MUTED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field GROUP_CHAT_LIMITED Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field LIMITED_MESSAGING Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static final field MESSAGE_TOO_LARGE Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; public static fun values ()[Lnet/mamoe/mirai/contact/SendMessageFailedException$Reason; } public abstract interface class net/mamoe/mirai/contact/Stranger : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/User { public fun delete ()V public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/Stranger;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/StrangerKt { public static final fun asFriend (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/contact/Friend; public static final fun asFriendOrNull (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/contact/Friend; } public abstract interface class net/mamoe/mirai/contact/User : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/UserOrBot { public abstract fun getId ()J public abstract fun getRemark ()Ljava/lang/String; public abstract fun nudge ()Lnet/mamoe/mirai/message/action/UserNudge; public fun queryProfile ()Lnet/mamoe/mirai/data/UserProfile; public fun queryProfile (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun queryProfile$suspendImpl (Lnet/mamoe/mirai/contact/User;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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 static synthetic fun sendMessage$suspendImpl (Lnet/mamoe/mirai/contact/User;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/UserKt { public static final fun getRemarkOrNameCard (Lnet/mamoe/mirai/contact/Member;)Ljava/lang/String; public static final fun getRemarkOrNameCardOrNick (Lnet/mamoe/mirai/contact/Member;)Ljava/lang/String; public static final fun getRemarkOrNick (Lnet/mamoe/mirai/contact/User;)Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/UserOrBot : net/mamoe/mirai/contact/ContactOrBot { public abstract fun getNick ()Ljava/lang/String; public abstract fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; } public final class net/mamoe/mirai/contact/active/ActiveChart { public final fun getActives ()Ljava/util/Map; public final fun getExit ()Ljava/util/Map; public final fun getJoin ()Ljava/util/Map; public final fun getMembers ()Ljava/util/Map; public final fun getSentences ()Ljava/util/Map; } public final class net/mamoe/mirai/contact/active/ActiveHonorInfo { public final fun getAvatar ()Ljava/lang/String; public final fun getHistoryDays ()I public final fun getMaxTermDays ()I public final fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getMemberId ()J public final fun getMemberName ()Ljava/lang/String; public final fun getTermDays ()I } public final class net/mamoe/mirai/contact/active/ActiveHonorList { public final fun getCurrent ()Lnet/mamoe/mirai/contact/active/ActiveHonorInfo; public final fun getRecords ()Ljava/util/List; public final fun getType ()I } public final class net/mamoe/mirai/contact/active/ActiveRankRecord { public final fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getMemberId ()J public final fun getMemberName ()Ljava/lang/String; public final fun getScore ()I public final fun getTemperature ()I } public final class net/mamoe/mirai/contact/active/ActiveRecord { public final fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getMemberId ()J public final fun getMemberName ()Ljava/lang/String; public final fun getMessagesCount ()I public final fun getPeriodDays ()I } public abstract interface class net/mamoe/mirai/contact/active/GroupActive : net/mamoe/mirai/utils/Streamable { public abstract fun getRankTitles ()Ljava/util/Map; public abstract fun getTemperatureTitles ()Ljava/util/Map; public abstract fun isHonorVisible ()Z public abstract fun isTemperatureVisible ()Z public abstract fun isTitleVisible ()Z public fun queryActiveRank ()Ljava/util/List; public abstract fun queryActiveRank (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun queryChart ()Lnet/mamoe/mirai/contact/active/ActiveChart; public abstract fun queryChart (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun queryHonorHistory (ILkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun refresh ()V public abstract fun refresh (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setHonorVisible (Z)V public abstract fun setHonorVisible (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setRankTitles (Ljava/util/Map;)V public abstract fun setRankTitles (Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setTemperatureTitles (Ljava/util/Map;)V public abstract fun setTemperatureTitles (Ljava/util/Map;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setTemperatureVisible (Z)V public abstract fun setTemperatureVisible (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun setTitleVisible (Z)V public abstract fun setTitleVisible (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/active/MemberActive { public abstract fun getHonors ()Ljava/util/Set; public abstract fun getPoint ()I public abstract fun getRank ()I public abstract fun getTemperature ()I public fun queryMedal ()Lnet/mamoe/mirai/contact/active/MemberMedalInfo; public abstract fun queryMedal (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/active/MemberMedalInfo { public final fun getColor ()Ljava/lang/String; public final fun getMedals ()Ljava/util/Set; public final fun getTitle ()Ljava/lang/String; public final fun getWearing ()Lnet/mamoe/mirai/contact/active/MemberMedalType; } public final class net/mamoe/mirai/contact/active/MemberMedalType : java/lang/Enum { public static final field ACTIVE Lnet/mamoe/mirai/contact/active/MemberMedalType; public static final field ADMIN Lnet/mamoe/mirai/contact/active/MemberMedalType; public static final field OWNER Lnet/mamoe/mirai/contact/active/MemberMedalType; public static final field SPECIAL Lnet/mamoe/mirai/contact/active/MemberMedalType; public final fun getMask ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/contact/active/MemberMedalType; public static fun values ()[Lnet/mamoe/mirai/contact/active/MemberMedalType; } public abstract interface class net/mamoe/mirai/contact/announcement/Announcement { public static final field Companion Lnet/mamoe/mirai/contact/announcement/Announcement$Companion; public abstract fun getContent ()Ljava/lang/String; public abstract fun getParameters ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public static fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun publishTo (Lnet/mamoe/mirai/contact/Group;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public fun publishTo (Lnet/mamoe/mirai/contact/Group;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun publishTo$suspendImpl (Lnet/mamoe/mirai/contact/announcement/Announcement;Lnet/mamoe/mirai/contact/Group;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/Announcement$Companion { public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public final fun publishAnnouncement (Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun publishAnnouncement$default (Lnet/mamoe/mirai/contact/announcement/Announcement$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public static synthetic fun publishAnnouncement$default (Lnet/mamoe/mirai/contact/announcement/Announcement$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/AnnouncementImage { public static final field Companion Lnet/mamoe/mirai/contact/announcement/AnnouncementImage$Companion; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;IILkotlinx/serialization/internal/SerializationConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;IILkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun create (Ljava/lang/String;II)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public fun equals (Ljava/lang/Object;)Z public final fun getHeight ()I public final fun getId ()Ljava/lang/String; public final fun getUrl ()Ljava/lang/String; public final fun getWidth ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/contact/announcement/AnnouncementImage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/contact/announcement/AnnouncementImage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/contact/announcement/AnnouncementImage$$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/contact/announcement/AnnouncementImage; 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/contact/announcement/AnnouncementImage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementImage$Companion { public final fun create (Ljava/lang/String;II)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementKt { public static final fun toOffline (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParameters { public static final field Companion Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters$Companion; public static final field SERIAL_NAME Ljava/lang/String; public fun <init> ()V public synthetic fun <init> (ILnet/mamoe/mirai/contact/announcement/AnnouncementImage;ZZZZZLkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun builder ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public fun equals (Ljava/lang/Object;)Z public static final fun getDefault ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public final fun getImage ()Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public final fun getRequireConfirmation ()Z public final fun getSendToNewMember ()Z public final fun getShowEditCard ()Z public final fun getShowPopup ()Z public fun hashCode ()I public final fun isPinned ()Z public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/contact/announcement/AnnouncementParameters$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters$$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/contact/announcement/AnnouncementParameters; 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/contact/announcement/AnnouncementParameters;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParameters$Companion { public final fun getDefault ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder { public fun <init> ()V public fun <init> (Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)V public synthetic fun <init> (Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; public final fun image ()Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public final fun image (Lnet/mamoe/mirai/contact/announcement/AnnouncementImage;)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun isPinned ()Z public final fun isPinned (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun requireConfirmation ()Z public final fun requireConfirmation (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun sendToNewMember ()Z public final fun sendToNewMember (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final synthetic fun setImage (Lnet/mamoe/mirai/contact/announcement/AnnouncementImage;)V public final synthetic fun setPinned (Z)V public final synthetic fun setRequireConfirmation (Z)V public final synthetic fun setSendToNewMember (Z)V public final synthetic fun setShowEditCard (Z)V public final synthetic fun setShowPopup (Z)V public final fun showEditCard ()Z public final fun showEditCard (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; public final fun showPopup ()Z public final fun showPopup (Z)Lnet/mamoe/mirai/contact/announcement/AnnouncementParametersBuilder; } public final class net/mamoe/mirai/contact/announcement/AnnouncementParametersBuilderKt { public static final fun buildAnnouncementParameters (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters; } public abstract interface class net/mamoe/mirai/contact/announcement/Announcements : net/mamoe/mirai/utils/Streamable { public synthetic fun asFlow (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun asFlow (Lnet/mamoe/mirai/contact/announcement/Announcements;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun delete (Ljava/lang/String;)Z public abstract fun delete (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun get (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun members (Ljava/lang/String;Z)Ljava/util/List; public abstract fun members (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remind (Ljava/lang/String;)V public abstract fun remind (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/announcement/OfflineAnnouncement : net/mamoe/mirai/contact/announcement/Announcement { public static final field Companion Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement$Companion; public static final field SERIAL_NAME Ljava/lang/String; public static fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static fun create (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static fun from (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; } public final class net/mamoe/mirai/contact/announcement/OfflineAnnouncement$Companion { public static final field SERIAL_NAME Ljava/lang/String; public final fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final synthetic fun create (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final fun create (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static synthetic fun create$default (Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement$Companion;Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final fun from (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/contact/announcement/OfflineAnnouncementKt { public static final fun OfflineAnnouncement (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static final fun OfflineAnnouncement (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static final fun OfflineAnnouncement (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; public static synthetic fun OfflineAnnouncement$default (Ljava/lang/String;Lnet/mamoe/mirai/contact/announcement/AnnouncementParameters;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/announcement/OfflineAnnouncement; } public abstract interface class net/mamoe/mirai/contact/announcement/OnlineAnnouncement : net/mamoe/mirai/contact/announcement/Announcement { public fun delete ()Z public fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun delete$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getAllConfirmed ()Z public abstract fun getConfirmedMembersCount ()I public abstract fun getFid ()Ljava/lang/String; public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; public abstract fun getPublicationTime ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getSenderId ()J public fun members (Z)Ljava/util/List; public fun members (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun members$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remind ()V public fun remind (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun remind$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt { public static final fun getBot (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;)Lnet/mamoe/mirai/Bot; } public final class net/mamoe/mirai/contact/essence/EssenceMessageRecord { public final fun getFullSource ()Lnet/mamoe/mirai/message/data/MessageSource; public final fun getFullSource (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getOperatorId ()J public final fun getOperatorNick ()Ljava/lang/String; public final fun getOperatorTime ()I public final fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getSenderId ()J public final fun getSenderNick ()Ljava/lang/String; public final fun getSenderTime ()I public final fun getSource ()Lnet/mamoe/mirai/message/data/MessageSource; public final fun getSource (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/essence/Essences : net/mamoe/mirai/utils/Streamable { public fun getPage (II)Ljava/util/List; public abstract fun getPage (IILkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun remove (Lnet/mamoe/mirai/message/data/MessageSource;)V public abstract fun remove (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun share (Lnet/mamoe/mirai/message/data/MessageSource;)Ljava/lang/String; public abstract fun share (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFile : net/mamoe/mirai/contact/file/AbsoluteFileFolder { public abstract fun getExpiryTime ()J public abstract fun getMd5 ()[B public abstract fun getSha1 ()[B public abstract fun getSize ()J public fun getUrl ()Ljava/lang/String; public abstract fun getUrl (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;)Z public abstract fun moveTo (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun toMessage ()Lnet/mamoe/mirai/message/data/FileMessage; } public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFileFolder { public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion; public fun delete ()Z public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun exists ()Z public abstract fun exists (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getAbsolutePath ()Ljava/lang/String; public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported; public static fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; public abstract fun getId ()Ljava/lang/String; public abstract fun getLastModifiedTime ()J public abstract fun getName ()Ljava/lang/String; public static fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; public abstract fun getParent ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun getUploadTime ()J public abstract fun getUploaderId ()J public abstract fun isFile ()Z public abstract fun isFolder ()Z public fun refresh ()Z public abstract fun refresh (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder; public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun renameTo (Ljava/lang/String;)Z public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/contact/file/AbsoluteFileFolder$Companion { public final fun getExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; public final fun getNameWithoutExtension (Lnet/mamoe/mirai/contact/file/AbsoluteFileFolder;)Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/file/AbsoluteFolder : net/mamoe/mirai/contact/file/AbsoluteFileFolder { public static final field Companion Lnet/mamoe/mirai/contact/file/AbsoluteFolder$Companion; public static final field ROOT_FOLDER_ID Ljava/lang/String; public fun children ()Lkotlinx/coroutines/flow/Flow; public abstract fun children (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun childrenStream ()Ljava/util/stream/Stream; public abstract fun childrenStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun createFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun createFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun files ()Lkotlinx/coroutines/flow/Flow; public abstract fun files (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun filesStream ()Ljava/util/stream/Stream; public abstract fun filesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun folders ()Lkotlinx/coroutines/flow/Flow; public abstract fun folders (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun foldersStream ()Ljava/util/stream/Stream; public abstract fun foldersStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getContentsCount ()I public fun isEmpty ()Z public fun refreshed ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun refreshed (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveAll (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; public abstract fun resolveAll (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveAllStream (Ljava/lang/String;)Ljava/util/stream/Stream; public abstract fun resolveAllStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFileById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun resolveFileById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFileById (Ljava/lang/String;Z)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun resolveFileById (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public static synthetic fun resolveFileById$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun resolveFiles (Ljava/lang/String;)Lkotlinx/coroutines/flow/Flow; public abstract fun resolveFiles (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFilesStream (Ljava/lang/String;)Ljava/util/stream/Stream; public abstract fun resolveFilesStream (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFolder (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun resolveFolder (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveFolderById (Ljava/lang/String;)Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public abstract fun resolveFolderById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/AbsoluteFolder;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/file/AbsoluteFolder$Companion { public static final field ROOT_FOLDER_ID Ljava/lang/String; } public abstract interface class net/mamoe/mirai/contact/file/RemoteFiles { public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported; public abstract fun getRoot ()Lnet/mamoe/mirai/contact/file/AbsoluteFolder; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public fun uploadNewFile (Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public static synthetic fun uploadNewFile$default (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadNewFile$suspendImpl (Lnet/mamoe/mirai/contact/file/RemoteFiles;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroup { public fun delete ()Z public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getCount ()I public abstract fun getFriends ()Ljava/util/Collection; public abstract fun getId ()I public abstract fun getName ()Ljava/lang/String; public fun moveIn (Lnet/mamoe/mirai/contact/Friend;)Z public abstract fun moveIn (Lnet/mamoe/mirai/contact/Friend;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun renameTo (Ljava/lang/String;)Z public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/friendgroup/FriendGroups { public abstract fun asCollection ()Ljava/util/Collection; public fun create (Ljava/lang/String;)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public abstract fun create (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun get (I)Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; public fun getDefault ()Lnet/mamoe/mirai/contact/friendgroup/FriendGroup; } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessage { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getContact ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getIds ()[I public abstract fun getInternalIds ()[I public abstract fun getSender ()J public abstract fun getTarget ()J public abstract fun getTime ()J } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessageFilter { public static final field ANY Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public static final field Companion Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion; public static final field RECEIVED Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public static final field SENT Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public fun and (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public abstract fun invoke (Lnet/mamoe/mirai/contact/roaming/RoamingMessage;)Z public fun not ()Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; public fun or (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter; } public final class net/mamoe/mirai/contact/roaming/RoamingMessageFilter$Companion { } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingMessages { public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; public fun getAllMessages (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; public static synthetic fun getAllMessages$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun getAllMessages$suspendImpl (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getAllMessagesStream ()Ljava/util/stream/Stream; public fun getAllMessagesStream (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; public fun getAllMessagesStream (Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; public static synthetic fun getAllMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun getAllMessagesStream$suspendImpl (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;Lnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Lkotlinx/coroutines/flow/Flow; public abstract fun getMessagesIn (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow; public static synthetic fun getMessagesIn$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun getMessagesStream (JJ)Ljava/util/stream/Stream; public fun getMessagesStream (JJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;)Ljava/util/stream/Stream; public fun getMessagesStream (JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;ILjava/lang/Object;)Ljava/util/stream/Stream; public static synthetic fun getMessagesStream$default (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun getMessagesStream$suspendImpl (Lnet/mamoe/mirai/contact/roaming/RoamingMessages;JJLnet/mamoe/mirai/contact/roaming/RoamingMessageFilter;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/contact/roaming/RoamingSupported : net/mamoe/mirai/contact/Contact { public abstract fun getRoamingMessages ()Lnet/mamoe/mirai/contact/roaming/RoamingMessages; } public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getFriendGroupId ()I 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 { public fun <init> (JLjava/lang/String;Ljava/lang/String;)V public fun getFriendGroupId ()I public fun getNick ()Ljava/lang/String; public fun getRemark ()Ljava/lang/String; public fun getUin ()J public fun setFriendGroupId (I)V public fun setNick (Ljava/lang/String;)V public fun setRemark (Ljava/lang/String;)V } public final class net/mamoe/mirai/data/GroupHonorType { public static final field BRONZE_ID I public static final field Companion Lnet/mamoe/mirai/data/GroupHonorType$Companion; public static final field EMOTION_ID I public static final field GOLDEN_ID I public static final field LEGEND_ID I public static final field PERFORMER_ID I public static final field RED_PACKET_ID I public static final field RICHER_ID I public static final field SILVER_ID I public static final field STRONG_NEWBIE_ID I public static final field TALKATIVE_ID I public static final field WHIRLWIND_ID I public static final synthetic fun box-impl (I)Lnet/mamoe/mirai/data/GroupHonorType; public static fun constructor-impl (I)I public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (ILjava/lang/Object;)Z public static final fun equals-impl0 (II)Z public static final fun getBRONZE-AVr_HNQ ()I public static final fun getEMOTION-AVr_HNQ ()I public static final fun getGOLDEN-AVr_HNQ ()I public final fun getId ()I public static final fun getLEGEND-AVr_HNQ ()I public static final fun getPERFORMER-AVr_HNQ ()I public static final fun getRED_PACKET-AVr_HNQ ()I public static final fun getRICHER-AVr_HNQ ()I public static final fun getSILVER-AVr_HNQ ()I public static final fun getSTRONG_NEWBIE-AVr_HNQ ()I public static final fun getTALKATIVE-AVr_HNQ ()I public static final fun getWHIRLWIND-AVr_HNQ ()I public fun hashCode ()I public static fun hashCode-impl (I)I public fun toString ()Ljava/lang/String; public static fun toString-impl (I)Ljava/lang/String; public final synthetic fun unbox-impl ()I } public final class net/mamoe/mirai/data/GroupHonorType$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/GroupHonorType$$serializer; public fun childSerializers ()[Lkotlinx/serialization/KSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize-NYH6FXw (Lkotlinx/serialization/encoding/Decoder;)I public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V public fun serialize-aLnpm_Q (Lkotlinx/serialization/encoding/Encoder;I)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/GroupHonorType$Companion { public final fun getBRONZE-AVr_HNQ ()I public final fun getEMOTION-AVr_HNQ ()I public final fun getGOLDEN-AVr_HNQ ()I public final fun getLEGEND-AVr_HNQ ()I public final fun getPERFORMER-AVr_HNQ ()I public final fun getRED_PACKET-AVr_HNQ ()I public final fun getRICHER-AVr_HNQ ()I public final fun getSILVER-AVr_HNQ ()I public final fun getSTRONG_NEWBIE-AVr_HNQ ()I public final fun getTALKATIVE-AVr_HNQ ()I public final fun getWHIRLWIND-AVr_HNQ ()I public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/data/GroupInfo { public abstract fun getAllowAnonymousChat ()Z public abstract fun getAllowMemberInvite ()Z public abstract fun getAutoApprove ()Z public abstract fun getBotMuteTimestamp ()I public abstract fun getConfessTalk ()Z public abstract fun getGroupCode ()J public abstract fun getMemo ()Ljava/lang/String; public abstract fun getMuteAll ()Z public abstract fun getName ()Ljava/lang/String; public abstract fun getOwner ()J public abstract fun getRankTitles ()Ljava/util/Map; public abstract fun getTemperatureTitles ()Ljava/util/Map; public abstract fun getUin ()J public abstract fun isHonorVisible ()Z public abstract fun isTemperatureVisible ()Z public abstract fun isTitleVisible ()Z } public abstract interface class net/mamoe/mirai/data/MemberInfo : net/mamoe/mirai/data/UserInfo { public fun getAnonymousId ()Ljava/lang/String; public abstract fun getHonors ()Ljava/util/Set; public abstract fun getJoinTimestamp ()I public abstract fun getLastSpeakTimestamp ()I public abstract fun getMuteTimestamp ()I public abstract fun getNameCard ()Ljava/lang/String; public abstract fun getPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public abstract fun getPoint ()I public abstract fun getRank ()I public abstract fun getSpecialTitle ()Ljava/lang/String; public abstract fun getTemperature ()I public abstract fun isOfficialBot ()Z } public final class net/mamoe/mirai/data/OnlineStatus : java/lang/Enum { public static final field AWAY Lnet/mamoe/mirai/data/OnlineStatus; public static final field BUSY Lnet/mamoe/mirai/data/OnlineStatus; public static final field Companion Lnet/mamoe/mirai/data/OnlineStatus$Companion; public static final field DND Lnet/mamoe/mirai/data/OnlineStatus; public static final field INVISIBLE Lnet/mamoe/mirai/data/OnlineStatus; public static final field OFFLINE Lnet/mamoe/mirai/data/OnlineStatus; public static final field ONLINE Lnet/mamoe/mirai/data/OnlineStatus; public static final field Q_ME Lnet/mamoe/mirai/data/OnlineStatus; public static final field RECEIVE_OFFLINE_MESSAGE Lnet/mamoe/mirai/data/OnlineStatus; public static final field UNKNOWN Lnet/mamoe/mirai/data/OnlineStatus; public final fun getId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/data/OnlineStatus; public static fun values ()[Lnet/mamoe/mirai/data/OnlineStatus; } public final class net/mamoe/mirai/data/OnlineStatus$Companion { public final fun ofId (I)Lnet/mamoe/mirai/data/OnlineStatus; public final fun ofIdOrNull (I)Lnet/mamoe/mirai/data/OnlineStatus; } public abstract class net/mamoe/mirai/data/RequestEventData { public static final field Factory Lnet/mamoe/mirai/data/RequestEventData$Factory; public synthetic fun <init> (ILkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;)V public abstract fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun from (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest; public static final fun from (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest; public static final fun from (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest; public abstract fun getEventId ()J public fun reject (Lnet/mamoe/mirai/Bot;)V public abstract fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest : net/mamoe/mirai/data/RequestEventData { public static final field Companion Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$Companion; public synthetic fun <init> (IJJLjava/lang/String;JLjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getEventId ()J public final fun getGroupId ()J public final fun getGroupName ()Ljava/lang/String; public final fun getInvitor ()J public final fun getInvitorNick ()Ljava/lang/String; public fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$$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/data/RequestEventData$BotInvitedJoinGroupRequest; 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/data/RequestEventData$BotInvitedJoinGroupRequest;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$Factory { public final fun from (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$BotInvitedJoinGroupRequest; public final fun from (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest; public final fun from (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;)Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$MemberJoinRequest : net/mamoe/mirai/data/RequestEventData { public static final field Companion Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest$Companion; public synthetic fun <init> (IJJLjava/lang/String;JLjava/lang/String;JLjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public synthetic fun <init> (JJLjava/lang/String;JLjava/lang/String;JLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getEventId ()J public final fun getGroupId ()J public final fun getGroupName ()Ljava/lang/String; public final fun getInvitor ()J public final fun getMessage ()Ljava/lang/String; public final fun getRequester ()J public final fun getRequesterNick ()Ljava/lang/String; public final fun reject (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Lnet/mamoe/mirai/Bot;ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun reject$default (Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest;Lnet/mamoe/mirai/Bot;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$MemberJoinRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/RequestEventData$MemberJoinRequest$$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/data/RequestEventData$MemberJoinRequest; 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/data/RequestEventData$MemberJoinRequest;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$MemberJoinRequest$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$NewFriendRequest : net/mamoe/mirai/data/RequestEventData { public static final field Companion Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest$Companion; public synthetic fun <init> (IJJLjava/lang/String;JLjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun accept (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun getEventId ()J public final fun getFromGroupId ()J public final fun getMessage ()Ljava/lang/String; public final fun getRequester ()J public final fun getRequesterNick ()Ljava/lang/String; public fun reject (Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Lnet/mamoe/mirai/Bot;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/data/RequestEventData$NewFriendRequest$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/RequestEventData$NewFriendRequest$$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/data/RequestEventData$NewFriendRequest; 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/data/RequestEventData$NewFriendRequest;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/data/RequestEventData$NewFriendRequest$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/data/StrangerInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getFromGroup ()J public abstract fun getNick ()Ljava/lang/String; public abstract fun getUin ()J } public abstract interface class net/mamoe/mirai/data/UserInfo { public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; public abstract fun getUin ()J } public abstract interface class net/mamoe/mirai/data/UserProfile { public abstract fun getAge ()I public abstract fun getEmail ()Ljava/lang/String; public abstract fun getFriendGroupId ()I public abstract fun getNickname ()Ljava/lang/String; public abstract fun getQLevel ()I public abstract fun getSex ()Lnet/mamoe/mirai/data/UserProfile$Sex; public abstract fun getSign ()Ljava/lang/String; } public final class net/mamoe/mirai/data/UserProfile$Sex : java/lang/Enum { public static final field FEMALE Lnet/mamoe/mirai/data/UserProfile$Sex; public static final field MALE Lnet/mamoe/mirai/data/UserProfile$Sex; public static final field UNKNOWN Lnet/mamoe/mirai/data/UserProfile$Sex; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/data/UserProfile$Sex; public static fun values ()[Lnet/mamoe/mirai/data/UserProfile$Sex; } public abstract class net/mamoe/mirai/event/AbstractEvent : net/mamoe/mirai/event/Event { public field _intercepted Z public final field broadCastLock Lkotlinx/coroutines/sync/Mutex; public fun <init> ()V public final fun cancel ()V public fun intercept ()V public final fun isCancelled ()Z public fun isIntercepted ()Z } public abstract interface class net/mamoe/mirai/event/BroadcastControllable : net/mamoe/mirai/event/Event { public fun getShouldBroadcast ()Z } public abstract interface class net/mamoe/mirai/event/CancellableEvent : net/mamoe/mirai/event/Event { public abstract fun cancel ()V public abstract fun isCancelled ()Z } public final class net/mamoe/mirai/event/ConcurrencyKind : java/lang/Enum { public static final field CONCURRENT Lnet/mamoe/mirai/event/ConcurrencyKind; public static final field LOCKED Lnet/mamoe/mirai/event/ConcurrencyKind; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/event/ConcurrencyKind; public static fun values ()[Lnet/mamoe/mirai/event/ConcurrencyKind; } public abstract interface class net/mamoe/mirai/event/Event { public abstract fun intercept ()V public abstract fun isIntercepted ()Z } public abstract class net/mamoe/mirai/event/EventChannel { public static synthetic fun asChannel$default (Lnet/mamoe/mirai/event/EventChannel;ILkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;ILjava/lang/Object;)Lkotlinx/coroutines/channels/Channel; public abstract fun asFlow ()Lkotlinx/coroutines/flow/Flow; public abstract fun context ([Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/event/EventChannel; public final fun exceptionHandler (Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/EventChannel; public final fun exceptionHandler (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/event/EventChannel; public final fun exceptionHandler (Lkotlinx/coroutines/CoroutineExceptionHandler;)Lnet/mamoe/mirai/event/EventChannel; public final fun filter (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/event/EventChannel; public final synthetic fun filter (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/event/EventChannel; public final fun filterIsInstance (Ljava/lang/Class;)Lnet/mamoe/mirai/event/EventChannel; public final fun filterIsInstance (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/event/EventChannel; public final fun forwardToChannel (Lkotlinx/coroutines/channels/SendChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun forwardToChannel$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlinx/coroutines/channels/SendChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public final fun getBaseEventClass ()Lkotlin/reflect/KClass; public final fun getDefaultCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun parentJob (Lkotlinx/coroutines/Job;)Lnet/mamoe/mirai/event/EventChannel; public final fun parentScope (Lkotlinx/coroutines/CoroutineScope;)Lnet/mamoe/mirai/event/EventChannel; public final fun registerListenerHost (Lnet/mamoe/mirai/event/ListenerHost;)V public final fun registerListenerHost (Lnet/mamoe/mirai/event/ListenerHost;Lkotlin/coroutines/CoroutineContext;)V public static synthetic fun registerListenerHost$default (Lnet/mamoe/mirai/event/EventChannel;Lnet/mamoe/mirai/event/ListenerHost;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)V public final fun subscribe (Ljava/lang/Class;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final fun subscribe (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final fun subscribe (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final fun subscribe (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Function;)Lnet/mamoe/mirai/event/Listener; public final synthetic fun subscribe (Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribe$default (Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Function;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribe$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeAlways (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final synthetic fun subscribeAlways (Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeAlways$default (Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeAlways$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;)Lnet/mamoe/mirai/event/Listener; public final fun subscribeOnce (Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeOnce$default (Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/Class;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Ljava/util/function/Consumer;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; public static synthetic fun subscribeOnce$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lnet/mamoe/mirai/event/Listener; } public final class net/mamoe/mirai/event/EventChannelKt { public static final synthetic fun globalEventChannel (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/event/EventChannel; public static synthetic fun globalEventChannel$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lnet/mamoe/mirai/event/EventChannel; } public abstract interface annotation class net/mamoe/mirai/event/EventHandler : java/lang/annotation/Annotation { public abstract fun concurrency ()Lnet/mamoe/mirai/event/ConcurrencyKind; public abstract fun ignoreCancelled ()Z public abstract fun priority ()Lnet/mamoe/mirai/event/EventPriority; } public final class net/mamoe/mirai/event/EventKt { public static final fun broadcast (Lnet/mamoe/mirai/event/Event;)Lnet/mamoe/mirai/event/Event; public static final fun broadcast (Lnet/mamoe/mirai/event/Event;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/EventPriority : java/lang/Enum { public static final field HIGH Lnet/mamoe/mirai/event/EventPriority; public static final field HIGHEST Lnet/mamoe/mirai/event/EventPriority; public static final field LOW Lnet/mamoe/mirai/event/EventPriority; public static final field LOWEST Lnet/mamoe/mirai/event/EventPriority; public static final field MONITOR Lnet/mamoe/mirai/event/EventPriority; public static final field NORMAL Lnet/mamoe/mirai/event/EventPriority; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/event/EventPriority; public static fun values ()[Lnet/mamoe/mirai/event/EventPriority; } public final class net/mamoe/mirai/event/Events { public static final synthetic fun registerTo (Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventChannel;)V } public final class net/mamoe/mirai/event/ExceptionInEventChannelFilterException : java/lang/IllegalStateException { public fun <init> (Lnet/mamoe/mirai/event/Event;Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Lnet/mamoe/mirai/event/Event;Lnet/mamoe/mirai/event/EventChannel;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; public final fun getEvent ()Lnet/mamoe/mirai/event/Event; public final fun getEventChannel ()Lnet/mamoe/mirai/event/EventChannel; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/event/ExceptionInEventHandlerException : java/lang/IllegalStateException { public fun <init> (Lnet/mamoe/mirai/event/Event;Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Lnet/mamoe/mirai/event/Event;Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; public final fun getEvent ()Lnet/mamoe/mirai/event/Event; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/event/ExtensionsKt { public static final fun nextEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun syncFromEventImpl (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/GlobalEventChannel : net/mamoe/mirai/event/EventChannel { public static final field INSTANCE Lnet/mamoe/mirai/event/GlobalEventChannel; public fun asFlow ()Lkotlinx/coroutines/flow/Flow; public fun context ([Lkotlin/coroutines/CoroutineContext;)Lnet/mamoe/mirai/event/EventChannel; } public abstract interface class net/mamoe/mirai/event/Listener : kotlinx/coroutines/CompletableJob { public abstract fun getConcurrencyKind ()Lnet/mamoe/mirai/event/ConcurrencyKind; public fun getPriority ()Lnet/mamoe/mirai/event/EventPriority; public abstract fun onEvent (Lnet/mamoe/mirai/event/Event;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/Listener$DefaultImpls { public static synthetic fun cancel (Lnet/mamoe/mirai/event/Listener;)V public static fun fold (Lnet/mamoe/mirai/event/Listener;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static fun get (Lnet/mamoe/mirai/event/Listener;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element; public static fun minusKey (Lnet/mamoe/mirai/event/Listener;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext; public static fun plus (Lnet/mamoe/mirai/event/Listener;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; public static fun plus (Lnet/mamoe/mirai/event/Listener;Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; } public abstract interface class net/mamoe/mirai/event/ListenerHost { } public final class net/mamoe/mirai/event/ListeningStatus : java/lang/Enum { public static final field LISTENING Lnet/mamoe/mirai/event/ListeningStatus; public static final field STOPPED Lnet/mamoe/mirai/event/ListeningStatus; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/event/ListeningStatus; public static fun values ()[Lnet/mamoe/mirai/event/ListeningStatus; } public abstract interface annotation class net/mamoe/mirai/event/MessageDsl : java/lang/annotation/Annotation { } public abstract class net/mamoe/mirai/event/MessageSelectBuilder : net/mamoe/mirai/event/MessageSelectBuilderUnit { public fun <init> (Lnet/mamoe/mirai/event/events/MessageEvent;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V public synthetic fun always (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun always (Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun containsReply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun containsReply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun containsReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun containsReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun endsWithReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun endsWithReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun findingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun findingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun mapping (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun mapping (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun matchingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun matchingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public synthetic fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLjava/lang/String;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLjava/lang/String;)V public synthetic fun quoteReply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)V public synthetic fun quoteReply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun quoteReply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)V public synthetic fun reply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun reply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun reply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun reply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun reply (Ljava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public synthetic fun reply (Ljava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Void; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Void; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public synthetic fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLjava/lang/String;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLjava/lang/String;)V public synthetic fun reply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLkotlin/jvm/functions/Function1;)V public synthetic fun reply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public synthetic fun reply-8NSq9Eo (JLnet/mamoe/mirai/message/data/Message;)V } public abstract class net/mamoe/mirai/event/MessageSelectBuilderUnit : net/mamoe/mirai/event/CommonMessageSelectBuilderUnit { public fun <init> (Lnet/mamoe/mirai/event/events/MessageEvent;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V public final synthetic fun invoke-RNyhSv4 (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public final synthetic fun invoke-RNyhSv4 (JLkotlin/jvm/functions/Function1;)V public final synthetic fun quoteReply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public final synthetic fun quoteReply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)V public final synthetic fun quoteReply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public final synthetic fun quoteReply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)V public final synthetic fun quoteReply-sCZ5gAI (JLjava/lang/String;)Ljava/lang/Void; public final synthetic fun quoteReply-sCZ5gAI (JLjava/lang/String;)V public final synthetic fun reply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)Ljava/lang/Void; public final synthetic fun reply-AVDwu3U (JLnet/mamoe/mirai/message/data/Message;)V public final synthetic fun reply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)Ljava/lang/Void; public final synthetic fun reply-RNyhSv4 (JLkotlin/jvm/functions/Function1;)V public final synthetic fun reply-sCZ5gAI (JLjava/lang/String;)Ljava/lang/Void; public final synthetic fun reply-sCZ5gAI (JLjava/lang/String;)V public final synthetic fun timeout-ncvN2qU (J)J } public final class net/mamoe/mirai/event/MessageSelectionTimeoutChecker { public static final synthetic fun box-impl (J)Lnet/mamoe/mirai/event/MessageSelectionTimeoutChecker; public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (JLjava/lang/Object;)Z public static final fun equals-impl0 (JJ)Z public final fun getTimeoutMillis ()J public fun hashCode ()I public static fun hashCode-impl (J)I public fun toString ()Ljava/lang/String; public static fun toString-impl (J)Ljava/lang/String; public final synthetic fun unbox-impl ()J } public final class net/mamoe/mirai/event/MessageSelectionTimeoutException : java/lang/RuntimeException { public fun <init> ()V } public class net/mamoe/mirai/event/MessageSubscribersBuilder { public fun always (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun at (J)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun at (Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun atAll ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun atBot ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun atBot (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun case (Ljava/lang/String;ZZ)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun case (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun case$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun case$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; public final fun contains (Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun contains (Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun contains (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun contains$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun contains$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; public final fun containsAll ([Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAll ([Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAll ([Ljava/lang/String;ZZ)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun containsAll$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;[Ljava/lang/String;ZZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAny ([Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAny ([Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun containsAny ([Ljava/lang/String;ZZ)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun containsAny$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;[Ljava/lang/String;ZZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public fun containsReply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public fun containsReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun content (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun endsWith (Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun endsWith (Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun endsWith (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun endsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun endsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; public fun endsWithReply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun finding (Lkotlin/text/Regex;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun finding (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final synthetic fun findingExtension (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun findingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun getSubscriber ()Lkotlin/jvm/functions/Function2; public final fun invoke (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun mapping (Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun matching (Lkotlin/text/Regex;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun matching (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final synthetic fun matchingExtension (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun matchingReply (Lkotlin/text/Regex;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun newListeningFilter (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun quoteReply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public fun reply (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object; public fun reply (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun reply (Ljava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Ljava/lang/String;)Ljava/lang/Object; public fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public fun reply (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/Object; public final fun sentBy (J)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentBy (JLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun sentBy (Ljava/lang/String;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentBy (Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByAdministrator ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByFriend ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByFriend (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun sentByGroupTemp ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByOperator ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByOwner ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByStranger ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentByStranger (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final synthetic fun sentByTemp ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentFrom (J)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun sentFrom (Lnet/mamoe/mirai/contact/Group;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun startsWith (Ljava/lang/String;Z)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun startsWith (Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;)Ljava/lang/Object; public static synthetic fun startsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public static synthetic fun startsWith$default (Lnet/mamoe/mirai/event/MessageSubscribersBuilder;Ljava/lang/String;ZZLkotlin/jvm/functions/Function3;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter { public final fun and (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun getFilter ()Lkotlin/jvm/functions/Function2; public final fun invoke (Lkotlin/jvm/functions/Function3;)Ljava/lang/Object; public final fun nand (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun not ()Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun or (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; public final fun xor (Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter;)Lnet/mamoe/mirai/event/MessageSubscribersBuilder$ListeningFilter; } public final class net/mamoe/mirai/event/NextEventKt { public static final synthetic fun nextBotEventImpl (Lnet/mamoe/mirai/Bot;Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun nextEventImpl (Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun parentJob (Lnet/mamoe/mirai/event/EventChannel;Lkotlinx/coroutines/Job;)Lnet/mamoe/mirai/event/EventChannel; public static final synthetic fun withTimeoutOrCoroutineScope (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun withTimeoutOrCoroutineScope (JLkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun withTimeoutOrCoroutineScope$default (JLkotlinx/coroutines/CoroutineScope;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/SelectKt { public static synthetic fun selectMessagesImpl$default (Lnet/mamoe/mirai/event/events/MessageEvent;JZZLnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static final synthetic fun withSilentTimeoutOrCoroutineScope (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract class net/mamoe/mirai/event/SimpleListenerHost : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/event/ListenerHost { protected static final field Companion Lnet/mamoe/mirai/event/SimpleListenerHost$Companion; public fun <init> ()V public fun <init> (Lkotlin/coroutines/CoroutineContext;)V public synthetic fun <init> (Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun cancelAll ()V public final fun getCoroutineContext ()Lkotlin/coroutines/CoroutineContext; protected static final fun getEvent (Ljava/lang/Throwable;)Lnet/mamoe/mirai/event/Event; protected static final fun getRootCause (Ljava/lang/Throwable;)Ljava/lang/Throwable; public fun handleException (Lkotlin/coroutines/CoroutineContext;Ljava/lang/Throwable;)V } protected final class net/mamoe/mirai/event/SimpleListenerHost$Companion { } public final class net/mamoe/mirai/event/SubscribeMessagesKt { public static final fun subscribeFriendMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeFriendMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeGroupMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeGroupMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeGroupTempMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeGroupTempMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeOtherClientMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeOtherClientMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeStrangerMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeStrangerMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final synthetic fun subscribeTempMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeTempMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; public static final fun subscribeUserMessages (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static synthetic fun subscribeUserMessages$default (Lnet/mamoe/mirai/event/EventChannel;Lkotlin/coroutines/CoroutineContext;Lnet/mamoe/mirai/event/ConcurrencyKind;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/SyncFromEventKt { public static final synthetic fun syncFromEventImpl (Lkotlin/reflect/KClass;Lkotlinx/coroutines/CoroutineScope;Lnet/mamoe/mirai/event/EventPriority;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/event/events/BeforeImageUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Contact; public final fun component2 ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun copy (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/event/events/BeforeImageUploadEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BeforeImageUploadEvent;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BeforeImageUploadEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/BeforeShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public final fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; } public abstract interface class net/mamoe/mirai/event/events/BotActiveEvent : net/mamoe/mirai/event/events/BotEvent { } public final class net/mamoe/mirai/event/events/BotAvatarChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, 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/BotAvatarChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotAvatarChangedEvent;Lnet/mamoe/mirai/Bot;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotAvatarChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/BotEvent : net/mamoe/mirai/event/Event { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; } public final class net/mamoe/mirai/event/events/BotEventsKt { public static final synthetic fun getOperatorOrBot (Lnet/mamoe/mirai/event/events/GroupOperableEvent;)Lnet/mamoe/mirai/contact/Member; public static final fun getResult (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Ljava/lang/Object; public static final synthetic fun getSource (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Lnet/mamoe/mirai/message/data/MessageSource; public static final synthetic fun getSourceResult (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Ljava/lang/Object; public static final synthetic fun isByBot (Lnet/mamoe/mirai/event/events/GroupOperableEvent;)Z public static final fun isByBot (Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall;)Z public static final fun isByBot (Lnet/mamoe/mirai/event/events/MessageRecallEvent;)Z public static final synthetic fun isFailure (Lnet/mamoe/mirai/event/events/MessagePostSendEvent;)Z 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun component3 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;)Lnet/mamoe/mirai/event/events/BotGroupPermissionChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotGroupPermissionChangeEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotGroupPermissionChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getNew ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun getOrigin ()Lnet/mamoe/mirai/contact/MemberPermission; public fun hashCode ()I 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/BaseGroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public final fun accept ()V public final fun accept (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()J public final fun component4 ()J public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/Bot;JJJLjava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotInvitedJoinGroupRequestEvent;Lnet/mamoe/mirai/Bot;JJJLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/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 fun getGroupId ()J public final fun getGroupName ()Ljava/lang/String; public final fun getInvitor ()Lnet/mamoe/mirai/contact/Friend; public final fun getInvitorId ()J public final fun getInvitorNick ()Ljava/lang/String; public fun hashCode ()I public final fun ignore ()V public final fun ignore (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; } 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; } 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 final fun component1 ()I public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ILnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/BotMuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotMuteEvent;ILnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotMuteEvent; public fun equals (Ljava/lang/Object;)Z public final fun getDurationSeconds ()I public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/BotNickChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, 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; public final fun copy (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/BotNickChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotNickChangedEvent;Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotNickChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getFrom ()Ljava/lang/String; public final fun getTo ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/event/events/BotOfflineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent { public fun getReconnect ()Z } public final class net/mamoe/mirai/event/events/BotOfflineEvent$Active : net/mamoe/mirai/event/events/BotOfflineEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotOfflineEvent$CauseAware { 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$Active; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOfflineEvent$Active;Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Active; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getCause ()Ljava/lang/Throwable; public fun getReconnect ()Z public fun hashCode ()I public fun toString ()Ljava/lang/String; } 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 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; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOfflineEvent$Dropped;Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Dropped; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getCause ()Ljava/lang/Throwable; public fun getReconnect ()Z public fun hashCode ()I public fun setReconnect (Z)V public fun toString ()Ljava/lang/String; } 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 final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Force; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotOfflineEvent$Force;Lnet/mamoe/mirai/Bot;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotOfflineEvent$Force; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getMessage ()Ljava/lang/String; public fun getReconnect ()Z public final fun getTitle ()Ljava/lang/String; public fun hashCode ()I public fun setReconnect (Z)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/BotOnlineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent { 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; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/BotPassiveEvent : net/mamoe/mirai/event/events/BotEvent { } 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 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; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotReloginEvent;Lnet/mamoe/mirai/Bot;Ljava/lang/Throwable;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotReloginEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getCause ()Ljava/lang/Throwable; public fun hashCode ()I 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/BotUnmuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/BotUnmuteEvent;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/BotUnmuteEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/EventCancelledException : java/lang/RuntimeException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V 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/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/FriendAddEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendAddEvent;Lnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendAddEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendAvatarChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, 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/FriendAvatarChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendAvatarChangedEvent;Lnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendAvatarChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I 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/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; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/FriendEvent : net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/event/events/UserEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getUser ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/event/events/FriendInputStatusChangedEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/internal/event/VerboseEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Friend; public final fun component2 ()Z public final fun copy (Lnet/mamoe/mirai/contact/Friend;Z)Lnet/mamoe/mirai/event/events/FriendInputStatusChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendInputStatusChangedEvent;Lnet/mamoe/mirai/contact/Friend;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendInputStatusChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public final fun getInputting ()Z public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/UserMessageEvent { public fun <init> (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessagePostSendEvent : net/mamoe/mirai/event/events/UserMessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Friend; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/FriendMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendMessagePostSendEvent;Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessagePreSendEvent : net/mamoe/mirai/event/events/UserMessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Friend; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/FriendMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendMessagePreSendEvent;Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/FriendEvent, net/mamoe/mirai/event/events/MessageSyncEvent { public fun <init> (Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/Friend;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Friend; 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/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; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/FriendNickChangedEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendNickChangedEvent;Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendNickChangedEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public final fun getFrom ()Ljava/lang/String; public final fun getTo ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/FriendRemarkChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/BroadcastControllable, 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; public final fun copy (Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/FriendRemarkChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/FriendRemarkChangeEvent;Lnet/mamoe/mirai/contact/Friend;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/FriendRemarkChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public final fun getNewRemark ()Ljava/lang/String; public final fun getOldRemark ()Ljava/lang/String; public fun getShouldBroadcast ()Z public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent;ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupAllowAnonymousChatEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Z public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Z)Lnet/mamoe/mirai/event/events/GroupAllowConfessTalkEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupAllowConfessTalkEvent;ZZLnet/mamoe/mirai/contact/Group;ZILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupAllowConfessTalkEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I public final fun isByBot ()Z 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupAllowMemberInviteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupAllowMemberInviteEvent;ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupAllowMemberInviteEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/GroupAwareMessageEvent : net/mamoe/mirai/event/events/MessageEvent { 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupEntranceAnnouncementChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public synthetic fun getNew ()Ljava/lang/Object; public fun getNew ()Ljava/lang/String; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getOrigin ()Ljava/lang/Object; public fun getOrigin ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/GroupEvent : net/mamoe/mirai/event/events/BotEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getGroup ()Lnet/mamoe/mirai/contact/Group; } public abstract interface class net/mamoe/mirai/event/events/GroupMemberEvent : net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/UserEvent { public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public abstract fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getUser ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/event/events/GroupMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/event/events/MessageEvent { public fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun getPermission ()Lnet/mamoe/mirai/contact/MemberPermission; public fun getSender ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Group; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupMessagePostSendEvent : net/mamoe/mirai/event/events/MessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/GroupMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupMessagePostSendEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Group; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupMessagePreSendEvent : net/mamoe/mirai/event/events/MessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/GroupMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupMessagePreSendEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Group; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/MessageSyncEvent { public fun <init> (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/contact/Member;Ljava/lang/String;I)V public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/contact/Member;Ljava/lang/String;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Group; public fun getTime ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Z public final fun component2 ()Z public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupMuteAllEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupMuteAllEvent;ZZLnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupMuteAllEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getNew ()Ljava/lang/Boolean; public synthetic fun getNew ()Ljava/lang/Object; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun getOrigin ()Ljava/lang/Boolean; public synthetic fun getOrigin ()Ljava/lang/Object; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/event/events/GroupSettingChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/Group; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupNameChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupNameChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupNameChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public synthetic fun getNew ()Ljava/lang/Object; public fun getNew ()Ljava/lang/String; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getOrigin ()Ljava/lang/Object; public fun getOrigin ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/GroupOperableEvent : net/mamoe/mirai/event/events/GroupEvent { public abstract fun getOperator ()Lnet/mamoe/mirai/contact/Member; } public abstract interface class net/mamoe/mirai/event/events/GroupSettingChangeEvent : net/mamoe/mirai/event/BroadcastControllable, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent { public abstract fun getNew ()Ljava/lang/Object; public abstract fun getOrigin ()Ljava/lang/Object; public fun getShouldBroadcast ()Z } public final class net/mamoe/mirai/event/events/GroupTalkativeChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/GroupEvent, net/mamoe/mirai/internal/network/Packet { public fun <init> (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)V public final fun component1 ()Lnet/mamoe/mirai/contact/Group; public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component3 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/GroupTalkativeChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupTalkativeChangeEvent;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupTalkativeChangeEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getNow ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getPrevious ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessageEvent : net/mamoe/mirai/event/events/TempMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/UserMessageEvent { public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessagePostSendEvent : net/mamoe/mirai/event/events/TempMessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/GroupTempMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupTempMessagePostSendEvent;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupTempMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Member; public fun getTarget ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessagePreSendEvent : net/mamoe/mirai/event/events/TempMessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/GroupTempMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/GroupTempMessagePreSendEvent;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/GroupTempMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Member; public fun getTarget ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/GroupTempMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/MessageSyncEvent { public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/NormalMember; public fun getTime ()I } public abstract class net/mamoe/mirai/event/events/ImageUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public final class net/mamoe/mirai/event/events/ImageUploadEvent$Failed : net/mamoe/mirai/event/events/ImageUploadEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Contact; public final fun component2 ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun component3 ()I public final fun component4 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;ILjava/lang/String;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Failed; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/ImageUploadEvent$Failed;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;ILjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Failed; public fun equals (Ljava/lang/Object;)Z public final fun getErrno ()I public final fun getMessage ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/ImageUploadEvent$Succeed : net/mamoe/mirai/event/events/ImageUploadEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Contact; public final fun component2 ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun component3 ()Lnet/mamoe/mirai/message/data/Image; public final fun copy (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Succeed; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/ImageUploadEvent$Succeed;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/message/data/Image;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/ImageUploadEvent$Succeed; public fun equals (Ljava/lang/Object;)Z public final fun getImage ()Lnet/mamoe/mirai/message/data/Image; public fun getSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun hashCode ()I 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberCardChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberCardChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberCardChangeEvent; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getNew ()Ljava/lang/String; public final fun getOrigin ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve : net/mamoe/mirai/event/events/MemberHonorChangeEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2-AVr_HNQ ()I public final fun copy-aLnpm_Q (Lnet/mamoe/mirai/contact/NormalMember;I)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve; public static synthetic fun copy-aLnpm_Q$default (Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve;Lnet/mamoe/mirai/contact/NormalMember;IILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Achieve; public fun equals (Ljava/lang/Object;)Z public fun getHonorType ()I public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose : net/mamoe/mirai/event/events/MemberHonorChangeEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2-AVr_HNQ ()I public final fun copy-aLnpm_Q (Lnet/mamoe/mirai/contact/NormalMember;I)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose; public static synthetic fun copy-aLnpm_Q$default (Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose;Lnet/mamoe/mirai/contact/NormalMember;IILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberHonorChangeEvent$Lose; public fun equals (Ljava/lang/Object;)Z public fun getHonorType ()I public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I 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/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; } public final class net/mamoe/mirai/event/events/MemberJoinEvent$Active : net/mamoe/mirai/event/events/MemberJoinEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Active; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinEvent$Active;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Active; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberJoinEvent$Invite : net/mamoe/mirai/event/events/MemberJoinEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Invite; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinEvent$Invite;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Invite; public fun equals (Ljava/lang/Object;)Z public final fun getInvitor ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberJoinEvent$Retrieve : net/mamoe/mirai/event/events/MemberJoinEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Retrieve; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinEvent$Retrieve;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinEvent$Retrieve; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I 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/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 synthetic fun <init> (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/Long;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun accept ()V public final fun accept (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()Ljava/lang/String; public final fun component4 ()J public final fun component5 ()J public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public final fun component8 ()Ljava/lang/Long; public final synthetic fun copy (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public final fun copy (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/Long;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public static final synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;Ljava/lang/String;Ljava/lang/Long;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getEventId ()J public final fun getFromId ()J public final fun getFromNick ()Ljava/lang/String; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; 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; public final fun getMessage ()Ljava/lang/String; public fun hashCode ()I public final fun ignore (Z)V public final fun ignore (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun ignore$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZILjava/lang/Object;)V public static synthetic fun ignore$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun reject ()V public final fun reject (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (Z)V public final fun reject (ZLjava/lang/String;)V public final fun reject (ZLjava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun reject (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;ILjava/lang/Object;)V public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/MemberJoinRequestEvent;ZLjava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; 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, 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 { public fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)V public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Kick; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Kick;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Kick; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MemberLeaveEvent$Quit : net/mamoe/mirai/event/events/MemberLeaveEvent, 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/MemberLeaveEvent$Quit; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Quit;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberLeaveEvent$Quit; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Member; public final fun component2 ()I public final fun component3 ()Lnet/mamoe/mirai/contact/Member; public final fun copy (Lnet/mamoe/mirai/contact/Member;ILnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/event/events/MemberMuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberMuteEvent;Lnet/mamoe/mirai/contact/Member;ILnet/mamoe/mirai/contact/Member;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberMuteEvent; public fun equals (Ljava/lang/Object;)Z public final fun getDurationSeconds ()I public fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun hashCode ()I 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/event/events/GroupMemberInfoChangeEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component2 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun component3 ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun copy (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;)Lnet/mamoe/mirai/event/events/MemberPermissionChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberPermissionChangeEvent;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/MemberPermission;Lnet/mamoe/mirai/contact/MemberPermission;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberPermissionChangeEvent; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getNew ()Lnet/mamoe/mirai/contact/MemberPermission; public final fun getOrigin ()Lnet/mamoe/mirai/contact/MemberPermission; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Ljava/lang/String; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun component4 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberSpecialTitleChangeEvent; public fun equals (Ljava/lang/Object;)Z public synthetic fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getMember ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getNew ()Ljava/lang/String; public synthetic fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/NormalMember; public final fun getOrigin ()Ljava/lang/String; public fun hashCode ()I 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/GroupMemberInfoChangeEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Member; public final fun component2 ()Lnet/mamoe/mirai/contact/Member; public final fun copy (Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/event/events/MemberUnmuteEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MemberUnmuteEvent;Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Member;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MemberUnmuteEvent; public fun equals (Ljava/lang/Object;)Z public fun getMember ()Lnet/mamoe/mirai/contact/Member; public fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/MessageEvent : net/mamoe/mirai/event/Event, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/internal/network/Packet { public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun getSender ()Lnet/mamoe/mirai/contact/User; public abstract fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getTime ()I } public abstract class net/mamoe/mirai/event/events/MessagePostSendEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public final fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getException ()Ljava/lang/Throwable; public abstract fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; } public abstract class net/mamoe/mirai/event/events/MessagePreSendEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public final fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public abstract fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V } public abstract class net/mamoe/mirai/event/events/MessageRecallEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent { public abstract fun getAuthor ()Lnet/mamoe/mirai/contact/UserOrBot; public abstract fun getAuthorId ()J public abstract fun getMessageIds ()[I public abstract fun getMessageInternalIds ()[I public abstract fun getMessageTime ()I } public final class net/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall : net/mamoe/mirai/event/events/MessageRecallEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()[I public final fun component3 ()[I public final fun component4 ()I public final fun component5 ()J public final fun component6 ()Lnet/mamoe/mirai/contact/Friend; public final fun copy (Lnet/mamoe/mirai/Bot;[I[IIJLnet/mamoe/mirai/contact/Friend;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall;Lnet/mamoe/mirai/Bot;[I[IIJLnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$FriendRecall; public fun equals (Ljava/lang/Object;)Z public fun getAuthor ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getAuthor ()Lnet/mamoe/mirai/contact/UserOrBot; public fun getAuthorId ()J public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessageIds ()[I public fun getMessageInternalIds ()[I public fun getMessageTime ()I public final fun getOperator ()Lnet/mamoe/mirai/contact/Friend; public final fun getOperatorId ()J public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall : net/mamoe/mirai/event/events/MessageRecallEvent, net/mamoe/mirai/event/events/GroupOperableEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()[I public final fun component4 ()[I public final fun component5 ()I public final fun component6 ()Lnet/mamoe/mirai/contact/Member; public final fun component7 ()Lnet/mamoe/mirai/contact/Group; public final fun component8 ()Lnet/mamoe/mirai/contact/NormalMember; public final fun copy (Lnet/mamoe/mirai/Bot;J[I[IILnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall;Lnet/mamoe/mirai/Bot;J[I[IILnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/MessageRecallEvent$GroupRecall; public fun equals (Ljava/lang/Object;)Z public fun getAuthor ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getAuthor ()Lnet/mamoe/mirai/contact/UserOrBot; public fun getAuthorId ()J public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessageIds ()[I public fun getMessageInternalIds ()[I public fun getMessageTime ()I public fun getOperator ()Lnet/mamoe/mirai/contact/Member; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/MessageSyncEvent : net/mamoe/mirai/event/events/MessageEvent, net/mamoe/mirai/event/events/OtherClientEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; } 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 fun accept ()V public final fun accept (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun component1 ()Lnet/mamoe/mirai/Bot; public final fun component2 ()J public final fun component3 ()Ljava/lang/String; public final fun component4 ()J public final fun component5 ()J public final fun component6 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;)Lnet/mamoe/mirai/event/events/NewFriendRequestEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;Lnet/mamoe/mirai/Bot;JLjava/lang/String;JJLjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/NewFriendRequestEvent; public fun equals (Ljava/lang/Object;)Z public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getEventId ()J public final fun getFromGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getFromGroupId ()J public final fun getFromId ()J public final fun getFromNick ()Ljava/lang/String; public final fun getMessage ()Ljava/lang/String; public fun hashCode ()I public final fun reject (Z)V public final fun reject (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;ZILjava/lang/Object;)V public static synthetic fun reject$default (Lnet/mamoe/mirai/event/events/NewFriendRequestEvent;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/NudgeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun component2 ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun component3 ()Lnet/mamoe/mirai/contact/Contact; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/event/events/NudgeEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/NudgeEvent;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/NudgeEvent; public fun equals (Ljava/lang/Object;)Z public final fun getAction ()Ljava/lang/String; public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getFrom ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSuffix ()Ljava/lang/String; public final fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/OtherClientEvent : net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; } public final class net/mamoe/mirai/event/events/OtherClientMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/MessageEvent, net/mamoe/mirai/event/events/OtherClientEvent { public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/OtherClient; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/OtherClientOfflineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/OtherClientEvent { public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;)V public final fun component1 ()Lnet/mamoe/mirai/contact/OtherClient; public final fun copy (Lnet/mamoe/mirai/contact/OtherClient;)Lnet/mamoe/mirai/event/events/OtherClientOfflineEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/OtherClientOfflineEvent;Lnet/mamoe/mirai/contact/OtherClient;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/OtherClientOfflineEvent; public fun equals (Ljava/lang/Object;)Z public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/OtherClientOnlineEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotPassiveEvent, net/mamoe/mirai/event/events/OtherClientEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/OtherClient; public final fun component2 ()Lnet/mamoe/mirai/contact/ClientKind; public final fun copy (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/ClientKind;)Lnet/mamoe/mirai/event/events/OtherClientOnlineEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/OtherClientOnlineEvent;Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/ClientKind;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/OtherClientOnlineEvent; public fun equals (Ljava/lang/Object;)Z public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public final fun getKind ()Lnet/mamoe/mirai/contact/ClientKind; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/event/events/ShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; } public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Failed : net/mamoe/mirai/event/events/ShortVideoUploadEvent { public final fun getErrno ()I public final fun getMessage ()Ljava/lang/String; public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Succeed : net/mamoe/mirai/event/events/ShortVideoUploadEvent { public fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource; public final fun getVideo ()Lnet/mamoe/mirai/message/data/ShortVideo; public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/SignEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet { public fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getRank ()Ljava/lang/Integer; public final fun getSign ()Ljava/lang/String; public final fun getUser ()Lnet/mamoe/mirai/contact/UserOrBot; public final fun hasRank ()Z public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerAddEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/StrangerEvent, net/mamoe/mirai/internal/network/Packet { public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/event/events/StrangerAddEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/StrangerAddEvent;Lnet/mamoe/mirai/contact/Stranger;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/StrangerAddEvent; public fun equals (Ljava/lang/Object;)Z public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/event/events/StrangerEvent : net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/event/events/UserEvent { public fun getBot ()Lnet/mamoe/mirai/Bot; public abstract fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public fun getUser ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getUser ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/event/events/StrangerMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/StrangerEvent, net/mamoe/mirai/event/events/UserMessageEvent { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerMessagePostSendEvent : net/mamoe/mirai/event/events/UserMessagePostSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun component2 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun component3 ()Ljava/lang/Throwable; public final fun component4 ()Lnet/mamoe/mirai/message/MessageReceipt; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/event/events/StrangerMessagePostSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/StrangerMessagePostSendEvent;Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/StrangerMessagePostSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getException ()Ljava/lang/Throwable; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerMessagePreSendEvent : net/mamoe/mirai/event/events/UserMessagePreSendEvent { public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun component2 ()Lnet/mamoe/mirai/message/data/Message; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/event/events/StrangerMessagePreSendEvent; public static synthetic fun copy$default (Lnet/mamoe/mirai/event/events/StrangerMessagePreSendEvent;Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/Message;ILjava/lang/Object;)Lnet/mamoe/mirai/event/events/StrangerMessagePreSendEvent; public fun equals (Ljava/lang/Object;)Z public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun hashCode ()I public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/event/events/StrangerMessageSyncEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/MessageSyncEvent, net/mamoe/mirai/event/events/StrangerEvent { public fun <init> (Lnet/mamoe/mirai/contact/OtherClient;Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/message/data/MessageChain;I)V public fun getBot ()Lnet/mamoe/mirai/Bot; public fun getClient ()Lnet/mamoe/mirai/contact/OtherClient; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getSenderName ()Ljava/lang/String; public fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger; public synthetic fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming; public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public fun getTime ()I } public abstract class net/mamoe/mirai/event/events/StrangerRelationChangeEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/StrangerEvent, net/mamoe/mirai/internal/network/Packet { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; } public final class net/mamoe/mirai/event/events/StrangerRelationChangeEvent$Deleted : net/mamoe/mirai/event/events/StrangerRelationChangeEvent { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;)V } public final class net/mamoe/mirai/event/events/StrangerRelationChangeEvent$Friended : net/mamoe/mirai/event/events/StrangerRelationChangeEvent { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;Lnet/mamoe/mirai/contact/Friend;)V public final fun getFriend ()Lnet/mamoe/mirai/contact/Friend; public fun getStranger ()Lnet/mamoe/mirai/contact/Stranger; } public abstract class net/mamoe/mirai/event/events/TempMessageEvent : net/mamoe/mirai/event/events/AbstractMessageEvent, net/mamoe/mirai/event/events/GroupAwareMessageEvent, net/mamoe/mirai/event/events/UserMessageEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/NormalMember;Lnet/mamoe/mirai/message/data/MessageChain;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getTime ()I } public abstract class net/mamoe/mirai/event/events/TempMessagePostSendEvent : net/mamoe/mirai/event/events/UserMessagePostSendEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/Throwable;Lnet/mamoe/mirai/message/MessageReceipt;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getException ()Ljava/lang/Throwable; public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getReceipt ()Lnet/mamoe/mirai/message/MessageReceipt; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Member; } public abstract class net/mamoe/mirai/event/events/TempMessagePreSendEvent : net/mamoe/mirai/event/events/UserMessagePreSendEvent { public synthetic fun <init> (Lnet/mamoe/mirai/contact/Member;Lnet/mamoe/mirai/message/data/Message;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getGroup ()Lnet/mamoe/mirai/contact/Group; public fun getMessage ()Lnet/mamoe/mirai/message/data/Message; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public fun getTarget ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/User; public fun setMessage (Lnet/mamoe/mirai/message/data/Message;)V } public abstract interface class net/mamoe/mirai/event/events/UserEvent : net/mamoe/mirai/event/events/BotEvent { public abstract fun getUser ()Lnet/mamoe/mirai/contact/User; } public abstract interface class net/mamoe/mirai/event/events/UserMessageEvent : net/mamoe/mirai/event/events/MessageEvent { public abstract fun getSubject ()Lnet/mamoe/mirai/contact/User; } public abstract class net/mamoe/mirai/event/events/UserMessagePostSendEvent : net/mamoe/mirai/event/events/MessagePostSendEvent { } public abstract class net/mamoe/mirai/event/events/UserMessagePreSendEvent : net/mamoe/mirai/event/events/MessagePreSendEvent { public abstract fun getTarget ()Lnet/mamoe/mirai/contact/User; } public final class net/mamoe/mirai/message/MessageEventKt { public static final fun isContextIdenticalWith (Lnet/mamoe/mirai/event/events/MessageEvent;Lnet/mamoe/mirai/event/events/MessageEvent;)Z } public class net/mamoe/mirai/message/MessageReceipt { public static final field Companion Lnet/mamoe/mirai/message/MessageReceipt$Companion; public final fun getSource ()Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing; public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public final fun isToGroup ()Z public final fun quote ()Lnet/mamoe/mirai/message/data/QuoteReply; public fun quoteReply (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun quoteReply (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun quoteReply (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun quoteReply (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun recall ()V public final fun recall (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun recallIn (J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; } public final class net/mamoe/mirai/message/MessageReceipt$Companion { } public final class net/mamoe/mirai/message/MessageReceiptKt { public static final fun getBot (Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/Bot; public static final fun getSourceIds (Lnet/mamoe/mirai/message/MessageReceipt;)[I public static final fun getSourceInternalIds (Lnet/mamoe/mirai/message/MessageReceipt;)[I public static final fun getSourceMessage (Lnet/mamoe/mirai/message/MessageReceipt;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun getSourceTime (Lnet/mamoe/mirai/message/MessageReceipt;)I } public abstract interface class net/mamoe/mirai/message/MessageSerializers { public static final field INSTANCE Lnet/mamoe/mirai/message/MessageSerializers$INSTANCE; public abstract fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; } public final class net/mamoe/mirai/message/MessageSerializers$INSTANCE : net/mamoe/mirai/message/MessageSerializers { public fun getSerializersModule ()Lkotlinx/serialization/modules/SerializersModule; } public final class net/mamoe/mirai/message/action/AsyncRecallResult { public final fun awaitException ()Ljava/lang/Throwable; public final fun awaitException (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun awaitIsSuccess ()Z public final fun awaitIsSuccess (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun getException ()Lkotlinx/coroutines/Deferred; public final fun getExceptionFuture ()Ljava/util/concurrent/CompletableFuture; public final fun isSuccess ()Lkotlinx/coroutines/Deferred; public final fun isSuccessFuture ()Ljava/util/concurrent/CompletableFuture; } public final class net/mamoe/mirai/message/action/BotNudge : net/mamoe/mirai/message/action/Nudge { 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/message/action/BotNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/BotNudge;Lnet/mamoe/mirai/Bot;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/BotNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/Bot; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/action/FriendNudge : net/mamoe/mirai/message/action/UserNudge { 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/message/action/FriendNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/FriendNudge;Lnet/mamoe/mirai/contact/Friend;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/FriendNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/action/MemberNudge : net/mamoe/mirai/message/action/UserNudge { 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/message/action/MemberNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/MemberNudge;Lnet/mamoe/mirai/contact/NormalMember;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/MemberNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/contact/NormalMember; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/message/action/Nudge { public static final field Companion Lnet/mamoe/mirai/message/action/Nudge$Companion; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public static final synthetic fun sendNudge (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/action/Nudge;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sendTo (Lnet/mamoe/mirai/contact/Contact;)Z public final fun sendTo (Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/action/Nudge$Companion { public final synthetic fun sendNudge (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/action/Nudge;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/action/StrangerNudge : net/mamoe/mirai/message/action/UserNudge { public fun <init> (Lnet/mamoe/mirai/contact/Stranger;)V public final fun component1 ()Lnet/mamoe/mirai/contact/Stranger; public final fun copy (Lnet/mamoe/mirai/contact/Stranger;)Lnet/mamoe/mirai/message/action/StrangerNudge; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/action/StrangerNudge;Lnet/mamoe/mirai/contact/Stranger;ILjava/lang/Object;)Lnet/mamoe/mirai/message/action/StrangerNudge; public fun equals (Ljava/lang/Object;)Z public fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; public fun hashCode ()I public fun toString ()Ljava/lang/String; } public abstract class net/mamoe/mirai/message/action/UserNudge : net/mamoe/mirai/message/action/Nudge { public abstract fun getTarget ()Lnet/mamoe/mirai/contact/UserOrBot; } public abstract interface class net/mamoe/mirai/message/code/CodableMessage : net/mamoe/mirai/message/data/Message { public fun serializeToMiraiCode ()Ljava/lang/String; } public final class net/mamoe/mirai/message/code/MiraiCode { public static final field INSTANCE Lnet/mamoe/mirai/message/code/MiraiCode; public static final fun deserializeMiraiCode (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun deserializeMiraiCode (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeMiraiCode$default (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public final synthetic fun parseMiraiCode1 (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun parseMiraiCode1$default (Lnet/mamoe/mirai/message/code/MiraiCode;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun serializeToMiraiCode (Ljava/lang/Iterable;)Ljava/lang/String; public static final fun serializeToMiraiCode (Ljava/util/Iterator;)Ljava/lang/String; public static final fun serializeToMiraiCode (Lkotlin/sequences/Sequence;)Ljava/lang/String; public static final fun serializeToMiraiCode (Lnet/mamoe/mirai/message/code/CodableMessage;)Ljava/lang/String; public static final fun serializeToMiraiCode ([Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/String; } public abstract class net/mamoe/mirai/message/data/AbstractMessageKey : net/mamoe/mirai/message/data/MessageKey { public fun <init> (Lkotlin/jvm/functions/Function1;)V public fun getSafeCast ()Lkotlin/jvm/functions/Function1; } public abstract class net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey : net/mamoe/mirai/message/data/AbstractMessageKey, net/mamoe/mirai/message/data/MessageKey { public fun <init> (Lnet/mamoe/mirai/message/data/MessageKey;Lkotlin/jvm/functions/Function1;)V } public final class net/mamoe/mirai/message/data/AbstractServiceMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/At : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field Companion Lnet/mamoe/mirai/message/data/At$Companion; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (IJLkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (J)V public final fun component1 ()J public fun contentToString ()Ljava/lang/String; public final fun copy (J)Lnet/mamoe/mirai/message/data/At; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/At;JILjava/lang/Object;)Lnet/mamoe/mirai/message/data/At; public fun equals (Ljava/lang/Object;)Z public fun followedBy (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun getDisplay (Lnet/mamoe/mirai/contact/Group;)Ljava/lang/String; public final fun getTarget ()J public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/At;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/At$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/At$$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/At; 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/At;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/At$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/AtAll : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field INSTANCE Lnet/mamoe/mirai/message/data/AtAll; public static final field SERIAL_NAME Ljava/lang/String; public static final field display Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public fun equals (Ljava/lang/Object;)Z public synthetic fun followedBy (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun hashCode ()I public fun serializeToMiraiCode ()Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; public fun toString ()Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/Audio : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/Audio$Key; public fun contentToString ()Ljava/lang/String; public abstract fun getCodec ()Lnet/mamoe/mirai/message/data/AudioCodec; public abstract fun getExtraData ()[B public abstract fun getFileMd5 ()[B public abstract fun getFileSize ()J public abstract fun getFilename ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/Audio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/AudioCodec : java/lang/Enum { public static final field AMR Lnet/mamoe/mirai/message/data/AudioCodec; public static final field Companion Lnet/mamoe/mirai/message/data/AudioCodec$Companion; public static final field SILK Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec; public static final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun getFormatName ()Ljava/lang/String; public final fun getId ()I public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public static fun values ()[Lnet/mamoe/mirai/message/data/AudioCodec; } public final class net/mamoe/mirai/message/data/AudioCodec$Companion { public final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/ConstrainSingle : net/mamoe/mirai/message/data/SingleMessage { public abstract fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; } public final class net/mamoe/mirai/message/data/CustomMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/CustomMessage$Companion$CustomMessageFullDataDeserializeInternalException : java/lang/RuntimeException { public fun <init> (Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/message/data/CustomMessage$Companion$CustomMessageFullDataDeserializeUserException : java/lang/RuntimeException { public fun <init> ([BLjava/lang/Throwable;)V public final fun getBody ()[B } public abstract class net/mamoe/mirai/message/data/CustomMessage$JsonSerializerFactory : net/mamoe/mirai/message/data/CustomMessage$Factory { public fun <init> (Ljava/lang/String;)V public fun dump (Lnet/mamoe/mirai/message/data/CustomMessage;)[B public fun getJson ()Lkotlinx/serialization/json/Json; public fun load ([B)Lnet/mamoe/mirai/message/data/CustomMessage; public abstract fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class net/mamoe/mirai/message/data/CustomMessage$ProtoBufSerializerFactory : net/mamoe/mirai/message/data/CustomMessage$Factory { public fun <init> (Ljava/lang/String;)V public fun dump (Lnet/mamoe/mirai/message/data/CustomMessage;)[B public fun load ([B)Lnet/mamoe/mirai/message/data/CustomMessage; public abstract fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/CustomMessageKt { } 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 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/DirectSizeAccess, net/mamoe/mirai/message/data/DirectToStringAccess, net/mamoe/mirai/message/data/MessageChain { public static final field INSTANCE Lnet/mamoe/mirai/message/data/EmptyMessageChain; public fun contains (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun containsAll (Ljava/util/Collection;)Z public fun contentToString ()Ljava/lang/String; public synthetic fun get (I)Ljava/lang/Object; public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun getHasConstrainSingle ()Z public fun getSize ()I public fun indexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public fun lastIndexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun listIterator ()Ljava/util/ListIterator; public fun listIterator (I)Ljava/util/ListIterator; public fun serializeToMiraiCode ()Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; public fun subList (II)Ljava/util/List; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/Face : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field AI_NI I public static final field AI_QING I public static final field AI_XIN I public static final field AN_ZHONG_GUAN_CHA I public static final field AO_MAN I public static final field BAI_TUO I public static final field BAI_XIE I public static final field BAI_YAN I public static final field BANG_BANG_TANG I public static final field BAN_ZHUAN_ZHONG I public static final field BAO_BAO I public static final field BAO_FU I public static final field BAO_JI I public static final field BAO_JIN I public static final field BAO_QUAN I public static final field BIAN_BIAN I public static final field BIAN_PAO I public static final field BIAN_XING I public static final field BIAO_LEI I public static final field BI_SHI I public static final field BI_XIN I public static final field BI_ZUI I public static final field BO_BO I public static final field BU I public static final field BU_KAI_XIN I public static final field CAI I public static final field CAI_DAO I public static final field CAI_GOU I public static final field CANG_SANG I public static final field CA_HAN I public static final field CENG_YI_CENG I public static final field CHA I public static final field CHAN_DOU I public static final field CHAO_FENG I public static final field CHAO_PIAO I public static final field CHA_JIN I public static final field CHE_YI_CHE I public static final field CHI I public static final field CHI_GUA I public static final field CHI_TANG I public static final field CHONG_BAI I public static final field CUO_HAO I public static final field Companion Lnet/mamoe/mirai/message/data/Face$Companion; public static final field DAN I public static final field DAN_GAO I public static final field DAO I public static final field DA_CALL I public static final field DA_KU I public static final field DA_LIAN I public static final field DA_XIAO I public static final field DA_YUAN_ZHONG I public static final field DA_ZHAN_HONG_TU I public static final field DA_ZHAO_HU I public static final field DENG_LONG I public static final field DE_YI I public static final field DIAN_ZAN I public static final field DIAO_XIE I public static final field DING_GUA_GUA I public static final field DUI_HAO I public static final field E I public static final field FAN I public static final field FA_DAI I public static final field FA_DOU I public static final field FA_NU I public static final field FEI_JI I public static final field FEI_WEN I public static final field FEN_DOU I public static final field FO_XI I public static final field FU_LUO_BO I public static final field GAN_BEI I public static final field GAN_GA I public static final field GONG_XI I public static final field GOU_YIN I public static final field GU_ZHANG I public static final field HAI_PA I public static final field HAI_XIU I public static final field HAN I public static final field HAN_XIAO I public static final field HAO I public static final field HAO_BANG I public static final field HAO_SHAN I public static final field HA_QIAN I public static final field HENG I public static final field HE_CAI I public static final field HE_HE_DA I public static final field HE_NAI I public static final field HE_XIE I public static final field HONG_BAO I public static final field HONG_BAO_BAO I public static final field HONG_BAO_DUO_DUO I public static final field HUAI_XIAO I public static final field HUA_CHI I public static final field HUA_DUO_LIAN I public static final field HUI_SHOU I public static final field HUI_TOU I public static final field HU_HU_SHENG_WEI I public static final field HU_LIAN I public static final field JIA_YI I public static final field JIA_YOU I public static final field JIA_YOU_BAO_BAO I public static final field JIA_YOU_BI_SHENG I public static final field JIE_WU I public static final field JING_DAI I public static final field JING_KONG I public static final field JING_LI I public static final field JING_XI I public static final field JING_XIA I public static final field JING_YA I public static final field JI_DONG I public static final field JI_E I public static final field JI_ZHANG I public static final field JU_HUA I public static final field JU_JUE I public static final field JU_PAI_PAI I public static final field KAI_QIANG I public static final field KA_FEI I public static final field KEN_TOU I public static final field KE_AI I public static final field KE_DAO_LE I public static final field KE_LIAN I public static final field KE_TOU I public static final field KOU_BI I public static final field KOU_ZHAO_HU_TI I public static final field KU I public static final field KUAI_KU_LE I public static final field KUANG_XIAO I public static final field KUN I public static final field KU_LOU I public static final field K_GE I public static final field K歌 I public static final field LAN_QIU I public static final field LAO_SE_PI I public static final field LA_JIAO_JIANG I public static final field LA_YAN_JING I public static final field LEI_BEN I public static final field LENG_HAN I public static final field LENG_MO I public static final field LIAO_YI_LIAO I public static final field LIU_HAN I public static final field LIU_LEI I public static final field LI_WU I public static final field MAI_MENG I public static final field MANG_DAO_FEI_QI I public static final field MEI_GUI I public static final field MIAN_WU_BIAO_QING I public static final field MIAO_MIAO I public static final field MING_BAI I public static final field MO_GUI_XIAO I public static final field MO_JIN_LI I public static final field MO_YU I public static final field NAN_GUO I public static final field NAO_KUO_TENG I public static final field NA_DAO_HONG_BAO I public static final field NIU_A I public static final field NIU_QI_CHONG_TIAN I public static final field NI_ZHEN_BANG_BANG I public static final field NO I public static final field O I public static final field OK I public static final field OU_HUO I public static final field O_YO I public static final field PAI_SHOU I public static final field PAI_TOU I public static final field PAI_ZHUO I public static final field PANG_SAN_JIN I public static final field PEN_LIAN I public static final field PEN_XIE I public static final field PIAO_CHONG I public static final field PIE_ZUI I public static final field PING_PANG I public static final field PI_JIU I public static final field QIAO_DA I public static final field QIAO_KAI_XIN I public static final field QIA_YI_QIA I public static final field QING I public static final field QING_ZHU I public static final field QIN_QIN I public static final field QIU_DA_LE I public static final field QIU_HONG_BAO I public static final field QI_DAI I public static final field QI_DAO I public static final field QUAN_TOU I public static final field RANG_WO_KANG_KANG I public static final field RENG_GOU I public static final field RE_HUA_LE I public static final field SAO_RAO I public static final field SE I public static final field SERIAL_NAME Ljava/lang/String; public static final field SHAN_DIAN I public static final field SHAN_LIAN I public static final field SHENG_LI I public static final field SHENG_QI I public static final field SHENG_RI_KUAI_LE I public static final field SHI_AI I public static final field SHOU_QIANG I public static final field SHUAI I public static final field SHUAI_TOU I public static final field SHUANG_XI I public static final field SHUI I public static final field SONG_HUA I public static final field SUAN_Q I public static final field TAI_NAN_LE I public static final field TAI_YANG I public static final field TIAN_PING I public static final field TIAN_YI_TIAN I public static final field TIAO_PI I public static final field TIAO_SHENG I public static final field TIAO_TIAO I public static final field TOU_KAN I public static final field TOU_TU I public static final field TOU_XIAO I public static final field TOU_ZHUANG_JI I public static final field TU I public static final field TUO_LIAN I public static final field TUO_SAI I public static final field WANG_WANG I public static final field WAN_CHENG I public static final field WEI_QU I public static final field WEI_XIAO I public static final field WEN_HAO_LIAN I public static final field WO_BU_KAN I public static final field WO_FANG_LE I public static final field WO_MEI_SHI I public static final field WO_SHOU I public static final field WO_SUAN_LE I public static final field WO_XIANG_KAI_LE I public static final field WO_ZUI_MEI I public static final field WU_LIAN I public static final field WU_LIAO I public static final field WU_NAI I public static final field WU_YAN_XIAO I public static final field XIA I public static final field XIAN_QI I public static final field XIAN_WEN I public static final field XIAO_JIU_JIE I public static final field XIAO_KU I public static final field XIAO_YANG_ER I public static final field XIE_HONG_BAO I public static final field XIE_YAN_XIAO I public static final field XIN_NIAN_YAN_HUA I public static final field XIN_SUI I public static final field XI_GUA I public static final field XU I public static final field YANG_TUO I public static final field YAN_HUA I public static final field YAO I public static final field YIN_XIAN I public static final field YI_WEN I public static final field YONG_BAO I public static final field YOU_BAI_NIAN I public static final field YOU_HENG_HENG I public static final field YOU_LING I public static final field YOU_QIN_QIN I public static final field YOU_TAI_JI I public static final field YOU_XIAN I public static final field YUAN_BAO I public static final field YUAN_LIANG I public static final field YUE_LIANG I public static final field YUN I public static final field ZAI_JIAN I public static final field ZAN I public static final field ZHA_DAN I public static final field ZHA_YAN_JING I public static final field ZHENG_YAN I public static final field ZHEN_HAO I public static final field ZHEN_JING I public static final field ZHE_MO I public static final field ZHOU_MA I public static final field ZHUAI_ZHA_TIAN I public static final field ZHUAN_QUAN I public static final field ZHUA_KUANG I public static final field ZHU_TOU I public static final field ZI_XI_FEN_XI I public static final field ZI_YA I public static final field ZUO_BAI_NIAN I public static final field ZUO_HENG_HENG I public static final field ZUO_QIN_QIN I public static final field ZUO_TAI_JI I public static final field ZU_QIU I public static final field doge I public static final field emm I public static final field names [Ljava/lang/String; public static final field 不 I public static final field 不开心 I public static final field 举牌牌 I public static final field 乒乓 I public static final field 亲亲 I public static final field 仔细分析 I public static final field 佛系 I public static final field 你真棒棒 I public static final field 便便 I public static final field 偷看 I public static final field 偷笑 I public static final field 傲慢 I public static final field 元宝 I public static final field 再见 I public static final field 冷汗 I public static final field 冷漠 I public static final field 凋谢 I public static final field 击掌 I public static final field 刀 I public static final field 加一 I public static final field 加油 I public static final field 加油必胜 I public static final field 加油抱抱 I public static final field 勾引 I public static final field 卖萌 I public static final field 原谅 I public static final field 双喜 I public static final field 发呆 I public static final field 发怒 I public static final field 发抖 I public static final field 变形 I public static final field 口罩护体 I public static final field 可怜 I public static final field 可爱 I public static final field 右亲亲 I public static final field 右哼哼 I public static final field 右太极 I public static final field 右拜年 I public static final field 吃 I public static final field 吃瓜 I public static final field 吃糖 I public static final field 吐 I public static final field 吓 I public static final field 呃 I public static final field 呲牙 I public static final field 呵呵哒 I public static final field 咒骂 I public static final field 咖啡 I public static final field 哈欠 I public static final field 哦 I public static final field 哦哟 I public static final field 哼 I public static final field 啃头 I public static final field 啤酒 I public static final field 啵啵 I public static final field 喝奶 I public static final field 喝彩 I public static final field 喵喵 I public static final field 喷脸 I public static final field 喷血 I public static final field 嗑到了 I public static final field 嘘 I public static final field 嘲讽 I public static final field 回头 I public static final field 困 I public static final field 坏笑 I public static final field 大哭 I public static final field 大展宏兔 I public static final field 大怨种 I public static final field 大笑 I public static final field 太南了 I public static final field 太阳 I public static final field 头撞击 I public static final field 头秃 I public static final field 奋斗 I public static final field 好 I public static final field 好棒 I public static final field 好闪 I public static final field 委屈 I public static final field 嫌弃 I public static final field 完成 I public static final field 害怕 I public static final field 害羞 I public static final field 对号 I public static final field 小样儿 I public static final field 小纠结 I public static final field 尴尬 I public static final field 崇拜 I public static final field 左亲亲 I public static final field 左哼哼 I public static final field 左太极 I public static final field 左拜年 I public static final field 差劲 I public static final field 干杯 I public static final field 幽灵 I public static final field 庆祝 I public static final field 开枪 I public static final field 得意 I public static final field 微笑 I public static final field 心碎 I public static final field 忙到飞起 I public static final field 快哭了 I public static final field 怄火 I public static final field 恭喜 I public static final field 悠闲 I public static final field 惊吓 I public static final field 惊呆 I public static final field 惊喜 I public static final field 惊恐 I public static final field 惊讶 I public static final field 憨笑 I public static final field 我不看 I public static final field 我想开了 I public static final field 我方了 I public static final field 我最美 I public static final field 我没事 I public static final field 我酸了 I public static final field 扇脸 I public static final field 手枪 I public static final field 打call I public static final field 打招呼 I public static final field 打脸 I public static final field 扔狗 I public static final field 托脸 I public static final field 托腮 I public static final field 扯一扯 I public static final field 抓狂 I public static final field 折磨 I public static final field 抠鼻 I public static final field 抱抱 I public static final field 抱拳 I public static final field 拍头 I public static final field 拍手 I public static final field 拍桌 I public static final field 拒绝 I public static final field 拜托 I public static final field 拜谢 I public static final field 拥抱 I public static final field 拳头 I public static final field 拽炸天 I public static final field 拿到红包 I public static final field 挥手 I public static final field 捂脸 I public static final field 掐一掐 I public static final field 握手 I public static final field 搬砖中 I public static final field 摸锦鲤 I public static final field 摸鱼 I public static final field 撇嘴 I public static final field 撩一撩 I public static final field 擦汗 I public static final field 敬礼 I public static final field 敲开心 I public static final field 敲打 I public static final field 斜眼笑 I public static final field 新年烟花 I public static final field 无奈 I public static final field 无眼笑 I public static final field 无聊 I public static final field 明白 I public static final field 晕 I public static final field 暗中观察 I public static final field 暴击 I public static final field 月亮 I public static final field 期待 I public static final field 棒棒糖 I public static final field 比心 I public static final field 求红包 I public static final field 汗 I public static final field 汪汪 I public static final field 沧桑 I public static final field 河蟹 I public static final field 泪奔 I public static final field 流汗 I public static final field 流泪 I public static final field 激动 I public static final field 灯笼 I public static final field 炸弹 I public static final field 点赞 I public static final field 烟花 I public static final field 热化了 I public static final field 爆筋 I public static final field 爱你 I public static final field 爱心 I public static final field 爱情 I public static final field 牛啊 I public static final field 牛气冲天 I public static final field 狂笑 I public static final field 猪头 I public static final field 献吻 I public static final field 玫瑰 I public static final field 瓢虫 I public static final field 生日快乐 I public static final field 生气 I public static final field 甩头 I public static final field 疑问 I public static final field 白眼 I public static final field 真好 I public static final field 眨眼睛 I public static final field 睁眼 I public static final field 睡 I public static final field 磕头 I public static final field 示爱 I public static final field 礼物 I public static final field 祈祷 I public static final field 福萝卜 I public static final field 笑哭 I public static final field 篮球 I public static final field 糊脸 I public static final field 糗大了 I public static final field 红包 I public static final field 红包包 I public static final field 红包多多 I public static final field 羊驼 I public static final field 老色痞 I public static final field 胖三斤 I public static final field 胜利 I public static final field 脑阔疼 I public static final field 舔一舔 I public static final field 舔屏 I public static final field 色 I public static final field 花朵脸 I public static final field 花痴 I public static final field 茶 I public static final field 药 I public static final field 菊花 I public static final field 菜刀 I public static final field 菜狗 I public static final field 虎虎生威 I public static final field 蛋 I public static final field 蛋糕 I public static final field 街舞 I public static final field 衰 I public static final field 西瓜 I public static final field 让我康康 I public static final field 请 I public static final field 调皮 I public static final field 谢红包 I public static final field 豹富 I public static final field 赞 I public static final field 足球 I public static final field 跳绳 I public static final field 跳跳 I public static final field 踩 I public static final field 蹭一蹭 I public static final field 转圈 I public static final field 辣椒酱 I public static final field 辣眼睛 I public static final field 送花 I public static final field 鄙视 I public static final field 酷 I public static final field 酸Q I public static final field 钞票 I public static final field 错号 I public static final field 闪电 I public static final field 闭嘴 I public static final field 问号脸 I public static final field 阴险 I public static final field 难过 I public static final field 震惊 I public static final field 面无表情 I public static final field 鞭炮 I public static final field 顶呱呱 I public static final field 颤抖 I public static final field 飙泪 I public static final field 飞吻 I public static final field 飞机 I public static final field 饥饿 I public static final field 饭 I public static final field 骚扰 I public static final field 骷髅 I public static final field 魔鬼笑 I public static final field 鼓掌 I public fun <init> (I)V public synthetic fun <init> (IILkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun component1 ()I public fun contentToString ()Ljava/lang/String; public final fun copy (I)Lnet/mamoe/mirai/message/data/Face; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/Face;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Face; public fun equals (Ljava/lang/Object;)Z public final fun getId ()I public final fun getName ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/Face;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/Face$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Face$$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/Face; 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/Face;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/Face$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/FileMessage : 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/FileMessage$Key; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public static fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; public abstract fun getId ()Ljava/lang/String; public abstract fun getInternalId ()I public fun getKey ()Lnet/mamoe/mirai/message/data/FileMessage$Key; public synthetic fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getName ()Ljava/lang/String; public abstract fun getSize ()J public fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/contact/file/AbsoluteFile; public abstract fun toAbsoluteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;)Lnet/mamoe/mirai/utils/RemoteFile; public fun toRemoteFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun toRemoteFile$suspendImpl (Lnet/mamoe/mirai/message/data/FileMessage;Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/FileMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; public final fun create (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; } public final class net/mamoe/mirai/message/data/FileMessage$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/FileMessage$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/FileMessage; 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/FileMessage;)V } public final class net/mamoe/mirai/message/data/FlashImage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/HummerMessage, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/FlashImage$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILnet/mamoe/mirai/message/data/Image;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/message/data/Image;)V public final fun component1 ()Lnet/mamoe/mirai/message/data/Image; public fun contentToString ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/FlashImage;Lnet/mamoe/mirai/message/data/Image;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FlashImage; public fun equals (Ljava/lang/Object;)Z public static final fun from (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FlashImage; public static final fun from (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; public final fun getImage ()Lnet/mamoe/mirai/message/data/Image; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public fun hashCode ()I public fun serializeToMiraiCode ()Ljava/lang/String; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/FlashImage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/FlashImage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/FlashImage$$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/FlashImage; 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/FlashImage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/FlashImage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun from (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FlashImage; public final fun from (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/FlashImageKt { public static final synthetic fun FlashImage (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FlashImage; public static final synthetic fun flash (Lnet/mamoe/mirai/message/data/Image;)Lnet/mamoe/mirai/message/data/FlashImage; } public final class net/mamoe/mirai/message/data/ForwardMessage : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/ForwardMessage$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/util/List; public fun contentToString ()Ljava/lang/String; public final fun copy (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/ForwardMessage;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public fun equals (Ljava/lang/Object;)Z public final fun getBrief ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getNodeList ()Ljava/util/List; public final fun getPreview ()Ljava/util/List; public final fun getSource ()Ljava/lang/String; public final fun getSummary ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/ForwardMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/ForwardMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/ForwardMessage$$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/ForwardMessage; 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/ForwardMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy { public static final field Default Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy$Default; public fun generateBrief (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; public fun generatePreview (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/util/List; public fun generateSource (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; public fun generateSummary (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; public fun generateTitle (Lnet/mamoe/mirai/message/data/RawForwardMessage;)Ljava/lang/String; } public final class net/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy$Default : net/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy { } public final class net/mamoe/mirai/message/data/ForwardMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/ForwardMessage$Node : net/mamoe/mirai/message/data/ForwardMessage$INode { public static final field Companion Lnet/mamoe/mirai/message/data/ForwardMessage$Node$Companion; public synthetic fun <init> (IJILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (JILjava/lang/String;Lnet/mamoe/mirai/message/data/Message;)V public fun <init> (JILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;)V public final fun component1 ()J public final fun component2 ()I public final fun component3 ()Ljava/lang/String; public final fun component4 ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun copy (JILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/ForwardMessage$Node; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/ForwardMessage$Node;JILjava/lang/String;Lnet/mamoe/mirai/message/data/MessageChain;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage$Node; public fun equals (Ljava/lang/Object;)Z public fun getMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSenderId ()J public fun getSenderName ()Ljava/lang/String; public fun getTime ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/ForwardMessage$Node;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/ForwardMessage$Node$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/ForwardMessage$Node$$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/ForwardMessage$Node; 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/ForwardMessage$Node;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/ForwardMessage$Node$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/ForwardMessageBuilder : java/util/List, kotlin/jvm/internal/markers/KMutableList { public fun <init> (Lnet/mamoe/mirai/contact/Contact;)V public fun <init> (Lnet/mamoe/mirai/contact/Contact;I)V public synthetic fun add (ILjava/lang/Object;)V public fun add (ILnet/mamoe/mirai/message/data/ForwardMessage$INode;)V public final fun add (JLjava/lang/String;ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (JLjava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (JLjava/lang/String;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (JLjava/lang/String;Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public synthetic fun add (Ljava/lang/Object;)Z public final fun add (Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun add (Lnet/mamoe/mirai/event/events/MessageEvent;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public fun add (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)Z public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;JLjava/lang/String;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;JLjava/lang/String;Lnet/mamoe/mirai/message/data/Message;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;Lnet/mamoe/mirai/contact/UserOrBot;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public static synthetic fun add$default (Lnet/mamoe/mirai/message/data/ForwardMessageBuilder;Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public fun addAll (ILjava/util/Collection;)Z public fun addAll (Ljava/util/Collection;)Z public final fun at (II)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun at (JI)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun at (Lnet/mamoe/mirai/contact/User;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun at (Lnet/mamoe/mirai/contact/UserOrBot;I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun build ()Lnet/mamoe/mirai/message/data/ForwardMessage; public fun clear ()V public final fun contains (Ljava/lang/Object;)Z public fun contains (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)Z public fun containsAll (Ljava/util/Collection;)Z public synthetic fun get (I)Ljava/lang/Object; public fun get (I)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public final fun getContext ()Lnet/mamoe/mirai/contact/Contact; public final fun getCurrentTime ()I public final fun getDisplayStrategy ()Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy; public fun getSize ()I public final fun indexOf (Ljava/lang/Object;)I public fun indexOf (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public final fun lastIndexOf (Ljava/lang/Object;)I public fun lastIndexOf (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)I public fun listIterator ()Ljava/util/ListIterator; public fun listIterator (I)Ljava/util/ListIterator; public final fun named (ILjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (JLjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (Lnet/mamoe/mirai/contact/User;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (Lnet/mamoe/mirai/contact/UserOrBot;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public synthetic fun remove (I)Ljava/lang/Object; public final fun remove (I)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public final fun remove (Ljava/lang/Object;)Z public fun remove (Lnet/mamoe/mirai/message/data/ForwardMessage$INode;)Z public fun removeAll (Ljava/util/Collection;)Z public fun removeAt (I)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public fun retainAll (Ljava/util/Collection;)Z public final fun says (ILjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (ILnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (JLjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (JLkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (JLnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/Bot;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/Bot;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/User;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/User;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/User;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/UserOrBot;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/contact/UserOrBot;Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object; public fun set (ILnet/mamoe/mirai/message/data/ForwardMessage$INode;)Lnet/mamoe/mirai/message/data/ForwardMessage$INode; public final fun setCurrentTime (I)V public final fun setDisplayStrategy (Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)V public final fun size ()I public fun subList (II)Ljava/util/List; public fun toArray ()[Ljava/lang/Object; public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; public final fun toRawForwardMessage ()Lnet/mamoe/mirai/message/data/RawForwardMessage; } public final class net/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode : net/mamoe/mirai/message/data/ForwardMessage$INode { public field messageChain Lnet/mamoe/mirai/message/data/MessageChain; public final fun at (I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public fun getMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getSenderId ()J public fun getSenderName ()Ljava/lang/String; public fun getTime ()I public final fun message (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun message (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun named (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun says (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun says (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder; public final fun sender (Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun sender (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun senderId (I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun senderId (J)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public final fun senderName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public fun setMessageChain (Lnet/mamoe/mirai/message/data/MessageChain;)V public fun setSenderId (J)V public fun setSenderName (Ljava/lang/String;)V public fun setTime (I)V public final fun time (I)Lnet/mamoe/mirai/message/data/ForwardMessageBuilder$BuilderNode; public fun toString ()Ljava/lang/String; } public abstract interface annotation class net/mamoe/mirai/message/data/ForwardMessageDsl : java/lang/annotation/Annotation { } public final class net/mamoe/mirai/message/data/ForwardMessageKt { public static final synthetic fun buildForwardMessage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final synthetic fun buildForwardMessage (Lnet/mamoe/mirai/event/events/MessageEvent;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun buildForwardMessage$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun buildForwardMessage$default (Lnet/mamoe/mirai/event/events/MessageEvent;Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Ljava/lang/Iterable;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;I)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;I)Lnet/mamoe/mirai/message/data/ForwardMessage; public static final fun toForwardMessage (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun toForwardMessage$default (Ljava/lang/Iterable;Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun toForwardMessage$default (Lnet/mamoe/mirai/message/data/Message;JLjava/lang/String;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; public static synthetic fun toForwardMessage$default (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/User;ILnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ForwardMessage; } public final class net/mamoe/mirai/message/data/HummerMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/Image : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/Message, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/Image$Key; public static final field SERIAL_NAME Ljava/lang/String; public static fun fromId (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public abstract fun getHeight ()I public abstract fun getImageId ()Ljava/lang/String; public static fun getImageIdRegex ()Lkotlin/text/Regex; public static fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public static fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; public abstract fun getImageType ()Lnet/mamoe/mirai/message/data/ImageType; public fun getMd5 ()[B public abstract fun getSize ()J public abstract fun getWidth ()I public fun isEmoji ()Z public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z public static fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z public static fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public static fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/Image$AsStringSerializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Image$AsStringSerializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Image; 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/Image;)V } public final class net/mamoe/mirai/message/data/Image$Builder { public static final field Companion Lnet/mamoe/mirai/message/data/Image$Builder$Companion; public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun build ()Lnet/mamoe/mirai/message/data/Image; public final fun getHeight ()I public final fun getImageId ()Ljava/lang/String; public final fun getSize ()J public final fun getType ()Lnet/mamoe/mirai/message/data/ImageType; public final fun getWidth ()I public final fun isEmoji ()Z public static final fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; public final fun setEmoji (Z)V public final fun setHeight (I)V public final fun setImageId (Ljava/lang/String;)V public final fun setSize (J)V public final fun setType (Lnet/mamoe/mirai/message/data/ImageType;)V public final fun setWidth (I)V } public final class net/mamoe/mirai/message/data/Image$Builder$Companion { public final fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; } public final class net/mamoe/mirai/message/data/Image$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public static final field SERIAL_NAME Ljava/lang/String; public final fun calculateImageMd5ByImageId (Ljava/lang/String;)[B public final fun fromId (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun getImageIdRegex ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex1 ()Lkotlin/text/Regex; public final fun getImageResourceIdRegex2 ()Lkotlin/text/Regex; public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJ)Z public final fun isUploaded (Lnet/mamoe/mirai/Bot;[BJLkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;)Z public final fun isUploaded (Lnet/mamoe/mirai/message/data/Image;Lnet/mamoe/mirai/Bot;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun newBuilder (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image$Builder; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;)Ljava/lang/String; public final fun queryUrl (Lnet/mamoe/mirai/message/data/Image;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/Image$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Image$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Image; 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/Image;)V } public final class net/mamoe/mirai/message/data/ImageType : java/lang/Enum { public static final field APNG Lnet/mamoe/mirai/message/data/ImageType; public static final field BMP Lnet/mamoe/mirai/message/data/ImageType; public static final field Companion Lnet/mamoe/mirai/message/data/ImageType$Companion; public static final field GIF Lnet/mamoe/mirai/message/data/ImageType; public static final field JPG Lnet/mamoe/mirai/message/data/ImageType; public static final field PNG Lnet/mamoe/mirai/message/data/ImageType; public static final field UNKNOWN Lnet/mamoe/mirai/message/data/ImageType; public final fun getFormatName ()Ljava/lang/String; public static final fun match (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static final fun matchOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public static fun values ()[Lnet/mamoe/mirai/message/data/ImageType; } public final class net/mamoe/mirai/message/data/ImageType$Companion { public final fun match (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public final fun matchOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ImageType; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/LightApp : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/RichMessage { public static final field Key Lnet/mamoe/mirai/message/data/LightApp$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public final fun copy (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/LightApp; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/LightApp;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/LightApp; public fun equals (Ljava/lang/Object;)Z public fun getContent ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/LightApp;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/LightApp$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/LightApp$$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/LightApp; 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/LightApp;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/LightApp$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/MarketFace : net/mamoe/mirai/message/data/HummerMessage { public static final field Key Lnet/mamoe/mirai/message/data/MarketFace$Key; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public abstract fun getId ()I public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getName ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MarketFace$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/Message { public static final field Companion Lnet/mamoe/mirai/message/data/Message$Companion; public fun contentEquals (Ljava/lang/String;Z)Z public fun contentEquals (Lnet/mamoe/mirai/message/data/Message;Z)Z public fun contentEquals (Lnet/mamoe/mirai/message/data/Message;ZZ)Z public static synthetic fun contentEquals$default (Lnet/mamoe/mirai/message/data/Message;Ljava/lang/String;ZILjava/lang/Object;)Z public static synthetic fun contentEquals$default (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;ZILjava/lang/Object;)Z public static synthetic fun contentEquals$default (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/message/data/Message;ZZILjava/lang/Object;)Z public abstract fun contentToString ()Ljava/lang/String; public synthetic fun followedBy (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Ljava/lang/CharSequence;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lkotlin/sequences/Sequence;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus (Lnet/mamoe/mirai/message/data/SingleMessage;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plus ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public fun plusIterableString (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/Message$Companion { } public abstract interface class net/mamoe/mirai/message/data/MessageChain : java/util/List, java/util/RandomAccess, kotlin/jvm/internal/markers/KMappedMarker, net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/Message { public static final field Companion Lnet/mamoe/mirai/message/data/MessageChain$Companion; public fun contains (Lnet/mamoe/mirai/message/data/MessageKey;)Z public static fun deserializeFromJsonString (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public static fun deserializeFromJsonString (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public static fun deserializeFromMiraiCode (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static fun deserializeFromMiraiCode (Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public fun get (Lnet/mamoe/mirai/message/data/MessageKey;)Lnet/mamoe/mirai/message/data/SingleMessage; public static fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/lang/String; public static fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/json/Json;)Ljava/lang/String; public static fun serializeToString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/StringFormat;)Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MessageChain$Companion { public final fun deserializeFromJsonString (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun deserializeFromJsonString (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeFromJsonString$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Ljava/lang/String;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun deserializeFromMiraiCode (Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun deserializeFromMiraiCode (Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeFromMiraiCode$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public static synthetic fun deserializeFromMiraiCode$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Lnet/mamoe/mirai/message/data/MessageChain;Ljava/lang/String;Lnet/mamoe/mirai/contact/Contact;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MessageChain; public final synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageChain; public final synthetic fun deserializeJsonToMessageChain (Ljava/lang/String;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/message/data/MessageChain; public final fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/lang/String; public final fun serializeToJsonString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/json/Json;)Ljava/lang/String; public static synthetic fun serializeToJsonString$default (Lnet/mamoe/mirai/message/data/MessageChain$Companion;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Ljava/lang/String; public final fun serializeToString (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlinx/serialization/StringFormat;)Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageChain$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageChain$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/MessageChain; 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/MessageChain;)V } public final class net/mamoe/mirai/message/data/MessageChainBuilder : java/lang/Appendable, java/util/List, kotlin/jvm/internal/markers/KMutableList { public fun <init> ()V public fun <init> (I)V public synthetic fun add (ILjava/lang/Object;)V public fun add (ILnet/mamoe/mirai/message/data/SingleMessage;)V public synthetic fun add (Ljava/lang/Object;)Z public final fun add (Ljava/lang/String;)V public final fun add (Lnet/mamoe/mirai/message/data/Message;)Z public fun add (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun addAll (ILjava/util/Collection;)Z public final fun addAll (Ljava/lang/Iterable;)Z public fun addAll (Ljava/util/Collection;)Z public final fun addAllFlatten (Ljava/lang/Iterable;)Z public synthetic fun append (C)Ljava/lang/Appendable; public fun append (C)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public synthetic fun append (Ljava/lang/CharSequence;)Ljava/lang/Appendable; public fun append (Ljava/lang/CharSequence;)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public synthetic fun append (Ljava/lang/CharSequence;II)Ljava/lang/Appendable; public fun append (Ljava/lang/CharSequence;II)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public final fun append (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public final fun append (Lnet/mamoe/mirai/message/data/SingleMessage;)Lnet/mamoe/mirai/message/data/MessageChainBuilder; public final fun asMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public final fun build ()Lnet/mamoe/mirai/message/data/MessageChain; public fun clear ()V public final fun contains (Ljava/lang/Object;)Z public fun contains (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun containsAll (Ljava/util/Collection;)Z public final fun copy ()Lnet/mamoe/mirai/message/data/MessageChainBuilder; public synthetic fun get (I)Ljava/lang/Object; public fun get (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun getSize ()I public final fun indexOf (Ljava/lang/Object;)I public fun indexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun isEmpty ()Z public fun iterator ()Ljava/util/Iterator; public final fun lastIndexOf (Ljava/lang/Object;)I public fun lastIndexOf (Lnet/mamoe/mirai/message/data/SingleMessage;)I public fun listIterator ()Ljava/util/ListIterator; public fun listIterator (I)Ljava/util/ListIterator; public final synthetic fun plusAssign (Ljava/lang/CharSequence;)V public final synthetic fun plusAssign (Ljava/lang/String;)V public final synthetic fun plusAssign (Lnet/mamoe/mirai/message/data/Message;)V public final synthetic fun plusAssign (Lnet/mamoe/mirai/message/data/SingleMessage;)V public synthetic fun remove (I)Ljava/lang/Object; public final fun remove (I)Lnet/mamoe/mirai/message/data/SingleMessage; public final fun remove (Ljava/lang/Object;)Z public fun remove (Lnet/mamoe/mirai/message/data/SingleMessage;)Z public fun removeAll (Ljava/util/Collection;)Z public fun removeAt (I)Lnet/mamoe/mirai/message/data/SingleMessage; public fun retainAll (Ljava/util/Collection;)Z public synthetic fun set (ILjava/lang/Object;)Ljava/lang/Object; public fun set (ILnet/mamoe/mirai/message/data/SingleMessage;)Lnet/mamoe/mirai/message/data/SingleMessage; public final fun size ()I public fun subList (II)Ljava/util/List; public fun toArray ()[Ljava/lang/Object; public fun toArray ([Ljava/lang/Object;)[Ljava/lang/Object; public final synthetic fun unaryPlus (Ljava/lang/String;)V public final synthetic fun unaryPlus (Lnet/mamoe/mirai/message/data/Message;)V } public abstract interface class net/mamoe/mirai/message/data/MessageContent : net/mamoe/mirai/message/data/SingleMessage { public static final field Key Lnet/mamoe/mirai/message/data/MessageContent$Key; } public final class net/mamoe/mirai/message/data/MessageContent$Key : net/mamoe/mirai/message/data/AbstractMessageKey { } public abstract interface class net/mamoe/mirai/message/data/MessageKey { public abstract fun getSafeCast ()Lkotlin/jvm/functions/Function1; } public final class net/mamoe/mirai/message/data/MessageKeyKt { public static final fun getTopmostKey (Lnet/mamoe/mirai/message/data/MessageKey;)Lnet/mamoe/mirai/message/data/MessageKey; public static final fun isInstance (Lnet/mamoe/mirai/message/data/MessageKey;Lnet/mamoe/mirai/message/data/SingleMessage;)Z } public abstract interface class net/mamoe/mirai/message/data/MessageMetadata : net/mamoe/mirai/message/data/SingleMessage { public fun contentToString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MessageOrigin$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageOrigin$$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/MessageOrigin; 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/MessageOrigin;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageOrigin$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageOriginKind : java/lang/Enum { public static final field Companion Lnet/mamoe/mirai/message/data/MessageOriginKind$Companion; public static final field FORWARD Lnet/mamoe/mirai/message/data/MessageOriginKind; public static final field LONG Lnet/mamoe/mirai/message/data/MessageOriginKind; public static final field MUSIC_SHARE Lnet/mamoe/mirai/message/data/MessageOriginKind; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageOriginKind; public static fun values ()[Lnet/mamoe/mirai/message/data/MessageOriginKind; } public final class net/mamoe/mirai/message/data/MessageOriginKind$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract class net/mamoe/mirai/message/data/MessageSource : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/Message, net/mamoe/mirai/message/data/MessageMetadata { public static final field Key Lnet/mamoe/mirai/message/data/MessageSource$Key; public static final field SERIAL_NAME Ljava/lang/String; public abstract fun getBotId ()J public abstract fun getFromId ()J public abstract fun getIds ()[I public abstract fun getInternalIds ()[I public final fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public abstract fun getTargetId ()J public abstract fun getTime ()I public abstract fun isOriginalMessageInitialized ()Z public static final fun quote (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/QuoteReply; public static final fun quote (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/QuoteReply; public static final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;)V public static final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;)V public static final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun recallIn (Lnet/mamoe/mirai/message/data/MessageChain;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public static final fun recallIn (Lnet/mamoe/mirai/message/data/MessageSource;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public abstract fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/MessageSource$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun quote (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/QuoteReply; public final fun quote (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/QuoteReply; public final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;)V public final fun recall (Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;)V public final fun recall (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun recallIn (Lnet/mamoe/mirai/message/data/MessageChain;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public final fun recallIn (Lnet/mamoe/mirai/message/data/MessageSource;J)Lnet/mamoe/mirai/message/action/AsyncRecallResult; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageSource$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MessageSource$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/MessageSource; 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/MessageSource;)V } public final class net/mamoe/mirai/message/data/MessageSourceAmender : net/mamoe/mirai/message/data/MessageSourceBuilder { public fun <init> (Lnet/mamoe/mirai/message/data/MessageSource;)V public fun getFromId ()J public fun getIds ()[I public fun getInternalIds ()[I public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public final fun getOriginalMessage ()Lnet/mamoe/mirai/message/data/MessageChain; public fun getTargetId ()J public fun getTime ()I public fun setFromId (J)V public fun setIds ([I)V public fun setInternalIds ([I)V public final fun setKind (Lnet/mamoe/mirai/message/data/MessageSourceKind;)V public final fun setOriginalMessage (Lnet/mamoe/mirai/message/data/MessageChain;)V public fun setTargetId (J)V public fun setTime (I)V } public class net/mamoe/mirai/message/data/MessageSourceBuilder { public fun <init> ()V public final fun allFrom (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun build (JLnet/mamoe/mirai/message/data/MessageSourceKind;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public final fun clearMessages ()Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public fun getFromId ()J public fun getIds ()[I public fun getInternalIds ()[I public fun getTargetId ()J public fun getTime ()I public final fun id (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun id ([I)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun internalId (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun internalId ([I)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun messages (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final synthetic fun messages (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun messages ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun messagesFrom (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun metadata (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun sender (J)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun sender (Lnet/mamoe/mirai/contact/ContactOrBot;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public fun setFromId (J)V public fun setIds ([I)V public fun setInternalIds ([I)V public final fun setSenderAndTarget (Lnet/mamoe/mirai/contact/ContactOrBot;Lnet/mamoe/mirai/contact/ContactOrBot;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public fun setTargetId (J)V public fun setTime (I)V public final fun target (J)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun target (Lnet/mamoe/mirai/contact/ContactOrBot;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun time (I)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; public final fun time (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceBuilder; } public final class net/mamoe/mirai/message/data/MessageSourceKind : java/lang/Enum { public static final field Companion Lnet/mamoe/mirai/message/data/MessageSourceKind$Companion; public static final field FRIEND Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final field GROUP Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final field STRANGER Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final field TEMP Lnet/mamoe/mirai/message/data/MessageSourceKind; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MessageSourceKind; public static fun values ()[Lnet/mamoe/mirai/message/data/MessageSourceKind; } public final class net/mamoe/mirai/message/data/MessageSourceKind$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MessageUtils { public static final synthetic fun At (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/At; public static final synthetic fun FileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage; public static final synthetic fun Image (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static final synthetic fun Image (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun Image$default (Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static final synthetic fun OfflineAudio (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio; public static final synthetic fun OfflineAudio (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio; public static final synthetic fun UnsupportedMessage ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; public static final synthetic fun at (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/message/data/At; public static final fun buildMessageChain (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun buildMessageChain (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun buildMessageSource (Lnet/mamoe/mirai/IMirai;JLnet/mamoe/mirai/message/data/MessageSourceKind;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun calculateImageMd5 (Lnet/mamoe/mirai/message/data/Image;)[B public static final fun contentsList (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/util/List; public static final synthetic fun contentsSequence (Lnet/mamoe/mirai/message/data/MessageChain;)Lkotlin/sequences/Sequence; public static final fun copySource (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final fun emptyMessageChain ()Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun getBot (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/Bot; public static final fun getBot (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/Bot; public static final fun getBotOrNull (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/Bot; public static final synthetic fun getContent (Lnet/mamoe/mirai/message/data/Message;)Ljava/lang/String; public static final synthetic fun getIds (Lnet/mamoe/mirai/message/data/MessageChain;)[I public static final synthetic fun getInternalId (Lnet/mamoe/mirai/message/data/MessageChain;)[I public static final synthetic fun getKind (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final synthetic fun getKind (Lnet/mamoe/mirai/message/data/OnlineMessageSource;)Lnet/mamoe/mirai/message/data/MessageSourceKind; public static final synthetic fun getLengthDuration (Lnet/mamoe/mirai/message/data/OnlineAudio;)J public static final synthetic fun getOrFail (Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/message/data/MessageKey;Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/SingleMessage; public static synthetic fun getOrFail$default (Lnet/mamoe/mirai/message/data/MessageChain;Lnet/mamoe/mirai/message/data/MessageKey;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/SingleMessage; public static final synthetic fun getSource (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/MessageSource; public static final synthetic fun getSourceOrNull (Lnet/mamoe/mirai/message/data/MessageChain;)Lnet/mamoe/mirai/message/data/MessageSource; public static final synthetic fun getTime (Lnet/mamoe/mirai/message/data/MessageChain;)I public static final fun isContentBlank (Lnet/mamoe/mirai/message/data/Message;)Z public static final fun isContentEmpty (Lnet/mamoe/mirai/message/data/Message;)Z public static final fun metadataList (Lnet/mamoe/mirai/message/data/MessageChain;)Ljava/util/List; public static final synthetic fun metadataSequence (Lnet/mamoe/mirai/message/data/MessageChain;)Lkotlin/sequences/Sequence; public static final fun newChain (Ljava/lang/Iterable;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Ljava/util/Iterator;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Ljava/util/stream/Stream;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Lkotlin/sequences/Sequence;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun newChain (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun newChain ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun plus (Lnet/mamoe/mirai/message/data/Message;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun recallSource (Lnet/mamoe/mirai/message/data/QuoteReply;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun repeat (Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun sendTo (Lnet/mamoe/mirai/message/data/Message;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun times (Lnet/mamoe/mirai/message/data/Message;I)Lnet/mamoe/mirai/message/data/MessageChain; public static final synthetic fun toMessageChain ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain; public static final fun toOfflineMessageSource (Lnet/mamoe/mirai/message/data/OnlineMessageSource;)Lnet/mamoe/mirai/message/data/OfflineMessageSource; public static final synthetic fun toPlainText (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/PlainText; public static final synthetic fun toVoice (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice; } public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum { public static final field KugouMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field KuwoMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field MiguMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field NeteaseCloudMusic Lnet/mamoe/mirai/message/data/MusicKind; public static final field QQMusic Lnet/mamoe/mirai/message/data/MusicKind; public final fun getAppId ()J public final fun getPackageName ()Ljava/lang/String; public final fun getPlatform ()I public final fun getSdkVersion ()Ljava/lang/String; public final fun getSignature ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MusicKind; public static fun values ()[Lnet/mamoe/mirai/message/data/MusicKind; } 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 final fun component1 ()Lnet/mamoe/mirai/message/data/MusicKind; public final fun component2 ()Ljava/lang/String; public final fun component3 ()Ljava/lang/String; public final fun component4 ()Ljava/lang/String; public final fun component5 ()Ljava/lang/String; public final fun component6 ()Ljava/lang/String; public final fun component7 ()Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/MusicShare; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/MusicShare;Lnet/mamoe/mirai/message/data/MusicKind;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/MusicShare; public fun equals (Ljava/lang/Object;)Z public final fun getBrief ()Ljava/lang/String; public final fun getJumpUrl ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getKind ()Lnet/mamoe/mirai/message/data/MusicKind; public final fun getMusicUrl ()Ljava/lang/String; public final fun getPictureUrl ()Ljava/lang/String; public final fun getSummary ()Ljava/lang/String; public final fun getTitle ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/MusicShare;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/MusicShare$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/MusicShare$$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/MusicShare; 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/MusicShare;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/MusicShare$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/OfflineAudio : net/mamoe/mirai/message/data/Audio { public static final field Key Lnet/mamoe/mirai/message/data/OfflineAudio$Key; public static final field SERIAL_NAME Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/OfflineAudio$Factory { public static final field INSTANCE Lnet/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE; public abstract fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio; public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio; } public final class net/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE : net/mamoe/mirai/message/data/OfflineAudio$Factory { public fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio; public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio; } public final class net/mamoe/mirai/message/data/OfflineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/mamoe/mirai/message/data/MessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OfflineMessageSource$Key; public fun <init> ()V public abstract fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OfflineMessageSource$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/OfflineShortVideo : net/mamoe/mirai/message/data/ShortVideo { public static final field Key Lnet/mamoe/mirai/message/data/OfflineShortVideo$Key; public static final field SERIAL_NAME Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder { public static final field Companion Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion; public final fun build ()Lnet/mamoe/mirai/message/data/OfflineShortVideo; public final fun getFileFormat ()Ljava/lang/String; public final fun getFileMd5 ()[B public final fun getFileName ()Ljava/lang/String; public final fun getFileSize ()J public final fun getThumbnailMd5 ()[B public final fun getThumbnailSize ()J public final fun getVideoId ()Ljava/lang/String; public static final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder; public final fun setFileFormat (Ljava/lang/String;)V public final fun setFileMd5 ([B)V public final fun setFileName (Ljava/lang/String;)V public final fun setFileSize (J)V public final fun setThumbnailMd5 ([B)V public final fun setThumbnailSize (J)V public final fun setVideoId (Ljava/lang/String;)V } public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion { public final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder; } public final class net/mamoe/mirai/message/data/OfflineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract interface class net/mamoe/mirai/message/data/OnlineAudio : net/mamoe/mirai/message/data/Audio { public static final field Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key; public static final field SERIAL_NAME Ljava/lang/String; public abstract fun getLength ()J public abstract fun getUrlForDownload ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource : net/mamoe/mirai/message/data/MessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Key; public abstract fun getBot ()Lnet/mamoe/mirai/Bot; public final fun getBotId ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/ContactOrBot; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming : net/mamoe/mirai/message/data/OnlineMessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$Key; public fun getFromId ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/User; public fun getTargetId ()J } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Friend; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Friend; public final synthetic fun getTarget ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromFriend$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup$Key; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Member; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public fun getSubject ()Lnet/mamoe/mirai/contact/Group; public synthetic fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun getTarget ()Lnet/mamoe/mirai/contact/Group; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromGroup$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Stranger; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public final synthetic fun getTarget ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromStranger$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp : net/mamoe/mirai/message/data/OnlineMessageSource$Incoming { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp$Key; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public abstract fun getSender ()Lnet/mamoe/mirai/contact/Member; public abstract fun getSubject ()Lnet/mamoe/mirai/contact/Member; public final synthetic fun getTarget ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/ContactOrBot; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$FromTemp$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Incoming$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Key : net/mamoe/mirai/message/data/AbstractMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing : net/mamoe/mirai/message/data/OnlineMessageSource { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$Key; public final fun getFromId ()J public abstract fun getSender ()Lnet/mamoe/mirai/Bot; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact; public final fun getTargetId ()J } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToFriend : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToFriend$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Friend; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Friend; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToFriend$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToGroup : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToGroup$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Group; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Group; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToGroup$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToStranger : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToStranger$Key; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Stranger; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Stranger; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToStranger$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp : net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing { public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp$Key; public final fun getGroup ()Lnet/mamoe/mirai/contact/Group; public final fun getKind ()Lnet/mamoe/mirai/message/data/MessageSourceKind; public synthetic fun getSubject ()Lnet/mamoe/mirai/contact/Contact; public final fun getSubject ()Lnet/mamoe/mirai/contact/Member; public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Member; public final fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/OnlineShortVideo : net/mamoe/mirai/message/data/ShortVideo { public static final field Key Lnet/mamoe/mirai/message/data/OnlineShortVideo$Key; public static final field SERIAL_NAME Ljava/lang/String; public abstract fun getUrlForDownload ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OnlineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public static final field SERIAL_NAME Ljava/lang/String; } public final class net/mamoe/mirai/message/data/OrNullDelegate { public static final synthetic fun box-impl (Ljava/lang/Object;)Lnet/mamoe/mirai/message/data/OrNullDelegate; public static fun constructor-impl (Ljava/lang/Object;)Ljava/lang/Object; public fun equals (Ljava/lang/Object;)Z public static fun equals-impl (Ljava/lang/Object;Ljava/lang/Object;)Z public static final fun equals-impl0 (Ljava/lang/Object;Ljava/lang/Object;)Z public static final fun getValue-impl (Ljava/lang/Object;Ljava/lang/Object;Lkotlin/reflect/KProperty;)Ljava/lang/Object; public fun hashCode ()I public static fun hashCode-impl (Ljava/lang/Object;)I public fun toString ()Ljava/lang/String; public static fun toString-impl (Ljava/lang/Object;)Ljava/lang/String; public final synthetic fun unbox-impl ()Ljava/lang/Object; } public final class net/mamoe/mirai/message/data/PlainText : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MessageContent { public static final field Companion Lnet/mamoe/mirai/message/data/PlainText$Companion; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/lang/CharSequence;)V public fun <init> (Ljava/lang/String;)V public final fun component1 ()Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public final fun copy (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/PlainText; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/PlainText;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/PlainText; public fun equals (Ljava/lang/Object;)Z public final fun getContent ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/PlainText;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/PlainText$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/PlainText$$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/PlainText; 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/PlainText;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PlainText$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PokeMessage : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/HummerMessage { public static final field BaoBeiQiu Lnet/mamoe/mirai/message/data/PokeMessage; public static final field BiXin Lnet/mamoe/mirai/message/data/PokeMessage; public static final field ChuoYiChuo Lnet/mamoe/mirai/message/data/PokeMessage; public static final field DianZan Lnet/mamoe/mirai/message/data/PokeMessage; public static final field FangDaZhao Lnet/mamoe/mirai/message/data/PokeMessage; public static final field GouYin Lnet/mamoe/mirai/message/data/PokeMessage; public static final field JieYin Lnet/mamoe/mirai/message/data/PokeMessage; public static final field Key Lnet/mamoe/mirai/message/data/PokeMessage$Key; public static final field LiuLiuLiu Lnet/mamoe/mirai/message/data/PokeMessage; public static final field QiaoMen Lnet/mamoe/mirai/message/data/PokeMessage; public static final field RangNiPi Lnet/mamoe/mirai/message/data/PokeMessage; public static final field Rose Lnet/mamoe/mirai/message/data/PokeMessage; public static final field SERIAL_NAME Ljava/lang/String; public static final field ShouLei Lnet/mamoe/mirai/message/data/PokeMessage; public static final field SuiPing Lnet/mamoe/mirai/message/data/PokeMessage; public static final field XinSui Lnet/mamoe/mirai/message/data/PokeMessage; public static final field ZhaoHuanShu Lnet/mamoe/mirai/message/data/PokeMessage; public static final field ZhuaYiXia Lnet/mamoe/mirai/message/data/PokeMessage; public static final field values [Lnet/mamoe/mirai/message/data/PokeMessage; public synthetic fun <init> (ILjava/lang/String;IILkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun component1 ()Ljava/lang/String; public final fun component2 ()I public final fun component3 ()I public fun contentToString ()Ljava/lang/String; public final fun copy (Ljava/lang/String;II)Lnet/mamoe/mirai/message/data/PokeMessage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/PokeMessage;Ljava/lang/String;IIILjava/lang/Object;)Lnet/mamoe/mirai/message/data/PokeMessage; public fun equals (Ljava/lang/Object;)Z public final fun getId ()I public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getName ()Ljava/lang/String; public final fun getPokeType ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/PokeMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/PokeMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/PokeMessage$$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/PokeMessage; 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/PokeMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PokeMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/PttMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/QuoteReply : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/Message, net/mamoe/mirai/message/data/MessageMetadata { public static final field Key Lnet/mamoe/mirai/message/data/QuoteReply$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILnet/mamoe/mirai/message/data/MessageSource;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/message/data/MessageChain;)V public fun <init> (Lnet/mamoe/mirai/message/data/MessageSource;)V public final fun component1 ()Lnet/mamoe/mirai/message/data/MessageSource; public final fun copy (Lnet/mamoe/mirai/message/data/MessageSource;)Lnet/mamoe/mirai/message/data/QuoteReply; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/QuoteReply;Lnet/mamoe/mirai/message/data/MessageSource;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/QuoteReply; public fun equals (Ljava/lang/Object;)Z public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getSource ()Lnet/mamoe/mirai/message/data/MessageSource; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/QuoteReply;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/QuoteReply$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/QuoteReply$$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/QuoteReply; 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/QuoteReply;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/QuoteReply$Key : net/mamoe/mirai/message/data/AbstractMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/RawForwardMessage { public static final field Companion Lnet/mamoe/mirai/message/data/RawForwardMessage$Companion; public synthetic fun <init> (ILjava/util/List;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (Ljava/util/List;)V public final fun component1 ()Ljava/util/List; public final fun copy (Ljava/util/List;)Lnet/mamoe/mirai/message/data/RawForwardMessage; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/RawForwardMessage;Ljava/util/List;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/RawForwardMessage; public fun equals (Ljava/lang/Object;)Z public final fun getNodeList ()Ljava/util/List; public fun hashCode ()I public final fun render (Lnet/mamoe/mirai/message/data/ForwardMessage$DisplayStrategy;)Lnet/mamoe/mirai/message/data/ForwardMessage; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/RawForwardMessage;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/RawForwardMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/RawForwardMessage$$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/RawForwardMessage; 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/RawForwardMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/RawForwardMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/RichMessage : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/RichMessage$Key; public fun contentToString ()Ljava/lang/String; public abstract fun getContent ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; } 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 synthetic 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 final class net/mamoe/mirai/message/data/RockPaperScissors : java/lang/Enum, net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/MarketFace { public static final field Key Lnet/mamoe/mirai/message/data/RockPaperScissors$Key; public static final field PAPER Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final field ROCK Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final field SCISSORS Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public final fun eliminates (Lnet/mamoe/mirai/message/data/RockPaperScissors;)Ljava/lang/Boolean; public final fun getContent ()Ljava/lang/String; public fun getId ()I public final fun getInternalId ()B public synthetic fun getName ()Ljava/lang/String; public static final fun random ()Lnet/mamoe/mirai/message/data/RockPaperScissors; public static final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/message/data/RockPaperScissors; public fun toString ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/RockPaperScissors; public static fun values ()[Lnet/mamoe/mirai/message/data/RockPaperScissors; } public final class net/mamoe/mirai/message/data/RockPaperScissors$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun random ()Lnet/mamoe/mirai/message/data/RockPaperScissors; public final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/message/data/RockPaperScissors; public static synthetic fun random$default (Lnet/mamoe/mirai/message/data/RockPaperScissors$Key;Lkotlin/random/Random;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/RockPaperScissors; 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 abstract fun getServiceId ()I } public final class net/mamoe/mirai/message/data/ServiceMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public abstract interface class net/mamoe/mirai/message/data/ShortVideo : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent { public static final field Key Lnet/mamoe/mirai/message/data/ShortVideo$Key; public abstract fun getFileFormat ()Ljava/lang/String; public abstract fun getFileMd5 ()[B public abstract fun getFileSize ()J public abstract fun getFilename ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public abstract fun getVideoId ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/ShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { } public final class net/mamoe/mirai/message/data/ShortVideoKt { public static final synthetic fun OfflineShortVideo (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo; public static synthetic fun OfflineShortVideo$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJILjava/lang/Object;)Lnet/mamoe/mirai/message/data/OfflineShortVideo; } 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 final fun serializer ()Lkotlinx/serialization/KSerializer; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/message/data/SimpleServiceMessage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/SimpleServiceMessage$$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/SimpleServiceMessage; 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/SimpleServiceMessage;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/SimpleServiceMessage$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public abstract interface class net/mamoe/mirai/message/data/SingleMessage : net/mamoe/mirai/message/data/Message { } public final class net/mamoe/mirai/message/data/SingleMessage$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/SingleMessage$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/SingleMessage; 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/SingleMessage;)V } public final class net/mamoe/mirai/message/data/SuperFace : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/HummerMessage { public static final field Key Lnet/mamoe/mirai/message/data/SuperFace$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (IILjava/lang/String;ILkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun contentToString ()Ljava/lang/String; public fun equals (Ljava/lang/Object;)Z public static final fun from (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public static final fun fromOrNull (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public final fun getFace ()I public final fun getId ()Ljava/lang/String; public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getName ()Ljava/lang/String; public final fun getType ()I public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/SuperFace;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/SuperFace$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/SuperFace$$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/SuperFace; 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/SuperFace;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/SuperFace$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun from (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public final fun fromOrNull (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/SuperFaceKt { public static final synthetic fun toFace (Lnet/mamoe/mirai/message/data/SuperFace;)Lnet/mamoe/mirai/message/data/Face; public static final synthetic fun toSuperFace (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; public static final synthetic fun toSuperFaceOrNull (Lnet/mamoe/mirai/message/data/Face;)Lnet/mamoe/mirai/message/data/SuperFace; } public abstract interface class net/mamoe/mirai/message/data/UnsupportedMessage : net/mamoe/mirai/message/data/MessageContent { public static final field Companion Lnet/mamoe/mirai/message/data/UnsupportedMessage$Companion; public static final field SERIAL_NAME Ljava/lang/String; public fun contentToString ()Ljava/lang/String; public static fun create ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; public abstract fun getStruct ()[B } public final class net/mamoe/mirai/message/data/UnsupportedMessage$Companion { public static final field SERIAL_NAME Ljava/lang/String; public final fun create ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage; } public final class net/mamoe/mirai/message/data/UnsupportedMessage$Serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/UnsupportedMessage$Serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/UnsupportedMessage; 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/UnsupportedMessage;)V } public final class net/mamoe/mirai/message/data/VipFace : net/mamoe/mirai/message/code/CodableMessage, net/mamoe/mirai/message/data/HummerMessage { public static final field AiXin Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field BianBian Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field ChaoPiao Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field DianZan Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field HaHa Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field Key Lnet/mamoe/mirai/message/data/VipFace$Key; public static final field LiuLian Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field LueLueLue Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field PingDiGuo Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field QinQin Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field SERIAL_NAME Ljava/lang/String; public static final field YaoWan Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field ZhaDan Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field ZhuTou Lnet/mamoe/mirai/message/data/VipFace$Kind; public static final field values [Lnet/mamoe/mirai/message/data/VipFace$Kind; public synthetic fun <init> (ILnet/mamoe/mirai/message/data/VipFace$Kind;ILkotlinx/serialization/internal/SerializationConstructorMarker;)V public final fun component1 ()Lnet/mamoe/mirai/message/data/VipFace$Kind; public final fun component2 ()I public fun contentToString ()Ljava/lang/String; public final fun copy (Lnet/mamoe/mirai/message/data/VipFace$Kind;I)Lnet/mamoe/mirai/message/data/VipFace; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/VipFace;Lnet/mamoe/mirai/message/data/VipFace$Kind;IILjava/lang/Object;)Lnet/mamoe/mirai/message/data/VipFace; public fun equals (Ljava/lang/Object;)Z public final fun getCount ()I public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey; public final fun getKind ()Lnet/mamoe/mirai/message/data/VipFace$Kind; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/VipFace;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/VipFace$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/VipFace$$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/VipFace; 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/VipFace;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/VipFace$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/VipFace$Kind { public static final field Companion Lnet/mamoe/mirai/message/data/VipFace$Kind$Companion; public synthetic fun <init> (IILjava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> (ILjava/lang/String;)V public final fun component1 ()I public final fun component2 ()Ljava/lang/String; public final fun copy (ILjava/lang/String;)Lnet/mamoe/mirai/message/data/VipFace$Kind; public static synthetic fun copy$default (Lnet/mamoe/mirai/message/data/VipFace$Kind;ILjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/VipFace$Kind; public fun equals (Ljava/lang/Object;)Z public final fun getId ()I public final fun getName ()Ljava/lang/String; public fun hashCode ()I public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/VipFace$Kind;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/VipFace$Kind$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/VipFace$Kind$$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/VipFace$Kind; 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/VipFace$Kind;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/VipFace$Kind$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage { public static final field Key Lnet/mamoe/mirai/message/data/Voice$Key; public static final field SERIAL_NAME Ljava/lang/String; public synthetic fun <init> (ILjava/lang/String;[BJILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V public synthetic fun <init> (Ljava/lang/String;[BJILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun contentToString ()Ljava/lang/String; public static final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice; public fun getFileName ()Ljava/lang/String; public fun getFileSize ()J public fun getMd5 ()[B public fun getUrl ()Ljava/lang/String; public final fun get_codec ()I public final fun toAudio ()Lnet/mamoe/mirai/message/data/Audio; public fun toString ()Ljava/lang/String; public static final fun write$Self (Lnet/mamoe/mirai/message/data/Voice;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/message/data/Voice$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$$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/Voice; 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/Voice;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey { public final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder { public fun <init> ()V public fun <init> (II)V public final fun getBg ()I public final fun getLayout ()I public final fun getText ()Ljava/lang/String; public final fun picture (Ljava/lang/String;)V public final fun setBg (I)V public final fun setLayout (I)V public final fun summary (Ljava/lang/String;Ljava/lang/String;)V public static synthetic fun summary$default (Lnet/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V public final fun title (Ljava/lang/String;ILjava/lang/String;)V public static synthetic fun title$default (Lnet/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder;Ljava/lang/String;ILjava/lang/String;ILjava/lang/Object;)V } public final class net/mamoe/mirai/message/data/visitor/MessageVisitorKt { } public final class net/mamoe/mirai/network/BotAuthorizationException : net/mamoe/mirai/network/LoginFailedException { public final fun getAuthorization ()Lnet/mamoe/mirai/auth/BotAuthorization; } public abstract class net/mamoe/mirai/network/CustomLoginFailedException : net/mamoe/mirai/network/LoginFailedException { public fun <init> (Z)V public fun <init> (ZLjava/lang/String;)V public fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;)V public fun <init> (ZLjava/lang/Throwable;)V } public final class net/mamoe/mirai/network/ForceOfflineException : java/util/concurrent/CancellationException { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/lang/Throwable;)V public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; public fun getMessage ()Ljava/lang/String; } public final class net/mamoe/mirai/network/InconsistentBotIdException : net/mamoe/mirai/network/LoginFailedException { public synthetic fun <init> (JJLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getActual ()J public final fun getExpected ()J } public abstract class net/mamoe/mirai/network/LoginFailedException : java/lang/RuntimeException { public synthetic fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public synthetic fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;Lkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getKillBot ()Z } public final class net/mamoe/mirai/network/NoServerAvailableException : net/mamoe/mirai/network/LoginFailedException { public fun getCause ()Ljava/lang/Throwable; } public final class net/mamoe/mirai/network/NoStandardInputForCaptchaException : net/mamoe/mirai/network/LoginFailedException { public synthetic fun <init> (Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun getCause ()Ljava/lang/Throwable; } public final class net/mamoe/mirai/network/RetryLaterException : net/mamoe/mirai/network/LoginFailedException { public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V } public class net/mamoe/mirai/network/UnsupportedCaptchaMethodException : net/mamoe/mirai/network/LoginFailedException { public fun <init> (Z)V public fun <init> (ZLjava/lang/String;)V public fun <init> (ZLjava/lang/String;Ljava/lang/Throwable;)V public fun <init> (ZLjava/lang/Throwable;)V } public final class net/mamoe/mirai/network/UnsupportedQRCodeCaptchaException : net/mamoe/mirai/network/UnsupportedCaptchaMethodException { public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/network/UnsupportedSliderCaptchaException : net/mamoe/mirai/network/UnsupportedCaptchaMethodException { public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/network/UnsupportedSmsLoginException : net/mamoe/mirai/network/UnsupportedCaptchaMethodException { public fun <init> (Ljava/lang/String;)V } public final class net/mamoe/mirai/network/WrongPasswordException : net/mamoe/mirai/network/LoginFailedException { } public abstract interface class net/mamoe/mirai/spi/AudioToSilkService : net/mamoe/mirai/spi/BaseService { public static final field Companion Lnet/mamoe/mirai/spi/AudioToSilkService$Companion; public abstract fun convert (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun getInstance ()Lnet/mamoe/mirai/spi/AudioToSilkService; } public final class net/mamoe/mirai/spi/AudioToSilkService$Companion { public final fun getInstance ()Lnet/mamoe/mirai/spi/AudioToSilkService; } public abstract interface class net/mamoe/mirai/spi/BaseService { public fun getPriority ()I } public abstract class net/mamoe/mirai/utils/AbstractBotConfiguration { public fun <init> ()V public final fun fileBasedDeviceInfo ()V public final fun fileBasedDeviceInfo (Ljava/lang/String;)V public static synthetic fun fileBasedDeviceInfo$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/lang/String;ILjava/lang/Object;)V protected abstract fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1; public final fun getCacheDir ()Ljava/io/File; protected abstract fun getDeviceInfo ()Lkotlin/jvm/functions/Function1; protected abstract fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1; public final fun getWorkingDir ()Ljava/io/File; public final fun redirectBotLogToDirectory ()V public final fun redirectBotLogToDirectory (Ljava/io/File;)V public final fun redirectBotLogToDirectory (Ljava/io/File;J)V public final fun redirectBotLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V public static synthetic fun redirectBotLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun redirectBotLogToFile ()V public final fun redirectBotLogToFile (Ljava/io/File;)V public final fun redirectBotLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V public static synthetic fun redirectBotLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun redirectNetworkLogToDirectory ()V public final fun redirectNetworkLogToDirectory (Ljava/io/File;)V public final fun redirectNetworkLogToDirectory (Ljava/io/File;J)V public final fun redirectNetworkLogToDirectory (Ljava/io/File;JLkotlin/jvm/functions/Function1;)V public static synthetic fun redirectNetworkLogToDirectory$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;JLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun redirectNetworkLogToFile ()V public final fun redirectNetworkLogToFile (Ljava/io/File;)V public final fun redirectNetworkLogToFile (Ljava/io/File;Lkotlin/jvm/functions/Function1;)V public static synthetic fun redirectNetworkLogToFile$default (Lnet/mamoe/mirai/utils/AbstractBotConfiguration;Ljava/io/File;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V protected abstract fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)V public final fun setCacheDir (Ljava/io/File;)V protected abstract fun setDeviceInfo (Lkotlin/jvm/functions/Function1;)V protected abstract fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V public final fun setWorkingDir (Ljava/io/File;)V } public abstract class net/mamoe/mirai/utils/AbstractExternalResource : net/mamoe/mirai/utils/ExternalResource { public fun <init> ()V public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V public synthetic fun <init> (Ljava/lang/String;Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V public synthetic fun <init> (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun close ()V protected final fun dontRegisterLeakObserver ()V public final fun getClosed ()Lkotlinx/coroutines/Deferred; public fun getFormatName ()Ljava/lang/String; public fun getMd5 ()[B public fun getSha1 ()[B public final fun inputStream ()Ljava/io/InputStream; protected abstract fun inputStream0 ()Ljava/io/InputStream; protected final fun registerToLeakObserver ()V protected final fun setResourceCleanCallback (Lnet/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback;)V } public abstract interface class net/mamoe/mirai/utils/AbstractExternalResource$ResourceCleanCallback { public abstract fun cleanup ()V } public class net/mamoe/mirai/utils/BotConfiguration : net/mamoe/mirai/utils/AbstractBotConfiguration { 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 disableAccountSecretes ()V public final fun disableContactCache ()V public final fun enableContactCache ()V public final fun getAutoReconnectOnForceOffline ()Z public final fun getBotLoggerSupplier ()Lkotlin/jvm/functions/Function1; 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 synthetic fun getFirstReconnectDelayMillis ()J public final fun getHeartbeatPeriodMillis ()J public final fun getHeartbeatStrategy ()Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public final fun getHeartbeatTimeoutMillis ()J public final fun getHighwayUploadCoroutineCount ()I public final fun getLoginCacheEnabled ()Z public final fun getLoginSolver ()Lnet/mamoe/mirai/utils/LoginSolver; public final fun getNetworkLoggerSupplier ()Lkotlin/jvm/functions/Function1; public final fun getParentCoroutineContext ()Lkotlin/coroutines/CoroutineContext; public final fun getProtocol ()Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public final synthetic fun getReconnectPeriodMillis ()J public final fun getReconnectionRetryTimes ()I public final fun getStatHeartbeatPeriodMillis ()J public final synthetic fun inheritCoroutineContext (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun isConvertLineSeparator ()Z public final fun isShowingVerboseEventLog ()Z public final fun loadDeviceInfoJson (Ljava/lang/String;)V public final fun noBotLog ()V public final fun noNetworkLog ()V public final fun randomDeviceInfo ()V public final fun setAutoReconnectOnForceOffline (Z)V public final fun setBotLoggerSupplier (Lkotlin/jvm/functions/Function1;)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 synthetic fun setFirstReconnectDelayMillis (J)V public final fun setHeartbeatPeriodMillis (J)V public final fun setHeartbeatStrategy (Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy;)V public final fun setHeartbeatTimeoutMillis (J)V public final fun setHighwayUploadCoroutineCount (I)V public final fun setLoginCacheEnabled (Z)V public final fun setLoginSolver (Lnet/mamoe/mirai/utils/LoginSolver;)V public final fun setNetworkLoggerSupplier (Lkotlin/jvm/functions/Function1;)V public final fun setParentCoroutineContext (Lkotlin/coroutines/CoroutineContext;)V public final fun setProtocol (Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol;)V public final synthetic fun setReconnectPeriodMillis (J)V public final fun setReconnectionRetryTimes (I)V public final fun setShowingVerboseEventLog (Z)V public final fun setStatHeartbeatPeriodMillis (J)V } public final class net/mamoe/mirai/utils/BotConfiguration$Companion { public final fun getDefault ()Lnet/mamoe/mirai/utils/BotConfiguration; } 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 ()J public final fun getSaveIntervalMillis ()J public final fun setFriendListCacheEnabled (Z)V public final fun setGroupMemberListCacheEnabled (Z)V public final synthetic fun setSaveInterval-LRDsOJo (J)V public final fun setSaveIntervalMillis (J)V } public final class net/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy : java/lang/Enum { public static final field NONE Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static final field REGISTER Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static final field STAT_HB Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; public static fun values ()[Lnet/mamoe/mirai/utils/BotConfiguration$HeartbeatStrategy; } 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; public static final field ANDROID_WATCH Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public static final field IPAD Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public static final field MACOS Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public final fun isNudgeSupported ()Z public final fun isQRLoginSupported ()Z public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; public static fun values ()[Lnet/mamoe/mirai/utils/BotConfiguration$MiraiProtocol; } public final class net/mamoe/mirai/utils/DeviceInfo { public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Companion; public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B)V public fun <init> ([B[B[B[B[B[B[B[B[B[B[BLnet/mamoe/mirai/utils/DeviceInfo$Version;[B[B[B[B[B[BLjava/lang/String;[B[B)V public static final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo; public fun equals (Ljava/lang/Object;)Z public static final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo; public static final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun getAndroidId ()[B public final fun getApn ()[B public final fun getBaseBand ()[B public final fun getBoard ()[B public final fun getBootId ()[B public final fun getBootloader ()[B public final fun getBrand ()[B public final fun getDevice ()[B public final fun getDisplay ()[B public final fun getFingerprint ()[B public final fun getGuid ()[B public final fun getImei ()Ljava/lang/String; public final fun getImsiMd5 ()[B public final fun getIpAddress ()[B public final fun getMacAddress ()[B public final fun getModel ()[B public final fun getOsType ()[B public final fun getProcVersion ()[B public final fun getProduct ()[B public final fun getSimInfo ()[B public final fun getVersion ()Lnet/mamoe/mirai/utils/DeviceInfo$Version; public final fun getWifiBSSID ()[B public final fun getWifiSSID ()[B public fun hashCode ()I public static final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo; public static final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo; public static final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String; } public final class net/mamoe/mirai/utils/DeviceInfo$$serializer : kotlinx/serialization/KSerializer { public static final field INSTANCE Lnet/mamoe/mirai/utils/DeviceInfo$$serializer; public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/utils/DeviceInfo; 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/utils/DeviceInfo;)V } public final class net/mamoe/mirai/utils/DeviceInfo$Companion { public final fun deserializeFromString (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun from (Ljava/io/File;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun from (Ljava/io/File;Lkotlinx/serialization/json/Json;)Lnet/mamoe/mirai/utils/DeviceInfo; public static synthetic fun from$default (Lnet/mamoe/mirai/utils/DeviceInfo$Companion;Ljava/io/File;Lkotlinx/serialization/json/Json;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun random ()Lnet/mamoe/mirai/utils/DeviceInfo; public final fun random (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfo; public final fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String; public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/utils/DeviceInfo$Version { public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfo$Version$Companion; public fun <init> ()V public synthetic fun <init> (I[B[B[BILkotlinx/serialization/internal/SerializationConstructorMarker;)V public fun <init> ([B[B[BI)V public synthetic fun <init> ([B[B[BIILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun equals (Ljava/lang/Object;)Z public final fun getCodename ()[B public final fun getIncremental ()[B public final fun getRelease ()[B public final fun getSdk ()I public fun hashCode ()I public static final fun write$Self (Lnet/mamoe/mirai/utils/DeviceInfo$Version;Lkotlinx/serialization/encoding/CompositeEncoder;Lkotlinx/serialization/descriptors/SerialDescriptor;)V } public final class net/mamoe/mirai/utils/DeviceInfo$Version$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/utils/DeviceInfo$Version$$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/utils/DeviceInfo$Version; 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/utils/DeviceInfo$Version;)V public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/utils/DeviceInfo$Version$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } public final class net/mamoe/mirai/utils/DeviceInfoBuilder { public static final field Companion Lnet/mamoe/mirai/utils/DeviceInfoBuilder$Companion; public fun <init> ()V public final fun androidId (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun androidId ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun apn (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun apn ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun baseBand (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun baseBand ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun board (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun board ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootId (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootId ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootloader (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun bootloader ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun brand (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun brand ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun build ()Lnet/mamoe/mirai/utils/DeviceInfo; public final fun device (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun device ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun display (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun display ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fingerprint (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fingerprint ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static final fun fromPrototype (Lnet/mamoe/mirai/utils/DeviceInfo;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static final fun fromRandom ()Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static final fun fromRandom (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun imei (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun imsiMd5 (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun imsiMd5 ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun macAddress (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun macAddress ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun model (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun model ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun osType (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun osType ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun procVersion (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun procVersion ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun product (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun product ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun simInfo (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun simInfo ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun version (Lnet/mamoe/mirai/utils/DeviceInfo$Version;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiBSSID (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiBSSID ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiSSID (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun wifiSSID ([B)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; } public final class net/mamoe/mirai/utils/DeviceInfoBuilder$Companion { public final fun fromPrototype (Lnet/mamoe/mirai/utils/DeviceInfo;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fromRandom ()Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public final fun fromRandom (Lkotlin/random/Random;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; public static synthetic fun fromRandom$default (Lnet/mamoe/mirai/utils/DeviceInfoBuilder$Companion;Lkotlin/random/Random;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/DeviceInfoBuilder; } public final class net/mamoe/mirai/utils/DeviceInfoKt { public static final fun generateDeviceInfoData (Lnet/mamoe/mirai/utils/DeviceInfo;)[B public static final synthetic fun serializeToString (Lnet/mamoe/mirai/utils/DeviceInfo;)Ljava/lang/String; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests { public abstract fun getFallback ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest; public abstract fun getPreferSms ()Z public abstract fun getSms ()Lnet/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$FallbackRequest { public abstract fun getUrl ()Ljava/lang/String; public abstract fun solved ()Lnet/mamoe/mirai/utils/DeviceVerificationResult; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationRequests$SmsRequest { public abstract fun getCountryCode ()Ljava/lang/String; public abstract fun getPhoneNumber ()Ljava/lang/String; public abstract fun requestSms (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun solved (Ljava/lang/String;)Lnet/mamoe/mirai/utils/DeviceVerificationResult; } public abstract interface class net/mamoe/mirai/utils/DeviceVerificationResult { } public final class net/mamoe/mirai/utils/DirectoryLogger : net/mamoe/mirai/utils/SimpleLogger { public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/io/File;)V public fun <init> (Ljava/lang/String;Ljava/io/File;J)V public synthetic fun <init> (Ljava/lang/String;Ljava/io/File;JILkotlin/jvm/internal/DefaultConstructorMarker;)V } public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io/Closeable { public static final field Companion Lnet/mamoe/mirai/utils/ExternalResource$Companion; public static final field DEFAULT_FORMAT_NAME Ljava/lang/String; public static fun create (Ljava/io/File;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/InputStream;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/RandomAccessFile;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([B)Lnet/mamoe/mirai/utils/ExternalResource; public static fun create ([BLjava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun getClosed ()Lkotlinx/coroutines/Deferred; public abstract fun getFormatName ()Ljava/lang/String; public abstract fun getMd5 ()[B public fun getOrigin ()Ljava/lang/Object; public fun getSha1 ()[B public abstract fun getSize ()J public abstract fun inputStream ()Ljava/io/InputStream; public fun isAutoClose ()Z public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toAutoCloseable ()Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice; public static synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/ExternalResource$Companion { public static final field DEFAULT_FORMAT_NAME Ljava/lang/String; public final fun create (Ljava/io/File;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/File;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/InputStream;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/RandomAccessFile;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create (Ljava/io/RandomAccessFile;Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create ([B)Lnet/mamoe/mirai/utils/ExternalResource; public final fun create ([BLjava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/RandomAccessFile;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;[BLjava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; public final synthetic fun createAutoCloseable (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/utils/ExternalResource; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final fun sendTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadAsFile (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadAsFile$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image; public final fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image; public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice; public final synthetic fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final synthetic fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadTo$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/ExternalResourceKt { public static final fun runAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; public static final fun useAutoClose (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/jvm/functions/Function1;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/utils/FileCacheStrategy { public static final field Companion Lnet/mamoe/mirai/utils/FileCacheStrategy$Companion; public static fun getPlatformDefault ()Lnet/mamoe/mirai/utils/FileCacheStrategy; public fun newCache (Ljava/io/InputStream;)Lnet/mamoe/mirai/utils/ExternalResource; public abstract fun newCache (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; public static synthetic fun newCache$default (Lnet/mamoe/mirai/utils/FileCacheStrategy;Ljava/io/InputStream;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/ExternalResource; } public final class net/mamoe/mirai/utils/FileCacheStrategy$Companion { public final fun getPlatformDefault ()Lnet/mamoe/mirai/utils/FileCacheStrategy; } public final class net/mamoe/mirai/utils/FileCacheStrategy$MemoryCache : net/mamoe/mirai/utils/FileCacheStrategy { public static final field INSTANCE Lnet/mamoe/mirai/utils/FileCacheStrategy$MemoryCache; public fun newCache (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; } public final class net/mamoe/mirai/utils/FileCacheStrategy$TempCache : net/mamoe/mirai/utils/FileCacheStrategy { public fun <init> ()V public fun <init> (Ljava/io/File;)V public synthetic fun <init> (Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun getDirectory ()Ljava/io/File; public fun newCache (Ljava/io/InputStream;Ljava/lang/String;)Lnet/mamoe/mirai/utils/ExternalResource; } public abstract interface annotation class net/mamoe/mirai/utils/JavaFriendlyAPI : java/lang/annotation/Annotation { } public final class net/mamoe/mirai/utils/LoggerAdapters { public static final field INSTANCE Lnet/mamoe/mirai/utils/LoggerAdapters; public static final fun asMiraiLogger (Ljava/util/logging/Logger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun asMiraiLogger (Lorg/apache/logging/log4j/Logger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun asMiraiLogger (Lorg/apache/logging/log4j/Logger;Lorg/apache/logging/log4j/Marker;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun asMiraiLogger (Lorg/slf4j/Logger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static final fun useLog4j2 ()V } public abstract class net/mamoe/mirai/utils/LoginSolver { public static final field Companion Lnet/mamoe/mirai/utils/LoginSolver$Companion; public static final field Default Lnet/mamoe/mirai/utils/LoginSolver; public fun <init> ()V public fun createQRCodeLoginListener (Lnet/mamoe/mirai/Bot;)Lnet/mamoe/mirai/auth/QRCodeLoginListener; public fun isSliderCaptchaSupported ()Z public fun onSolveDeviceVerification (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/utils/DeviceVerificationRequests;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun onSolvePicCaptcha (Lnet/mamoe/mirai/Bot;[BLkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun onSolveSliderCaptcha (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onSolveUnsafeDeviceLoginVerify (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/LoginSolver$Companion { public final synthetic fun getDefault ()Lnet/mamoe/mirai/utils/LoginSolver; } public abstract interface annotation class net/mamoe/mirai/utils/MiraiExperimentalApi : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public abstract interface annotation class net/mamoe/mirai/utils/MiraiInternalApi : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public abstract interface annotation class net/mamoe/mirai/utils/MiraiInternalFile : java/lang/annotation/Annotation { } public abstract interface class net/mamoe/mirai/utils/MiraiLogger { public static final field Companion Lnet/mamoe/mirai/utils/MiraiLogger$Companion; public fun call (Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;Ljava/lang/String;Ljava/lang/Throwable;)V public static synthetic fun call$default (Lnet/mamoe/mirai/utils/MiraiLogger;Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority;Ljava/lang/String;Ljava/lang/Throwable;ILjava/lang/Object;)V public static synthetic fun create (Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun debug (Ljava/lang/String;)V public abstract fun debug (Ljava/lang/String;Ljava/lang/Throwable;)V public fun debug (Ljava/lang/Throwable;)V public abstract fun error (Ljava/lang/String;)V public abstract fun error (Ljava/lang/String;Ljava/lang/Throwable;)V public fun error (Ljava/lang/Throwable;)V public synthetic fun getFollower ()Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun getIdentity ()Ljava/lang/String; public abstract fun info (Ljava/lang/String;)V public abstract fun info (Ljava/lang/String;Ljava/lang/Throwable;)V public fun info (Ljava/lang/Throwable;)V public fun isDebugEnabled ()Z public abstract fun isEnabled ()Z public fun isErrorEnabled ()Z public fun isInfoEnabled ()Z public fun isVerboseEnabled ()Z public fun isWarningEnabled ()Z public synthetic fun plus (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLogger; public static synthetic fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V public synthetic fun setFollower (Lnet/mamoe/mirai/utils/MiraiLogger;)V public abstract fun verbose (Ljava/lang/String;)V public abstract fun verbose (Ljava/lang/String;Ljava/lang/Throwable;)V public fun verbose (Ljava/lang/Throwable;)V public abstract fun warning (Ljava/lang/String;)V public abstract fun warning (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning (Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/utils/MiraiLogger$Companion { public final synthetic fun create (Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public final synthetic fun getTopLevel ()Lnet/mamoe/mirai/utils/MiraiLogger; public final synthetic fun setDefaultLoggerCreator (Lkotlin/jvm/functions/Function1;)V } public abstract interface class net/mamoe/mirai/utils/MiraiLogger$Factory { public static final field INSTANCE Lnet/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE; public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger; public abstract fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Ljava/lang/Class;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger; public static synthetic fun create$default (Lnet/mamoe/mirai/utils/MiraiLogger$Factory;Lkotlin/reflect/KClass;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLogger; } public final class net/mamoe/mirai/utils/MiraiLogger$Factory$INSTANCE : net/mamoe/mirai/utils/MiraiLogger$Factory { public fun create (Ljava/lang/Class;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Ljava/lang/Class;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;)Lnet/mamoe/mirai/utils/MiraiLogger; public fun create (Lkotlin/reflect/KClass;Ljava/lang/String;)Lnet/mamoe/mirai/utils/MiraiLogger; } public abstract class net/mamoe/mirai/utils/MiraiLoggerPlatformBase : net/mamoe/mirai/utils/MiraiLogger { public fun <init> ()V public final fun debug (Ljava/lang/String;)V public final fun debug (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun debug0 (Ljava/lang/String;)V protected abstract fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun error (Ljava/lang/String;)V public final fun error (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun error0 (Ljava/lang/String;)V protected abstract fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun info (Ljava/lang/String;)V public final fun info (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun info0 (Ljava/lang/String;)V protected abstract fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun isEnabled ()Z public final fun verbose (Ljava/lang/String;)V public final fun verbose (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun verbose0 (Ljava/lang/String;)V protected abstract fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun warning (Ljava/lang/String;)V public final fun warning (Ljava/lang/String;Ljava/lang/Throwable;)V protected fun warning0 (Ljava/lang/String;)V protected abstract fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/utils/MiraiLoggerWithSwitch : net/mamoe/mirai/utils/MiraiLoggerPlatformBase { public fun debug0 (Ljava/lang/String;)V public fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun disable ()V public final fun enable ()V public fun error0 (Ljava/lang/String;)V public fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun getIdentity ()Ljava/lang/String; public fun info0 (Ljava/lang/String;)V public fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun isEnabled ()Z public fun verbose0 (Ljava/lang/String;)V public fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning0 (Ljava/lang/String;)V public fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public abstract interface annotation class net/mamoe/mirai/utils/NotStableForInheritance : java/lang/annotation/Annotation { public abstract fun message ()Ljava/lang/String; } public final class net/mamoe/mirai/utils/OverFileSizeMaxException : java/lang/IllegalStateException { public fun <init> ()V } public abstract interface class net/mamoe/mirai/utils/ProgressionCallback { public static final field Companion Lnet/mamoe/mirai/utils/ProgressionCallback$Companion; public static fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback; public fun onBegin (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;)V public fun onFailure (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Throwable;)V public fun onFinished (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V public fun onProgression (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V public fun onSuccess (Ljava/lang/Object;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Object;)V } public final class net/mamoe/mirai/utils/ProgressionCallback$Companion { public final fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/ProgressionCallback; public static synthetic fun asProgressionCallback$default (Lnet/mamoe/mirai/utils/ProgressionCallback$Companion;Lkotlinx/coroutines/channels/SendChannel;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/ProgressionCallback; } public abstract interface class net/mamoe/mirai/utils/RemoteFile { public static final field Companion Lnet/mamoe/mirai/utils/RemoteFile$Companion; public static final field ROOT_PATH Ljava/lang/String; public fun delete ()Z public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun exists ()Z public abstract fun exists (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getContact ()Lnet/mamoe/mirai/contact/FileSupported; public fun getDownloadInfo ()Lnet/mamoe/mirai/utils/RemoteFile$DownloadInfo; public abstract fun getDownloadInfo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getId ()Ljava/lang/String; public fun getInfo ()Lnet/mamoe/mirai/utils/RemoteFile$FileInfo; public abstract fun getInfo (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getName ()Ljava/lang/String; public abstract fun getParent ()Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun getPath ()Ljava/lang/String; public fun isDirectory ()Z public fun isDirectory (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun isDirectory$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun isFile ()Z public abstract fun isFile (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun length ()J public abstract fun length (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun listFiles ()Lkotlinx/coroutines/flow/Flow; public abstract fun listFiles (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun listFilesCollection ()Ljava/util/List; public fun listFilesCollection (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun listFilesCollection$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun listFilesIterator (Z)Ljava/util/Iterator; public abstract fun listFilesIterator (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun mkdir ()Z public abstract fun mkdir (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun moveTo (Ljava/lang/String;)Z public fun moveTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun moveTo (Lnet/mamoe/mirai/utils/RemoteFile;)Z public abstract fun moveTo (Lnet/mamoe/mirai/utils/RemoteFile;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun moveTo$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun renameTo (Ljava/lang/String;)Z public abstract fun renameTo (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolve (Ljava/lang/String;)Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun resolve (Lnet/mamoe/mirai/utils/RemoteFile;)Lnet/mamoe/mirai/utils/RemoteFile; public fun resolveById (Ljava/lang/String;)Lnet/mamoe/mirai/utils/RemoteFile; public fun resolveById (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun resolveById (Ljava/lang/String;Z)Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun resolveById (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun resolveById$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/RemoteFile; public static synthetic fun resolveById$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;ZLkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun resolveById$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun resolveSibling (Ljava/lang/String;)Lnet/mamoe/mirai/utils/RemoteFile; public abstract fun resolveSibling (Lnet/mamoe/mirai/utils/RemoteFile;)Lnet/mamoe/mirai/utils/RemoteFile; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun toMessage ()Lnet/mamoe/mirai/message/data/FileMessage; public abstract fun toMessage (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun toString ()Ljava/lang/String; public fun upload (Ljava/io/File;)Lnet/mamoe/mirai/message/data/FileMessage; public fun upload (Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun upload (Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public fun upload (Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun upload (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/FileMessage; public fun upload (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun upload (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public abstract fun upload (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun upload$default (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun upload$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun upload$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun upload$suspendImpl (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public static fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/RemoteFile$Companion { public static final field ROOT_PATH Ljava/lang/String; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/MessageReceipt; public final synthetic fun sendFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/MessageReceipt; public static synthetic fun sendFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage; public final fun uploadFile (Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Ljava/io/File;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/FileMessage; public static synthetic fun uploadFile$default (Lnet/mamoe/mirai/utils/RemoteFile$Companion;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/RemoteFile$DownloadInfo { public final fun getFilename ()Ljava/lang/String; public final fun getId ()Ljava/lang/String; public final fun getMd5 ()[B public final fun getPath ()Ljava/lang/String; public final fun getSha1 ()[B public final fun getUrl ()Ljava/lang/String; public fun toString ()Ljava/lang/String; } public final class net/mamoe/mirai/utils/RemoteFile$FileInfo { public final fun getDownloadTimes ()I public final fun getId ()Ljava/lang/String; public final fun getLastModifyTime ()J public final fun getLength ()J public final fun getMd5 ()[B public final fun getName ()Ljava/lang/String; public final fun getPath ()Ljava/lang/String; public final fun getSha1 ()[B public final fun getUploadTime ()J public final fun getUploaderId ()J public final fun resolveToFile (Lnet/mamoe/mirai/contact/FileSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public abstract interface class net/mamoe/mirai/utils/RemoteFile$ProgressionCallback { public static final field Companion Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback$Companion; public static fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback; public fun onBegin (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;)V public fun onFailure (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/Throwable;)V public fun onProgression (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;J)V public fun onSuccess (Lnet/mamoe/mirai/utils/RemoteFile;Lnet/mamoe/mirai/utils/ExternalResource;)V } public final class net/mamoe/mirai/utils/RemoteFile$ProgressionCallback$Companion { public final fun asProgressionCallback (Lkotlinx/coroutines/channels/SendChannel;Z)Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback; public static synthetic fun asProgressionCallback$default (Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback$Companion;Lkotlinx/coroutines/channels/SendChannel;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback; } public final class net/mamoe/mirai/utils/SilentLogger : net/mamoe/mirai/utils/PlatformLogger { public static final field INSTANCE Lnet/mamoe/mirai/utils/SilentLogger; public fun debug0 (Ljava/lang/String;)V public fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun error0 (Ljava/lang/String;)V public fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun getIdentity ()Ljava/lang/String; public fun info0 (Ljava/lang/String;)V public fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun isDebugEnabled ()Z public fun isEnabled ()Z public fun isErrorEnabled ()Z public fun isInfoEnabled ()Z public fun isVerboseEnabled ()Z public fun isWarningEnabled ()Z public fun verbose0 (Ljava/lang/String;)V public fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning0 (Ljava/lang/String;)V public fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public class net/mamoe/mirai/utils/SimpleLogger : net/mamoe/mirai/utils/MiraiLoggerPlatformBase { public static final field Companion Lnet/mamoe/mirai/utils/SimpleLogger$Companion; public fun <init> (Ljava/lang/String;Lkotlin/jvm/functions/Function3;)V public fun debug0 (Ljava/lang/String;)V public fun debug0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun error0 (Ljava/lang/String;)V public fun error0 (Ljava/lang/String;Ljava/lang/Throwable;)V public final fun getIdentity ()Ljava/lang/String; protected fun getLogger ()Lkotlin/jvm/functions/Function3; public fun info0 (Ljava/lang/String;)V public fun info0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun verbose0 (Ljava/lang/String;)V public fun verbose0 (Ljava/lang/String;Ljava/lang/Throwable;)V public fun warning0 (Ljava/lang/String;)V public fun warning0 (Ljava/lang/String;Ljava/lang/Throwable;)V } public final class net/mamoe/mirai/utils/SimpleLogger$Companion { public final fun invoke (Ljava/lang/String;Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/utils/SimpleLogger; public final fun invoke (Lkotlin/jvm/functions/Function2;)Lnet/mamoe/mirai/utils/SimpleLogger; public final fun invoke (Lkotlin/jvm/functions/Function3;)Lnet/mamoe/mirai/utils/SimpleLogger; } public final class net/mamoe/mirai/utils/SimpleLogger$LogPriority : java/lang/Enum { public static final field DEBUG Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field ERROR Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field INFO Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field VERBOSE Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static final field WARNING Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public final fun getCorrespondingFunction ()Lkotlin/jvm/functions/Function3; public final fun getNameAligned ()Ljava/lang/String; public final fun getSimpleName ()Ljava/lang/String; public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; public static fun values ()[Lnet/mamoe/mirai/utils/SimpleLogger$LogPriority; } public final class net/mamoe/mirai/utils/SingleFileLogger : net/mamoe/mirai/utils/PlatformLogger, net/mamoe/mirai/utils/MiraiLogger { public fun <init> (Ljava/lang/String;)V public fun <init> (Ljava/lang/String;Ljava/io/File;)V public synthetic fun <init> (Ljava/lang/String;Ljava/io/File;ILkotlin/jvm/internal/DefaultConstructorMarker;)V } public final class net/mamoe/mirai/utils/StandardCharImageLoginSolver : net/mamoe/mirai/utils/LoginSolver { public static final field Companion Lnet/mamoe/mirai/utils/StandardCharImageLoginSolver$Companion; public fun <init> ()V public fun <init> (Lkotlin/jvm/functions/Function1;)V public fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun <init> (Lkotlin/jvm/functions/Function1;Lnet/mamoe/mirai/utils/MiraiLogger;)V public synthetic fun <init> (Lkotlin/jvm/functions/Function1;Lnet/mamoe/mirai/utils/MiraiLogger;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public static final fun createBlocking (Lkotlin/jvm/functions/Function0;)Lnet/mamoe/mirai/utils/StandardCharImageLoginSolver; public static final fun createBlocking (Lkotlin/jvm/functions/Function0;Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/StandardCharImageLoginSolver; public fun createQRCodeLoginListener (Lnet/mamoe/mirai/Bot;)Lnet/mamoe/mirai/auth/QRCodeLoginListener; public fun isSliderCaptchaSupported ()Z public fun onSolveDeviceVerification (Lnet/mamoe/mirai/Bot;Lnet/mamoe/mirai/utils/DeviceVerificationRequests;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onSolvePicCaptcha (Lnet/mamoe/mirai/Bot;[BLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onSolveSliderCaptcha (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun onSolveUnsafeDeviceLoginVerify (Lnet/mamoe/mirai/Bot;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/StandardCharImageLoginSolver$Companion { public final fun createBlocking (Lkotlin/jvm/functions/Function0;)Lnet/mamoe/mirai/utils/StandardCharImageLoginSolver; public final fun createBlocking (Lkotlin/jvm/functions/Function0;Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/StandardCharImageLoginSolver; } public abstract interface class net/mamoe/mirai/utils/Streamable { public abstract fun asFlow ()Lkotlinx/coroutines/flow/Flow; public fun asStream ()Ljava/util/stream/Stream; public fun toList ()Ljava/util/List; public fun toList (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun toList$suspendImpl (Lnet/mamoe/mirai/utils/Streamable;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/utils/Utils { public static final synthetic fun BotConfiguration (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/utils/BotConfiguration; public static final fun debug (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun debug (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun error (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun error (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun info (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun info (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun verbose (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun verbose (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun warning (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;)V public static final fun warning (Lnet/mamoe/mirai/utils/MiraiLogger;Lkotlin/jvm/functions/Function0;Ljava/lang/Throwable;)V public static final fun withSwitch (Lnet/mamoe/mirai/utils/MiraiLogger;)Lnet/mamoe/mirai/utils/MiraiLoggerWithSwitch; public static final fun withSwitch (Lnet/mamoe/mirai/utils/MiraiLogger;Z)Lnet/mamoe/mirai/utils/MiraiLoggerWithSwitch; public static synthetic fun withSwitch$default (Lnet/mamoe/mirai/utils/MiraiLogger;ZILjava/lang/Object;)Lnet/mamoe/mirai/utils/MiraiLoggerWithSwitch; } ================================================ FILE: mirai-core-api/src/androidMain/AndroidManifest.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright 2019-2023 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/dev/LICENSE --> <manifest package="net.mamoe.mirai" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> </manifest> ================================================ FILE: mirai-core-api/src/androidMain/kotlin/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai ================================================ FILE: mirai-core-api/src/androidMain/kotlin/utils/LoginSolver.android.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils internal actual object PlatformLoginSolverImplementations { actual val isSliderCaptchaSupported: Boolean by lazy { System.getProperty("mirai.slider.captcha.supported") != null } actual val default: LoginSolver? get() = null } ================================================ FILE: mirai-core-api/src/androidMain/kotlin/utils/PlatformLogger.android.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import android.util.Log /** * [Log] 日志实现 * * @see MiraiLogger.create * @see SingleFileLogger 使用单一文件记录日志 * @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志 */ @MiraiInternalApi public actual open class PlatformLogger actual constructor( public override val identity: String?, ) : MiraiLoggerPlatformBase() { public override fun verbose0(message: String?) { Log.v(identity, message.toString()) } public override fun verbose0(message: String?, e: Throwable?) { Log.v(identity, message, e) } public override fun info0(message: String?) { Log.i(identity, message.toString()) } public override fun info0(message: String?, e: Throwable?) { Log.i(identity, message, e) } public override fun warning0(message: String?) { Log.w(identity, message.toString()) } public override fun warning0(message: String?, e: Throwable?) { Log.w(identity, message, e) } public override fun error0(message: String?) { Log.e(identity, message.toString()) } public override fun error0(message: String?, e: Throwable?) { Log.e(identity, message, e) } public override fun debug0(message: String?) { Log.d(identity, message.toString()) } public override fun debug0(message: String?, e: Throwable?) { Log.d(identity, message, e) } } ================================================ FILE: mirai-core-api/src/androidMain/kotlin/utils/SingleFileLogger.android.kt ================================================ /* * 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.utils import net.mamoe.mirai.internal.utils.StdoutLogger import java.io.File /** * 将日志写入('append')到特定文件. * * @see PlatformLogger 查看格式信息 */ public actual class SingleFileLogger actual constructor( identity: String, file: File ) : MiraiLogger by StdoutLogger(identity, { file.appendText(it + "\n") }) { public actual constructor(identity: String) : this(identity, File("$identity-${getCurrentDate()}.log")) init { file.createNewFile() require(file.isFile) { "Log file must be a file: $file" } require(file.canWrite()) { "Log file must be write: $file" } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/Bot.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") @file:JvmBlockingBridge package net.mamoe.mirai import kotlinx.coroutines.* import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.friendgroup.FriendGroups import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.message.action.BotNudge import net.mamoe.mirai.message.action.MemberNudge import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.ConcurrentHashMap import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * 登录, 返回 [this] */ @JvmSynthetic public suspend inline fun <B : Bot> B.alsoLogin(): B = also { login() } /** * 机器人对象. 一个机器人实例登录一个 QQ 账号. * Mirai 为多账号设计, 可同时维护多个机器人. * * 有关 [Bot] 生命管理, 请查看 [BotConfiguration.inheritCoroutineContext] * * @see Contact 联系人 * @see isActive 判断 [Bot] 是否正常运行中. (协程正常运行) (但不能判断是否在线, 需使用 [isOnline]) * * @see BotFactory 构造 [Bot] 的工厂, [Bot] 唯一的构造方式. */ @NotStableForInheritance public interface Bot : CoroutineScope, ContactOrBot, UserOrBot { /** * Bot 配置 */ public val configuration: BotConfiguration /** * 返回 `this` */ public override val bot: Bot get() = this /** * 日志记录器 */ public val logger: MiraiLogger /** * 当 Bot 在线 (可正常收发消息) 时返回 `true`. */ public val isOnline: Boolean /** * 来自这个 [Bot] 的 [BotEvent] 的事件通道. * @see EventChannel */ public val eventChannel: EventChannel<BotEvent> // region contacts /** * 其他设备列表 */ public val otherClients: ContactList<OtherClient> /** * [User.id] 与 [Bot.id] 相同的 [Friend] 实例 */ public val asFriend: Friend /** * [User.id] 与 [Bot.id] 相同的 [Stranger] 实例 */ public val asStranger: Stranger /** * 陌生人列表. 与服务器同步更新. */ public val strangers: ContactList<Stranger> /** * 以 [对方 QQ 号码][id] 获取一个陌生人对象, 在获取失败时返回 `null`. */ public fun getStranger(id: Long): Stranger? = strangers.firstOrNull { it.id == id } /** * 以 [对方 QQ 号码][id] 获取一个陌生人对象, 在获取失败时抛出 [NoSuchElementException]. */ public fun getStrangerOrFail(id: Long): Stranger = getStranger(id) ?: throw NoSuchElementException("stranger $id") /** * 好友列表. 与服务器同步更新. */ public val friends: ContactList<Friend> /** * 全部的好友分组 * * @since 2.13 */ public val friendGroups: FriendGroups /** * 以 [对方 QQ 号码][id] 获取一个好友对象, 在获取失败时返回 `null`. * 在 [id] 与 [Bot.id] 相同时返回 [Bot.asFriend]. */ public fun getFriend(id: Long): Friend? = friends.firstOrNull { it.id == id } ?: if (id == this.id) asFriend else null /** * 以 [对方 QQ 号码][id] 获取一个好友对象, 在获取失败时抛出 [NoSuchElementException]. * 在 [id] 与 [Bot.id] 相同时返回 [Bot.asFriend]. */ public fun getFriendOrFail(id: Long): Friend = getFriend(id) ?: throw NoSuchElementException("friend $id") /** * 加入的群列表. 与服务器同步更新. */ public val groups: ContactList<Group> /** * 以 [群号码][id] 获取一个群对象, 在获取失败时返回 `null`. */ public fun getGroup(id: Long): Group? = groups.firstOrNull { it.id == id } /** * 以 [群号码][id] 获取一个群对象, 在获取失败时抛出 [NoSuchElementException]. */ public fun getGroupOrFail(id: Long): Group = getGroup(id) ?: throw NoSuchElementException("group $id") // endregion /** * 登录, 或重新登录. * 这个函数总是关闭一切现有网路任务 (但不会关闭其他任务), 然后重新登录并重新缓存好友列表和群列表. * * 一般情况下不需要重新登录. Mirai 能够自动处理掉线情况. * * @throws LoginFailedException 正常登录失败时抛出 * @see alsoLogin `.apply { login() }` 捷径 */ public suspend fun login() /** * 创建一个 "戳一戳" 消息 * * @see MemberNudge.sendTo 发送这个戳一戳消息 */ public override fun nudge(): BotNudge = BotNudge(this) /** * 关闭这个 [Bot], 立即取消 [Bot] 的 [SupervisorJob], 取消与这个 [Bot] 相关的所有有协程联系的任务. * * **注意:** 不可重新登录. 必须重新实例化一个 [Bot]. * * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭. 在关闭 [Bot] 作用域下的协程时将会传递这个原因. * * @see closeAndJoin 取消并 [Bot.join], 以确保 [Bot] 相关的活动被完全关闭 */ public fun close(cause: Throwable? = null) /** * 关闭这个 [Bot]. 查看 [close] 获取更多信息. */ public fun close(): Unit = close(null) public companion object { @Suppress("ObjectPropertyName") internal val _instances: MutableMap<Long, Bot> = ConcurrentHashMap() /** * 复制一份此时的 [Bot] 实例列表. */ @JvmStatic public val instances: List<Bot> get() = _instances.values.toList() /** * 复制一份此时的 [Bot] 实例列表. */ @JvmStatic public val instancesSequence: Sequence<Bot> get() = _instances.values.asSequence().filterNotNull() /** * 获取一个 [Bot] 实例, 无对应实例时抛出 [NoSuchElementException] */ @JvmStatic @Throws(NoSuchElementException::class) public fun getInstance(qq: Long): Bot = findInstance(qq) ?: throw NoSuchElementException(qq.toString()) /** * 获取一个 [Bot] 实例, 无对应实例时返回 `null` */ @JvmStatic public inline fun getInstanceOrNull(qq: Long): Bot? = findInstance(qq) /** * 获取一个 [Bot] 实例, 无对应实例时返回 `null` */ @JvmStatic public fun findInstance(qq: Long): Bot? = _instances[qq] } /** * 挂起协程直到 [Bot] 协程被关闭 ([Bot.close]). * 即使 [Bot] 离线, 也会等待直到协程关闭. */ public suspend fun join(): Unit = supervisorJob.join() /** * 关闭这个 [Bot], 停止一切相关活动. 所有引用都会被释放. * * 注: 不可重新登录. 必须重新实例化一个 [Bot]. * * @param cause 原因. 为 null 时视为正常关闭, 非 null 时视为异常关闭 */ public suspend fun closeAndJoin(cause: Throwable? = null) { close(cause) join() } } /** * 获取 [Job] 的协程 [Job]. 此 [Job] 为一个 [SupervisorJob] */ @get:JvmSynthetic public inline val Bot.supervisorJob: CompletableJob get() = this.coroutineContext[Job] as CompletableJob /** * 当 [Bot] 拥有 [Friend.id] 为 [id] 的好友时返回 `true`. */ @JvmSynthetic public inline fun Bot.containsFriend(id: Long): Boolean = this.friends.contains(id) /** * 当 [Bot] 拥有 [Group.id] 为 [id] 的群时返回 `true`. */ @JvmSynthetic public inline fun Bot.containsGroup(id: Long): Boolean = this.groups.contains(id) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/BotFactory.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "NOTHING_TO_INLINE") package net.mamoe.mirai import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.utils.BotConfiguration import kotlin.jvm.JvmSynthetic /** * 构造 [Bot] 的工厂. 这是 [Bot] 唯一的构造方式. * * @see IMirai.BotFactory */ public interface BotFactory { /** * 相当于 Kotlin lambda `BotConfiguration.() -> Unit` 和 Java `Consumer<BotConfiguration>` * * @see newBot */ public fun interface BotConfigurationLambda { public operator fun BotConfiguration.invoke() } /////////////////////////////////////////////////////////////////////////// // Plain Password /////////////////////////////////////////////////////////////////////////// /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 */ public fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 * * Kotlin: * ``` * newBot(123, "") { * // this: BotConfiguration * fileBasedDeviceInfo() * } * ``` * * Java: * ```java * newBot(123, "", configuration -> { * configuration.fileBasedDeviceInfo() * }) * ``` */ public fun newBot( qq: Long, password: String, configuration: BotConfigurationLambda /* = BotConfiguration.() -> Unit */ ): Bot = newBot(qq, password, configuration.run { BotConfiguration().apply { invoke() } }) /** * 使用 [默认配置][BotConfiguration.Default] 构造 [Bot] 实例 */ public fun newBot(qq: Long, password: String): Bot = newBot(qq, password, BotConfiguration.Default) /////////////////////////////////////////////////////////////////////////// // MD5 Password /////////////////////////////////////////////////////////////////////////// /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 * * @param passwordMd5 16 bytes */ public fun newBot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 * * Kotlin: * ``` * newBot(123, password) { * // this: BotConfiguration * fileBasedDeviceInfo() * } * ``` * * Java: * ```java * newBot(123, password, configuration -> { * configuration.fileBasedDeviceInfo() * }) * ``` * * @param passwordMd5 16 bytes */ public fun newBot( qq: Long, passwordMd5: ByteArray, configuration: BotConfigurationLambda /* = BotConfiguration.() -> Unit */ ): Bot = newBot(qq, passwordMd5, configuration.run { BotConfiguration().apply { invoke() } }) /** * 使用 [默认配置][BotConfiguration.Default] 构造 [Bot] 实例 * * @param passwordMd5 16 bytes */ public fun newBot(qq: Long, passwordMd5: ByteArray): Bot = newBot(qq, passwordMd5, BotConfiguration.Default) /////////////////////////////////////////////////////////////////////////// // BotAuthorization /////////////////////////////////////////////////////////////////////////// /** * 使用 [默认配置][BotConfiguration.Default] 构造 [Bot] 实例 * * @since 2.15 */ public fun newBot(qq: Long, authorization: BotAuthorization): Bot = newBot(qq, authorization, BotConfiguration.Default) /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 * * @since 2.15 */ public fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 * * Kotlin: * ``` * newBot(123, password) { * // this: BotConfiguration * fileBasedDeviceInfo() * } * ``` * * Java: * ```java * newBot(123, password, configuration -> { * configuration.fileBasedDeviceInfo() * }) * ``` * * @since 2.15 */ public fun newBot( qq: Long, authorization: BotAuthorization, configuration: BotConfigurationLambda /* = BotConfiguration.() -> Unit */ ): Bot = newBot(qq, authorization, configuration.run { BotConfiguration().apply { invoke() } }) public companion object INSTANCE : BotFactory { override fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot { return Mirai.BotFactory.newBot(qq, password, configuration) } override fun newBot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot { return Mirai.BotFactory.newBot(qq, passwordMd5, configuration) } /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 * * ``` * newBot(123, "") { * // this: BotConfiguration * fileBasedDeviceInfo() * } * ``` * * @since 2.7 */ @JvmSynthetic public inline fun newBot( qq: Long, password: String, configuration: BotConfiguration.() -> Unit /* = BotConfiguration.() -> Unit */ ): Bot = newBot(qq, password, BotConfiguration().apply(configuration)) // implementation notes: this is inline for `inheritCoroutineContext()` // see https://github.com/mamoe/mirai/commit/0dbb448cad1ed4773d48ccb8c0b497841bc9fa4c#r50249446 /** * 使用指定的 [配置][configuration] 构造 [Bot] 实例 * * ``` * newBot(123, password) { * // this: BotConfiguration * fileBasedDeviceInfo() * } * ``` * * @since 2.7 */ @JvmSynthetic public inline fun newBot( qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration.() -> Unit /* = BotConfiguration.() -> Unit */ ): Bot = newBot(qq, passwordMd5, BotConfiguration().apply(configuration)) override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot { return Mirai.BotFactory.newBot(qq, authorization, configuration) } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/IMirai.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INTERFACE_NOT_SUPPORTED", "PropertyName") @file:JvmName("Mirai") @file:JvmBlockingBridge package net.mamoe.mirai import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.UserProfile import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.Image.Key.queryUrl import net.mamoe.mirai.message.data.MessageSource.Key.recall import net.mamoe.mirai.utils.* import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * [IMirai] 实例. */ @get:JvmName("getInstance") // Java 调用: Mirai.getInstance() public val Mirai: IMirai get() = _MiraiInstance.get() /** * Mirai API 接口. 是 Mirai API 与 Mirai 协议实现对接的接口. * * ## 获取实例 * * 通常在引用 `net.mamoe:mirai-core` 模块后就可以通过 [Mirai] 获取到 [IMirai] 实例. * 在 Kotlin 调用顶层定义 `Mirai`, 在 Java 调用 `Mirai.getInstance()`. * * ### 使用 [IMirai] 的接口 * * [IMirai] 中的接口通常是稳定 * * ### 手动提供实例 * * 默认通过 [_MiraiInstance.get] 使用 [java.util.ServiceLoader] 寻找实例. 若某些环境下 [java.util.ServiceLoader] 不可用, 可在 Kotlin 手动设置实例: * ``` * @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") // 必要 * net.mamoe.mirai._MiraiInstance.set(net.mamoe.mirai.internal.MiraiImpl()) * ``` * * 但通常都可用自动获取而不需要手动设置. * * ## 稳定性 * * ### 使用稳定 * * 所有接口默认是可以稳定使用的. 但 [LowLevelApiAccessor] 中的方法默认是非常不稳定的. * * ### 继承不稳定 * * **[IMirai] 可能会增加新的抽象属性或函数. 因此不适合被继承或实现.** * * @see Mirai 获取实例 */ @OptIn(LowLevelApi::class, MiraiExperimentalApi::class) @NotStableForInheritance public interface IMirai : LowLevelApiAccessor { /** * 请优先使用 [BotFactory.INSTANCE] * * @see BotFactory.INSTANCE */ public val BotFactory: BotFactory /** * Mirai 全局使用的 [FileCacheStrategy]. * * 覆盖后将会立即应用到全局. * * @see FileCacheStrategy */ public var FileCacheStrategy: FileCacheStrategy /** * 获取 uin. * * - 用户的 uin 就是用户的 ID (QQ 号码, [User.id]). * - 部分旧群的 uin 需要通过算法计算 [calculateGroupUinByGroupCode]. 新群的 uin 与在客户端能看到的群号码 ([Group.id]) 相同. * * 除了一些偏底层的 API 如 [MessageSourceBuilder.id] 外, mirai 的所有其他 API 都使用在客户端能看到的用户 QQ 号码和群号码 ([Contact.id]). 并会在需要的时候进行合适转换. * 若需要使用 uin, 在特定方法的文档中会标出. */ public fun getUin(contactOrBot: ContactOrBot): Long { return if (contactOrBot is Group) @Suppress("DEPRECATION") calculateGroupUinByGroupCode(contactOrBot.id) else contactOrBot.id } /** * 使用 groupCode 计算 groupUin. 这两个值仅在 mirai 内部协议区分, 一般人使用时无需在意. * @see getUin */ @Deprecated( "The result might be wrong. Consider using getUin", level = DeprecationLevel.WARNING ) // deprecated since 2.8.0-RC, see #1479 public fun calculateGroupUinByGroupCode(groupCode: Long): Long { var left: Long = groupCode / 1000000L when (left) { in 0..10 -> left += 202 in 11..19 -> left += 480 - 11 in 20..66 -> left += 2100 - 20 in 67..156 -> left += 2010 - 67 in 157..209 -> left += 2147 - 157 in 210..309 -> left += 4100 - 210 in 310..499 -> left += 3800 - 310 } return left * 1000000L + groupCode % 1000000L } /** * 使用 groupUin 计算 groupCode. 这两个值仅在 mirai 内部协议区分, 一般人使用时无需在意. * @see getUin */ public fun calculateGroupCodeByGroupUin(groupUin: Long): Long { var left: Long = groupUin / 1000000L when (left) { in 0 + 202..10 + 202 -> left -= 202 in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11 in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20 in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67 in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157 in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210 in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310 } return left * 1000000L + groupUin % 1000000L } /** * 撤回这条消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. * * [Bot] 撤回自己的消息不需要权限. * [Bot] 撤回群员的消息需要管理员权限. * * @param source 消息源. 可从 [MessageReceipt.source] 获得, 或从消息事件中的 [MessageChain] 获得, 或通过 [buildMessageSource] 构建. * * @throws PermissionDeniedException 当 [Bot] 无权限操作时抛出 * @throws IllegalStateException 当这条消息已经被撤回时抛出 (仅同步主动操作) * * @see IMirai.recallMessage (扩展函数) 接受参数 [MessageChain] * @see MessageSource.recall 撤回消息扩展 */ public suspend fun recallMessage(bot: Bot, source: MessageSource) /** * 发送戳一戳消息 */ public suspend fun sendNudge(bot: Bot, nudge: Nudge, receiver: Contact): Boolean /** * 构造 [Image]. 请优先使用 [Image.Factory.create]. * * @see Image * @see Image.fromId * @see Image.Factory.create */ public fun createImage(imageId: String): Image = Image.Builder.newBuilder(imageId).build() /** * 创建一个 [FileMessage]. [name] 与 [size] 只供本地使用, 发送消息时只会使用 [id] 和 [internalId]. * @since 2.5 */ public fun createFileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage /** * 创建 [UnsupportedMessage] * @since 2.6 */ public fun createUnsupportedMessage(struct: ByteArray): UnsupportedMessage /** * 获取图片下载链接 * * @see Image.queryUrl [Image] 的扩展函数 */ public suspend fun queryImageUrl(bot: Bot, image: Image): String /** * 查询某个用户的信息 * * 此函数不会缓存信息. 每次调用此函数都会向服务器查询新信息. * * @since 2.1 */ public suspend fun queryProfile(bot: Bot, targetId: Long): UserProfile /** * 构造一个 [OfflineMessageSource]. * * 更推荐使用 [MessageSourceBuilder] 和 [MessageSource.copyAmend] 创建 [OfflineMessageSource]. * * @param ids 即 [MessageSource.ids] * @param internalIds 即 [MessageSource.internalIds] */ public fun constructMessageSource( botId: Long, kind: MessageSourceKind, fromId: Long, targetId: Long, ids: IntArray, time: Int, internalIds: IntArray, 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 好友验证的事件对象 */ public suspend fun acceptNewFriendRequest(event: NewFriendRequestEvent) /** * 拒绝好友验证 * * @param event 好友验证的事件对象 * @param blackList 拒绝后是否拉入黑名单 */ public suspend fun rejectNewFriendRequest(event: NewFriendRequestEvent, blackList: Boolean = false) /** * 通过加群验证(需管理员权限) * * @param event 加群验证的事件对象 */ public suspend fun acceptMemberJoinRequest(event: MemberJoinRequestEvent) /** * 拒绝加群验证(需管理员权限) * * @param event 加群验证的事件对象 * @param blackList 拒绝后是否拉入黑名单 */ public suspend fun rejectMemberJoinRequest( event: MemberJoinRequestEvent, blackList: Boolean = false, message: String = "" ) /** * 获取在线的 [OtherClient] 列表 * @param mayIncludeSelf 服务器返回的列表可能包含 [Bot] 自己. [mayIncludeSelf] 为 `false` 会排除自己 */ public suspend fun getOnlineOtherClientsList( bot: Bot, mayIncludeSelf: Boolean = false ): List<OtherClientInfo> /** * 忽略加群验证(需管理员权限) * * @param event 加群验证的事件对象 * @param blackList 忽略后是否拉入黑名单 */ public suspend fun ignoreMemberJoinRequest(event: MemberJoinRequestEvent, blackList: Boolean = false) /** * 接收邀请入群(需管理员权限) * * @param event 邀请入群的事件对象 */ public suspend fun acceptInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) /** * 忽略邀请入群(需管理员权限) * * @param event 邀请入群的事件对象 */ public suspend fun ignoreInvitedJoinGroupRequest(event: BotInvitedJoinGroupRequestEvent) /** * 广播一个事件. 由 [Event.broadcast] 调用. */ public suspend fun broadcastEvent(event: Event) } /** * 撤回这条消息. * * [Bot] 撤回自己的消息不需要权限, 但需要在发出后 2 分钟内撤回. * [Bot] 撤回群员的消息需要管理员权限, 可在任意时间撤回. * * @throws PermissionDeniedException 当 [Bot] 无权限操作时 * @see IMirai.recallMessage */ @JvmSynthetic public suspend inline fun IMirai.recallMessage(bot: Bot, message: MessageChain): Unit = this.recallMessage(bot, message.source) /** * @since 2.6-RC */ @PublishedApi // for tests and potential public uses. @Suppress("ClassName") internal object _MiraiInstance { @JvmStatic fun set(instance: IMirai) { miraiInstance = instance } /** * 获取通过 [set] 设置的实例, 或使用 [findMiraiInstance] 寻找一个实例. */ @JvmStatic fun get(): IMirai { return miraiInstance ?: findMiraiInstance().also { miraiInstance = it } } } // to overcome native gc issue private var miraiInstance: IMirai? = null @JvmSynthetic internal fun findMiraiInstance(): IMirai { return loadService(IMirai::class, "net.mamoe.mirai.internal.MiraiImpl") } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai import kotlinx.coroutines.Job import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.data.StrangerInfo import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.annotation.AnnotationTarget.* /** * 标示这个 API 是低级的 API. * * 低级的 API 可能在任意时刻被改动. * 使用低级的 API 无法带来任何安全和便捷保障. * 仅在某些使用结构化 API 可能影响性能的情况下使用这些低级 API. */ @LowLevelApi @RequiresOptIn @Retention(AnnotationRetention.BINARY) @Target(CLASS, FUNCTION, PROPERTY, CONSTRUCTOR) public annotation class LowLevelApi /** * [IMirai] 协议层低级 API. * * **警告**: 所有的低级 API 都可能在任意时刻不经过任何警告和迭代就被修改. 因此非常不建议在任何情况下使用这些 API. */ @LowLevelApi @NotStableForInheritance public interface LowLevelApiAccessor { /** * 主动刷新 keys, 如 SKey, PSKey 等. * * 通常 mirai 会自动刷新, 不需要手动刷新. * * @since 2.2 */ @MiraiExperimentalApi public suspend fun refreshKeys(bot: Bot) /** * 构造一个 [Friend] 对象. * * [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job. * 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭. */ @LowLevelApi public fun newFriend(bot: Bot, friendInfo: FriendInfo): Friend /** * 构造一个 [Stranger] 对象. * * [Bot] 无法管理这个对象, 但这个对象会以 [Bot] 的 [Job] 作为父 Job. * 因此, 当 [Bot] 被关闭后, 这个对象也会被关闭. */ @LowLevelApi public fun newStranger(bot: Bot, strangerInfo: StrangerInfo): Stranger /** * 撤回这条消息. 不会广播事件, 不会有安全性检查. * @see recallMessage */ @LowLevelApi public suspend fun recallGroupMessageRaw( bot: Bot, groupCode: Long, messageIds: IntArray, messageInternalIds: IntArray, ): Boolean /** * 撤回这条消息. 不会广播事件, 不会有安全性检查. * @see recallMessage */ @LowLevelApi public suspend fun recallFriendMessageRaw( bot: Bot, targetId: Long, messageIds: IntArray, messageInternalIds: IntArray, time: Int, ): Boolean /** * 撤回这条消息. 不会广播事件, 不会有安全性检查. * @see recallMessage */ @LowLevelApi public suspend fun recallGroupTempMessageRaw( bot: Bot, groupUin: Long, targetId: Long, messageIds: IntArray, messageInternalIds: IntArray, time: Int, ): Boolean /** * 向服务器查询群列表. 返回值高 32 bits 为 uin, 低 32 bits 为 groupCode */ @LowLevelApi public suspend fun getRawGroupList(bot: Bot): Sequence<Long> /** * 向服务器查询群成员列表. * 请优先使用 [Bot.getGroup], [Group.members] 查看群成员. * * 这个函数很慢. 请不要频繁使用. * * @see IMirai.calculateGroupUinByGroupCode 使用 groupCode 计算 groupUin */ @LowLevelApi public suspend fun getRawGroupMemberList( bot: Bot, groupUin: Long, groupCode: Long, ownerId: Long ): Sequence<MemberInfo> /** * 处理一个账号请求添加机器人为好友的事件 */ @LowLevelApi public suspend fun solveNewFriendRequestEvent( bot: Bot, eventId: Long, fromId: Long, fromNick: String, accept: Boolean, blackList: Boolean ) /** * 处理被邀请加入一个群请求事件 */ @LowLevelApi public suspend fun solveBotInvitedJoinGroupRequestEvent( bot: Bot, eventId: Long, invitorId: Long, groupId: Long, accept: Boolean ) /** * 处理账号请求加入群事件 */ @LowLevelApi public suspend fun solveMemberJoinRequestEvent( bot: Bot, eventId: Long, fromId: Long, fromNick: String, groupId: Long, accept: Boolean?, blackList: Boolean, message: String = "" ) /** * 查询语音的下载连接 */ @LowLevelApi public suspend fun getGroupVoiceDownloadUrl( bot: Bot, md5: ByteArray, groupId: Long, dstUin: Long ): String /** * 禁言一个匿名用户 * * @param anonymousId [AnonymousMember.anonymousId] */ @LowLevelApi public suspend fun muteAnonymousMember( bot: Bot, anonymousId: String, anonymousNick: String, groupId: Long, seconds: Int, ) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/auth/BotAuthorization.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.auth import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.Mirai import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.RetryLaterException import net.mamoe.mirai.utils.* import kotlin.jvm.JvmStatic /** * Bot 的登录鉴权方式 * * @see BotFactory.newBot * * @since 2.15 */ public interface BotAuthorization { /** * 此方法控制 Bot 如何进行登录. * * Bot 只能使用一种登录方式, 但是可以在一种登录方式失败的时候尝试其他登录方式 * * ## 异常类型 * * 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]). * 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录. * * 抛出任意其他 [Throwable] 将视为鉴权选择器的自身错误. * * ## 示例代码 * ```kotlin * override suspend fun authorize( * authComponent: BotAuthSession, * bot: BotAuthInfo, * ) { * return kotlin.runCatching { * authComponent.authByQRCode() * }.recover { * authComponent.authByPassword("...") * }.getOrThrow() * } * ``` */ public suspend fun authorize( session: BotAuthSession, info: BotAuthInfo, ): BotAuthResult /** * 计算 `cache/account.secrets` 的加密秘钥 */ @OptIn(MiraiInternalApi::class) public fun calculateSecretsKey( bot: BotAuthInfo, ): ByteArray = bot.deviceInfo.guid + bot.id.toByteArray() public companion object { @JvmStatic public fun byPassword(password: String): BotAuthorization = byPassword(password.md5()) @JvmStatic public fun byPassword(passwordMd5: ByteArray): BotAuthorization = factory.byPassword(passwordMd5) @JvmStatic public fun byQRCode(): BotAuthorization = factory.byQRCode() public operator fun invoke( block: suspend (BotAuthSession, BotAuthInfo) -> BotAuthResult ): BotAuthorization { return object : BotAuthorization { override suspend fun authorize( session: BotAuthSession, info: BotAuthInfo ): BotAuthResult { return block(session, info) } } } private val factory: DefaultBotAuthorizationFactory by lazy { Mirai // Ensure services loaded loadService() } } } @NotStableForInheritance public interface BotAuthResult @NotStableForInheritance public interface BotAuthInfo { public val id: Long public val deviceInfo: DeviceInfo public val configuration: BotConfiguration /** * 是否是首次登录 * * 首次登录指的是首次调用 [Bot.login] 进行登录,直到登录成功的过程。 * * 若在首次登录过程中多次进入[认证流程][BotAuthorization.authorize],则这流程些均被视为首次登录。 * * @see Bot.login * @see BotAuthorization.authorize */ public val isFirstLogin: Boolean /** * 导致进入[认证流程][BotAuthorization.authorize]的原因。 */ public val reason: AuthReason } /** * 导致进行[认证流程][BotAuthorization.authorize]的原因 */ public sealed class AuthReason { public abstract val bot: Bot public abstract val message: String? /** * Bot 全新[登录][Bot.login] * * 全新登录指登录前本地没有任何当前 Bot 的登录缓存信息而进行的登录。 * * 全新登录时将会进入[认证流程][BotAuthorization.authorize]。 * * @see Bot.login * @see FastLoginError */ public class FreshLogin @MiraiInternalApi constructor( override val bot: Bot, override val message: String? ) : AuthReason() /** * Bot 被挤下线 * * 当 Bot 账号在其他客户端使用相同(或相似)协议登录时,Bot 会下线, * 被挤下线后当前的登录会话将失效。 * * 当 [BotConfiguration.autoReconnectOnForceOffline] 为 `true` 时, * Bot 会尝试重新登录,并会以此原因进入[认证流程][BotAuthorization.authorize]。 * * @see BotConfiguration.autoReconnectOnForceOffline * @see BotOfflineEvent.Force */ public class ForceOffline @MiraiInternalApi constructor( override val bot: Bot, override val message: String? ) : AuthReason() /** * Bot 被服务器断开 * * 因其他原因导致 Bot 被服务器断开。这些原因包括账号被封禁、被其他客户端手动下线等, * 被服务器断开下线后当前的登录会话将失效。 * * Bot 会尝试重新登录,并会以此原因进入[认证流程][BotAuthorization.authorize]。 * * @see BotOfflineEvent.MsfOffline */ public class MsfOffline @MiraiInternalApi constructor( override val bot: Bot, override val message: String? ) : AuthReason() /** * 由网络原因引起的掉线 * * 一般情况下,Bot 被服务器断开后会尝试重新登录。 * * 由网络问题引起的掉线不一定会使当前的登录会话失效, * 仅登录会话失效时 Bot 会以此原因进入[认证流程][BotAuthorization.authorize]。 */ public class NetworkError @MiraiInternalApi constructor( override val bot: Bot, override val message: String? ) : AuthReason() /** * 快速登录失败 * * Bot 账号首次 [登录][Bot.login] 成功后,会保存登录缓存信息用于下次登录。 * * 下次登录时,Bot 会首先使用登录缓存信息尝试快速登录, * 若快速登录失败,则会以此原因进入[认证流程][BotAuthorization.authorize]。 * * @see BotAuthorization.authorize * @see Bot.login */ public class FastLoginError @MiraiInternalApi constructor( override val bot: Bot, override val message: String? ) : AuthReason() public class Unknown @MiraiInternalApi constructor( override val bot: Bot, public val cause: Throwable? ) : AuthReason() { override val message: String? = cause?.message } } @NotStableForInheritance public interface BotAuthSession { /** * @throws LoginFailedException */ public suspend fun authByPassword(password: String): BotAuthResult /** * @throws LoginFailedException */ public suspend fun authByPassword(passwordMd5: ByteArray): BotAuthResult /** * @throws LoginFailedException */ public suspend fun authByQRCode(): BotAuthResult } internal interface DefaultBotAuthorizationFactory { fun byPassword(passwordMd5: ByteArray): BotAuthorization fun byQRCode(): BotAuthorization } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/auth/QRCodeLoginListener.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.auth import net.mamoe.mirai.Bot import net.mamoe.mirai.network.LoginFailedException /** * 二维码扫描登录监听器 * * @since 2.15 */ public interface QRCodeLoginListener { /** * 使用二维码登录时获取的二维码图片大小字节数. */ public val qrCodeSize: Int get() = 3 /** * 使用二维码登录时获取的二维码边框宽度像素. */ public val qrCodeMargin: Int get() = 4 /** * 使用二维码登录时获取的二维码校正等级,必须为 1-3 之间. */ public val qrCodeEcLevel: Int get() = 2 /** * 每隔 [qrCodeStateUpdateInterval] 毫秒更新一次[二维码状态][State] */ public val qrCodeStateUpdateInterval: Long get() = 5000 /** * 从服务器获取二维码时调用,在下级显示二维码并扫描. * * @param data 二维码图像数据 (文件) */ public fun onFetchQRCode(bot: Bot, data: ByteArray) /** * 当二维码状态变化时调用. * @see State */ public fun onStateChanged(bot: Bot, state: State) /** * 每隔一段时间会调用一次此函数 * * 在此函数抛出 [LoginFailedException] 以中断登录 */ public fun onIntervalLoop() { } /** * 当二维码登录扫描完毕时执行, 在此执行资源释放 */ public fun onCompleted() { } public enum class State { /** * 等待扫描中,请在此阶段请扫描二维码. * @see QRCodeLoginListener.onFetchQRCode */ WAITING_FOR_SCAN, /** * 二维码已扫描,等待扫描端确认登录. */ WAITING_FOR_CONFIRM, /** * 扫描后取消了确认. */ CANCELLED, /** * 二维码超时,必须重新获取二维码. */ TIMEOUT, /** * 二维码已确认,将会继续登录. */ CONFIRMED, /** * 默认状态,在登录前通常为此状态. */ DEFAULT, } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/AnonymousMember.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("DeprecatedCallableAddReplaceWith") package net.mamoe.mirai.contact import net.mamoe.mirai.message.action.MemberNudge import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.NotStableForInheritance /** * 代表匿名群成员. * * 可通过 [anonymousId] 获取其识别属性. [AnonymousMember.id] 的值由服务器提供因此不可靠. * * 匿名群成员不支持发送私聊消息, 戳一戳, 上传图片. * * @see NormalMember */ @NotStableForInheritance public interface AnonymousMember : Member { /** 该匿名群成员 ID */ public val anonymousId: String @Deprecated(level = DeprecationLevel.ERROR, message = "无法发送信息至 AnonymousMember") // diagnostic deprecation public override suspend fun sendMessage(message: Message): Nothing = throw UnsupportedOperationException("Cannot send message to AnonymousMember") @Deprecated(level = DeprecationLevel.ERROR, message = "无法发送信息至 AnonymousMember") // diagnostic deprecation public override suspend fun sendMessage(message: String): Nothing = throw UnsupportedOperationException("Cannot send message to AnonymousMember") override fun nudge(): MemberNudge = throw UnsupportedOperationException("Cannot nudge AnonymousMember") override suspend fun uploadImage(resource: ExternalResource): Image = throw UnsupportedOperationException("Cannot upload image to AnonymousMember") } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/AudioSupported.kt ================================================ /* * 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.message.data.Audio import net.mamoe.mirai.message.data.OfflineAudio import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.OverFileSizeMaxException /** * 支持发送语音的 [Contact] * * @since 2.7 */ @NotStableForInheritance public interface AudioSupported : Contact { /** * 上传一个语音文件以备发送. [resource] 需要调用方[关闭][ExternalResource.close]. * * 多次调用 [uploadAudio] 使用同一个 [resource] 时, 将会发生多次上传, 且有可能产生不同的 [OfflineAudio] 对象, 因为服务器不会提供有关文件是否已经存在于服务器的信息. * * 返回的 [OfflineAudio] 支持序列化, 可以保存后在将来使用, 而不需要立即[发送][Contact.sendMessage]. 但不建议保存太久, 无法确定服务器保留一个文件的时间. * * 建议使用同一个 [Contact] 进行 [uploadAudio] 和 [sendMessage]. 目标对象不同时的行为是不确定的. * * 要获取更多语音相关的信息, 参阅 [Audio]. * * @param resource 支持 AMR 和 SILK 格式. 若要支持 MP3 格式, 请参考 [mirai-silk-converter](https://github.com/project-mirai/mirai-silk-converter) * * @throws OverFileSizeMaxException 当语音文件过大而被服务器拒绝上传时. (最大大小约为 1 MB) * **注意**: 由于服务器不一定会检查大小, 该异常就不一定会因大小超过 1MB 而抛出. * * @since 2.7 */ public suspend fun uploadAudio(resource: ExternalResource): OfflineAudio } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/AvatarSpec.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact import net.mamoe.mirai.utils.MiraiInternalApi /** * 头像的规格, [size] 单位为 px. * @since 2.11 */ public enum class AvatarSpec(@MiraiInternalApi public val size: Int) : Comparable<AvatarSpec> { /** * 最高压缩等级 */ SMALLEST(40), /** * 群员列表中的显示大小, 实际上是 40 px, 但会比 [SMALLEST] 好一些 */ SMALL(41), /** * 联系人列表中的显示大小 */ MEDIUM(100), /** * 消息列表中的显示大小 */ LARGE(140), /** * 联系人详情页面中的显示大小 */ LARGEST(640), /** * 原图 */ ORIGINAL(0); } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/Contact.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "EXPERIMENTAL_OVERRIDE") @file:JvmBlockingBridge package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.recallMessage import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage import java.io.File import java.io.InputStream /** * 联系对象, 即可以与 [Bot] 互动的对象. 包含 [用户][User], 和 [群][Group]. */ @NotStableForInheritance public interface Contact : ContactOrBot, CoroutineScope { /** * 这个联系对象所属 [Bot]. */ public override val bot: Bot /** * 可以是 QQ 号码或者群号码. * * @see User.id * @see Group.id */ public override val id: Long /** * 向这个对象发送消息. * * 单条消息最大可发送 4500 字符或 50 张图片. * * @see MessagePreSendEvent 发送消息前事件 * @see MessagePostSendEvent 发送消息后事件 * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 * @throws MessageTooLargeException 当消息过长时抛出 * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) * * @return 消息回执. 可 [引用][MessageReceipt.quote] 或 [撤回][MessageReceipt.recall] 这条消息. */ public suspend fun sendMessage(message: Message): MessageReceipt<Contact> /** * 发送纯文本消息 * @see sendMessage */ public suspend fun sendMessage(message: String): MessageReceipt<Contact> = this.sendMessage(message.toPlainText()) /** * 上传一个 [资源][ExternalResource] 作为图片以备发送. * * 也可以使用其他扩展: [ExternalResource.uploadAsImage] 使用 [File], [InputStream] 等上传. * * @see Image 查看有关图片的更多信息, 如上传图片 * * @see BeforeImageUploadEvent 图片发送前事件, 可拦截. * @see ImageUploadEvent 图片发送完成事件, 不可拦截. * * @see ExternalResource * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时抛出. (最大大小约为 20 MB, 但 mirai 限制的大小为 30 MB) */ public suspend fun uploadImage(resource: ExternalResource): Image /** * 上传 [资源][ExternalResource] 作为短视频发送. * 同时需要上传缩略图作为视频消息显示的封面. * * @see ShortVideo 查看有关短视频的更多信息 * * @see BeforeShortVideoUploadEvent 短视频发送前事件,可通过中断来拦截视频上传. * @see ShortVideoUploadEvent 短视频上传完成事件,不可拦截. * * @param thumbnail 短视频封面图,为图片资源. * @param video 视频资源,目前仅支持上传 mp4 格式的视频. * @param fileName 文件名,若为 `null` 则根据 [video] 自动生成. * * @since 2.16 */ public suspend fun uploadShortVideo( thumbnail: ExternalResource, video: ExternalResource, fileName: String? = null ): ShortVideo @JvmBlockingBridge public companion object { /** * 读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 * * 注意:此函数不会关闭 [imageStream] * * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException * @see FileCacheStrategy */ @JvmStatic @JvmOverloads public suspend fun <C : Contact> C.sendImage( imageStream: InputStream, formatName: String? = null ): MessageReceipt<C> = imageStream.sendAsImageTo(this, formatName) /** * 将文件作为图片发送到指定联系人 * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException * @see FileCacheStrategy */ @JvmStatic @JvmOverloads public suspend fun <C : Contact> C.sendImage( file: File, formatName: String? = null ): MessageReceipt<C> = file.sendAsImageTo(this, formatName) /** * 将资源作为单独的图片消息发送给 [this] * * @see Contact.sendMessage 最终调用, 发送消息. */ @JvmStatic public suspend fun <C : Contact> C.sendImage(resource: ExternalResource): MessageReceipt<C> = resource.sendAsImageTo(this) /** * 读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送 * * 注意:本函数不会关闭流 * * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException */ @JvmStatic @JvmOverloads public suspend fun Contact.uploadImage( imageStream: InputStream, formatName: String? = null ): Image = imageStream.uploadAsImage(this@uploadImage, formatName) /** * 将文件作为图片上传, 但不发送 * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException */ @JvmStatic @JvmOverloads public suspend fun Contact.uploadImage( file: File, formatName: String? = null ): Image = file.uploadAsImage(this, formatName) /** * 将文件作为图片上传, 但不发送 * @throws OverFileSizeMaxException */ @Throws(OverFileSizeMaxException::class) @JvmStatic @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) } } /** * @see IMirai.recallMessage */ @JvmSynthetic public suspend inline fun Contact.recallMessage(source: MessageChain): Unit = Mirai.recallMessage(bot, source) /** * @see IMirai.recallMessage */ @JvmSynthetic public suspend inline fun Contact.recallMessage(source: MessageSource): Unit = Mirai.recallMessage(bot, source) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/ContactList.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.contact import net.mamoe.mirai.utils.ConcurrentLinkedDeque import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmField /** * 只读联系人列表. 元素列表仍可能会被 mirai 内部修改. * * @see ContactList.asSequence */ @Suppress("unused") public class ContactList<out C : Contact> @MiraiInternalApi public constructor(@JvmField @MiraiInternalApi public val delegate: MutableCollection<@UnsafeVariance C>) : Collection<C> by delegate { @MiraiInternalApi public constructor() : this(ConcurrentLinkedDeque()) /** * 获取一个 [Contact.id] 为 [id] 的元素. 在不存在时返回 `null`. */ public operator fun get(id: Long): C? { @OptIn(MiraiInternalApi::class) return delegate.firstOrNull { it.id == id } } /** * 获取一个 [Contact.id] 为 [id] 的元素. 在不存在时抛出 [NoSuchElementException]. */ public fun getOrFail(id: Long): C = get(id) ?: throw NoSuchElementException("Contact $id not found.") /** * 删除 [Contact.id] 为 [id] 的元素. */ public fun remove(id: Long): Boolean { @OptIn(MiraiInternalApi::class) return delegate.removeAll { it.id == id } } /** * 当存在 [Contact.id] 为 [id] 的元素时返回 `true`. */ public operator fun contains(id: Long): Boolean = get(id) != null override fun toString(): String { @OptIn(MiraiInternalApi::class) return delegate.joinToString(separator = ", ", prefix = "ContactList(", postfix = ")") } override fun equals(other: Any?): Boolean { @OptIn(MiraiInternalApi::class) return other is ContactList<*> && delegate == other.delegate } override fun hashCode(): Int { @OptIn(MiraiInternalApi::class) return delegate.hashCode() } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/ContactOrBot.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.jvm.JvmName /** * 拥有 [id] 的对象. * 此为 [Contact] 与 [Bot] 的唯一公共接口. * * @see UserOrBot * * @see Contact * @see Bot */ @NotStableForInheritance public interface ContactOrBot : CoroutineScope { /** * QQ 号或群号. */ public val id: Long /** * 相关 [Bot] */ public val bot: Bot /** * 头像下载链接, 规格默认为 [AvatarSpec.LARGEST] * @see avatarUrl */ public val avatarUrl: String get() = avatarUrl(spec = AvatarSpec.LARGEST) /** * 头像下载链接. * @param spec 头像的规格. * @since 2.11 */ @Suppress("INAPPLICABLE_JVM_NAME") @JvmName("getAvatarUrl") public fun avatarUrl(spec: AvatarSpec): String { @OptIn(MiraiInternalApi::class) return "http://q.qlogo.cn/g?b=qq&nk=${id}&s=${spec.size}" } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/Exceptions.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.contact import net.mamoe.mirai.message.data.AtAll import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.millisToHumanReadableString /** * 发送消息时消息过长抛出的异常. * * @see Contact.sendMessage */ @OptIn(MiraiInternalApi::class) public class MessageTooLargeException( public override val target: Contact, /** * 原发送消息 */ originalMessage: Message, /** * 经过事件拦截处理后的消息 */ public val messageAfterEvent: Message, exceptionMessage: String ) : SendMessageFailedException(target, Reason.MESSAGE_TOO_LARGE, originalMessage) { override val message: String = exceptionMessage } /** * 发送消息时 bot 正处于被禁言状态时抛出的异常. * * @see Group.sendMessage */ @OptIn(MiraiInternalApi::class) public class BotIsBeingMutedException @MiraiInternalApi constructor( // this constructor is since 2.9.0-RC public override val target: Group, originalMessage: Message, ) : SendMessageFailedException(target, Reason.BOT_MUTED, originalMessage) { @DeprecatedSinceMirai(warningSince = "2.9", errorSince = "2.11", hiddenSince = "2.12") @Deprecated( "Deprecated without replacement. Please consider copy this exception to your code.", level = DeprecationLevel.HIDDEN ) // this constructor is since 2.0 public constructor( target: Group, ) : this(target, messageChainOf()) override val message: String = "bot is being muted, remaining ${ target.botMuteRemaining.times(1000).millisToHumanReadableString() } seconds" } public inline val BotIsBeingMutedException.botMuteRemaining: Int get() = target.botMuteRemaining /** * 发送消息失败时抛出的异常 * * @since 2.9.0 */ public open class SendMessageFailedException @MiraiInternalApi constructor( public open val target: Contact, public val reason: Reason, public val originalMessage: Message, /** * @since 2.14 */ tips: String? = null, ) : RuntimeException( "Failed sending message to $target, reason=$reason. Tips: $tips" ) { public enum class Reason { /** * 消息过长 */ MESSAGE_TOO_LARGE, /** * 机器人被禁言 */ BOT_MUTED, /** * 达到群每分钟发言次数限制 */ GROUP_CHAT_LIMITED, /** * 达到每日发送 [AtAll] 的次数限制 * @since 2.11 */ AT_ALL_LIMITED, /** * 被服务器限制发送消息, 可能是由冻结引起. * @since 2.14 */ LIMITED_MESSAGING, } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/FileSupported.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.contact import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.NotStableForInheritance /** * 支持文件操作的 [Contact]. 目前仅 [Group]. * * 获取文件操作相关示例: [RemoteFiles] * * @since 2.5 * * @see RemoteFiles */ @NotStableForInheritance public interface FileSupported : Contact { /** * 文件根目录. 可通过 [net.mamoe.mirai.utils.RemoteFile.listFiles] 获取目录下文件列表. * * **注意:** 已弃用, 请使用 [files]. * * @since 2.5 */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Please use files instead.", replaceWith = ReplaceWith("files.root"), level = DeprecationLevel.ERROR ) // deprecated since 2.8.0-RC @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.14") public val filesRoot: net.mamoe.mirai.utils.RemoteFile /** * 获取远程文件列表 (管理器). * * @since 2.8 */ public val files: RemoteFiles } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/Friend.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "UnusedImport") @file:JvmBlockingBridge package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.roaming.RoamingSupported import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.action.FriendNudge import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.data.toPlainText import net.mamoe.mirai.utils.NotStableForInheritance /** * 代表一位好友. * * 一个 [Friend] 实例并不是独立的, 它属于一个 [Bot]. * 对于同一个 [Bot], 任何一个人的 [Friend] 实例都是单一的. * [Friend] 无法通过任何方式直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取. * * @see FriendMessageEvent */ @Suppress("RedundantSetter") @NotStableForInheritance public interface Friend : User, CoroutineScope, AudioSupported, RoamingSupported { /** * 该好友所在的好友分组 * * @since 2.13 */ public val friendGroup: FriendGroup /** * 备注信息 * * 更改备注后会广播 [FriendRemarkChangeEvent] * * @see User.remarkOrNick * @see FriendRemarkChangeEvent */ override var remark: String /** * @since 2.13 */ set /** * 向这个对象发送消息. * * 单条消息最大可发送 4500 字符或 50 张图片. * * @see FriendMessagePreSendEvent 发送消息前事件 * @see FriendMessagePostSendEvent 发送消息后事件 * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 * @throws MessageTooLargeException 当消息过长时抛出 * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) * * @return 消息回执. 可 [引用][MessageReceipt.quote] 或 [撤回][MessageReceipt.recall] 这条消息. */ public override suspend fun sendMessage(message: Message): MessageReceipt<Friend> /** * 删除并屏蔽该好友, 屏蔽后对方将无法发送临时会话消息 * * @see FriendDeleteEvent 好友删除事件 */ public suspend fun delete() /** * 发送纯文本消息 * @see sendMessage */ public override suspend fun sendMessage(message: String): MessageReceipt<Friend> = this.sendMessage(message.toPlainText()) /** * 创建一个 "戳一戳" 消息 * * @see Nudge.sendTo 发送这个戳一戳消息 */ public override fun nudge(): FriendNudge = FriendNudge(this) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/Group.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "UnusedImport") @file:JvmBlockingBridge package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.active.GroupActive import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.contact.essence.Essences import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.contact.roaming.RoamingSupported import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.* import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * 群. * * ## 群员操作 * * ### 获取群成员 * * 使用 [Group.members] 可获取除 [Bot] 自身外的所有群成员 (包括管理员和群主) 列表. [Bot] 自身在群内的对象可通过 [botAsMember] 获取. * * [get] 可以按 QQ 号码获取一个群成员对象, 在不存在时返回 `null`. [getOrFail] 则在不存在时抛出 [NoSuchElementException]. * * ### 判断管理员权限 * * 首先获取到目标群成员对象, 然后使用 [NormalMember.permission] 获取其权限. * * [Bot] 在群内的权限可通过 [Group.botPermission] 或其 [Member 对象][botAsMember] 的 [NormalMember.permission] 获取. * * ### 其他动作 * * - 设置管理员权限: [NormalMember.modifyAdmin] * - 禁言: [NormalMember.mute] * - 戳一戳: [NormalMember.nudge] * * ## 群公告 * * 可通过 [Group.announcements] 获取公告支持. 可在 [Announcements] 查看详细文档. * * ## 群文件 * * 使用 [Group.files] 获取群文件管理器后操作. 详见 [RemoteFiles]. */ @NotStableForInheritance public interface Group : Contact, CoroutineScope, FileSupported, AudioSupported, RoamingSupported { /** * 群名称. * * 在修改时将会异步上传至服务器, 也会广播事件 [GroupNameChangeEvent]. * 频繁修改可能会被服务器拒绝. * * @see GroupNameChangeEvent 群名片修改事件 * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ public var name: String /** * 群设置 */ public val settings: GroupSettings /** * 同为 groupCode, 用户看到的群号码. */ public override val id: Long /** * 群主. * * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 */ public val owner: NormalMember /** * [Bot] 在群内的 [Member] 实例 */ public val botAsMember: NormalMember /** * 机器人被禁言还剩余多少秒 * * @see BotMuteEvent 机器人被禁言事件 * @see isBotMuted 判断机器人是否正在被禁言 */ public val botMuteRemaining: Int get() = botAsMember.muteTimeRemaining /** * 机器人在这个群里的权限 * * @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限 * * @see BotGroupPermissionChangeEvent 机器人群员修改 */ public val botPermission: MemberPermission get() = botAsMember.permission /** * 群头像下载链接, 规格默认为 [AvatarSpec.LARGEST] * @see avatarUrl */ public override val avatarUrl: String get() = avatarUrl(spec = AvatarSpec.LARGEST) /** * 群头像下载链接. * @param spec 头像的规格. * @since 2.11 */ public override fun avatarUrl(spec: AvatarSpec): String { @OptIn(MiraiInternalApi::class) return "http://p.qlogo.cn/gh/${id}/${id}/${spec.size}" } /** * 群成员列表, 不含机器人自己, 含群主. * * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新. */ public val members: ContactList<NormalMember> /** * 获取群公告相关功能接口 * @since 2.7 */ public val announcements: Announcements /** * 获取群荣誉相关功能接口 * @since 2.13 */ public val active: GroupActive /** * 获取群成员实例. 不存在时返回 `null`. * * 当 [id] 为 [Bot.id] 时返回 [botAsMember]. */ public operator fun get(id: Long): NormalMember? /** * 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException]. * * 当 [id] 为 [Bot.id] 时返回 [botAsMember]. */ public fun getOrFail(id: Long): NormalMember = get(id) ?: throw NoSuchElementException("member $id not found in group ${this.id}") /** * 当本群存在 [Member.id] 为 [id] 的群员时返回 `true`. * * 当 [id] 为 [Bot.id] 时返回 `true` */ public operator fun contains(id: Long): Boolean /** * 当 [member] 是本群成员时返回 `true`. 将同时成员 [所属群][Member.group]. 同一个用户在不同群内的 [Member] 对象不相等. */ public operator fun contains(member: NormalMember): Boolean = member in members /** * 让机器人退出这个群. * @throws IllegalStateException 当机器人为群主时 * @return 退出成功时 true; 已经退出时 false */ public suspend fun quit(): Boolean /** * 向这个对象发送消息. * * 单条消息最大可发送 4500 字符或 50 张图片. * * @see GroupMessagePreSendEvent 发送消息前事件 * @see GroupMessagePostSendEvent 发送消息后事件 * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 * @throws MessageTooLargeException 当消息过长时抛出 * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) * * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) */ public override suspend fun sendMessage(message: Message): MessageReceipt<Group> /** * 发送纯文本消息 * @see sendMessage */ public override suspend fun sendMessage(message: String): MessageReceipt<Group> = this.sendMessage(message.toPlainText()) /** * 上传一个语音消息以备发送. 该方法已弃用且将在未来版本删除, 请使用 [uploadAudio]. */ @Suppress("DEPRECATION_ERROR") @Deprecated( "use uploadAudio", replaceWith = ReplaceWith("uploadAudio(resource)"), level = DeprecationLevel.HIDDEN ) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public suspend fun uploadVoice(resource: ExternalResource): net.mamoe.mirai.message.data.Voice /** * 将一条消息设置为群精华消息, 需要管理员或群主权限. * 操作成功返回 `true`. * * @throws PermissionDeniedException 没有权限时抛出 * * @since 2.2 */ public suspend fun setEssenceMessage(source: MessageSource): Boolean /** * 群精华消息相关功能接口 * * @since 2.15 */ public val essences: Essences public companion object { /** * 将一条消息设置为群精华消息, 需要管理员或群主权限. * 操作成功返回 `true`. * * @throws PermissionDeniedException 没有权限时抛出 * * @see Group.setEssenceMessage * @since 2.2 */ @JvmBlockingBridge @JvmStatic public suspend fun Group.setEssenceMessage(chain: MessageChain): Boolean = setEssenceMessage(chain.source) } } /** * 群设置 * * @see Group.settings 获取群设置 */ public interface GroupSettings { /** * 入群公告, 没有时为空字符串. * * 在修改时将会异步上传至服务器. * * @see GroupEntranceAnnouncementChangeEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 * @see Announcements * @see Group.announcements */ @Deprecated( level = DeprecationLevel.HIDDEN, message = "group.announcements.asFlow().filter { it.parameters.sendToNewMember }.firstOrNull()", ) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public var entranceAnnouncement: String /** * 全体禁言状态. `true` 为开启. * * 当前仅能修改状态. * * @see GroupMuteAllEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ public var isMuteAll: Boolean /** * 允许群员邀请好友入群的状态. `true` 为允许 * * 在修改时将会异步上传至服务器. * * @see GroupAllowMemberInviteEvent * @throws PermissionDeniedException 无权限修改时将会抛出异常 */ public var isAllowMemberInvite: Boolean /** * 自动加群审批 */ @MiraiExperimentalApi public val isAutoApproveEnabled: Boolean /** * 匿名聊天 */ public var isAnonymousChatEnabled: Boolean } /** * 同 [get]. 在一些不适合使用 [get] 的情境下使用 [getMember]. */ @JvmSynthetic public fun Group.getMember(id: Long): NormalMember? = get(id) /** * 同 [getMemberOrFail]. 在一些不适合使用 [getOrFail] 的情境下使用 [getMemberOrFail]. */ @JvmSynthetic public fun Group.getMemberOrFail(id: Long): NormalMember = getOrFail(id) /** * 返回机器人是否正在被禁言 * * @see Group.botMuteRemaining 剩余禁言时间 */ public val Group.isBotMuted: Boolean get() = this.botMuteRemaining != 0 ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/Member.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "UnusedImport") @file:JvmBlockingBridge package net.mamoe.mirai.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.active.MemberActive import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.action.MemberNudge import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.utils.NotStableForInheritance /** * 代表一位群成员. * * 群成员分为 [普通成员][NormalMember] 和 [匿名成员][AnonymousMember] * * 一个群成员可能也是机器人的好友, 但他们在对象类型上不同 ([Member] != [Friend]). 可以通过 [Member.asFriend] 得到相关好友对象. * * ## 相关的操作 * - [Member.isFriend] 判断此成员是否为好友 * - [Member.isStranger] 判断此成员是否为好友 * - [Member.asFriend] 转换为 [Friend] * - [Member.asStranger] 转换为 [Stranger] */ @NotStableForInheritance public interface Member : User { /** * 所在的群. */ public val group: Group /** * 成员的权限, 将会随服务器通知动态更新. * * [Member] 可能是 [普通成员][NormalMember] 或 [匿名成员][AnonymousMember], 要修改群成员权限, 请检查类型为 [NormalMember] 然后使用 [NormalMember.modifyAdmin] * * @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发. */ public val permission: MemberPermission /** * 群名片. 可能为空. */ public val nameCard: String /** * 群特殊头衔. * * 为 [AnonymousMember] 时一定是 `"匿名"` * * @see [NormalMember.specialTitle] */ public val specialTitle: String /** * 群等级头衔 (PC 端显示) * @see active * @since 2.13 */ public val rankTitle: String get() = group.active.rankTitles[active.rank].orEmpty() /** * 群活跃头衔 (手机端显示) * @see active * @since 2.13 */ public val temperatureTitle: String get() { val level = when (active.temperature) { in 1..10 -> 1 in 11..20 -> 2 in 21..40 -> 3 in 41..60 -> 4 in 61..80 -> 5 in 81..100 -> 6 else -> 0 } return group.active.temperatureTitles[level].orEmpty() } /** * 群活跃度相关属性. * @see [rankTitle] * @see [temperatureTitle] * @since 2.13 */ public val active: MemberActive /** * 禁言这个群成员 [durationSeconds] 秒, 在机器人无权限操作时抛出 [PermissionDeniedException]. * * QQ 中最小操作和显示的时间都是一分钟. 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间. * * 管理员可禁言成员, 群主可禁言管理员和群员. * * @param durationSeconds 持续时间. 精确到秒. 最短 0 秒, 最长 30 天. 超过范围则会抛出异常 [IllegalStateException]. * * @see NormalMember.isMuted 判断此成员是否正处于禁言状态中 * @see NormalMember.unmute 取消禁言此成员 * * @see MemberMuteEvent 成员被禁言事件 * @see BotMuteEvent Bot 被禁言事件 * * @see Member.mute 支持 Kotlin [kotlin.time.Duration] 的扩展 */ public suspend fun mute(durationSeconds: Int) /** * 向群成员发送消息. * 若群成员同时是好友, 则会发送好友消息. 否则发送临时会话消息. * * 单条消息最大可发送 4500 字符或 50 张图片. * * 注意: 只可以向 [NormalMember] 发送消息. 向 [AnonymousMember] 发送时将会得到异常 [UnsupportedOperationException]. * * @see FriendMessagePreSendEvent 当此成员是好友时发送消息前事件 * @see FriendMessagePostSendEvent 当此成员是好友时发送消息后事件 * * @see GroupTempMessagePreSendEvent 当此成员不是好友时发送消息前事件 * @see GroupTempMessagePostSendEvent 当此成员不是好友时发送消息后事件 * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 * @throws MessageTooLargeException 当消息过长时抛出 * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) * @throws UnsupportedOperationException 当向 [AnonymousMember] 发送消息时抛出 * * @return 消息回执. 可 [引用][MessageReceipt.quote] 或 [撤回][MessageReceipt.recall] 这条消息. */ public override suspend fun sendMessage(message: Message): MessageReceipt<Member> /** * 发送纯文本消息 * * @see sendMessage */ public override suspend fun sendMessage(message: String): MessageReceipt<Member> /** * 创建一个 "戳一戳" 消息 * * 注意: 只可以戳 [NormalMember]. 向 [AnonymousMember] 操作时将会得到异常. * * @see Nudge.sendTo 发送这个戳一戳消息 */ public override fun nudge(): MemberNudge } /** * 得到此成员作为好友的对象. * * @throws IllegalStateException 当此成员不是好友时抛出 */ public fun Member.asFriend(): Friend = this.bot.getFriend(this.id) ?: error("$this is not a friend") /** * 得到此成员作为好友的对象, 当此成员不是好友时返回 `null` */ public fun Member.asFriendOrNull(): Friend? = this.bot.getFriend(this.id) /** * 得到此成员作为陌生人的对象. * * @throws IllegalStateException 当此成员不是陌生人时抛出 */ public fun Member.asStranger(): Stranger = this.bot.getStranger(this.id) ?: error("$this is not a stranger") /** * 得到此成员作为陌生人的对象, 当此成员不是陌生人时返回 `null` */ public fun Member.asStrangerOrNull(): Stranger? = this.bot.getStranger(this.id) /** * 当此成员同时是 Bot 的好友时返回 `true` */ public inline val Member.isFriend: Boolean get() = this.bot.friends.contains(this.id) /** * 当此成员同时是 Bot 的陌生人时返回 `true` */ public inline val Member.isStranger: Boolean get() = this.bot.strangers.contains(this.id) /** * 获取非空群名片或昵称. * * 若 [群名片][Member.nameCard] 不为空则返回群名片, 为空则返回 [User.nick] */ public val Member.nameCardOrNick: String get() = this.nameCard.takeIf { it.isNotEmpty() } ?: this.nick ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/MemberPermission.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.contact import net.mamoe.mirai.Bot import kotlin.internal.InlineOnly /** * 群成员的权限. * * 可通过 [compareTo] 判断是否有更高的权限. * * 若要获得成员的权限, 使用 [Member.permission]. 若要获得 [Bot] 在某个群的权限, 使用 [Group.botPermission], 或 [Group.botAsMember] 再获取其 [Member.permission]. * * @see isOwner 判断权限是否为群主 * @see isOperator 判断权限是否为管理员或群主 * * @see Member.isOwner 对 [Member] 的扩展函数, 判断此成员是否为群主 * @see Member.isOperator 对 [Member] 的扩展函数, 判断此成员是否为管理员或群主 * @see Member.isAdministrator 对 [Member] 的扩展函数, 判断此成员是否为管理员 */ public enum class MemberPermission : Comparable<MemberPermission> { /** * 一般群成员 */ MEMBER, // ordinal = 0 /** * 管理员 */ ADMINISTRATOR, // ordinal = 1 /** * 群主 */ OWNER; // ordinal = 2 /** * 权限等级. [OWNER] 为 2, [ADMINISTRATOR] 为 1, [MEMBER] 为 0 */ public val level: Int get() = ordinal } /** * 判断权限是否为群主 */ @InlineOnly public inline fun MemberPermission.isOwner(): Boolean = this == MemberPermission.OWNER /** * 判断权限是否为管理员 */ @InlineOnly public inline fun MemberPermission.isAdministrator(): Boolean = this == MemberPermission.ADMINISTRATOR /** * 判断权限是否为管理员或群主 */ @InlineOnly public inline fun MemberPermission.isOperator(): Boolean = isAdministrator() || isOwner() /** * 判断权限是否为群主 */ public inline fun Member.isOwner(): Boolean = this.permission.isOwner() /** * 判断权限是否为管理员 */ public inline fun Member.isAdministrator(): Boolean = this.permission.isAdministrator() /** * 判断权限是否为管理员或群主 */ public inline fun Member.isOperator(): Boolean = this.permission.isOperator() /** * 权限不足 */ @Suppress("unused") public class PermissionDeniedException : IllegalStateException { public constructor() : super("Permission denied") public constructor(message: String?) : super(message) } /** * 要求 [Bot] 在这个群里的权限至少为 [required], 否则抛出异常 [PermissionDeniedException] * * @throws PermissionDeniedException */ public inline fun Group.checkBotPermission( required: MemberPermission, crossinline lazyMessage: () -> String = { "Permission denied: required $required, got actual $botPermission for $bot in group $id" } ) { if (botPermission < required) { throw PermissionDeniedException(lazyMessage()) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/NormalMember.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.action.MemberNudge import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.data.toPlainText import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.jvm.JvmName import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime /** * 代表一位普通的群成员. * * 要查询更多用户信息, 使用 [NormalMember.queryProfile]. * * @see AnonymousMember */ @NotStableForInheritance public interface NormalMember : Member { /** * 群名片. 可能为空. * * 管理员和群主都可修改任何人(包括群主)的群名片. * * 在修改时将会异步上传至服务器. * * @see [nameCardOrNick] 获取非空群名片或昵称 * * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. * @throws PermissionDeniedException 无权限修改时 */ public override var nameCard: String /** * 群特殊头衔. * * 仅群主可以修改群特殊头衔. * * 在修改时将会异步上传至服务器. * * @see MemberSpecialTitleChangeEvent 成员群特殊头衔改动事件. * @throws PermissionDeniedException 无权限修改时 * @suppress 请勿试图修改其为空字符串来产生空头衔效果,这将使成员实际佩戴头衔退回为[活跃度相关头衔][Member.active]或管理员头衔. */ public override var specialTitle: String /** * 被禁言剩余时长. 单位为秒. * * @see isMuted 判断改成员是否处于禁言状态 * @see mute 设置禁言 * @see unmute 取消禁言 */ public val muteTimeRemaining: Int /** * 当该群员处于禁言状态时返回 `true`. * @since 2.6 */ public val isMuted: Boolean get() = muteTimeRemaining != 0 /** * 入群时间. 单位为秒. * * @since 2.1 */ public val joinTimestamp: Int /** * 最后发言时间. 单位为秒. * * @since 2.1 */ public val lastSpeakTimestamp: Int /** * 解除禁言. * * 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言. * * @see NormalMember.isMuted 判断此成员是否正处于禁言状态中 * * @see MemberUnmuteEvent 成员被取消禁言事件 * * @throws PermissionDeniedException 无权限修改时抛出 */ public suspend fun unmute() /** * 踢出该成员. * * 管理员可踢出成员, 群主可踢出管理员和群员. * * @param block 为 `true` 时拉黑成员 * * @see MemberLeaveEvent.Kick 成员被踢出事件. * @throws PermissionDeniedException 无权限修改时 * */ public suspend fun kick(message: String, block: Boolean) /** * 踢出该成员, 默认不拉黑 * * 管理员可踢出成员, 群主可踢出管理员和群员. * * @see MemberLeaveEvent.Kick 成员被踢出事件. * @throws PermissionDeniedException 无权限修改时 * */ public suspend fun kick(message: String): Unit = kick(message, false) /** * 给予或移除群成员的管理员权限。 * * 此操作需要 Bot 为群主 [MemberPermission.OWNER] * * @param operation true 为给予 * * @see MemberPermissionChangeEvent 群成员权限变更事件 * @throws PermissionDeniedException 无权限修改时抛出 * * @since 2.7 */ public suspend fun modifyAdmin(operation: Boolean) /** * 向群成员发送消息. * 若群成员同时是好友, 则会发送好友消息. 否则发送临时会话消息. * * 单条消息最大可发送 4500 字符或 50 张图片. * * @see FriendMessagePreSendEvent 当此成员是好友时发送消息前事件 * @see FriendMessagePostSendEvent 当此成员是好友时发送消息后事件 * * @see GroupTempMessagePreSendEvent 当此成员不是好友时发送消息前事件 * @see GroupTempMessagePostSendEvent 当此成员不是好友时发送消息后事件 * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 * @throws MessageTooLargeException 当消息过长时抛出 * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) * * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) */ public override suspend fun sendMessage(message: Message): MessageReceipt<NormalMember> /** * 发送纯文本消息 * @see sendMessage */ public override suspend fun sendMessage(message: String): MessageReceipt<NormalMember> = this.sendMessage(message.toPlainText()) /** * 创建一个 "戳一戳" 消息 * * @see Nudge.sendTo 发送这个戳一戳消息 */ public override fun nudge(): MemberNudge = MemberNudge(this) } /** * 获取非空群名片或昵称. * @return 当 [User] 为 [NormalMember] 时返回 [Member.nameCardOrNick], 否则返回 [Member.nick] */ // Java: NormalMemberKt.getNameCardOrNick(user) public val User.nameCardOrNick: String get() = when (this) { is NormalMember -> this.nameCardOrNick else -> this.nick } /** * 获取非空群名片或昵称. * @return 当 [UserOrBot] 为 [NormalMember] 时返回 [Member.nameCardOrNick], 否则返回 [Member.nick] * @since 2.6 */ public val UserOrBot.nameCardOrNick: String get() = when (this) { is NormalMember -> this.nameCardOrNick else -> this.nick } /** * @see Member.mute */ @ExperimentalTime public suspend inline fun NormalMember.mute(duration: Duration) { require(duration.toDouble(DurationUnit.DAYS) <= 30) { "duration must be at most 1 month" } require(duration.toDouble(DurationUnit.SECONDS) > 0) { "duration must be greater than 0 second" } this.mute(duration.toDouble(DurationUnit.SECONDS).toInt()) } @OptIn(ExperimentalTime::class) @Suppress("unused") @JvmName("mute-fcu0wV4") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public suspend inline fun NormalMember.mute00(duration: Duration) { return mute(duration) } /** * 判断群成员是否处于禁言状态. * @suppress 在 2.6 移入了 [NormalMember] 成员函数. 保留二进制兼容. */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXTENSION_SHADOWED_BY_MEMBER") @kotlin.internal.LowPriorityInOverloadResolution public val NormalMember.isMuted: Boolean get() = muteTimeRemaining != 0 && muteTimeRemaining != 0xFFFFFFFF.toInt() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/OtherClient.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.contact import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.OtherClientOnlineEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol.ANDROID_PAD import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol.ANDROID_PHONE import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.toLongUnsigned /** * 其他设备. 如当 [Bot] 以 [ANDROID_PHONE] 登录时, 还可以有其他设备以 [ANDROID_PAD], iOS, PC 或其他设备登录. */ @NotStableForInheritance public interface OtherClient : Contact { public val info: OtherClientInfo /** * 此设备属于的 [Bot] */ public override val bot: Bot /** * 识别 id, 仅运行时使用. * * 此 id 由其他客户端控制, 重启可能会变化. */ public override val id: Long get() { @OptIn(MiraiInternalApi::class) return info.appId.toLongUnsigned() } override suspend fun sendMessage(message: Message): MessageReceipt<OtherClient> { throw UnsupportedOperationException("OtherClientImpl.sendMessage is not yet supported.") } override suspend fun uploadImage(resource: ExternalResource): Image { throw UnsupportedOperationException("OtherClientImpl.uploadImage is not yet supported.") } } public inline val OtherClient.platform: Platform? get() = info.platform public inline val OtherClient.deviceName: String get() = info.deviceName public inline val OtherClient.deviceKind: String get() = info.deviceKind public data class OtherClientInfo @MiraiInternalApi constructor( /** * 仅运行时识别. 随着客户端更新此 ID 可能有变化. * * 不可能有 [appId] 相同的两个客户端同时在线. */ @MiraiInternalApi public val appId: Int, /** * 登录平台 */ public val platform: Platform?, /** * 示例: * - Mi 10 Pro * - 电脑 * - xxx 的 iPad * - mirai */ public val deviceName: String, /** * 示例: * - Mi 10 Pro * - DESKTOP-ABCDEFG * - iPad * - mirai */ public val deviceKind: String, ) /** * @see OtherClientInfo.platform */ public enum class Platform( @MiraiInternalApi public val terminalId: Int, @MiraiInternalApi public val platformId: Int, ) { IOS(3, 1), MOBILE(2, 2), // android WINDOWS(1, 3), ; public companion object { @MiraiInternalApi public fun getByTerminalId(terminalId: Int): Platform? = values().find { it.terminalId == terminalId } } } /** * 详细设备类型. 在登录时查询到的设备列表中无此信息. 只在 [OtherClientOnlineEvent] 才有. */ public enum class ClientKind( @MiraiInternalApi public val id: Int, ) { ANDROID_PAD(68104), AOL_CHAOJIHUIYUAN(73730), AOL_HUIYUAN(73474), AOL_SQQ(69378), CAR(65806), HRTX_IPHONE(66566), HRTX_PC(66561), MC_3G(65795), MISRO_MSG(69634), MOBILE_ANDROID(65799), MOBILE_ANDROID_NEW(72450), MOBILE_HD(65805), MOBILE_HD_NEW(71426), MOBILE_IPAD(68361), MOBILE_IPAD_NEW(72194), MOBILE_IPHONE(67586), MOBILE_OTHER(65794), MOBILE_PC_QQ(65793), MOBILE_PC_TIM(77313), MOBILE_WINPHONE_NEW(72706), QQ_FORELDER(70922), QQ_SERVICE(71170), TV_QQ(69130), WIN8(69899), WINPHONE(65804); public companion object { @MiraiInternalApi public operator fun get(id: Int): ClientKind? = values().find { it.id == id } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/Stranger.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused", "UnusedImport") @file:JvmBlockingBridge package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.action.StrangerNudge import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.data.toPlainText import net.mamoe.mirai.utils.NotStableForInheritance /** * 代表一位陌生人. * * 一个 [Stranger] 实例并不是独立的, 它属于一个 [Bot]. * 对于同一个 [Bot], 任何一个人的 [Stranger] 实例都是单一的. * [Stranger] 无法通过任何方式直接构造. 任何时候都应从 [Bot.getStranger] 或事件中获取. * * 陌生人的来源:当将添加好友设置为 * ‘任何人可添加为好友’或‘需要回答对验证问题时’ * 且被他人成功添加时此人会成为陌生人 * * 陌生人需要主动添加好友才能构成好友关系 * 但 Mirai 将不会提供此功能 * 请手动在其他客户端添加好友 * * @see StrangerMessageEvent */ @NotStableForInheritance public interface Stranger : User, CoroutineScope { /** * 向这个对象发送消息. * * 单条消息最大可发送 4500 字符或 50 张图片. * * @see FriendMessagePreSendEvent 发送消息前事件 * @see FriendMessagePostSendEvent 发送消息后事件 * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws BotIsBeingMutedException 发送群消息时若 [Bot] 被禁言抛出 * @throws MessageTooLargeException 当消息过长时抛出 * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) * * @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) */ public override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> /** * 删除并屏蔽该陌生人, 屏蔽后对方将无法发送临时会话消息 * * @see StrangerRelationChangeEvent.Deleted 陌生人删除事件 */ public suspend fun delete() /** * 发送纯文本消息 * @see sendMessage */ public override suspend fun sendMessage(message: String): MessageReceipt<Stranger> = this.sendMessage(message.toPlainText()) /** * 创建一个 "戳一戳" 消息 * * @see Nudge.sendTo 发送这个戳一戳消息 */ public override fun nudge(): StrangerNudge = StrangerNudge(this) } /** * 得到此陌生人作为好友的对象. * * @throws IllegalStateException 当此成员不是好友时抛出 * @since 2.2 */ public fun Stranger.asFriend(): Friend = this.bot.getFriend(this.id) ?: error("$this is not a friend") /** * 得到此陌生人作为好友的对象, 当此成员不是好友时返回 `null` * @since 2.2 */ public fun Stranger.asFriendOrNull(): Friend? = this.bot.getFriend(this.id) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/TempUser.kt ================================================ /* * 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.contact import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance /** * 临时会话用户, 非群成员. * * [#429](https://github.com/mamoe/mirai/issues/429) */ @MiraiInternalApi("其他渠道的临时会话暂未支持. ") @NotStableForInheritance public interface TempUser : User ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/User.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused") @file:JvmBlockingBridge package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.data.UserProfile import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.UserMessagePostSendEvent import net.mamoe.mirai.event.events.UserMessagePreSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.action.UserNudge import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.isContentEmpty import net.mamoe.mirai.message.data.toPlainText import net.mamoe.mirai.utils.NotStableForInheritance /** * 代表一个 **用户**. * * 其子类有 [群成员][Member] 和 [好友][Friend]. * 虽然群成员也可能是好友, 但他们仍是不同的两个类型. * * 注意: 一个 [User] 实例并不是独立的, 它属于一个 [Bot]. * * 对于同一个 [Bot] 任何一个人的 [User] 实例都是单一的. */ @NotStableForInheritance public interface User : Contact, UserOrBot, CoroutineScope { /** * QQ 号码 */ public override val id: Long /** * 备注信息 * * 仅 [Bot] 与 [User] 存在好友关系的时候才可能存在备注 * * [Bot] 与 [User] 没有好友关系时永远为空[字符串][String] ("") * * @see [User.remarkOrNick] */ public val remark: String /** * 向这个对象发送消息. * * 单条消息最大可发送 4500 字符或 50 张图片. * * @see UserMessagePreSendEvent 发送消息前事件 * @see UserMessagePostSendEvent 发送消息后事件 * * @throws EventCancelledException 当发送消息事件被取消时抛出 * @throws MessageTooLargeException 当消息过长时抛出 * @throws IllegalArgumentException 当消息内容为空时抛出 (详见 [Message.isContentEmpty]) * * @return 消息回执. 可 [引用][MessageReceipt.quote] 或 [撤回][MessageReceipt.recall] 这条消息. */ public override suspend fun sendMessage(message: Message): MessageReceipt<User> /** * 发送纯文本消息 * @see sendMessage */ public override suspend fun sendMessage(message: String): MessageReceipt<User> = this.sendMessage(message.toPlainText()) /** * 创建一个 "戳一戳" 消息 * * @see Nudge.sendTo 发送这个戳一戳消息 */ public override fun nudge(): UserNudge /** * 查询用户信息. * * 此函数不会缓存信息. 每次调用此函数都会向服务器查询新信息. * * @since 2.1 */ public suspend fun queryProfile(): UserProfile = Mirai.queryProfile(bot, this.id) } /** * 获取非空备注或昵称. * * 若 [备注][User.remark] 不为空则返回备注, 为空则返回 [User.nick] */ public val User.remarkOrNick: String get() = this.remark.takeIf { it.isNotEmpty() } ?: this.nick /** * 获取非空备注或群名片. * * 若 [备注][User.remark] 不为空则返回备注, 为空则返回 [Member.nameCard] */ public val Member.remarkOrNameCard: String get() = this.remark.takeIf { it.isNotEmpty() } ?: this.nameCard /** * 获取非空备注或群名片或昵称. * * 若 [备注][User.remark] 不为空则返回备注, 为空则返回 [Member.nameCardOrNick] */ public val Member.remarkOrNameCardOrNick: String get() = this.remark.takeIf { it.isNotEmpty() } ?: this.nameCardOrNick ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/UserOrBot.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.contact import net.mamoe.mirai.Bot import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.utils.NotStableForInheritance /** * @see User * @see Bot * * @see ContactOrBot */ @NotStableForInheritance public interface UserOrBot : ContactOrBot { /** * 获取昵称 * * ### Kotlin 实用扩展: * * - [nameCardOrNick]: 若该用户是 [群成员][Member], 则优先返回其非空群名片, 否则返回 [nick]. * - [remarkOrNameCardOrNick]: 若 [Bot] 对该用户有[备注][User.remark]则返回备注, 否则返回 [nameCardOrNick]. * * @since 2.6 */ public val nick: String /** * 创建一个 "戳一戳" 消息 * * @see Nudge.sendTo 发送这个戳一戳消息 */ public fun nudge(): Nudge } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/ActiveChart.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.active import net.mamoe.mirai.utils.MiraiInternalApi /** * 活跃度数据图表, 键是 `yyyy-MM` 格式的日期,值是数量 * @property members 每日总人数 * @property actives 每日活跃人数 * @property sentences 每日申请人数 * @property join 每日入群人数 * @property exit 每日退群人数 * @since 2.13 */ public class ActiveChart @MiraiInternalApi public constructor( public val actives: Map<String, Int>, public val sentences: Map<String, Int>, public val members: Map<String, Int>, public val join: Map<String, Int>, public val exit: Map<String, Int> ) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/ActiveHonorInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.active import net.mamoe.mirai.contact.NormalMember /** * 群荣耀当前持有者 * @property memberName 群员昵称 * @property memberId 群员 ID * @property avatar 群员头像 * @property member 群员实例 * @property termDays 当前蝉联天数 * @property historyDays 历史获得天数 * @property maxTermDays 最大蝉联天数 * @since 2.13 */ public class ActiveHonorInfo internal constructor( public val memberName: String, public val memberId: Long, public val avatar: String, public val member: NormalMember?, public val termDays: Int, public val historyDays: Int, public val maxTermDays: Int, ) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/ActiveHonorList.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.active import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmName /** * 群荣耀历史数据 * @property type 群荣誉类型 * @property current 当前荣耀持有者 (龙王,壕礼皇冠, 善财福禄寿) * @property records 群荣耀历史记录 * @since 2.13 */ public class ActiveHonorList @MiraiInternalApi public constructor( @get:JvmName("getType") public val type: GroupHonorType, // `public int getType()` for Java public val current: ActiveHonorInfo?, public val records: List<ActiveHonorInfo> ) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/ActiveRankRecord.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.active import net.mamoe.mirai.contact.NormalMember /** * 活跃排行榜记录 * @property memberName 发言者名称 * @property memberId 发言者 ID * @property member 发言者的群员实例 * @property temperature 活跃度 * @property score 活跃积分 * @since 2.13 */ public class ActiveRankRecord internal constructor( public val memberName: String, public val memberId: Long, public val member: NormalMember?, public val temperature: Int, public val score: Int ) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/ActiveRecord.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.active import net.mamoe.mirai.contact.NormalMember /** * 活跃数据记录 * @property memberName 发言者名称 * @property memberId 发言者 ID * @property member 发言者的群员实例 * @property periodDays 活跃连续天数 * @property messagesCount 发言条数 * @since 2.13 */ public class ActiveRecord internal constructor( public val memberName: String, public val memberId: Long, public val member: NormalMember?, public val periodDays: Int, public val messagesCount: Int ) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/GroupActive.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact.active import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.Streamable import kotlin.jvm.JvmName /** * 表示一个群活跃度管理. * * ## 获取 [GroupActive] 实例 * * 只可以通过 [Group.active] 获取一个群的活跃度管理, 即 [GroupActive] 实例. * * ### 头衔设置 * * * 通过 [isHonorVisible] 可以获取和设置一个群的荣誉是否显示, * * 通过 [isTitleVisible] 可以获取和设置一个群的头衔是否显示, * * 通过 [isTemperatureVisible] 可以获取和设置一个群的活跃度是否显示, * * 通过 [rankTitles] 可以获取和设置一个群的等级头衔列表 (PC 端显示), * * 通过 [temperatureTitles] 可以获取和设置一个群的活跃度头衔列表 (手机端显示) * * ### 刷新群成员活跃数据 * * 通过 [refresh] 可以刷新 [Member.active] 中的属性 (不包括 honors 和 temperature) * * ### 活跃度记录 * * 通过 [asFlow] 可以获取群活跃度记录*惰性*流. * * 若要获取全部活跃度记录, 可使用 [toList]. * * ### 活跃度图表 * * 通过 [queryChart] 可以获取活跃度图表, * 包括 * * 每日总人数 [ActiveChart.members] * * 每日活跃人数 [ActiveChart.actives] * * 每日申请人数 [ActiveChart.sentences] * * 每日入群人数 [ActiveChart.join] * * 每日退群人数 [ActiveChart.exit] * * 通过 [queryHonorHistory] 可以获取群荣耀历史数据, * 包括 * * 当前荣耀持有者 (龙王,壕礼皇冠, 善财福禄寿) [ActiveHonorList.current] * * 群荣耀历史记录 [ActiveHonorList.records] * * @since 2.13 */ @NotStableForInheritance public interface GroupActive : Streamable<ActiveRecord> { /** * 是否在群聊中显示荣誉 * @see MemberActive.honors */ public val isHonorVisible: Boolean /** * 设置是否在群聊中显示荣誉 * @see MemberActive.honors */ public suspend fun setHonorVisible(newValue: Boolean) /** * 是否在群聊中显示头衔 * @see Member.rankTitle * @see Member.temperatureTitle */ public val isTitleVisible: Boolean /** * 设置是否在群聊中显示头衔。操作成功时会同时刷新等级头衔信息。 * @see Member.rankTitle * @see Member.temperatureTitle */ public suspend fun setTitleVisible(newValue: Boolean) /** * 是否在群聊中显示活跃度 * @see MemberActive.temperature */ public val isTemperatureVisible: Boolean /** * 设置是否在群聊中显示活跃度。操作成功时会同时刷新等级头衔信息。 * @see MemberActive.temperature */ public suspend fun setTemperatureVisible(newValue: Boolean) /** * 等级头衔列表,键是等级,值是头衔 * * @see Member.rankTitle */ public val rankTitles: Map<Int, String> /** * 设置等级头衔列表,键是等级,值是头衔。操作成功时会同时刷新等级头衔信息。 * @see Member.rankTitle */ public suspend fun setRankTitles(newValue: Map<Int, String>) /** * 活跃度头衔列表,键是等级,值是头衔。操作成功时会同时刷新活跃度头衔信息。 * @see Member.temperatureTitle */ public val temperatureTitles: Map<Int, String> /** * 设置活跃度头衔列表,键是等级,值是头衔。操作成功时会同时刷新活跃度头衔信息。 * @see Member.temperatureTitle */ public suspend fun setTemperatureTitles(newValue: Map<Int, String>) /** * 刷新 [Member.active] 中的属性 (不包括 [honors][MemberActive.honors] 和 [temperature][MemberActive.temperature]) * @see Member.active */ public suspend fun refresh() /** * 获取活跃度图表数据 */ public suspend fun queryChart(): ActiveChart /** * 获取群荣耀历史数据, 刷新 [Member.active] 中的 [MemberActive.honors] * @see Member.active */ @Suppress("INAPPLICABLE_JVM_NAME") @JvmName("queryHonorHistory") public suspend fun queryHonorHistory(type: GroupHonorType): ActiveHonorList /** * 获取活跃度排行榜,通常是前五十名 */ public suspend fun queryActiveRank(): List<ActiveRankRecord> } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/MemberActive.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact.active import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.utils.NotStableForInheritance /** * 群活跃度相关属性 * @since 2.13 */ @NotStableForInheritance public interface MemberActive { /** * 群活跃等级. 取值为 1~6 (包含) * * 这个等级是在 PC 端成员管理功能中显示的等级 * * @see point */ public val rank: Int /** * 群活跃积分. * * 这个积分是在 PC 端成员管理功能中显示的积分,和手机端显示的 群荣誉活跃积分 不同 * * @see rank */ public val point: Int /** * 群荣誉标识. */ public val honors: Set<GroupHonorType> /** * 群荣誉等级. 取值为 1~100 (包含) * * 这个等级是在手机端群荣誉功能中显示的等级 */ public val temperature: Int /** * 查询头衔佩戴情况 */ public suspend fun queryMedal(): MemberMedalInfo } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/MemberMedalInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.active import net.mamoe.mirai.utils.MiraiInternalApi /** * 群成员头衔详情 * @property title 当前佩戴的头衔 * @property color 当前佩戴的头衔的颜色 * @property wearing 当前佩戴的头衔类型 * @property medals 拥有的所有头衔 * @since 2.13 */ public class MemberMedalInfo @MiraiInternalApi public constructor( public val title: String, public val color: String, public val wearing: MemberMedalType, public val medals: Set<MemberMedalType>, ) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/active/MemberMedalType.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.active import net.mamoe.mirai.contact.active.MemberMedalType.* import net.mamoe.mirai.utils.MiraiInternalApi /** * 群成员头衔详情Detail * @property OWNER 群主独有的头衔 * @property ADMIN 管理员独有的头衔 * @property SPECIAL 群主授予的头衔 * @property ACTIVE 群主设定的头衔, 保持活跃即可获得 * @since 2.13 */ public enum class MemberMedalType(@MiraiInternalApi public val mask: Int) { OWNER(300), ADMIN(301), SPECIAL(302), ACTIVE(315); } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/announcement/Announcement.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INAPPLICABLE_JVM_NAME", "NOTHING_TO_INLINE") @file:JvmBlockingBridge package net.mamoe.mirai.contact.announcement import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.contact.announcement.Announcement.Companion.publishAnnouncement import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * 表示一个 (群) 公告. * * ## 公告类型 * * [Announcement] 可以是 [OnlineAnnouncement] 或 [OfflineAnnouncement]. * * - [OnlineAnnouncement] 表示从 [Announcements.get] 等途径在线获取的, 已经存在于服务器的公告. * - [OfflineAnnouncement] 表示在本地构建的 [Announcement]. * * ## 发布公告 * * ### 构造一条新公告并发布 * * 构造 [OfflineAnnouncement] 然后调用其 [OfflineAnnouncement.publishTo] 或 [Announcements.publish]. * * 在构造时可提供可选的 [AnnouncementParameters] 来设置一些附加属性. * * 也可以使用 [Announcement.publishAnnouncement] 扩展快捷创建并发布公告. * * ### 转发获取的公告到其他群 * * 通过一个群的 [Announcements] 获取到 [OnlineAnnouncement], 然后调用 [OnlineAnnouncement.publishTo] 到另一个群即可. * 由于目前不支持获取公告的图片, 转发的公告也就不会带有原公告的图片. * * ## 序列化 * * [OfflineAnnouncement] 支持 kotlinx-serialization 序列化, 可使用 serializer [OfflineAnnouncement.serializer]. * * [OnlineAnnouncement] 无法序列化. 只能将其转为 [OfflineAnnouncement] 再序列化. 在 Kotlin 使用 [Announcement.toOffline], 在 Java 使用 [OfflineAnnouncement.from]. * * @see Announcement * * @since 2.7 */ public sealed interface Announcement { /** * 内容 */ public val content: String /** * 附加参数. 可以通过 [AnnouncementParametersBuilder] 构建获得. * @see AnnouncementParameters * @see AnnouncementParametersBuilder */ public val parameters: AnnouncementParameters /** * 在该群发布群公告并获得 [OnlineAnnouncement], 需要管理员权限. 发布公告后群内将会出现 "有新公告" 系统提示. * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * @see Announcements.publish */ public suspend fun publishTo(group: Group): OnlineAnnouncement = group.announcements.publish(this) public companion object { /** * 在该群发布群公告并获得 [OnlineAnnouncement], 需要管理员权限. 发布公告后群内将会出现 "有新公告" 系统提示. * * @param content 公告内容 * @param parameters 可选的附加参数 * * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * * @see OfflineAnnouncement * @see Announcement.publishTo * @see AnnouncementParametersBuilder */ @JvmOverloads @JvmStatic public suspend fun Group.publishAnnouncement( content: String, parameters: AnnouncementParameters = AnnouncementParameters.DEFAULT ): OnlineAnnouncement = this.announcements.publish(OfflineAnnouncement(content, parameters)) /** * 在该群发布群公告并获得 [OnlineAnnouncement], 需要管理员权限. 发布公告后群内将会出现 "有新公告" 系统提示. * * @param content 公告内容 * @param parameters 可选的附加参数 * * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * * @see OfflineAnnouncement * @see Announcement.publishTo * @see AnnouncementParametersBuilder */ @JvmSynthetic public suspend inline fun Group.publishAnnouncement( content: String, parameters: AnnouncementParametersBuilder.() -> Unit ): OnlineAnnouncement { // contract { callsInPlace(parameters, InvocationKind.EXACTLY_ONCE) } // no contract and no inline: IDE fails to analyze this funciton return this.announcements.publish(OfflineAnnouncement(content, parameters)) } } } /** * 创建 [OfflineAnnouncement]. 若 [this] 类型为 [OfflineAnnouncement] 则直接返回 [this]. * @since 2.7 */ public inline fun Announcement.toOffline(): OfflineAnnouncement = OfflineAnnouncement.from(this) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/announcement/AnnouncementImage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.contact.announcement import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.utils.isSameClass import kotlin.jvm.JvmStatic /** * 群公告图片. 可通过 [Announcements.uploadImage] 上传获得. 不确定服务器会保存多久. * * 要发布一条带有图片的公告, 请在构造 [AnnouncementParameters] 时提供 [AnnouncementParameters.image] 参数. 详见 [Announcement]. * * @since 2.7 */ @SerialName(AnnouncementImage.SERIAL_NAME) @Serializable public class AnnouncementImage private constructor( public val id: String, public val height: Int, public val width: Int, ) { // For stability, do not make it `data class`. /** * @since 2.15 */ public val url: String get() = "https://gdynamic.qpic.cn/gdynamic/$id/628" public companion object { public const val SERIAL_NAME: String = "AnnouncementImage" /** * 创建 [AnnouncementImage] 实例. */ @JvmStatic public fun create(id: String, height: Int, width: Int): AnnouncementImage { return AnnouncementImage(id, height, width) } } override fun toString(): String { return "AnnouncementImage(id='$id', height=$height, width=$width)" } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is AnnouncementImage || !isSameClass(this, other)) return false if (id != other.id) return false if (height != other.height) return false if (width != other.width) return false return true } override fun hashCode(): Int { var result = id.hashCode() result = 31 * result + height.hashCode() result = 31 * result + width.hashCode() return result } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/announcement/AnnouncementParameters.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.announcement import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.announcement.AnnouncementParameters.Companion.DEFAULT import net.mamoe.mirai.utils.isSameClass import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic /** * 群公告的附加参数. * * 可通过 [AnnouncementParametersBuilder] 构建. 默认实例为 [DEFAULT]. * * @since 2.7 */ @SerialName(AnnouncementParameters.SERIAL_NAME) @Serializable public class AnnouncementParameters internal constructor( /** * 群公告的图片,目前仅支持发送图片,不支持获得图片. 可通过 [Announcements.uploadImage] 上传图片. * @see AnnouncementImage */ public val image: AnnouncementImage? = null, /** 发送给新成员 */ public val sendToNewMember: Boolean = false, /** 置顶. 可以有多个置顶公告 */ public val isPinned: Boolean = false, /** 显示能够引导群成员修改昵称的窗口 */ public val showEditCard: Boolean = false, /** 使用弹窗 */ public val showPopup: Boolean = false, /** 需要群成员确认 */ public val requireConfirmation: Boolean = false, ) { /** * 以该对象作为原型创建一个 [AnnouncementParametersBuilder]. */ public fun builder(): AnnouncementParametersBuilder = AnnouncementParametersBuilder().apply { val outer = this@AnnouncementParameters image(outer.image) sendToNewMember(outer.sendToNewMember) isPinned(outer.isPinned) showEditCard(outer.showEditCard) showPopup(outer.showPopup) requireConfirmation(outer.requireConfirmation) } public companion object { public const val SERIAL_NAME: String = "AnnouncementParameters" /** * 默认值的 [AnnouncementParameters] 实例 */ @JvmStatic @get:JvmName("getDefault") public val DEFAULT: AnnouncementParameters = AnnouncementParameters() } override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is AnnouncementParameters || !isSameClass(this, other)) return false if (image != other.image) return false if (sendToNewMember != other.sendToNewMember) return false if (isPinned != other.isPinned) return false if (showEditCard != other.showEditCard) return false if (showPopup != other.showPopup) return false if (requireConfirmation != other.requireConfirmation) return false return true } override fun hashCode(): Int { var result = image?.hashCode() ?: 0 result = 31 * result + sendToNewMember.hashCode() result = 31 * result + isPinned.hashCode() result = 31 * result + showEditCard.hashCode() result = 31 * result + showPopup.hashCode() result = 31 * result + requireConfirmation.hashCode() return result } override fun toString(): String { return "AnnouncementParameters(image=$image, sendToNewMember=$sendToNewMember, isPinned=$isPinned, showEditCard=$showEditCard, showPopup=$showPopup, requireConfirmation=$requireConfirmation)" } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/announcement/AnnouncementParametersBuilder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.contact.announcement import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic /** * [AnnouncementParameters] 的构建器. 可以构建一个 [AnnouncementParameters] 实例. * * ## 获得实例 * * 直接构造实例: `new AnnouncementParametersBuilder()` 或者从已有的公告中获取 [AnnouncementParameters.builder]. * * ## 使用 * * ### 在 Kotlin 使用 * * ``` * val parameters = buildAnnouncementParameters { * sendToNewMember = true * // ... * } * ``` * * ### 在 Java 使用 * * ```java * AnnouncementParameters parameters = new AnnouncementParametersBuilder() * .sendToNewMember(true) * .pinned(true) * .build(); * ``` * * @see buildAnnouncementParameters * * @since 2.7 */ public class AnnouncementParametersBuilder @JvmOverloads constructor( prototype: AnnouncementParameters = AnnouncementParameters.DEFAULT ) { /** * @see AnnouncementParameters.image */ public var image: AnnouncementImage? = prototype.image @JvmName("image") get @JvmSynthetic set /** * @see AnnouncementParameters.sendToNewMember */ public var sendToNewMember: Boolean = prototype.sendToNewMember @JvmName("sendToNewMember") get @JvmSynthetic set /** * @see AnnouncementParameters.isPinned */ public var isPinned: Boolean = prototype.isPinned @JvmName("isPinned") get @JvmSynthetic set /** * @see AnnouncementParameters.showEditCard */ public var showEditCard: Boolean = prototype.showEditCard @JvmName("showEditCard") get @JvmSynthetic set /** * @see AnnouncementParameters.showPopup */ public var showPopup: Boolean = prototype.showPopup @JvmName("showPopup") get @JvmSynthetic set /** * @see AnnouncementParameters.requireConfirmation */ public var requireConfirmation: Boolean = prototype.requireConfirmation @JvmName("requireConfirmation") get @JvmSynthetic set /** * @see AnnouncementParameters.image */ public fun image(image: AnnouncementImage?): AnnouncementParametersBuilder { this.image = image return this } /** * @see AnnouncementParameters.sendToNewMember */ public fun sendToNewMember(sendToNewMember: Boolean): AnnouncementParametersBuilder { this.sendToNewMember = sendToNewMember return this } /** * @see AnnouncementParameters.isPinned */ public fun isPinned(isPinned: Boolean): AnnouncementParametersBuilder { this.isPinned = isPinned return this } /** * @see AnnouncementParameters.showEditCard */ public fun showEditCard(isShowEditCard: Boolean): AnnouncementParametersBuilder { this.showEditCard = isShowEditCard return this } /** * @see AnnouncementParameters.showPopup */ public fun showPopup(showPopup: Boolean): AnnouncementParametersBuilder { this.showPopup = showPopup return this } /** * @see AnnouncementParameters.requireConfirmation */ public fun requireConfirmation(requireConfirmation: Boolean): AnnouncementParametersBuilder { this.requireConfirmation = requireConfirmation return this } /** * 使用当前参数构造 [AnnouncementParameters]. */ public fun build(): AnnouncementParameters = AnnouncementParameters(image, sendToNewMember, isPinned, showEditCard, showPopup, requireConfirmation) } /** * 使用 [AnnouncementParametersBuilder] 构建 [AnnouncementParameters]. * @see AnnouncementParametersBuilder * * @since 2.7 */ public inline fun buildAnnouncementParameters( builderAction: AnnouncementParametersBuilder.() -> Unit ): AnnouncementParameters { contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) } return AnnouncementParametersBuilder().apply(builderAction).build() } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/announcement/Announcements.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact.announcement import kotlinx.coroutines.flow.Flow import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.Streamable import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 表示一个群的公告列表 (管理器). * * ## 获取群公告 * * ### 获取 [Announcements] 实例 * * 只可以通过 [Group.announcements] 获取一个群的公告列表, 即 [Announcements] 实例. * * ### 获取公告列表 * * 通过 [asFlow] 或 `asStream` 可以获取到*惰性*流, 在从流中收集数据时才会请求服务器获取数据. 通常建议在 Kotlin 使用协程的 [asFlow], 在 Java 使用 `asStream`. * * 若要获取全部公告列表, 可使用 [toList]. * * ## 发布群公告 * * 查看 [Announcement] * * @since 2.7 */ @NotStableForInheritance public interface Announcements : Streamable<OnlineAnnouncement> { /** * 删除一条群公告. 需要管理员权限. 使用 [OnlineAnnouncement.delete] 与此方法效果相同. * * @param fid 公告的 [OnlineAnnouncement.fid] * @return 成功返回 `true`, 群公告不存在时返回 `false` * * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * * @see OnlineAnnouncement.delete */ public suspend fun delete(fid: String): Boolean /** * 获取一条群公告. * @param fid 公告的 [OnlineAnnouncement.fid] * @return 返回 `null` 表示不存在该 [fid] 的群公告 * @throws IllegalStateException 当协议异常时抛出 */ public suspend fun get(fid: String): OnlineAnnouncement? /** * 在该群发布群公告并获得 [OnlineAnnouncement], 需要管理员权限. 发布公告后群内将会出现 "有新公告" 系统提示. * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * @see Announcement.publishTo */ public suspend fun publish(announcement: Announcement): OnlineAnnouncement /** * 上传资源作为群公告图片. 返回值可用于 [AnnouncementParameters.image]. * * **注意**: 需要由调用方[关闭][ExternalResource.close] [resource]. * @throws IllegalStateException 当协议异常时抛出 */ public suspend fun uploadImage(resource: ExternalResource): AnnouncementImage /** * 获取 已确认/未确认 的群成员 * * @param fid 公告的 [OnlineAnnouncement.fid] * @param confirmed 是否确认 * @return 群成员列表 * * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * * @see OnlineAnnouncement.members * @since 2.14 */ public suspend fun members(fid: String, confirmed: Boolean): List<NormalMember> /** * 提醒 未确认 的群成员 * * @param fid 公告的 [OnlineAnnouncement.fid] * * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * * @see OnlineAnnouncement.remind */ public suspend fun remind(fid: String) // no blocking bridge for this method @Suppress("INAPPLICABLE_JVM_NAME") @JvmName("asFlow") @Deprecated( "Kept for binary compatibility. Use non-suspend overload instead.", level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(hiddenSince = "2.13") @JvmSynthetic // no blocking bridge public suspend fun asFlow0(): Flow<OnlineAnnouncement> = asFlow() } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/announcement/OfflineAnnouncement.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.contact.announcement import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.announcement.OfflineAnnouncement.Companion.serializer import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.map import net.mamoe.mirai.utils.safeCast import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** * 表示在本地构建的 [Announcement]. * * 支持序列化, 使用 [serializer]. * * 可以通过 [OfflineAnnouncement], [OfflineAnnouncement.create] 等方法构建, 然后使用 [OfflineAnnouncement.publishTo] 或 [Announcements.publish] 发布公告到群. * * 在 [Announcement] 获取更多信息. * * @see OnlineAnnouncement.publishTo * * @since 2.7 */ @Serializable(OfflineAnnouncement.Companion.Serializer::class) @SerialName(OfflineAnnouncement.SERIAL_NAME) public sealed interface OfflineAnnouncement : Announcement { public companion object { public const val SERIAL_NAME: String = "OfflineAnnouncement" /** * 创建 [OfflineAnnouncement]. 若 [announcement] 类型为 [OfflineAnnouncement] 则直接返回 [announcement]. * * 若要转发获取到的公告到一个群, 可直接调用 [Announcement.publishTo] 而不需要构造 [OfflineAnnouncement]. * * @see OnlineAnnouncement.toOffline */ @JvmStatic public inline fun from(announcement: Announcement): OfflineAnnouncement = announcement.safeCast() ?: announcement.run { create(content, parameters) } /** * 创建 [OfflineAnnouncement]. * @param content 公告内容 * @param parameters 可选的附加参数 */ @JvmOverloads @JvmStatic public fun create( content: String, parameters: AnnouncementParameters = AnnouncementParameters.DEFAULT ): OfflineAnnouncement = OfflineAnnouncementImpl(content, parameters) /** * 创建 [AnnouncementParameters] 并创建 [OfflineAnnouncement]. * @param content 公告内容 * @param parameters 可选的附加参数 * @see AnnouncementParametersBuilder */ @JvmSynthetic public inline fun create( content: String, parameters: AnnouncementParametersBuilder.() -> Unit ): OfflineAnnouncement { contract { callsInPlace(parameters, InvocationKind.EXACTLY_ONCE) } return create(content, buildAnnouncementParameters(parameters)) } internal object Serializer : KSerializer<OfflineAnnouncement> by OfflineAnnouncementImpl.serializer().map( resultantDescriptor = OfflineAnnouncementImpl.serializer().descriptor, deserialize = { it }, serialize = { it.safeCast<OfflineAnnouncementImpl>() ?: create(content, parameters).cast() } ) } } /** * 依据 [from] 创建 [OfflineAnnouncement]. 若 [from] 类型为 [OfflineAnnouncement] 则直接返回 [from]. * @since 2.7 */ public inline fun OfflineAnnouncement(from: Announcement): OfflineAnnouncement = OfflineAnnouncement.from(from) /** * 创建 [AnnouncementParameters] 并创建 [OfflineAnnouncement]. * @param content 公告内容 * @param parameters 可选的附加参数 * @since 2.7 */ public inline fun OfflineAnnouncement( content: String, parameters: AnnouncementParameters = AnnouncementParameters.DEFAULT ): OfflineAnnouncement = OfflineAnnouncement.create(content, parameters) /** * 创建 [AnnouncementParameters] 并创建 [OfflineAnnouncement]. * @param content 公告内容 * @param parameters 可选的附加参数 * @see AnnouncementParametersBuilder * @since 2.7 */ public inline fun OfflineAnnouncement( content: String, parameters: AnnouncementParametersBuilder.() -> Unit ): OfflineAnnouncement { contract { callsInPlace(parameters, InvocationKind.EXACTLY_ONCE) } return OfflineAnnouncement.create(content, parameters) } @SerialName(OfflineAnnouncement.SERIAL_NAME) @Serializable private data class OfflineAnnouncementImpl( override val content: String, override val parameters: AnnouncementParameters ) : OfflineAnnouncement { override fun toString() = "OfflineAnnouncement(body='$content', parameters=$parameters)" } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/announcement/OnlineAnnouncement.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INAPPLICABLE_JVM_NAME", "NOTHING_TO_INLINE") @file:JvmBlockingBridge package net.mamoe.mirai.contact.announcement import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.utils.NotStableForInheritance /** * 表示从 [Announcements.get] 等途径在线获取的, 已经存在于服务器的公告. * * [OnlineAnnouncement] 拥有唯一识别属性 [fid] 代表其存在于服务器中的 ID. 可进行 [删除][delete] * * 可在 [Announcement] 获取更多信息. * * @since 2.7 */ @NotStableForInheritance public interface OnlineAnnouncement : Announcement { /** * 公告所属群 */ public val group: Group /** * 公告发送者 [NormalMember.id] */ public val senderId: Long /** * 公告发送者. 当该成员已经离开群后为 `null` */ public val sender: NormalMember? /** * 唯一识别属性 */ public val fid: String /** * 所有人都已阅读, 如果 [AnnouncementParameters.requireConfirmation] 为 `true` 则为所有人都已确认. */ public val allConfirmed: Boolean /** * 已经阅读的成员数量,如果 [AnnouncementParameters.requireConfirmation] 为 `true` 则为已经确认的成员数量 */ public val confirmedMembersCount: Int /** * 公告发出的时间,为 EpochSecond (自 1970-01-01T00:00:00Z 的秒数) * * @see java.time.Instant.ofEpochSecond */ public val publicationTime: Long /** * 删除这个公告. 需要管理员权限. 使用 [Announcements.delete] 与此方法效果相同. * * @return 成功返回 `true`, 群公告已被删除时返回 `false` * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * @see Announcements.delete */ public suspend fun delete(): Boolean = group.announcements.delete(fid) /** * 获取 已确认/未确认 的群成员 * * @param confirmed 是否确认 * @return 群成员列表 * * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * * @see Announcements.members */ public suspend fun members(confirmed: Boolean): List<NormalMember> = group.announcements.members(fid, confirmed) /** * 提醒 未确认 的群成员 * * @throws PermissionDeniedException 当没有权限时抛出 * @throws IllegalStateException 当协议异常时抛出 * * @see Announcements.remind */ public suspend fun remind(): Unit = group.announcements.remind(fid) } /** * 公告所在群所属的 [Bot], 即 `group.bot`. * @since 2.7 */ public inline val OnlineAnnouncement.bot: Bot get() = group.bot ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/essence/EssenceMessageRecord.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.contact.essence import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.utils.MiraiInternalApi /** * 精华消息记录 * @since 2.15 * @param group 记录的群聊 * @param sender 消息的发送者 * @param senderId 消息的发送者的ID * @param senderNick 消息的发送者的Nick * @param senderTime 消息的发送的时间 * * @param operator 设置精华的操作者 * @param operatorId 设置精华的操作者的ID * @param operatorNick 设置精华的操作者的Nick * @param operatorTime 设置精华的时间 */ public class EssenceMessageRecord @MiraiInternalApi constructor( public val group: Group, public val sender: NormalMember?, public val senderId: Long, public val senderNick: String, public val senderTime: Int, public val operator: NormalMember?, public val operatorId: Long, public val operatorNick: String, public val operatorTime: Int, private val loadMessageSource: suspend (parse: Boolean) -> MessageSource ) { override fun toString(): String { return "EssenceMessageRecord(group=${group}, sender=${senderNick}(${senderId}), senderTime=${senderTime}, operator=${operatorNick}(${operatorId}), operatorTime=${operatorTime})" } /** * 获取消息源 * * 其中的 [MessageSource.originalMessage] 将会尝试以加载为原消息格式 * * **注意** 当精华消息中包含 图片 时,会尝试将其下载然后重新上传, 以保证可用性 * * @see getSource */ @JvmBlockingBridge public suspend fun getFullSource(): MessageSource { return loadMessageSource(true) } /** * 获取消息源 * * @see getFullSource */ @JvmBlockingBridge public suspend fun getSource(): MessageSource { return loadMessageSource(false) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/essence/Essences.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.contact.essence import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.contact.Group import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.Streamable /** * 表示一个群精华消息管理. * * ## 获取 [Essences] 实例 * * 只可以通过 [Group.essences] 获取一个群的精华消息管理, 即 [Essences] 实例. * * ### 获取精华消息列表 * * 通过 [asFlow] 或 `asStream` 可以获取到*惰性*流, 在从流中收集数据时才会请求服务器获取数据. 通常建议在 Kotlin 使用协程的 [asFlow], 在 Java 使用 `asStream`. * * 若要获取全部精华消息列表, 可使用 [toList]. * * ### 获取精华消息分享链接 * * 通过 [share] 可以获得一个精华消息的分享链接 * * ### 移除精华消息 * * 通过 [remove] 可以从列表中移除指定精华消息 (WEB API) * * @since 2.15 */ @NotStableForInheritance public interface Essences : Streamable<EssenceMessageRecord> { /** * 按页获取精华消息记录 * @param start 起始索引 从 0 开始 * @param limit 页大小 返回的记录最大数量,最大取 50 * @throws IllegalStateException [limit] 过大或其他参数错误时会触发异常 */ @JvmBlockingBridge public suspend fun getPage(start: Int, limit: Int): List<EssenceMessageRecord> /** * 分享精华消息 * @param source 要分享的消息源 * @throws IllegalStateException [source] 不为精华消息时将会触发异常 * @return 分享 URL */ @JvmBlockingBridge public suspend fun share(source: MessageSource): String /** * 移除精华消息 * @throws IllegalStateException [source] 不为精华消息或权限不足时将会触发异常 * @param source 要移除的消息源 */ @JvmBlockingBridge public suspend fun remove(source: MessageSource) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFile.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmBlockingBridge @file:Suppress("OVERLOADS_INTERFACE") package net.mamoe.mirai.contact.file import io.ktor.utils.io.errors.* import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.utils.NotStableForInheritance /** * 绝对文件标识. 精确表示一个远程文件. 不会受同名文件或目录的影响. * * @since 2.8 * @see RemoteFiles * @see AbsoluteFolder * @see AbsoluteFileFolder */ @NotStableForInheritance public interface AbsoluteFile : AbsoluteFileFolder { /** * 文件到期时间戳, 单位秒. */ public val expiryTime: Long /** * 文件大小 (占用空间), 单位 byte. */ public val size: Long /** * 文件内容 SHA-1. */ public val sha1: ByteArray /** * 文件内容 MD5. */ public val md5: ByteArray /** * 移动远程文件到 [folder] 目录下. 成功时返回 `true`, 当远程文件不存在时返回 `false`. * * 注意该操作有可能产生同名文件或目录 (当 [folder] 中已经存在一个名称为 [name] 的文件或目录时). * * @throws IOException 当发生网络错误时可能抛出 * @throws IllegalStateException 当发生已知的协议错误时抛出 * @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传) */ public suspend fun moveTo(folder: AbsoluteFolder): Boolean /** * 获得下载链接 URL 字符串. 当远程文件不存在时返回 `null`. */ public suspend fun getUrl(): String? /** * 得到 [AbsoluteFile] 所对应的 [FileMessage]. * * 注: 在 [上传文件][RemoteFiles.uploadNewFile] 时就已经发送了文件消息, [FileMessage] 不可手动发送 */ public fun toMessage(): FileMessage /** * 返回更新了文件或目录信息 ([lastModifiedTime] 等) 的, 指向相同文件的 [AbsoluteFileFolder]. * 不会更新当前 [AbsoluteFileFolder] 对象. * * 当远程文件或目录不存在时返回 `null`. * * 该函数会遍历上级目录的所有文件并匹配当前文件, 因此可能会非常慢, 请不要频繁使用. */ override suspend fun refreshed(): AbsoluteFile? } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFileFolder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmBlockingBridge @file:Suppress("OVERLOADS_INTERFACE") package net.mamoe.mirai.contact.file import io.ktor.utils.io.errors.* import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.jvm.JvmStatic /** * 绝对文件或目录标识. 精确表示一个远程文件. 不会受同名文件或目录的影响. * * @since 2.8 * @see RemoteFiles * @see AbsoluteFile * @see AbsoluteFolder */ @NotStableForInheritance public sealed interface AbsoluteFileFolder { /** * 该对象所属 [FileSupported] */ public val contact: FileSupported /** * 上级 [AbsoluteFileFolder]. * * - 当该 [AbsoluteFileFolder] 表示一个目录中的文件时返回文件所属目录的 [AbsoluteFolder]. * - 当该 [AbsoluteFileFolder] 表示子目录时返回父目录的 [AbsoluteFolder]. * * 特别地, * - 当该 [AbsoluteFileFolder] 表示根目录下的一个文件时返回根目录的 [AbsoluteFolder]. * - 当该 [AbsoluteFileFolder] 表示根目录时返回 `null` (表示无上级). * * 也就是说, 若 [AbsoluteFileFolder.parent] 为 `null`, 那么该 [AbsoluteFileFolder] 就表示根目录. */ public val parent: AbsoluteFolder? /** * 文件或目录的 ID, 即 `fileId` 或 `folderId`. 该属性由服务器维护, 通常唯一且持久. */ public val id: String /** * 文件名或目录名. * * 注意, 当远程文件或目录被 (其他人) 改名时, [name] 不会变动. * 只有在调用 [renameTo] 和 [refresh] 时才会更新. * * 不会包含 `:*?"<>|/\` 任一字符. */ public val name: String /** * 绝对路径, 如 `/foo/bar.txt`. * * 注意, 当远程文件或目录被 (其他人) 移动到其他位置或其父目录名称改名时, [absolutePath] 不会变动. * 只有在调用 [renameTo] 和 [refresh] 等时才会更新. */ public val absolutePath: String /** * 表示远程文件时返回 `true`. */ public val isFile: Boolean /** * 表示远程目录时返回 `true`. */ public val isFolder: Boolean /** * 远程文件或目录的创建时间, 时间戳秒. */ public val uploadTime: Long /** * 远程文件或目录的最后修改时间戳, 单位秒. * * 注意, 当远程文件或目录被 (其他人) 改动时, [lastModifiedTime] 不会变动. * 只有在调用 [renameTo] 和 [refresh] 等时才会更新. */ public val lastModifiedTime: Long /** * 上传者 ID. */ public val uploaderId: Long /** * 查询该远程文件或目录是否还存在于服务器. * * 只会精确地按 [id] 检查, 而不会考虑同名文件或目录. 当文件或目录存在时返回 `true`. * * 该操作不会更新 [absolutePath] 等属性. */ public suspend fun exists(): Boolean /** * 重命名远程文件或目录, **并且**修改当前(`this`) [AbsoluteFileFolder] 的 [name]. * 成功时返回 `true`, 当远程文件或目录不存在时返回 `false`. * * 注意该操作有可能产生同名文件或目录 (当服务器已经存在一个名称为 [newName] 的文件或目录时). * * @throws IOException 当发生网络错误时可能抛出 * @throws IllegalStateException 当发生已知的协议错误时抛出 * @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传) */ public suspend fun renameTo(newName: String): Boolean /** * 删除远程文件或目录. 只会根据 [id] 精确地删除一个文件或目录, 不会删除其他同名文件或目录. * 成功时返回 `true`, 当远程文件或目录不存在时返回 `false`. * * 若目录非空, 则会删除目录中的所有文件. 操作目录或非 Bot 自己上传的文件时需要管理员权限, 无管理员权限时抛出异常. * * @throws IOException 当发生网络错误时可能抛出 * @throws IllegalStateException 当发生已知的协议错误时抛出 * @throws PermissionDeniedException 当无管理员权限时抛出 */ public suspend fun delete(): Boolean /** * 更新当前 [AbsoluteFileFolder] 对象的文件或目录信息 ([lastModifiedTime], [absolutePath] 等). * 成功时返回 `true`, 当远程文件或目录不存在时返回 `false`. */ public suspend fun refresh(): Boolean /** * 返回更新了文件或目录信息 ([lastModifiedTime] 等) 的, 指向相同文件的 [AbsoluteFileFolder]. * 不会更新当前 [AbsoluteFileFolder] 对象. * * 当远程文件或目录不存在时返回 `null`. * * 该函数会遍历上级目录的所有文件并匹配当前文件, 因此可能会非常慢, 请不要频繁使用. */ public suspend fun refreshed(): AbsoluteFileFolder? public override fun toString(): String public companion object { /** * 返回去掉文件后缀的文件名. 如 `foo.txt` 返回 `foo`. * * 注意, 当远程文件或目录被 (其他人) 改名时, [nameWithoutExtension] 不会变动. * 只有在调用 [renameTo] 和 [refresh] 时才会更新. * * 不会包含 `:*?"<>|/\` 任一字符. * * @see File.nameWithoutExtension */ @get:JvmStatic public val AbsoluteFileFolder.nameWithoutExtension: String get() = name.substringBeforeLast('.') /** * 返回文件的后缀名. 如 `foo.txt` 返回 `txt`. * * 注意, 当远程文件或目录被 (其他人) 改名时, [extension] 不会变动. * 只有在调用 [renameTo] 和 [refresh] 时才会更新. * * 不会包含 `:*?"<>|/\` 任一字符. * * @see java.io.File.extension */ @get:JvmStatic public val AbsoluteFileFolder.extension: String get() = name.substringAfterLast('.', "") } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/file/AbsoluteFolder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact.file import kotlinx.coroutines.flow.Flow import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.JavaFriendlyAPI import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.ProgressionCallback import java.util.stream.Stream /** * 绝对目录标识. 精确表示一个远程目录. 不会受同名文件或目录的影响. * * @since 2.8 * @see RemoteFiles * @see AbsoluteFile * @see AbsoluteFileFolder */ @Suppress("SEALED_INHERITOR_IN_DIFFERENT_MODULE") @NotStableForInheritance public interface AbsoluteFolder : AbsoluteFileFolder { /** * 当前快照中文件数量, 当有文件更新时(上传/删除文件) 该属性不会更新. * * 只可能通过 [refresh] 手动刷新 * * 特别的, 若该目录表示根目录, [contentsCount] 返回 `0`. (无法快速获取) */ public val contentsCount: Int /** * 当该目录为空时返回 `true`. */ public fun isEmpty(): Boolean = contentsCount == 0 /** * 返回更新了文件或目录信息 ([lastModifiedTime] 等) 的, 指向相同文件的 [AbsoluteFileFolder]. * 不会更新当前 [AbsoluteFileFolder] 对象. * * 当远程文件或目录不存在时返回 `null`. * * 该函数会遍历上级目录的所有文件并匹配当前文件, 因此可能会非常慢, 请不要频繁使用. */ override suspend fun refreshed(): AbsoluteFolder? /////////////////////////////////////////////////////////////////////////// // list children /////////////////////////////////////////////////////////////////////////// /** * 获取该目录下所有子目录列表. */ public suspend fun folders(): Flow<AbsoluteFolder> /** * 获取该目录下所有子目录列表. * * 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [folders], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [folders]. */ @JavaFriendlyAPI public suspend fun foldersStream(): Stream<AbsoluteFolder> /** * 获取该目录下所有文件列表. */ public suspend fun files(): Flow<AbsoluteFile> /** * 获取该目录下所有文件列表. * * 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [files], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [files]. */ @JavaFriendlyAPI public suspend fun filesStream(): Stream<AbsoluteFile> /** * 获取该目录下所有文件和子目录列表. */ public suspend fun children(): Flow<AbsoluteFileFolder> /** * 获取该目录下所有文件和子目录列表. * * 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [children], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [children]. */ @JavaFriendlyAPI public suspend fun childrenStream(): Stream<AbsoluteFileFolder> /////////////////////////////////////////////////////////////////////////// // resolve and upload /////////////////////////////////////////////////////////////////////////// /** * 创建一个名称为 [name] 的子目录. 返回成功创建的或已有的子目录. 当目标目录已经存在时则直接返回该目录. * * @throws IllegalArgumentException 当 [name] 为空或包含非法字符 (`:*?"<>|`) 时抛出 * @throws PermissionDeniedException 当权限不足时抛出 */ public suspend fun createFolder(name: String): AbsoluteFolder /** * 获取一个已存在的名称为 [name] 的子目录. 当该名称的子目录不存在时返回 `null`. * * @throws IllegalArgumentException 当 [name] 为空或包含非法字符 (`:*?"<>|`) 时抛出 */ public suspend fun resolveFolder(name: String): AbsoluteFolder? /** * 获取一个已存在的 [AbsoluteFileFolder.id] 为 [id] 的子目录. 当该名称的子目录不存在时返回 `null`. * * @throws IllegalArgumentException 当 [id] 为空或无效时抛出 * * @since 2.9.0 */ public suspend fun resolveFolderById(id: String): AbsoluteFolder? /** * 精确获取 [AbsoluteFile.id] 为 [id] 的文件. 在目标文件不存在时返回 `null`. 当 [deep] 为 `true` 时还会深入子目录查找. */ @Suppress("OVERLOADS_INTERFACE", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") // Keep JVM ABI @JvmOverloads public suspend fun resolveFileById( id: String, deep: Boolean = false ): AbsoluteFile? /** * 根据路径获取指向的所有路径为 [path] 的文件列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件. */ public suspend fun resolveFiles( path: String ): Flow<AbsoluteFile> /** * 根据路径获取指向的所有路径为 [path] 的文件列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件. * * 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [resolveFiles], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [resolveFiles]. */ @JavaFriendlyAPI public suspend fun resolveFilesStream( path: String ): Stream<AbsoluteFile> /** * 根据路径获取指向的所有路径为 [path] 的文件和目录列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件和目录. */ public suspend fun resolveAll( path: String ): Flow<AbsoluteFileFolder> /** * 根据路径获取指向的所有路径为 [path] 的文件和目录列表. 同时支持相对路径和绝对路径. 支持获取子目录内的文件和目录. * * 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [resolveAll], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [resolveAll]. */ @JavaFriendlyAPI public suspend fun resolveAllStream( path: String ): Stream<AbsoluteFileFolder> /** * 上传一个文件到该目录, 返回上传成功的文件标识. * * 会在必要时尝试创建远程目录. * * ### [filepath] * * - 可以是 `foo.txt` 表示该目录下的文件 "foo.txt" * - 也可以是 `sub/foo.txt` 表示该目录的子目录 "sub" 下的文件 "foo.txt". * - 或是绝对路径 `/sub/foo.txt` 表示根目录的 "sub" 目录下的文件 "foo.txt" * * @param filepath 目标文件名, 即上传到远程后的远程路径, 不是本地文件路径 * @param content 文件内容 * @param callback 下载进度回调, 传递的 `progression` 是已下载字节数. * * @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传) */ @Suppress("OVERLOADS_INTERFACE", "ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") // Keep JVM ABI @JvmOverloads public suspend fun uploadNewFile( filepath: String, content: ExternalResource, callback: ProgressionCallback<AbsoluteFile, Long>? = null, ): AbsoluteFile public companion object { /** * 根目录 folder ID. * @see id */ public const val ROOT_FOLDER_ID: String = "/" } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/file/RemoteFiles.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmBlockingBridge @file:Suppress("OVERLOADS_INTERFACE") package net.mamoe.mirai.contact.file import kotlinx.coroutines.flow.Flow import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.ProgressionCallback import kotlin.jvm.JvmOverloads /** * 表示远程文件列表 (管理器). * * [RemoteFiles] 包含一些协议接口, * * # 文件和目录操作 * * 文件和目录的父类型是 [AbsoluteFileFolder]. * * - [AbsoluteFile] 表示一个文件 * - [AbsoluteFolder] 表示一个目录 * * 每个文件或目录都拥有一个唯一 ID: [AbsoluteFileFolder.id]. 该 ID 由服务器提供, 在重命名或移动时不会变化. * * 文件名可以通过 [AbsoluteFileFolder.name] 获得, 但注意文件名和其他属性都会随重命名或移动等操作更新. * * 除根目录 [root] 外, 每个文件或目录都拥有父目录 [AbsoluteFileFolder.parent]. * * # 根目录 * * 除了 [RemoteFiles] 中定义的捷径外, 一切文件目录操作都以获取根目录开始. 可通过 [RemoteFiles.root] 获取表示根目录的 [AbsoluteFolder]. * * # 绝对路径与相对路径 * * mirai 文件系统的绝对路径与相对路径与 Java [java.io.File] 实现的相同. * * 以 `/` 起始的路径表示绝对路径, 基于根目录 [root] 处理. 其他路径均表示相对路径. * * 可由 [AbsoluteFileFolder.absolutePath] 获取其绝对路径. 值得注意的是, 所有文件与目录对象都表示绝对路径下的目标, 因此它们都总是精确地表示一个目标, 而不受环境影响. * * 除重命名外, 所有文件和目录操作都默认同时支持上述两种路径. * * # 操作 [AbsoluteFileFolder] * * ## 重命名, 移动 * * [AbsoluteFileFolder.renameTo], [AbsoluteFile.moveTo] 提供重命名和移动功能. 注意目录不支持移动. * * ## 获取目录中的子目录和文件列表 * * 一个目录 ([AbsoluteFolder]) 可以包含多个子文件, 根目录还可以包含多个子目录 (详见下文 '目录结构限制'). * * 使用 [AbsoluteFolder.children] 可以获得其内子目录和文件列表 [Flow]. [AbsoluteFolder.childrenStream] 提供适合 Java 的 [java.util.stream.Stream] 实现. * 使用 [AbsoluteFolder.folders] 或 [AbsoluteFolder.files] 可以特定地只获取子目录或文件列表. 这些函数也有其 `*Stream` 实现. * * 若要根据确定的文件或目录名称获取其 [AbsoluteFileFolder] 实例, 可使用 [AbsoluteFolder.resolveFiles] 或 [AbsoluteFolder.resolveFiles]. * 注意 [AbsoluteFolder.resolveFiles] 返回 [Flow] (其 Stream 版返回 [java.util.stream.Stream]), 因为服务器允许多个文件有相同名称. (详见下文 '允许重名'). * * 若已知文件 [AbsoluteFile.id], 可通过 [AbsoluteFolder.resolveFileById] 获得该文件. * * ## 上传新文件 * 可使用 [AbsoluteFolder.uploadNewFile] 上传新文件. 也可以通过 [RemoteFiles.uploadNewFile] 直接上传而跳过获取目录的步骤 (因为目录不允许同名). * * ## 覆盖一个旧文件 * 服务器不允许覆盖文件. 只能通过 [AbsoluteFile.delete] 删除文件后再上传新文件. 注意新旧文件的 [AbsoluteFile.id] 会不同. * * # 操作权限 * 操作一个目录时总是需要管理员权限. 若群设置 "允许任何人上传文件", 则上传文件和操作自己上传的文件时都不需要特殊权限. 注意, 操作他人的文件时总是需要管理员权限. * * # 服务器限制 * * ## 目录结构限制 * * 在 mirai 2.8.0 发布时, 服务器仅允许两层目录结构. 也就是说只允许根目录存在子目录, 子目录不能包含另一个子目录. * * 为了考虑将来服务器可能升级, mirai 没有做实现上的限制. mirai 所有操作都支持多层目录, 但进行这样的操作时将会得到服务器错误, 方法会抛出 [IllegalStateException]. * * ## 允许重名 * * 服务器允许同名目录和文件存在. 如下同名的三个文件与一个目录是允许的, 但它们的 [AbsoluteFileFolder.id] 都互不相同: * ``` * foo * |- test (目录) * |- test (文件) * |- test (文件) * |- test (文件) * ``` * 注意, 目录不允许同名. * * [AbsoluteFileFolder] 依据 [AbsoluteFileFolder.id] 定位文件, 而不是通过文件名. 因此 [AbsoluteFileFolder] 总是精确地代表一个文件或目录. * * @since 2.8 * @see FileSupported */ @NotStableForInheritance public interface RemoteFiles { /** * 获取表示根目录的 [AbsoluteFolder] */ public val root: AbsoluteFolder /** * 该对象所属 [FileSupported] */ public val contact: FileSupported /** * 上传一个文件到指定精确路径. 返回指代该远程文件的 [AbsoluteFile]. * * 会在必要时尝试创建远程目录. * * 也可以使用 [AbsoluteFolder.uploadNewFile]. * * @param filepath 文件路径, **包含目标文件名**. 如 `/foo/bar.txt`. 若是相对目录则基于 [根目录][root] 处理. * @param content 文件内容 * @param callback 下载进度回调, 传递的 `progression` 是已下载字节数. * * @throws PermissionDeniedException 当无管理员权限时抛出 (若群仅允许管理员上传) */ @JvmOverloads public suspend fun uploadNewFile( filepath: String, content: ExternalResource, callback: ProgressionCallback<AbsoluteFile, Long>? = null, ): AbsoluteFile = root.uploadNewFile(filepath, content, callback) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroup.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.friendgroup import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.utils.NotStableForInheritance /** * 一个好友分组. * 可能同时存在多个相同[名称][name]的分组, 但是每个分组的 [id] 都是唯一的. * * * 要获取一个分组, 可使用 [get] 根据 [ID][FriendGroup.id] 获取, 或者使用 [asCollection] 获取全部分组列表. * 也可以通过 [Friend.friendGroup] 获取一个好友所在的分组. * * 在每次登录会话中, [FriendGroup] 的实例是依据 [id] 唯一的. 存在于同一个分组中的多个好友的 [Friend.friendGroup] 会返回相同的 [FriendGroup] 实例. * 但当 bot 重新登录后, 实例可能变化. * * @see FriendGroups * @since 2.13 */ @JvmBlockingBridge @NotStableForInheritance public interface FriendGroup { /** * 好友分组 ID */ public val id: Int /** * 好友分组名 */ public val name: String /** * 好友分组内好友数量 */ public val count: Int /** * 属于本分组的好友集合 */ public val friends: Collection<Friend> /** * 更改好友分组名称. * * 允许存在同名分组. * 当操作成时返回 `true`; 当分组不存在时返回 `false`; 如果因为其他原因造成的改名失败时会抛出 [IllegalStateException] */ public suspend fun renameTo(newName: String): Boolean /** * 把一名好友移动至本分组内. * * 当远程分组不存在时会自动移动该好友到 ID 为 0 的默认好友分组. * 当操作成功时返回 `true`; 当分组不存在 (如已经在远程被删除) 时返回 `false`; 因为其他原因移动不成功时抛出 [IllegalStateException]. */ public suspend fun moveIn(friend: Friend): Boolean /** * 删除本分组. * * 删除后组内全部好友移动至 ID 为 0 的默认好友分组, 本分组的好友列表会被清空. * 当操作成功时返回 `true`; 当分组不存在或试图删除 ID 为 0 的默认好友分组时返回 `false`; * 因为其他原因删除不成功时抛出 [IllegalStateException]. */ public suspend fun delete(): Boolean } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/friendgroup/FriendGroups.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.friendgroup import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.utils.NotStableForInheritance /** * 好友分组列表 (管理器). * 允许存在重复名称的分组, 因此依赖于 name 判断不可靠, 需要依赖 ID 判断. * * @see FriendGroup * @since 2.13 */ @JvmBlockingBridge @NotStableForInheritance public interface FriendGroups { /** * 获取 [ID][FriendGroup.id] 为 `0` 的默认分组 ("我的好友"). */ public val default: FriendGroup get() = get(0) ?: error("Internal error: could not find FriendGroup with id = 0.") /** * 新建一个好友分组. * * 允许名称重复, 当新建一个已存在名称的分组时, 服务器会返回一个拥有重复名字的新分组; * 当因为其他原因创建不成功时抛出 [IllegalStateException]. * * 提示: 要删除一个好友分组, 使用 [FriendGroup.delete]. */ public suspend fun create(name: String): FriendGroup /** * 获取指定 ID 的好友分组, 不存在时返回 `null` */ public operator fun get(id: Int): FriendGroup? /** * 获取包含全部 [FriendGroup] 的 [Collection]. 返回的 [Collection] 只可读取. * * 此方法快速返回, 不会在调用时实例化新的 [Collection] 对象. * 返回的 [Collection] 是对缓存的引用, 会随着服务器通知和机器人操作 (如 [create]) 变化. */ public fun asCollection(): Collection<FriendGroup> } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessageFilter.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.contact.roaming import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.data.MessageSource import kotlin.jvm.JvmField /** * @since 2.8 */ public fun interface RoamingMessageFilter { public operator fun invoke(roamingMessage: RoamingMessage): Boolean public infix fun and(other: RoamingMessageFilter): RoamingMessageFilter { return RoamingMessageFilter { this.invoke(it) && other.invoke(it) } } public infix fun or(other: RoamingMessageFilter): RoamingMessageFilter { return RoamingMessageFilter { this.invoke(it) || other.invoke(it) } } public fun not(): RoamingMessageFilter { return RoamingMessageFilter { !this.invoke(it) } } public companion object { /** * 筛选任何消息 (相当于不筛选) */ @JvmField public val ANY: RoamingMessageFilter = RoamingMessageFilter { true } /** * 筛选 bot 接收的消息 */ @JvmField public val RECEIVED: RoamingMessageFilter = RoamingMessageFilter { it.sender != it.bot.id } /** * 筛选 bot 发送的消息 */ @JvmField public val SENT: RoamingMessageFilter = RoamingMessageFilter { it.sender == it.bot.id } } } /** * 还未解析的漫游消息. * * @since 2.8 */ public interface RoamingMessage { public val contact: Contact public val bot: Bot get() = contact.bot /** * 发送人 id */ public val sender: Long /** * 收信人或群的 id */ public val target: Long /** * 时间戳, 单位为秒, 服务器时间. */ public val time: Long /** * @see MessageSource.ids */ public val ids: IntArray /** * @see MessageSource.internalIds */ public val internalIds: IntArray } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingMessages.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact.roaming import kotlinx.coroutines.flow.Flow import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.utils.JavaFriendlyAPI import net.mamoe.mirai.utils.JdkStreamSupport.toStream import java.util.stream.Stream /** * 漫游消息记录管理器. 可通过 [RoamingSupported.roamingMessages] 获得. * * @since 2.8 * @see RoamingSupported */ public interface RoamingMessages { /////////////////////////////////////////////////////////////////////////// // Get list /////////////////////////////////////////////////////////////////////////// /** * 查询指定时间段内的漫游消息记录. Java Stream 方法查看 [getMessagesStream]. * * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. * * 注意, 返回的消息记录既包含机器人发送给目标用户的消息, 也包含目标用户发送给机器人的消息. * 可通过 [MessageChain] 获取 [MessageSource] (用法为 `messageChain.source`), 判断 [MessageSource.fromId] (发送人). * 消息的其他*元数据*信息也要通过 [MessageSource] 获取 (如 [MessageSource.time] 获取时间). * * 若只需要获取单向消息 (机器人发送给目标用户的消息或反之), 可使用 [RoamingMessageFilter.SENT] 或 [RoamingMessageFilter.RECEIVED] 作为 [filter] 参数传递. * * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. * * @param timeStart 起始时间戳, 单位为秒. 可以为 `0`, 即表示从可以获取的最早的消息起. 负数将会被看是 `0`. * @param timeEnd 结束时间戳, 单位为秒. 可以为 [Long.MAX_VALUE], 即表示到可以获取的最晚的消息为止. 低于 [timeStart] 的值将会被看作是 [timeStart] 的值. * @param filter 过滤器. */ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") // Keep JVM ABI public suspend fun getMessagesIn( timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter? = null ): Flow<MessageChain> /** * 查询所有漫游消息记录. Java Stream 方法查看 [getAllMessagesStream]. * * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. * * 注意, 返回的消息记录既包含机器人发送给目标用户的消息, 也包含目标用户发送给机器人的消息. * 可通过 [MessageChain] 获取 [MessageSource] (用法为 `messageChain.source`), 判断 [MessageSource.fromId] (发送人). * 消息的其他*元数据*信息也要通过 [MessageSource] 获取 (如 [MessageSource.time] 获取时间). * * 若只需要获取单向消息 (机器人发送给目标用户的消息或反之), 可使用 [RoamingMessageFilter.SENT] 或 [RoamingMessageFilter.RECEIVED] 作为 [filter] 参数传递. * * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. * * @param filter 过滤器. */ @Suppress("ACTUAL_FUNCTION_WITH_DEFAULT_ARGUMENTS") // Keep JVM ABI public suspend fun getAllMessages( filter: RoamingMessageFilter? = null ): Flow<MessageChain> = getMessagesIn(0, Long.MAX_VALUE, filter) /** * 查询指定时间段内的漫游消息记录. Kotlin Flow 版本查看 [getMessagesIn]. * * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. * * 注意, 返回的消息记录既包含机器人发送给目标用户的消息, 也包含目标用户发送给机器人的消息. * 可通过 [MessageChain] 获取 [MessageSource] (用法为 `messageChain.get(MessageSource.Key)`), 判断 [MessageSource.fromId] (发送人). * 消息的其他*元数据*信息也要通过 [MessageSource] 获取 (如 [MessageSource.time] 获取时间). * * 若只需要获取单向消息 (机器人发送给目标用户的消息或反之), 可使用 [RoamingMessageFilter.SENT] 或 [RoamingMessageFilter.RECEIVED] 作为 [filter] 参数传递. * * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. * * @param timeStart 起始时间戳, 单位为秒. 可以为 `0`, 即表示从可以获取的最早的消息起. 负数将会被看是 `0`. * @param timeEnd 结束时间戳, 单位为秒. 可以为 [Long.MAX_VALUE], 即表示到可以获取的最晚的消息为止. 低于 [timeStart] 的值将会被看作是 [timeStart] 的值. * @param filter 过滤器. */ @Suppress("OVERLOADS_INTERFACE") @JvmOverloads @JavaFriendlyAPI public suspend fun getMessagesStream( timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter? = null ): Stream<MessageChain> = getMessagesIn(timeStart, timeEnd, filter).toStream() /** * 查询所有漫游消息记录. Kotlin Flow 版本查看 [getAllMessages]. * * 返回查询到的漫游消息记录, 顺序为由新到旧. 这些 [MessageChain] 与从事件中收到的消息链相似, 属于在线消息. * 可从 [MessageChain] 获取 [MessageSource] 来确定发送人等相关信息, 也可以进行引用回复或撤回. * * 注意, 返回的消息记录既包含机器人发送给目标用户的消息, 也包含目标用户发送给机器人的消息. * 可通过 [MessageChain] 获取 [MessageSource] (用法为 `messageChain.get(MessageSource.Key)`), 判断 [MessageSource.fromId] (发送人). * 消息的其他*元数据*信息也要通过 [MessageSource] 获取 (如 [MessageSource.time] 获取时间). * * 若只需要获取单向消息 (机器人发送给目标用户的消息或反之), 可使用 [RoamingMessageFilter.SENT] 或 [RoamingMessageFilter.RECEIVED] 作为 [filter] 参数传递. * * 性能提示: 请在 [filter] 执行筛选, 若 [filter] 返回 `false` 则不会解析消息链, 这对本函数的处理速度有决定性影响. * * @param filter 过滤器. */ @Suppress("OVERLOADS_INTERFACE") @JvmOverloads @JavaFriendlyAPI public suspend fun getAllMessagesStream( filter: RoamingMessageFilter? = null ): Stream<MessageChain> = getMessagesStream(0, Long.MAX_VALUE, filter) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/contact/roaming/RoamingSupported.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.contact.roaming import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Friend /** * 支持查询漫游消息记录的 [Contact]. 目前仅 [Friend] 实现 [RoamingSupported]. * @since 2.8 */ public interface RoamingSupported : Contact { /** * 获取漫游消息记录管理器. */ public val roamingMessages: RoamingMessages } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/FriendInfo.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.data import net.mamoe.mirai.LowLevelApi @LowLevelApi public interface FriendInfo : UserInfo { public override val uin: Long public override val nick: String public override var remark: String public val friendGroupId: Int } @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, override var nick: String, override var remark: String, ) : FriendInfo { override var friendGroupId: Int = 0 } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/GroupHonorType.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.data import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.active.GroupActive import net.mamoe.mirai.contact.active.MemberActive import net.mamoe.mirai.data.GroupHonorType.Companion.BRONZE import net.mamoe.mirai.data.GroupHonorType.Companion.EMOTION import net.mamoe.mirai.data.GroupHonorType.Companion.GOLDEN import net.mamoe.mirai.data.GroupHonorType.Companion.LEGEND import net.mamoe.mirai.data.GroupHonorType.Companion.PERFORMER import net.mamoe.mirai.data.GroupHonorType.Companion.RED_PACKET import net.mamoe.mirai.data.GroupHonorType.Companion.RICHER import net.mamoe.mirai.data.GroupHonorType.Companion.SILVER import net.mamoe.mirai.data.GroupHonorType.Companion.STRONG_NEWBIE import net.mamoe.mirai.data.GroupHonorType.Companion.TALKATIVE import net.mamoe.mirai.data.GroupHonorType.Companion.WHIRLWIND import net.mamoe.mirai.event.events.MemberHonorChangeEvent import kotlin.jvm.JvmInline import kotlin.jvm.JvmStatic /** * 群荣誉信息 * @property TALKATIVE 龙王 * @property PERFORMER 群聊之火 * @property LEGEND 群聊炽焰 * @property STRONG_NEWBIE 冒尖小春笋 * @property EMOTION 快乐源泉 * @property BRONZE 学术新星 * @property SILVER 顶尖学霸 * @property GOLDEN 至尊学神 * @property WHIRLWIND 一笔当先 * @property RICHER 壕礼皇冠 * @property RED_PACKET 善财福禄寿 * @see GroupActive * @see MemberActive * @see MemberHonorChangeEvent */ @JvmInline @Serializable public value class GroupHonorType public constructor(public val id: Int) { // public for potential usages from Java. little compatibility burden. public companion object { // ID fields /** * 龙王 * @see TALKATIVE */ public const val TALKATIVE_ID: Int = 1 /** * 群聊之火 * @see PERFORMER */ public const val PERFORMER_ID: Int = 2 /** * 群聊炽焰 * @see LEGEND */ public const val LEGEND_ID: Int = 3 /** * 冒尖小春笋 * @see STRONG_NEWBIE */ public const val STRONG_NEWBIE_ID: Int = 4 /** * 快乐源泉 * @see EMOTION */ public const val EMOTION_ID: Int = 5 /** * 学术新星 * @see BRONZE */ public const val BRONZE_ID: Int = 6 /** * 顶尖学霸 * @see SILVER */ public const val SILVER_ID: Int = 7 /** * 至尊学神 * @see GOLDEN */ public const val GOLDEN_ID: Int = 8 /** * 一笔当先 * @see WHIRLWIND */ public const val WHIRLWIND_ID: Int = 9 /** * 壕礼皇冠 * @see RICHER */ public const val RICHER_ID: Int = 10 /** * 善财福禄寿 * @see RED_PACKET */ public const val RED_PACKET_ID: Int = 11 // Inline class 'instance's, invisible from Java. /** * 龙王 */ @JvmStatic public val TALKATIVE: GroupHonorType = GroupHonorType(TALKATIVE_ID) /** * 群聊之火 */ @JvmStatic public val PERFORMER: GroupHonorType = GroupHonorType(PERFORMER_ID) /** * 群聊炽焰 */ @JvmStatic public val LEGEND: GroupHonorType = GroupHonorType(LEGEND_ID) /** * 冒尖小春笋 */ @JvmStatic public val STRONG_NEWBIE: GroupHonorType = GroupHonorType(STRONG_NEWBIE_ID) /** * 快乐源泉 */ @JvmStatic public val EMOTION: GroupHonorType = GroupHonorType(EMOTION_ID) /** * 学术新星 */ @JvmStatic public val BRONZE: GroupHonorType = GroupHonorType(BRONZE_ID) /** * 顶尖学霸 */ @JvmStatic public val SILVER: GroupHonorType = GroupHonorType(SILVER_ID) /** * 至尊学神 */ @JvmStatic public val GOLDEN: GroupHonorType = GroupHonorType(GOLDEN_ID) /** * 一笔当先 */ @JvmStatic public val WHIRLWIND: GroupHonorType = GroupHonorType(WHIRLWIND_ID) /** * 壕礼皇冠 */ @JvmStatic public val RICHER: GroupHonorType = GroupHonorType(RICHER_ID) /** * 善财福禄寿 */ @JvmStatic public val RED_PACKET: GroupHonorType = GroupHonorType(RED_PACKET_ID) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/GroupInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.data import net.mamoe.mirai.LowLevelApi /** * 群资料. */ @LowLevelApi public interface GroupInfo { /** * Uin */ public val uin: Long /** * 群号码 */ // 由 uin 计算得到 public val groupCode: Long /** * 名称 */ public val name: String // 不一定能获取到 /** * 群主 */ public val owner: Long // 不一定能获取到 /** * 入群公告 */ public val memo: String // 不一定能获取到 /** * 允许群员邀请其他人加入群 */ public val allowMemberInvite: Boolean /** * 允许匿名聊天 */ public val allowAnonymousChat: Boolean /** * 自动审批加群请求 */ public val autoApprove: Boolean /** * 坦白说开启状态 */ public val confessTalk: Boolean /** * 全员禁言 */ public val muteAll: Boolean /** * 机器人被禁言还剩时间, 秒. */ public val botMuteTimestamp: Int /** * 荣誉是否显示 * @since 2.13 */ public val isHonorVisible: Boolean /** * 头衔是否显示 * @since 2.13 */ public val isTitleVisible: Boolean /** * 活跃度是否显示 * @since 2.13 */ public val isTemperatureVisible: Boolean /** * 等级头衔 * @since 2.13 */ public val rankTitles: Map<Int, String> /** * 活跃度头衔 * @since 2.13 */ public val temperatureTitles: Map<Int, String> } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/MemberInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.data import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.contact.MemberPermission @LowLevelApi public interface MemberInfo : UserInfo { public val nameCard: String public val permission: MemberPermission public val specialTitle: String public val muteTimestamp: Int public val anonymousId: String? get() = null /** * 入群时间 秒 */ public val joinTimestamp: Int /** * 上次发言时间 秒 */ public val lastSpeakTimestamp: Int /** * 是否为官方机器人 */ public val isOfficialBot: Boolean /** * 活跃等级 * @see point * @since 2.13 */ public val rank: Int /** * 活跃积分 * @see rank * @since 2.13 */ public val point: Int /** * 群荣誉标志 * @since 2.13 */ public val honors: Set<GroupHonorType> /** * 活跃度 * @since 2.13 */ public val temperature: Int } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/OnlineStatus.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.data /** * 在线状态 */ public enum class OnlineStatus(public val id: Int) { /** * 我在线上 */ ONLINE(11), /** * 离线 */ OFFLINE(21), /** * 离开 */ AWAY(31), /** * 隐身 */ INVISIBLE(41), /** * 忙碌 */ BUSY(50), /** * Q 我吧 */ Q_ME(60), /** * 请勿打扰 */ DND(70), /** * 离线但接收消息 */ RECEIVE_OFFLINE_MESSAGE(95), /** * 解析错误等 */ UNKNOWN(-1); public companion object { public fun ofId(id: Int): OnlineStatus = values().first { it.id == id } public fun ofIdOrNull(id: Int): OnlineStatus? = values().firstOrNull { it.id == id } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/Profile.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.data /* /** * 个人资料 */ @MiraiExperimentalAPI @Suppress("PropertyName") data class Profile( val qq: Long, val nickname: String, val englishName: String?, val chineseName: String?, val qAge: Int?, // q 龄 val zipCode: String?, val phone: String?, val gender: Gender, val birthday: GMTDate?, val personalStatement: String?,// 个人说明 val school: String?, val homepage: String?, val email: String?, val company: String? ) { override fun toString(): String = "Profile(qq=$qq, " + "nickname=$nickname, " + "gender=$gender, " + (englishName?.let { "englishName=$englishName, " } ?: "") + (chineseName?.let { "chineseName=$chineseName, " } ?: "") + (qAge?.toString()?.let { "qAge=$qAge, " } ?: "") + (zipCode?.let { "zipCode=$zipCode, " } ?: "") + (phone?.let { "phone=$phone, " } ?: "") + (birthday?.toString()?.let { "birthday=$birthday, " } ?: "") + (personalStatement?.let { "personalStatement=$personalStatement, " } ?: "") + (school?.let { "school=$school, " } ?: "") + (homepage?.let { "homepage=$homepage, " } ?: "") + (email?.let { "email=$email, " } ?: "") + (company?.let { "company=$company," } ?: "") + ")"// 最终会是 ", )", 但这并不影响什么. } /** * 性别 */ @MiraiExperimentalAPI enum class Gender(val value: Byte) { SECRET(0), MALE(1), FEMALE(2) }*/ ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/RequestEventData.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.Mirai import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.utils.MiraiExperimentalApi import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic @Serializable @SerialName("RequestEventData") public sealed class RequestEventData { public abstract val eventId: Long @JvmBlockingBridge public abstract suspend fun accept(bot: Bot) @JvmBlockingBridge public abstract suspend fun reject(bot: Bot) @Serializable @SerialName("NewFriendRequest") public class NewFriendRequest @MiraiExperimentalApi public constructor( override val eventId: Long, public val requester: Long, public val requesterNick: String, public val fromGroupId: Long, public val message: String, ) : RequestEventData() { @OptIn(LowLevelApi::class) override suspend fun accept(bot: Bot) { Mirai.solveNewFriendRequestEvent( bot, eventId = eventId, fromId = requester, fromNick = requesterNick, accept = true, blackList = false, ) } override suspend fun reject(bot: Bot) { reject(bot, false) } @JvmBlockingBridge @OptIn(LowLevelApi::class) public suspend fun reject(bot: Bot, blackList: Boolean) { Mirai.solveNewFriendRequestEvent( bot, eventId = eventId, fromId = requester, fromNick = requesterNick, accept = false, blackList = blackList, ) } override fun toString(): String { return "NewFriendRequest(eventId=$eventId, fromGroupId=$fromGroupId, message=$message, requester=$requester, requesterNick=$requesterNick)" } } @Serializable @SerialName("BotInvitedJoinGroupRequest") public class BotInvitedJoinGroupRequest @MiraiExperimentalApi public constructor( override val eventId: Long, public val invitor: Long, public val invitorNick: String, public val groupId: Long, public val groupName: String, ) : RequestEventData() { override suspend fun accept(bot: Bot) { @OptIn(LowLevelApi::class) Mirai.solveBotInvitedJoinGroupRequestEvent( bot, eventId = eventId, invitorId = invitor, groupId = groupId, accept = true, ) } override suspend fun reject(bot: Bot) { @OptIn(LowLevelApi::class) Mirai.solveBotInvitedJoinGroupRequestEvent( bot, eventId = eventId, invitorId = invitor, groupId = groupId, accept = false, ) } override fun toString(): String { return "BotInvitedJoinGroupRequest(eventId=$eventId, invitor=$invitor, invitorNick='$invitorNick', groupId=$groupId, groupName='$groupName')" } } @Serializable @SerialName("MemberJoinRequest") public class MemberJoinRequest @MiraiExperimentalApi public constructor( override val eventId: Long, public val requester: Long, public val requesterNick: String, public val groupId: Long, public val groupName: String, public val invitor: Long = 0L, // 如果不为 0 则为邀请入群 public val message: String, ) : RequestEventData() { override suspend fun accept(bot: Bot) { @OptIn(LowLevelApi::class) Mirai.solveMemberJoinRequestEvent( bot, eventId = eventId, fromId = requester, fromNick = requesterNick, groupId = groupId, accept = true, blackList = false, message = "", ) } override suspend fun reject(bot: Bot) { reject(bot, false) } @JvmBlockingBridge public suspend fun reject(bot: Bot, message: String) { reject(bot, false, message) } @JvmBlockingBridge @JvmOverloads public suspend fun reject(bot: Bot, blackList: Boolean, message: String = "") { @OptIn(LowLevelApi::class) Mirai.solveMemberJoinRequestEvent( bot, eventId = eventId, fromId = requester, fromNick = requesterNick, groupId = groupId, accept = false, blackList = blackList, message = message, ) } override fun toString(): String { return "MemberJoinRequest(eventId=$eventId, groupId=$groupId, groupName=$groupName, invitor=$invitor, message=$message, requester=$requester, requesterNick=$requesterNick)" } } public companion object Factory { @JvmStatic @JvmName("from") public fun NewFriendRequestEvent.toRequestEventData(): NewFriendRequest { @OptIn(MiraiExperimentalApi::class) return NewFriendRequest( eventId = eventId, message = message, requester = fromId, requesterNick = fromNick, fromGroupId = fromGroupId, ) } @JvmStatic @JvmName("from") public fun BotInvitedJoinGroupRequestEvent.toRequestEventData(): BotInvitedJoinGroupRequest { @OptIn(MiraiExperimentalApi::class) return BotInvitedJoinGroupRequest( eventId = eventId, invitor = invitorId, invitorNick = invitorNick, groupId = groupId, groupName = groupName, ) } @JvmStatic @JvmName("from") public fun MemberJoinRequestEvent.toRequestEventData(): MemberJoinRequest { @OptIn(MiraiExperimentalApi::class) return MemberJoinRequest( eventId = eventId, requester = fromId, requesterNick = fromNick, groupId = groupId, groupName = groupName, invitor = invitorId ?: 0L, message = message, ) } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/StrangerInfo.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.data import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.utils.MiraiExperimentalApi @OptIn(MiraiExperimentalApi::class) @LowLevelApi public interface StrangerInfo : UserInfo { /** * 陌生人的QQ号码 */ public override val uin: Long /** * 陌生人的昵称 * */ public override val nick: String /** * 陌生人来源的群 * * 当不是来源于群时为0 */ public val fromGroup: Long } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/UserInfo.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.data import net.mamoe.mirai.LowLevelApi @LowLevelApi public interface UserInfo { public val uin: Long public val nick: String public val remark: String } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/data/UserProfile.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.data import net.mamoe.mirai.contact.User import net.mamoe.mirai.utils.NotStableForInheritance /** * 用户详细资料 * * @see User.queryProfile * @suppress 使用这个接口是稳定的,但继承不稳定。将来可能会有新的属性添加。 * @since 2.1 */ @NotStableForInheritance public interface UserProfile { public val nickname: String public val email: String public val age: Int public val qLevel: Int public val sex: Sex /** * 好友分组 ID, 在非好友情况下或者位于默认分组情况下为 `0` * * @since 2.13 */ public val friendGroupId: Int /** * 个性签名 */ public val sign: String public enum class Sex { MALE, FEMALE, /** 保密 */ UNKNOWN; } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/Event.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.event import kotlinx.coroutines.sync.Mutex import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmField import kotlin.jvm.Volatile /** * 表示一个事件. * * 实现时应继承 [AbstractEvent] 而不要直接实现 [Event]. 否则将无法广播. * * ## 广播事件 * * 使用 [Event.broadcast] 或 [IMirai.broadcastEvent]. * * Kotlin: * ``` * val event: Event = ... * event.broadcast() * ``` * * Java: * ``` * Event event = ...; * Mirai.getInstance().broadcastEvent(event); * ``` * * ## 监听事件 * * 参阅 [EventChannel]. * * @see CancellableEvent 可被取消的事件 */ public interface Event { /** * 事件是否已被拦截. * * 所有事件都可以被拦截, 拦截后低优先级的监听器将不会处理到这个事件. * * @see intercept 拦截事件 */ public val isIntercepted: Boolean /** * 拦截这个事件 * * 当事件被 [拦截][Event.intercept] 后, 优先级较低 (靠右) 的监听器将不会被调用. * * 优先级为 [EventPriority.MONITOR] 的监听器不应该调用这个函数. * * @see EventPriority 查看优先级相关信息 */ public fun intercept() } /** * 所有实现了 [Event] 接口的类都应该继承的父类. * * 在使用事件时应使用类型 [Event]. 在实现自定义事件时应继承 [AbstractEvent]. */ public abstract class AbstractEvent : Event { /** 限制一个事件实例不能并行广播. (适用于 object 广播的情况) */ @JvmField @MiraiInternalApi public val broadCastLock: Mutex = Mutex() @Suppress("PropertyName") @JvmField @Volatile @MiraiInternalApi public var _intercepted: Boolean = false @Volatile private var _cancelled = false // 实现 Event /** * @see Event.isIntercepted */ public override val isIntercepted: Boolean get() { @OptIn(MiraiInternalApi::class) return _intercepted } /** * @see Event.intercept */ public override fun intercept() { @OptIn(MiraiInternalApi::class) _intercepted = true } // 实现 CancellableEvent /** * @see CancellableEvent.isCancelled */ public val isCancelled: Boolean get() = _cancelled /** * @see CancellableEvent.cancel * @throws IllegalStateException 当事件未实现接口 [CancellableEvent] 时抛出 */ public fun cancel() { check(this is CancellableEvent) { "Event $this is not cancellable" } _cancelled = true } } /** * 可被取消的事件 */ public interface CancellableEvent : Event { /** * 事件是否已被取消. * * 事件需实现 [CancellableEvent] 接口才可以被取消, * 否则此属性固定返回 false. */ public val isCancelled: Boolean /** * 取消这个事件. * 事件需实现 [CancellableEvent] 接口才可以被取消 */ public fun cancel() } /** * 广播一个事件的唯一途径. * * 当事件被实现为 Kotlin `object` 时, 同一时刻只能有一个 [广播][broadcast] 存在. * 较晚执行的 [广播][broadcast] 将会挂起协程并等待之前的广播任务结束. * * ## 异常处理 * * 作为广播方, 本函数不会抛出监听方产生的异常. * * [EventChannel.filter] 和 [Listener.onEvent] 时产生的异常只会由监听方处理. */ @JvmBlockingBridge @Suppress("TOP_LEVEL_FUNCTIONS_NOT_SUPPORTED") // compiler bug public suspend fun <E : Event> E.broadcast(): E { Mirai.broadcastEvent(this) return this } /** * 可控制是否需要广播这个事件 */ public interface BroadcastControllable : Event { /** * 返回 `false` 时将不会广播这个事件. */ public val shouldBroadcast: Boolean get() = true } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/EventChannel.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("EventChannelKt") package net.mamoe.mirai.event import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ClosedSendChannelException import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.sync.Mutex import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai import net.mamoe.mirai.event.ConcurrencyKind.CONCURRENT import net.mamoe.mirai.event.ConcurrencyKind.LOCKED import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.internal.event.JvmMethodListenersInternal import net.mamoe.mirai.utils.* import java.util.function.Consumer import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass /** * 事件通道. * * 事件通道是监听事件的入口, 但不负责广播事件. 要广播事件, 使用 [Event.broadcast] 或 [IMirai.broadcastEvent]. * * ## 获取事件通道 * * [EventChannel] 不可自行构造, 只能通过 [GlobalEventChannel], [BotEvent], 或基于一个通道的过滤等操作获得. * * ### 全局事件通道 * * [GlobalEventChannel] 是单例对象, 表示全局事件通道, 可以获取到在其中广播的所有事件. * * ### [BotEvent] 事件通道 * * 若只需要监听某个 [Bot] 的事件, 可通过 [Bot.eventChannel] 获取到这样的 [EventChannel]. * * ## 通道操作 * * ### 对通道的操作 * - 过滤通道: 通过 [EventChannel.filter]. 例如 `filter { it is BotEvent }` 得到一个只能监听到 [BotEvent] 的事件通道. * - 转换为 Kotlin 协程 [Channel]: [EventChannel.forwardToChannel] * - 添加 [CoroutineContext]: [context], [parentJob], [parentScope], [exceptionHandler] * * ### 创建事件监听 * - [EventChannel.subscribe] 创建带条件的一个事件监听器. * - [EventChannel.subscribeAlways] 创建一个总是监听事件的事件监听器. * - [EventChannel.subscribeOnce] 创建一个只监听单次的事件监听器. * * ### 监听器生命周期 * * 阅读 [EventChannel.subscribe] 以获取监听器生命周期相关信息. * * ## 与 kotlinx-coroutines 交互 * * mirai [EventChannel] 设计比 kotlinx-coroutines 的 [Flow] 稳定版更早. * [EventChannel] 的功能与 [Flow] 类似, 不过 [EventChannel] 在 [subscribe] (类似 [Flow.collect]) 时有优先级判定, 也允许[拦截][Event.intercept]. * * ### 通过 [Flow] 接收事件 * * 使用 [EventChannel.asFlow] 获得 [Flow], 然后可使用 [Flow.collect] 等操作. * * ### 转发事件到 [SendChannel] * * 使用 [EventChannel.forwardToChannel] 可将事件转发到指定 [SendChannel]. */ @NotStableForInheritance // since 2.12, before it was `final class`. public abstract class EventChannel<out BaseEvent : Event> @MiraiInternalApi public constructor( public val baseEventClass: KClass<out BaseEvent>, /** * 此事件通道的默认 [CoroutineScope.coroutineContext]. 将会被添加给所有注册的事件监听器. */ public val defaultCoroutineContext: CoroutineContext, ) { /** * 创建事件监听并将监听结果转发到 [channel]. 当 [Channel.send] 抛出 [ClosedSendChannelException] 时停止 [Listener] 监听和转发. * * 返回创建的会转发监听到的所有事件到 [channel] 的[事件监听器][Listener]. [停止][Listener.complete] 该监听器会停止转发, 不会影响目标 [channel]. * * 若 [Channel.send] 挂起, 则监听器也会挂起, 也就可能会导致事件广播过程挂起. * * 示例: * * ``` * val eventChannel: EventChannel<BotEvent> = ... * val channel = Channel<BotEvent>() // kotlinx.coroutines.channels.Channel * eventChannel.forwardToChannel(channel, priority = ...) * * // 其他地方 * val event: BotEvent = channel.receive() // 挂起并接收一个事件 * ``` * * @see subscribeAlways * @see Channel * @since 2.10 */ public fun forwardToChannel( channel: SendChannel<@UnsafeVariance BaseEvent>, coroutineContext: CoroutineContext = EmptyCoroutineContext, priority: EventPriority = EventPriority.MONITOR, ): Listener<@UnsafeVariance BaseEvent> { return subscribe(baseEventClass, coroutineContext, priority = priority) { try { channel.send(it) ListeningStatus.LISTENING } catch (_: ClosedSendChannelException) { ListeningStatus.STOPPED } } } /** * 通过 [Flow] 接收此通道内的所有事件. * * ``` * val eventChannel: EventChannel<BotEvent> = ... * val flow: Flow<BotEvent> = eventChannel.asFlow() * * flow.collect { // it * // * } * * flow.filterIsInstance<GroupMessageEvent>.collect { // it: GroupMessageEvent * // 处理事件 ... * } * * flow.filterIsInstance<FriendMessageEvent>.collect { // it: FriendMessageEvent * // 处理事件 ... * } * ``` * * 类似于 [SharedFlow], [EventChannel.asFlow] 返回的 [Flow] 永远都不会停止. 因此上述示例 [Flow.collect] 永远都不会正常 (以抛出异常之外的) 结束. * * 通过 [asFlow] 接收事件相当于通过 [subscribeAlways] 以 [EventPriority.MONITOR] 监听事件. * * **注意**: [context], [parentJob] 等控制 [EventChannel.defaultCoroutineContext] 的操作对 [asFlow] 无效. 因为 [asFlow] 并不创建协程. * * @see Flow * @since 2.12 */ public abstract fun asFlow(): Flow<BaseEvent> // region transforming operations /** * 添加一个过滤器. 过滤器将在收到任何事件之后, 传递给通过 [EventChannel.subscribe] 注册的监听器之前调用. * * 若 [filter] 返回 `true`, 该事件将会被传给监听器. 否则将会被忽略, **监听器继续监听**. * * ## 线性顺序 * 多个 [filter] 的处理是线性且有顺序的. 若一个 [filter] 已经返回了 `false` (代表忽略这个事件), 则会立即忽略, 而不会传递给后续过滤器. * * 示例: * ``` * GlobalEventChannel // GlobalEventChannel 会收到全局所有事件, 事件类型是 Event * .filterIsInstance<BotEvent>() // 过滤, 只接受 BotEvent * .filter { event: BotEvent -> * // 此时的 event 一定是 BotEvent * event.bot.id == 123456 // 再过滤 event 的 bot.id * } * .subscribeAlways { event: BotEvent -> * // 现在 event 是 BotEvent, 且 bot.id == 123456 * } * ``` * * ## 过滤器挂起 * [filter] 允许挂起协程. **过滤器的挂起将被认为是事件监听器的挂起**. * * 过滤器挂起是否会影响事件处理, * 取决于 [subscribe] 时的 [ConcurrencyKind] 和 [EventPriority]. * * ## 过滤器异常处理 * 若 [filter] 抛出异常, 将被包装为 [ExceptionInEventChannelFilterException] 并重新抛出. * * @see filterIsInstance 过滤指定类型的事件 */ @JvmSynthetic public fun filter(filter: suspend (event: BaseEvent) -> Boolean): EventChannel<BaseEvent> { return FilterEventChannel(this, filter) } /** * [EventChannel.filter] 的 Java 版本. * * 添加一个过滤器. 过滤器将在收到任何事件之后, 传递给通过 [EventChannel.subscribe] 注册的监听器之前调用. * * 若 [filter] 返回 `true`, 该事件将会被传给监听器. 否则将会被忽略, **监听器继续监听**. * * ## 线性顺序 * 多个 [filter] 的处理是线性且有顺序的. 若一个 [filter] 已经返回了 `false` (代表忽略这个事件), 则会立即忽略, 而不会传递给后续过滤器. * * 示例: * ``` * GlobalEventChannel // GlobalEventChannel 会收到全局所有事件, 事件类型是 Event * .filterIsInstance(BotEvent.class) // 过滤, 只接受 BotEvent * .filter(event -> * // 此时的 event 一定是 BotEvent * event.bot.id == 123456 // 再过滤 event 的 bot.id * ) * .subscribeAlways(event -> { * // 现在 event 是 BotEvent, 且 bot.id == 123456 * }) * ``` * * ## 过滤器阻塞 * [filter] 允许阻塞线程. **过滤器的阻塞将被认为是事件监听器的阻塞**. * * 过滤器阻塞是否会影响事件处理, * 取决于 [subscribe] 时的 [ConcurrencyKind] 和 [EventPriority]. * * ## 过滤器异常处理 * 若 [filter] 抛出异常, 将被包装为 [ExceptionInEventChannelFilterException] 并重新抛出. * * @see filterIsInstance 过滤指定类型的事件 * * @since 2.2 */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public fun filter(filter: (event: BaseEvent) -> Boolean): EventChannel<BaseEvent> { return filter { runBIO { filter(it) } } } /** * 过滤事件的类型. 返回一个只包含 [E] 类型事件的 [EventChannel] * @see filter 获取更多信息 */ @JvmSynthetic public inline fun <reified E : Event> filterIsInstance(): EventChannel<E> = filterIsInstance(E::class) /** * 过滤事件的类型. 返回一个只包含 [E] 类型事件的 [EventChannel] * @see filter 获取更多信息 */ public fun <E : Event> filterIsInstance(kClass: KClass<out E>): EventChannel<E> { return filter { kClass.isInstance(it) }.cast() } /** * 过滤事件的类型. 返回一个只包含 [E] 类型事件的 [EventChannel] * @see filter 获取更多信息 */ public fun <E : Event> filterIsInstance(clazz: Class<out E>): EventChannel<E> = filterIsInstance(clazz.kotlin) /** * 创建一个新的 [EventChannel], 该 [EventChannel] 包含 [`this.coroutineContext`][defaultCoroutineContext] 和添加的 [coroutineContexts]. * [coroutineContexts] 会覆盖 [defaultCoroutineContext] 中的重复元素. * * 此操作不会修改 [`this.coroutineContext`][defaultCoroutineContext], 只会创建一个新的 [EventChannel]. */ public abstract fun context(vararg coroutineContexts: CoroutineContext): EventChannel<BaseEvent> /** * 创建一个新的 [EventChannel], 该 [EventChannel] 包含 [this.coroutineContext][defaultCoroutineContext] 和添加的 [coroutineExceptionHandler] * @see context */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public fun exceptionHandler(coroutineExceptionHandler: CoroutineExceptionHandler): EventChannel<BaseEvent> { return context(coroutineExceptionHandler) } /** * 创建一个新的 [EventChannel], 该 [EventChannel] 包含 [`this.coroutineContext`][defaultCoroutineContext] 和添加的 [coroutineExceptionHandler] * @see context */ public fun exceptionHandler(coroutineExceptionHandler: (exception: Throwable) -> Unit): EventChannel<BaseEvent> { return context(CoroutineExceptionHandler { _, throwable -> coroutineExceptionHandler(throwable) }) } /** * 创建一个新的 [EventChannel], 该 [EventChannel] 包含 [`this.coroutineContext`][defaultCoroutineContext] 和添加的 [coroutineExceptionHandler] * @see context * @since 2.12 */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public fun exceptionHandler(coroutineExceptionHandler: Consumer<Throwable>): EventChannel<BaseEvent> { return exceptionHandler { coroutineExceptionHandler.accept(it) } } /** * 将 [coroutineScope] 作为这个 [EventChannel] 的父作用域. * * 实际作用为创建一个新的 [EventChannel], * 该 [EventChannel] 包含 [`this.coroutineContext`][defaultCoroutineContext] 和添加的 [CoroutineScope.coroutineContext], * 并以 [CoroutineScope] 中 [Job] (如果有) [作为父 Job][parentJob] * * @see parentJob * @see context * * @see CoroutineScope.globalEventChannel `GlobalEventChannel.parentScope()` 的扩展 */ public fun parentScope(coroutineScope: CoroutineScope): EventChannel<BaseEvent> { return context(coroutineScope.coroutineContext) } /** * 指定协程父 [Job]. 之后在此 [EventChannel] 下创建的事件监听器都会成为 [job] 的子任务, 当 [job] 被取消时, 所有的事件监听器都会被取消. * * 注意: 监听器不会失败 ([Job.cancel]). 监听器处理过程的异常都会被捕获然后交由 [CoroutineExceptionHandler] 处理, 因此 [job] 不会因为子任务监听器的失败而被取消. * * @see parentScope * @see context */ public fun parentJob(job: Job): EventChannel<BaseEvent> { return context(job) } // endregion // region subscribe /** * 创建一个事件监听器, 监听事件通道中所有 [E] 及其子类事件. * * 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行. * * * ## 创建监听 * 调用本函数: * ``` * eventChannel.subscribe<E> { /* 会收到此通道中的所有是 E 的事件 */ } * ``` * * ## 生命周期 * * ### 通过协程作用域管理监听器 * 本函数将会创建一个 [Job], 成为 [parentJob] 中的子任务. 可创建一个 [CoroutineScope] 来管理所有的监听器: * ``` * val scope = CoroutineScope(SupervisorJob()) * * val scopedChannel = eventChannel.parentScope(scope) // 将协程作用域 scope 附加到这个 EventChannel * * scopedChannel.subscribeAlways<MemberJoinEvent> { /* ... */ } // 启动监听, 监听器协程会作为 scope 的子任务 * scopedChannel.subscribeAlways<MemberMuteEvent> { /* ... */ } // 启动监听, 监听器协程会作为 scope 的子任务 * * scope.cancel() // 停止了协程作用域, 也就取消了两个监听器 * ``` * * 这个函数返回 [Listener], 它是一个 [CompletableJob]. 它会成为 [parentJob] 或 [parentScope] 的一个 [子任务][Job] * * ### 停止监听 * 如果 [handler] 返回 [ListeningStatus.STOPPED] 监听器将被停止. * * 也可以通过 [subscribe] 返回值 [Listener] 的 [Listener.complete] * * ## 监听器调度 * 监听器会被创建一个协程任务, 语义上在 [parentScope] 下运行. * 通过 Kotlin [默认协程调度器][Dispatchers.Default] 在固定的全局共享线程池里执行, 除非有 [coroutineContext] 指定. * * 默认在 [handler] 中不能处理阻塞任务. 阻塞任务将会阻塞一个 Kotlin 全局协程调度线程并可能导致严重问题. * 请通过 `withContext(Dispatchers.IO) { }` 等方法执行阻塞工作. * * ## 异常处理 * * **监听过程抛出的异常是需要尽可能避免的, 因为这将产生不确定性.** * * 当参数 [handler] 处理事件抛出异常时, 只会从监听方协程上下文 ([CoroutineContext]) 寻找 [CoroutineExceptionHandler] 处理异常, 即如下顺序: * 1. 本函数参数 [coroutineContext] * 2. [EventChannel.defaultCoroutineContext] * 3. 若以上步骤无法获取 [CoroutineExceptionHandler], 则只会在日志记录异常. * 因此建议先指定 [CoroutineExceptionHandler] (可通过 [EventChannel.exceptionHandler]) 再监听事件, 或者在监听事件中捕获异常. * * 因此, 广播方 ([Event.broadcast]) 不会知晓监听方产生的异常, 其 [Event.broadcast] 过程也不会因监听方产生异常而提前结束. * * ***备注***: 在 2.11 以前, 发生上述异常时还会从广播方和有关 [Bot] 协程域获取 [CoroutineExceptionHandler]. 因此行为不稳定而在 2.11 变更为上述过程. * * 事件处理时抛出异常不会停止监听器. * * 建议在事件处理中 (即 [handler] 里) 处理异常, * 或在参数 [coroutineContext] 中添加 [CoroutineExceptionHandler], 或通过 [EventChannel.exceptionHandler]. * * ## 并发安全性 * 基于 [concurrency] 参数, 事件监听器可以被允许并行执行. * * - 若 [concurrency] 为 [ConcurrencyKind.CONCURRENT], [handler] 可能被并行调用, 需要保证并发安全. * - 若 [concurrency] 为 [ConcurrencyKind.LOCKED], [handler] 会被 [Mutex] 限制, 串行异步执行. * * ## 衍生监听方法 * * 这些方法仅 Kotlin 可用. * * - [syncFromEvent]: 挂起当前协程, 监听一个事件, 并尝试从这个事件中**获取**一个值 * - [nextEvent]: 挂起当前协程, 直到监听到特定类型事件的广播并通过过滤器, 返回这个事件实例. * * @param coroutineContext 在 [defaultCoroutineContext] 的基础上, 给事件监听协程的额外的 [CoroutineContext]. * @param concurrency 并发类型. 查看 [ConcurrencyKind] * @param priority 监听优先级,优先级越高越先执行 * @param handler 事件处理器. 在接收到事件时会调用这个处理器. 其返回值意义参考 [ListeningStatus]. 其异常处理参考上文 * * @return 监听器实例. 此监听器已经注册到指定事件上, 在事件广播时将会调用 [handler] * * * @see selectMessages 以 `when` 的语法 '选择' 即将到来的一条消息. * @see whileSelectMessages 以 `when` 的语法 '选择' 即将到来的所有消息, 直到不满足筛选结果. * * @see subscribeAlways 一直监听 * @see subscribeOnce 只监听一次 * * @see subscribeMessages 监听消息 DSL */ @JvmSynthetic public inline fun <reified E : Event> subscribe( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrency: ConcurrencyKind = LOCKED, priority: EventPriority = EventPriority.NORMAL, noinline handler: suspend E.(E) -> ListeningStatus, ): Listener<E> = subscribe(E::class, coroutineContext, concurrency, priority, handler) /** * 与 [subscribe] 的区别是接受 [eventClass] 参数, 而不使用 `reified` 泛型. 通常推荐使用具体化类型参数. * * @return 监听器实例. 此监听器已经注册到指定事件上, 在事件广播时将会调用 [handler] * @see subscribe */ @JvmSynthetic public fun <E : Event> subscribe( eventClass: KClass<out E>, coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrency: ConcurrencyKind = LOCKED, priority: EventPriority = EventPriority.NORMAL, handler: suspend E.(E) -> ListeningStatus, ): Listener<E> = subscribeInternal( eventClass, createListener0(coroutineContext, concurrency, priority) { it.handler(it); } ) /** * 创建一个事件监听器, 监听事件通道中所有 [E] 及其子类事件. * 每当 [事件广播][Event.broadcast] 时, [handler] 都会被执行. * * 可在任意时候通过 [Listener.complete] 来主动停止监听. * * @param concurrency 并发类型默认为 [CONCURRENT] * @param coroutineContext 在 [defaultCoroutineContext] 的基础上, 给事件监听协程的额外的 [CoroutineContext] * @param priority 处理优先级, 优先级高的先执行 * * @return 监听器实例. 此监听器已经注册到指定事件上, 在事件广播时将会调用 [handler] * * @see subscribe 获取更多说明 */ @JvmSynthetic public inline fun <reified E : Event> subscribeAlways( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrency: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.NORMAL, noinline handler: suspend E.(E) -> Unit, ): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority, handler) /** * @see subscribe * @see subscribeAlways */ @JvmSynthetic public fun <E : Event> subscribeAlways( eventClass: KClass<out E>, coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrency: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.NORMAL, handler: suspend E.(E) -> Unit, ): Listener<E> = subscribeInternal( eventClass, createListener0(coroutineContext, concurrency, priority) { it.handler(it); ListeningStatus.LISTENING } ) /** * 创建一个事件监听器, 监听事件通道中所有 [E] 及其子类事件, 只监听一次. * 当 [事件广播][Event.broadcast] 时, [handler] 会被执行. * * 可在任意时候通过 [Listener.complete] 来主动停止监听. * * @param coroutineContext 在 [defaultCoroutineContext] 的基础上, 给事件监听协程的额外的 [CoroutineContext] * @param priority 处理优先级, 优先级高的先执行 * * @see subscribe 获取更多说明 */ @JvmSynthetic public inline fun <reified E : Event> subscribeOnce( coroutineContext: CoroutineContext = EmptyCoroutineContext, priority: EventPriority = EventPriority.NORMAL, noinline handler: suspend E.(E) -> Unit, ): Listener<E> = subscribeOnce(E::class, coroutineContext, priority, handler) /** * @see subscribeOnce */ public fun <E : Event> subscribeOnce( eventClass: KClass<out E>, coroutineContext: CoroutineContext = EmptyCoroutineContext, priority: EventPriority = EventPriority.NORMAL, handler: suspend E.(E) -> Unit, ): Listener<E> = subscribeInternal( eventClass, createListener0(coroutineContext, ConcurrencyKind.LOCKED, priority) { it.handler(it); ListeningStatus.STOPPED } ) // endregion /** * 注册 [ListenerHost] 中的所有 [EventHandler] 标注的方法到这个 [EventChannel]. 查看 [EventHandler]. * * @param coroutineContext 在 [defaultCoroutineContext] 的基础上, 给事件监听协程的额外的 [CoroutineContext] * * @see subscribe * @see EventHandler * @see ListenerHost */ @JvmOverloads public fun registerListenerHost( host: ListenerHost, coroutineContext: CoroutineContext = EmptyCoroutineContext, ) { val jobOfListenerHost: Job? val coroutineContext0 = if (host is SimpleListenerHost) { val listenerCoroutineContext = host.coroutineContext val listenerJob = listenerCoroutineContext[Job] val rsp = listenerCoroutineContext.minusKey(Job) + coroutineContext + (listenerCoroutineContext[CoroutineExceptionHandler] ?: EmptyCoroutineContext) val registerCancelHook = when { listenerJob === null -> false // Registering cancellation hook is needless // if [Job] of [EventChannel] is same as [Job] of [SimpleListenerHost] (rsp[Job] ?: this.defaultCoroutineContext[Job]) === listenerJob -> false else -> true } jobOfListenerHost = if (registerCancelHook) { listenerCoroutineContext[Job] } else { null } rsp } else { jobOfListenerHost = null coroutineContext } for (method in host.javaClass.declaredMethods) { method.getAnnotation(EventHandler::class.java)?.let { val listener = JvmMethodListenersInternal.registerEventHandler(method, host, this, it, coroutineContext0) // For [SimpleListenerHost.cancelAll] jobOfListenerHost?.invokeOnCompletion { exception -> listener.cancel( when (exception) { is CancellationException -> exception is Throwable -> CancellationException(null, exception) else -> null } ) } } } } // region Java API /** * Java API. 查看 [subscribeAlways] 获取更多信息. * * ```java * eventChannel.subscribeAlways(GroupMessageEvent.class, (event) -> { }); * ``` * * @see subscribe * @see subscribeAlways */ @JvmOverloads @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public fun <E : Event> subscribeAlways( eventClass: Class<out E>, coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrency: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.NORMAL, handler: Consumer<E>, ): Listener<E> = subscribeInternal( eventClass.kotlin, createListener0(coroutineContext, concurrency, priority) { event -> runInterruptible(Dispatchers.IO) { handler.accept(event) } ListeningStatus.LISTENING } ) /** * Java API. 查看 [subscribe] 获取更多信息. * * ```java * eventChannel.subscribe(GroupMessageEvent.class, (event) -> { * return ListeningStatus.LISTENING; * }); * ``` * * @see subscribe */ @JvmOverloads @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public fun <E : Event> subscribe( eventClass: Class<out E>, coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrency: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.NORMAL, handler: java.util.function.Function<E, ListeningStatus>, ): Listener<E> = subscribeInternal( eventClass.kotlin, createListener0(coroutineContext, concurrency, priority) { event -> runInterruptible(Dispatchers.IO) { handler.apply(event) } } ) /** * Java API. 查看 [subscribeOnce] 获取更多信息. * * ```java * eventChannel.subscribeOnce(GroupMessageEvent.class, (event) -> { }); * ``` * * @see subscribe * @see subscribeOnce */ @JvmOverloads @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public fun <E : Event> subscribeOnce( eventClass: Class<out E>, coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrency: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.NORMAL, handler: Consumer<E>, ): Listener<E> = subscribeInternal( eventClass.kotlin, createListener0(coroutineContext, concurrency, priority) { event -> runInterruptible(Dispatchers.IO) { handler.accept(event) } ListeningStatus.STOPPED } ) // endregion // region deprecated /** * 创建事件监听并将监听结果发送在 [Channel]. 将返回值 [Channel] [关闭][Channel.close] 时将会同时关闭事件监听. * * ## 已弃用 * * 请使用 [forwardToChannel] 替代. * * @param capacity Channel 容量. 详见 [Channel] 构造. * * @see subscribeAlways * @see Channel */ @Deprecated( "Please use forwardToChannel instead.", replaceWith = ReplaceWith( "Channel<BaseEvent>(capacity).apply { forwardToChannel(this, coroutineContext, priority) }", "kotlinx.coroutines.channels.Channel" ), level = DeprecationLevel.ERROR, ) @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.14") @MiraiExperimentalApi public fun asChannel( capacity: Int = Channel.RENDEZVOUS, coroutineContext: CoroutineContext = EmptyCoroutineContext, @Suppress("UNUSED_PARAMETER") concurrency: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.NORMAL, ): Channel<out BaseEvent> = Channel<BaseEvent>(capacity).apply { forwardToChannel(this, coroutineContext, priority) } // endregion // region impl // protected, to hide from users @MiraiInternalApi protected abstract fun <E : Event> registerListener(eventClass: KClass<out E>, listener: Listener<E>) // to overcome visibility issue @OptIn(MiraiInternalApi::class) internal fun <E : Event> registerListener0(eventClass: KClass<out E>, listener: Listener<E>) { return registerListener(eventClass, listener) } @OptIn(MiraiInternalApi::class) private fun <L : Listener<E>, E : Event> subscribeInternal(eventClass: KClass<out E>, listener: L): L { registerListener(eventClass, listener) return listener } /** * Creates [Listener] instance using the [listenerBlock] action. */ // @Contract("_ -> new") // always creates new instance @MiraiInternalApi protected abstract fun <E : Event> createListener( coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority, listenerBlock: suspend (E) -> ListeningStatus, ): Listener<E> // to overcome visibility issue @OptIn(MiraiInternalApi::class) internal fun <E : Event> createListener0( coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority, listenerBlock: suspend (E) -> ListeningStatus, ): Listener<E> = createListener(coroutineContext, concurrencyKind, priority, listenerBlock) // endregion } // used by mirai-core @OptIn(MiraiInternalApi::class) internal open class FilterEventChannel<BaseEvent : Event>( private val delegate: EventChannel<BaseEvent>, private val filter: suspend (event: BaseEvent) -> Boolean, ) : EventChannel<BaseEvent>(delegate.baseEventClass, delegate.defaultCoroutineContext) { private fun <E : Event> intercept(block: suspend (E) -> ListeningStatus): suspend (E) -> ListeningStatus { return { ev -> val filterResult = try { @Suppress("UNCHECKED_CAST") baseEventClass.isInstance(ev) && filter(ev as BaseEvent) } catch (e: Throwable) { if (e is ExceptionInEventChannelFilterException) throw e // wrapped by another filter throw ExceptionInEventChannelFilterException(ev, this, cause = e) } if (filterResult) block.invoke(ev) else ListeningStatus.LISTENING } } override fun asFlow(): Flow<BaseEvent> = delegate.asFlow().filter(filter) override fun <E : Event> registerListener(eventClass: KClass<out E>, listener: Listener<E>) { delegate.registerListener0(eventClass, listener) } override fun <E : Event> createListener( coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority, listenerBlock: suspend (E) -> ListeningStatus ): Listener<E> = delegate.createListener0(coroutineContext, concurrencyKind, priority, intercept(listenerBlock)) override fun context(vararg coroutineContexts: CoroutineContext): EventChannel<BaseEvent> { return delegate.context(*coroutineContexts) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/EventChannelKotlinExtensions.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @file:JvmName("EventChannelKotlinExtensions") package net.mamoe.mirai.event import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 支持 Kotlin 带接收者的挂起函数的函数引用的监听方式. * * ``` * suspend fun GroupMessageEvent.onMessage(event: GroupMessageEvent): ListeningStatus { * return ListeningStatus.LISTENING * } * * eventChannel.subscribe(GroupMessageEvent::onMessage) * ``` * @see EventChannel.subscribe */ @JvmSynthetic public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribe( crossinline handler: suspend E.(E) -> ListeningStatus, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribe(E::class, coroutineContext, concurrency, priority) { handler(this) } /** * 支持 Kotlin 挂起函数的函数引用的监听方式. * * ``` * suspend fun onMessage(event: GroupMessageEvent): ListeningStatus { * return ListeningStatus.LISTENING * } * * eventChannel.subscribe(::onMessage) * ``` * @see EventChannel.subscribe */ @JvmSynthetic @JvmName("subscribe1") public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribe( crossinline handler: suspend (E) -> ListeningStatus, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribe(E::class, coroutineContext, concurrency, priority) { handler(this) } /** * 支持 Kotlin 带接收者的函数的函数引用的监听方式. * * ``` * fun GroupMessageEvent.onMessage(event: GroupMessageEvent): ListeningStatus { * return ListeningStatus.LISTENING * } * * eventChannel.subscribe(GroupMessageEvent::onMessage) * ``` * @see EventChannel.subscribe */ @JvmSynthetic public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribe( crossinline handler: E.(E) -> ListeningStatus, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribe(E::class, coroutineContext, concurrency, priority) { handler(this) } /** * 支持 Kotlin 带接收者的挂起函数的函数引用的监听方式. * * ``` * fun onMessage(event: GroupMessageEvent): ListeningStatus { * return ListeningStatus.LISTENING * } * * eventChannel.subscribe(::onMessage) * ``` * @see EventChannel.subscribe */ @JvmSynthetic public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribe( crossinline handler: (E) -> ListeningStatus, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribe(E::class, coroutineContext, concurrency, priority) { handler(this) } /** * 支持 Kotlin 挂起函数的函数引用的监听方式. * ``` * suspend fun onMessage(event: GroupMessageEvent) { * * } * eventChannel.subscribeAlways(::onMessage) * ``` * @see EventChannel.subscribeAlways */ @JvmName("subscribeAlways1") @JvmSynthetic public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribeAlways( crossinline handler: suspend (E) -> Unit, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority) { handler(this) } /** * 支持 Kotlin 带接收者的挂起函数的函数引用的监听方式. * ``` * suspend fun GroupMessageEvent.onMessage(event: GroupMessageEvent) { * * } * eventChannel.subscribeAlways(GroupMessageEvent::onMessage) * ``` * @see EventChannel.subscribeAlways */ @JvmSynthetic public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribeAlways( crossinline handler: suspend E.(E) -> Unit, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority) { handler(this) } /** * 支持 Kotlin 带接收者的函数的函数引用的监听方式. * ``` * fun GroupMessageEvent.onMessage(event: GroupMessageEvent) { * * } * eventChannel.subscribeAlways(GroupMessageEvent::onMessage) * ``` * @see EventChannel.subscribeAlways */ @JvmSynthetic public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribeAlways( crossinline handler: E.(E) -> Unit, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority) { handler(this) } /** * 支持 Kotlin 带接收者的挂起函数的函数引用的监听方式. * ``` * fun onMessage(event: GroupMessageEvent) { * * } * eventChannel.subscribeAlways(::onMessage) * ``` * @see EventChannel.subscribeAlways */ @JvmSynthetic public inline fun <BaseEvent : Event, reified E : Event> EventChannel<BaseEvent>.subscribeAlways( crossinline handler: (E) -> Unit, priority: EventPriority = EventPriority.NORMAL, concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT, coroutineContext: CoroutineContext = EmptyCoroutineContext ): Listener<E> = subscribeAlways(E::class, coroutineContext, concurrency, priority) { handler(this) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/ExceptionInEventChannelFilterException.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.event /** * 包装 [EventChannel.filter] 的 `filter` lambda 抛出的异常并重新抛出. * * @see EventChannel.filter */ public class ExceptionInEventChannelFilterException( /** * 当时正在处理的事件 */ public val event: Event, public val eventChannel: EventChannel<*>, override val message: String = "Exception in EventHandler", /** * 原异常 */ override val cause: Throwable ) : IllegalStateException() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/Extensions.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.event import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlin.coroutines.resume import kotlin.reflect.KClass /** * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * * 本函数是 [EventChannel.subscribe] 的衍生工具函数, 内部会调用 [EventChannel.subscribe]. * * ## 挂起可取消 * * 本函数的挂起过程可以被[取消][CancellableContinuation.cancel]. 这意味着若在 [CoroutineScope.launch] 中使用本函数, 则 [launch] 启动的 [Job] 可以通过 [Job.cancel] 取消 (停止), 届时本函数会抛出 [CancellationException]. * * ## 异常处理 * * [filter] 抛出的异常属于监听方异常, 将会由 [nextEvent] 原样重新抛出. * * ## 使用 [Flow] 的替代方法 * * 在 Kotlin 可使用 [EventChannel.asFlow] 配合 [Flow.filter] 和 [Flow.first] 实现与 [nextEvent] 相似的功能 (注意, Flow 方法将会使用 [EventPriority.MONITOR] 优先级). * * 示例: * * ``` * val event: GroupMessageEvent = GlobalEventChannel.asFlow().filterIsInstance<GroupMessageEvent>().filter { it.sender.id == 123456 }.first() * // 上下两行代码等价 * val event: GroupMessageEvent = GlobalEventChannel.filterIsInstance<GroupMessageEvent>().nextEvent(EventPriority.MONITOR) { it.sender.id == 123456 } * ``` * * 由于 [Flow] 拥有更多操作 (如 [Flow.firstOrNull]), 在不需要指定[事件优先级][EventPriority]时使用 [Flow] 拥有更高自由度. * * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * * @since 2.10 */ public suspend inline fun <reified E : Event> EventChannel<*>.nextEvent( priority: EventPriority = EventPriority.NORMAL, noinline filter: suspend (E) -> Boolean = { true } ): E = nextEvent(priority, false, filter) /** * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * * 本函数是 [EventChannel.subscribe] 的衍生工具函数, 内部会调用 [EventChannel.subscribe]. * * ## 挂起可取消 * * 本函数的挂起过程可以被[取消][CancellableContinuation.cancel]. 这意味着若在 [CoroutineScope.launch] 中使用本函数, 则 [launch] 启动的 [Job] 可以通过 [Job.cancel] 取消 (停止), 届时本函数会抛出 [CancellationException]. * * ## 异常处理 * * [filter] 抛出的异常属于监听方异常, 将会由 [nextEvent] 原样重新抛出. * * ## 使用 [Flow] 的替代方法 * * 在 Kotlin 可使用 [EventChannel.asFlow] 配合 [Flow.filter] 和 [Flow.first] 实现与 [nextEvent] 相似的功能 (注意, Flow 方法将会使用 [EventPriority.MONITOR] 优先级). * * 示例: * * ``` * val event: GroupMessageEvent = GlobalEventChannel.asFlow().filterIsInstance<GroupMessageEvent>().filter { it.sender.id == 123456 }.first() * // 上下两行代码等价 * val event: GroupMessageEvent = GlobalEventChannel.filterIsInstance<GroupMessageEvent>().nextEvent(EventPriority.MONITOR) { it.sender.id == 123456 } * ``` * * 由于 [Flow] 拥有更多操作 (如 [Flow.firstOrNull]), 在不需要指定[事件优先级][EventPriority]时使用 [Flow] 拥有更高自由度. * * @param intercept 是否拦截, 传入 `true` 时表示拦截此事件不让接下来的监听器处理, 传入 `false` 时表示让接下来的监听器处理 * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * * @since 2.13 */ public suspend inline fun <reified E : Event> EventChannel<*>.nextEvent( priority: EventPriority = EventPriority.NORMAL, intercept: Boolean = false, noinline filter: suspend (E) -> Boolean = { true } ): E = coroutineScope { suspendCancellableCoroutine { cont -> var listener: Listener<E>? = null listener = this@nextEvent.parentScope(this@coroutineScope).subscribe(E::class, priority = priority) { event -> val result = kotlin.runCatching { if (!filter(event)) return@subscribe ListeningStatus.LISTENING event } try { cont.resumeWith(result.apply { onSuccess { if (intercept) intercept() } }) } finally { listener?.complete() // ensure completed on exceptions } return@subscribe ListeningStatus.STOPPED } cont.invokeOnCancellation { kotlin.runCatching { listener.cancel("nextEvent outer scope cancelled", it) } } } } /** * 挂起当前协程, 监听事件 [E], 并尝试从这个事件中**获取**一个值, 在超时时抛出 [TimeoutCancellationException] * * 本函数是 [EventChannel.subscribe] 的衍生工具函数, 内部会调用 [EventChannel.subscribe]. * * ## 挂起可取消 * * 本函数的挂起过程可以被[取消][CancellableContinuation.cancel]. 这意味着若在 [CoroutineScope.launch] 中使用本函数, 则 [launch] 启动的 [Job] 可以通过 [Job.cancel] 取消 (停止), 届时本函数会抛出 [CancellationException]. * * ## 异常处理 * * [filter] 抛出的异常属于监听方异常, 将会由 [nextEvent] 原样重新抛出. * * ## 使用 [Flow] 的替代方法 * * 在 Kotlin 可使用 [EventChannel.asFlow] 配合 [Flow.filter] 和 [Flow.first] 实现与 [nextEvent] 相似的功能 (注意, Flow 方法将会使用 [EventPriority.MONITOR] 优先级). * * 示例: * * ``` * val senderId: Long = GlobalEventChannel.asFlow() * .filterIsInstance<GroupMessageEvent>() * .filter { it.sender.id == 123456L } * .map { it.sender.id }.first() * * // 上下代码等价 * * val senderId: Long = GlobalEventChannel * .filterIsInstance<GroupMessageEvent>() * .syncFromEvent(EventPriority.MONITOR) { if (it.sender.id = 123456) it.sender.name else null } * ``` * * 由于 [Flow] 拥有更多操作且逻辑更清晰, 在不需要指定[事件优先级][EventPriority]时更推荐使用 [Flow]. * * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * * @see EventChannel.subscribe 普通地监听一个事件 * @see nextEvent 挂起当前协程, 并获取下一个事件实例 * * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 * * @since 2.10 */ public suspend inline fun <reified E : Event, R : Any> EventChannel<*>.syncFromEvent( priority: EventPriority = EventPriority.NORMAL, noinline mapper: suspend (E) -> R? ): R = coroutineScope { suspendCancellableCoroutine { cont -> var listener: Listener<E>? = null listener = this@syncFromEvent.parentScope(this).subscribe(E::class, priority = priority) { event -> val result = kotlin.runCatching { mapper(event) ?: return@subscribe ListeningStatus.LISTENING } try { cont.resumeWith(result) } finally { listener?.complete() // ensure completed on exceptions } return@subscribe ListeningStatus.STOPPED } cont.invokeOnCancellation { kotlin.runCatching { listener.cancel("syncFromEvent outer scope cancelled", it) } } } } // Can't move to JVM, filename clashes /** * @since 2.10 */ @PublishedApi @Deprecated("For binary compatibility") internal suspend fun <E : Event> EventChannel<Event>.nextEventImpl( eventClass: KClass<E>, coroutineScope: CoroutineScope, priority: EventPriority, filter: suspend (E) -> Boolean ): E = suspendCancellableCoroutine { cont -> var listener: Listener<E>? = null listener = parentScope(coroutineScope) .subscribe(eventClass, priority = priority) { event -> if (!filter(event)) return@subscribe ListeningStatus.LISTENING try { cont.resume(event) } finally { listener?.complete() // ensure completed on exceptions } return@subscribe ListeningStatus.STOPPED } cont.invokeOnCancellation { runCatching { listener.cancel("nextEvent outer scope cancelled", it) } } } /** * @since 2.10 */ @PublishedApi @Deprecated("For binary compatibility") internal suspend fun <E : Event, R> EventChannel<*>.syncFromEventImpl( eventClass: KClass<E>, coroutineScope: CoroutineScope, priority: EventPriority, mapper: suspend (E) -> R? ): R = suspendCancellableCoroutine { cont -> parentScope(coroutineScope).subscribe(eventClass, priority = priority) { event -> try { cont.resumeWith(kotlin.runCatching { mapper.invoke(event) ?: return@subscribe ListeningStatus.LISTENING }) } catch (_: Exception) { } return@subscribe ListeningStatus.STOPPED } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/GlobalEventChannel.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("EventChannelKt") package net.mamoe.mirai.event import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.loadService import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic import kotlin.reflect.KClass /** * 全局事件通道. 此通道包含来自所有 [Bot] 的所有类型的事件. 可通过 [EventChannel.filter] 过滤得到范围更小的 [EventChannel]. * * @see EventChannel */ @OptIn(MiraiInternalApi::class) public object GlobalEventChannel : EventChannel<Event>(Event::class, EmptyCoroutineContext) { private val instance by lazy { loadService(InternalGlobalEventChannelProvider::class).getInstance() } override fun asFlow(): Flow<Event> = instance.asFlow() override fun <E : Event> registerListener(eventClass: KClass<out E>, listener: Listener<E>) { return instance.registerListener0(eventClass, listener) } override fun <E : Event> createListener( coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority, listenerBlock: suspend (E) -> ListeningStatus ): Listener<E> = instance.createListener0(coroutineContext, concurrencyKind, priority, listenerBlock) override fun context(vararg coroutineContexts: CoroutineContext): EventChannel<Event> { return instance.context(*coroutineContexts) } } /** * 在此 [CoroutineScope] 下创建一个监听所有事件的 [EventChannel]. 相当于 `GlobalEventChannel.parentScope(this).context(coroutineContext)`. * * 在返回的 [EventChannel] 中的事件监听器都会以 [this] 作为父协程作用域. 即会 使用 [this] * * @param coroutineContext 额外的 [CoroutineContext] * * @throws IllegalStateException 当 [this] 和 [coroutineContext] 均不包含 [CoroutineContext] */ @JvmSynthetic public fun CoroutineScope.globalEventChannel(coroutineContext: CoroutineContext = EmptyCoroutineContext): EventChannel<Event> { return if (coroutineContext === EmptyCoroutineContext) GlobalEventChannel.parentScope(this) else GlobalEventChannel.parentScope(this).context(coroutineContext) } /** * @since 2.12 */ @MiraiInternalApi public interface InternalGlobalEventChannelProvider { public fun getInstance(): EventChannel<Event> } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/JvmMethodListeners.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("Events") @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "NOTHING_TO_INLINE") package net.mamoe.mirai.event import kotlinx.coroutines.* import net.mamoe.mirai.utils.castOrNull import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * 标注一个函数为事件监听器. * * ### Kotlin 函数 * Kotlin 函数要求: * - 接收者和函数参数: 所标注的 Kotlin 函数必须至少拥有一个接收者或一个函数参数, 或二者都具有. 接收者和函数参数的类型必须相同 (如果二者都存在) * 接收者或函数参数的类型都必须为 [Event] 或其子类. * - 返回值: 为 [Unit] 或不指定返回值时将注册为 [EventChannel.subscribeAlways], 为 [ListeningStatus] 时将注册为 [EventChannel.subscribe]. * 任何其他类型的返回值将会在注册时抛出异常. * * 所有 Kotlin 非 `suspend` 的函数都将会在 [Dispatchers.IO] 中调用 * * 支持的函数类型: * ``` * // 所有函数参数, 函数返回值都不允许标记为可空 (带有 '?' 符号) * // T 表示任何 Event 类型. * suspend fun T.onEvent(T) * suspend fun T.onEvent(T): ListeningStatus * suspend fun T.onEvent(T): Nothing * suspend fun onEvent(T) * suspend fun onEvent(T): ListeningStatus * suspend fun onEvent(T): Nothing * suspend fun T.onEvent() * suspend fun T.onEvent(): ListeningStatus * suspend fun T.onEvent(): Nothing * fun T.onEvent(T) * fun T.onEvent(T): ListeningStatus * fun T.onEvent(T): Nothing * fun onEvent(T) * fun onEvent(T): ListeningStatus * fun onEvent(T): Nothing * fun T.onEvent() * fun T.onEvent(): ListeningStatus * fun T.onEvent(): Nothing * ``` * * Kotlin 使用示例: * - 独立 [CoroutineScope] 和 [ListenerHost] * ``` * object MyEvents : ListenerHost { * override val coroutineContext = SupervisorJob() * * * // 可以抛出任何异常, 将在 this.coroutineContext 或 registerEvents 时提供的 CoroutineScope.coroutineContext 中的 CoroutineExceptionHandler 处理. * @EventHandler * suspend fun MessageEvent.onMessage() { * reply("received") * } * } * * myCoroutineScope.registerEvents(MyEvents) * ``` * `onMessage` 抛出的异常将会交给 `myCoroutineScope` 处理 * * * - 合并 [CoroutineScope] 和 [ListenerHost]: 使用 [SimpleListenerHost] * ``` * object MyEvents : SimpleListenerHost( /* override coroutineContext here */ ) { * override fun handleException(context: CoroutineContext, exception: Throwable) { * // 处理 onMessage 中未捕获的异常 * } * * @EventHandler * suspend fun MessageEvent.onMessage() { // 可以抛出任何异常, 将在 handleException 处理 * reply("received") * // 无返回值 (或者返回 Unit), 表示一直监听事件. * } * * @EventHandler * suspend fun MessageEvent.onMessage(): ListeningStatus { // 可以抛出任何异常, 将在 handleException 处理 * reply("received") * * return ListeningStatus.LISTENING // 表示继续监听事件 * // return ListeningStatus.STOPPED // 表示停止监听事件 * } * } * * MyEvents.registerTo(eventChannel) * // 或 eventChannel.registerListenerHost(MyEvents) * ``` * * * ### Java 方法 * 所有 Java 方法都会在 [Dispatchers.IO] 中调用. * * 支持的方法类型 * ``` * // T 表示任何 Event 类型. * void onEvent(T) * Void onEvent(T) * @NotNull ListeningStatus onEvent(T) // 禁止返回 null * ``` * * * Java 使用示例: * ``` * public class MyEventHandlers extends SimpleListenerHost { * @Override * public void handleException(@NotNull CoroutineContext context, @NotNull Throwable exception){ * // 处理事件处理时抛出的异常 * } * * @EventHandler * public void onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理 * event.subject.sendMessage("received"); * // 无返回值, 表示一直监听事件. * } * * @NotNull * @EventHandler * public ListeningStatus onMessage(@NotNull MessageEvent event) throws Exception { // 可以抛出任何异常, 将在 handleException 处理 * event.subject.sendMessage("received"); * * return ListeningStatus.LISTENING; // 表示继续监听事件 * // return ListeningStatus.STOPPED; // 表示停止监听事件 * } * } * * eventChannel.registerListenerHost(new MyEventHandlers()) * ``` * * //@sample net.mamoe.mirai.event.JvmMethodEventsTest */ @Target(AnnotationTarget.FUNCTION) @Retention(AnnotationRetention.RUNTIME) public annotation class EventHandler( /** * 监听器优先级 * @see EventPriority 查看优先级相关信息 * @see Event.intercept 拦截事件 */ public val priority: EventPriority = EventPriority.NORMAL, /** * 是否自动忽略被 [取消][CancellableEvent.isCancelled] * @see CancellableEvent */ public val ignoreCancelled: Boolean = true, /** * 并发类型 * @see ConcurrencyKind */ public val concurrency: ConcurrencyKind = ConcurrencyKind.CONCURRENT ) /** * 实现这个接口的对象可以通过 [EventHandler] 标注事件监听函数, 并通过 [registerTo] 注册. * * @see SimpleListenerHost 简单的实现 * @see EventHandler 查看更多信息 */ public interface ListenerHost /** * 携带一个异常处理器的 [ListenerHost]. * * @see registerTo * @see ListenerHost 查看更多信息 * @see EventHandler 查看更多信息 */ public abstract class SimpleListenerHost @JvmOverloads constructor(coroutineContext: CoroutineContext = EmptyCoroutineContext) : ListenerHost, CoroutineScope { public final override val coroutineContext: CoroutineContext = CoroutineExceptionHandler(::handleException) + coroutineContext + SupervisorJob(coroutineContext[Job]) /** * 处理事件处理中未捕获的异常. 在构造器中的 [coroutineContext] 未提供 [CoroutineExceptionHandler] 情况下必须继承此函数. * * [exception] 通常是 [ExceptionInEventHandlerException]. 可以获取事件: [ExceptionInEventHandlerException.event] */ public open fun handleException(context: CoroutineContext, exception: Throwable) { throw IllegalStateException( """ 未找到异常处理器. 请继承 SimpleListenerHost 中的 handleException 方法, 或在构造 SimpleListenerHost 时提供 CoroutineExceptionHandler ------------ Cannot find exception handler from coroutineContext. Please extend SimpleListenerHost.handleException or provide a CoroutineExceptionHandler to the constructor of SimpleListenerHost """.trimIndent(), exception ) } /** * 停止所有事件监听 */ public fun cancelAll() { this.cancel() } protected companion object { /** * 获取 [ExceptionInEventHandlerException.event] */ @JvmStatic protected val Throwable.event: Event? get() = this.castOrNull<ExceptionInEventHandlerException>()?.event /** * 递归获取 [Throwable.cause], 无 `cause` 时返回 `this` */ @JvmStatic protected val Throwable.rootCause: Throwable get() = generateSequence(this) { it.cause }.last() } } /** * [EventHandler] 标记的函数在处理事件时产生异常时包装异常并重新抛出 */ public class ExceptionInEventHandlerException( /** * 当时正在处理的事件 */ public val event: Event, override val message: String = "Exception in EventHandler", /** * 原异常 */ override val cause: Throwable ) : IllegalStateException() /** * 反射得到所有标注了 [EventHandler] 的函数 (Java 为方法), 并注册为事件监听器 * * @see EventHandler 获取更多信息 */ @JvmSynthetic // T 通常可以是 SimpleListenerHost public inline fun <T> T.registerTo(eventChannel: EventChannel<*>): Unit where T : CoroutineScope, T : ListenerHost = eventChannel.parentScope(this).registerListenerHost(this) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/Listener.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "DEPRECATION", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @file:JvmName("SubscriberKt") @file:JvmMultifileClass package net.mamoe.mirai.event import kotlinx.coroutines.CompletableJob import kotlinx.coroutines.sync.Mutex import net.mamoe.mirai.event.EventPriority.* import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.coroutines.CoroutineContext import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 订阅者的状态 */ public enum class ListeningStatus { /** * 表示继续监听 */ LISTENING, /** * 表示已停止. * * - 若监听器使用 [ConcurrencyKind.LOCKED], * 在这之后监听器将会被从监听器列表中删除, 因此不再能接收到事件. * - 若使用 [ConcurrencyKind.CONCURRENT], * 在这之后无法保证立即停止监听. */ STOPPED } /** * 事件监听器. * 由 [EventChannel.subscribe] 等方法返回. * * 取消监听: [complete] */ @NotStableForInheritance public interface Listener<in E : Event> : CompletableJob { // Impl notes: // Inheriting CompletableJob is a bad idea. See #1224. // However, we cannot change it as it leads to binary changes. // We can do it in 3.0 or when we found incompatibility with kotlinx.coroutines. /** * 并发类型 */ public val concurrencyKind: ConcurrencyKind /** * 事件优先级 * @see [EventPriority] */ public val priority: EventPriority get() = NORMAL /** * 这个方法将会调用 [EventChannel.subscribe] 时提供的参数 `noinline handler: suspend E.(E) -> ListeningStatus`. * * 这个函数会传递捕获的异常到本 [Listener] 创建时提供的监听方 [CoroutineContext] (通常). 详细行为可见 [EventChannel.subscribe]. */ public suspend fun onEvent(event: E): ListeningStatus } public enum class ConcurrencyKind { /** * 并发地同时处理多个事件, 但无法保证 [Listener.onEvent] 返回 [ListeningStatus.STOPPED] 后立即停止事件监听. */ CONCURRENT, /** * 使用 [Mutex] 保证同一时刻只处理一个事件. */ LOCKED } /** * 事件优先级. * * 在广播时, 事件监听器的调用顺序为 (从左到右): * [HIGHEST] -> [HIGH] -> [NORMAL] -> [LOW] -> [LOWEST] -> [MONITOR] * * - 使用 [MONITOR] 优先级的监听器将会被**并行**调用. * - 使用其他优先级的监听器都将会**按顺序**调用. * 因此一个监听器的挂起可以阻塞事件处理过程而导致低优先级的监听器较晚处理. * * 当事件被 [拦截][Event.intercept] 后, 优先级较低 (靠右) 的监听器将不会被调用. */ public enum class EventPriority { HIGHEST, HIGH, NORMAL, LOW, LOWEST, /** * 最低的优先级. * * 使用此优先级的监听器应遵循约束: * - 不 [拦截事件][Event.intercept] */ MONITOR; } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/MessageSelectBuilderUnit.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.event import kotlinx.coroutines.* import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.PlainText import net.mamoe.mirai.utils.MiraiInternalApi /** * [selectMessagesUnit] 或 [selectMessages] 时的 DSL 构建器. * * 它是特殊化的消息监听 ([EventChannel.subscribeMessages]) DSL * * @see MessageSubscribersBuilder 查看上层 API */ @OptIn(MiraiInternalApi::class) public abstract class MessageSelectBuilderUnit<M : MessageEvent, R> @PublishedApi internal constructor( ownerMessagePacket: M, stub: Any?, subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit ) : CommonMessageSelectBuilderUnit<M, R>(ownerMessagePacket, stub, subscriber) { @JvmName("timeout-ncvN2qU") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public fun timeout00(timeoutMillis: Long): MessageSelectionTimeoutChecker { return timeout(timeoutMillis) } @Suppress("unused") @JvmName("invoke-RNyhSv4") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public fun MessageSelectionTimeoutChecker.invoke00(block: suspend () -> R) { return invoke(block) } @Suppress("unused") @JvmName("invoke-RNyhSv4") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public fun MessageSelectionTimeoutChecker.invoke000(block: suspend () -> R): Nothing? { invoke(block) return null } @JvmName("reply-RNyhSv4") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.reply00(block: suspend () -> Any?) { return reply(block) } @JvmName("reply-RNyhSv4") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.reply000(block: suspend () -> Any?): Nothing? { reply(block) return null } @JvmName("reply-sCZ5gAI") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.reply00(message: String) { return reply(message) } @JvmName("reply-sCZ5gAI") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.reply000(message: String): Nothing? { reply(message) return null } @JvmName("reply-AVDwu3U") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.reply00(message: Message) { return reply(message) } @JvmName("reply-AVDwu3U") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.reply000(message: Message): Nothing? { reply(message) return null } @JvmName("quoteReply-RNyhSv4") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.quoteReply00(block: suspend () -> Any?) { return reply(block) } @JvmName("quoteReply-RNyhSv4") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.quoteReply000(block: suspend () -> Any?): Nothing? { reply(block) return null } @JvmName("quoteReply-sCZ5gAI") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.quoteReply00(message: String) { return reply(message) } @JvmName("quoteReply-sCZ5gAI") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.quoteReply000(message: String): Nothing? { reply(message) return null } @JvmName("quoteReply-AVDwu3U") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.quoteReply00(message: Message) { return reply(message) } @JvmName("quoteReply-AVDwu3U") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public infix fun MessageSelectionTimeoutChecker.quoteReply000(message: Message): Nothing? { reply(message) return null } } /** * [MessageSelectBuilderUnit] 的跨平台实现 */ @MiraiInternalApi public abstract class CommonMessageSelectBuilderUnit<M : MessageEvent, R> protected constructor( private val ownerMessagePacket: M, stub: Any?, subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit ) : MessageSubscribersBuilder<M, Unit, R, Any?>(stub, subscriber) { /** * 当其他条件都不满足时的默认处理. */ @MessageDsl public abstract fun default(onEvent: MessageListener<M, R>) // 需要后置默认监听器 @Deprecated("Use `default` instead", level = DeprecationLevel.HIDDEN) override fun always(onEvent: MessageListener<M, Any?>): Nothing = error("prohibited") /** * 限制本次 select 的最长等待时间, 当超时后抛出 [TimeoutCancellationException] */ @MessageDsl public fun timeoutException( timeoutMillis: Long, exception: () -> Throwable ) { require(timeoutMillis > 0) { "timeoutMillis must be positive" } obtainCurrentCoroutineScope().launch { delay(timeoutMillis) val deferred = obtainCurrentDeferred() ?: return@launch if (deferred.isActive && !deferred.isCompleted) { deferred.completeExceptionally(exception()) } } } /** * 限制本次 select 的最长等待时间, 当超时后执行 [block] 以完成 select */ @MessageDsl public fun timeout(timeoutMillis: Long, block: suspend () -> R) { require(timeoutMillis > 0) { "timeoutMillis must be positive" } obtainCurrentCoroutineScope().launch { delay(timeoutMillis) val deferred = obtainCurrentDeferred() ?: return@launch if (deferred.isActive && !deferred.isCompleted) { deferred.complete(block()) } } } /** * 返回一个限制本次 select 的最长等待时间的 [Deferred] * * @see invoke * @see reply */ @MessageDsl public fun timeout(timeoutMillis: Long): MessageSelectionTimeoutChecker { require(timeoutMillis > 0) { "timeoutMillis must be positive" } return MessageSelectionTimeoutChecker(timeoutMillis) } /** * 返回一个限制本次 select 的最长等待时间的 [Deferred] * * @see Deferred<Unit>.invoke */ @Suppress("unused") public fun MessageSelectionTimeoutChecker.invoke(block: suspend () -> R) { return timeout(this.timeoutMillis, block) } /** * 在超时后回复原消息 * * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText] * * @see timeout * @see quoteReply */ @Suppress("unused", "UNCHECKED_CAST") public open infix fun MessageSelectionTimeoutChecker.reply(block: suspend () -> Any?) { return timeout(this.timeoutMillis) { executeAndReply(block) Unit as R } } @Suppress("unused", "UNCHECKED_CAST") public open infix fun MessageSelectionTimeoutChecker.reply(message: Message) { return timeout(this.timeoutMillis) { ownerMessagePacket.subject.sendMessage(message) Unit as R } } @Suppress("unused", "UNCHECKED_CAST") public open infix fun MessageSelectionTimeoutChecker.reply(message: String) { return timeout(this.timeoutMillis) { ownerMessagePacket.subject.sendMessage(message) Unit as R } } /** * 在超时后引用回复原消息 * * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText] * * @see timeout * @see reply */ @Suppress("unused", "UNCHECKED_CAST") public open infix fun MessageSelectionTimeoutChecker.quoteReply(block: suspend () -> Any?) { return timeout(this.timeoutMillis) { executeAndQuoteReply(block) Unit as R } } @Suppress("unused", "UNCHECKED_CAST") public open infix fun MessageSelectionTimeoutChecker.quoteReply(message: Message) { return timeout(this.timeoutMillis) { ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + message) Unit as R } } @Suppress("unused", "UNCHECKED_CAST") public open infix fun MessageSelectionTimeoutChecker.quoteReply(message: String) { return timeout(this.timeoutMillis) { ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + message) Unit as R } } /** * 当其他条件都不满足时回复原消息. * * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText] */ @MessageDsl public fun defaultReply(block: suspend () -> Any?): Unit = subscriber({ true }, { this@CommonMessageSelectBuilderUnit.executeAndReply(block) }) /** * 当其他条件都不满足时引用回复原消息. * * 当 [block] 返回值为 [Unit] 时不回复, 为 [Message] 时回复 [Message], 其他将 [toString] 后回复为 [PlainText] */ @MessageDsl public fun defaultQuoteReply(block: suspend () -> Any?): Unit = subscriber({ true }, { this@CommonMessageSelectBuilderUnit.executeAndQuoteReply(block) }) private suspend inline fun executeAndReply(noinline block: suspend () -> Any?) { when (val result = block()) { Unit -> { } is Message -> ownerMessagePacket.subject.sendMessage(result) else -> ownerMessagePacket.subject.sendMessage(result.toString()) } } private suspend inline fun executeAndQuoteReply(noinline block: suspend () -> Any?) { when (val result = block()) { Unit -> { } is Message -> ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + result) else -> ownerMessagePacket.subject.sendMessage(ownerMessagePacket.message.quote() + result.toString()) } } protected abstract fun obtainCurrentCoroutineScope(): CoroutineScope protected abstract fun obtainCurrentDeferred(): CompletableDeferred<R>? } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/MessageSubscribersBuilder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress( "unused", "DSL_SCOPE_VIOLATION_WARNING", "INAPPLICABLE_JVM_NAME", "INVALID_CHARACTERS", "NAME_CONTAINS_ILLEGAL_CHARS", "FunctionName" ) package net.mamoe.mirai.event import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.event.* import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.annotation.AnnotationTarget.CONSTRUCTOR import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic /** * 消息事件的处理器. * * 注: * 接受者 T 为 [MessageEvent] * 参数 String 为 转为字符串了的消息 ([Message.toString]) */ public typealias MessageListener<T, R> = @MessageDsl suspend T.(String) -> R /** * 消息订阅构造器 * * @param M 消息类型 * @param R 消息监听器内部的返回值 * @param Ret 每个 DSL 函数创建监听器之后的返回值 * * @see EventChannel.subscribeMessages */ @MessageDsl public open class MessageSubscribersBuilder<M : MessageEvent, out Ret, R : RR, RR> internal constructor( /** * 用于 [MessageListener] 无返回值的替代. */ @PublishedApi internal val stub: RR, /** * invoke 这个 lambda 时, 它将会把 [消息事件的处理器][MessageListener] 注册给事件, 并返回注册完成返回的监听器. */ public val subscriber: (M.(String) -> Boolean, MessageListener<M, RR>) -> Ret ) { @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target(CONSTRUCTOR) private annotation class UseNewListenerFilterInstead @OptIn(UseNewListenerFilterInstead::class) public open fun newListeningFilter(filter: M.(String) -> Boolean): ListeningFilter = ListeningFilter(filter) /** * 由 [contains], [startsWith] 等 DSL 创建出的监听条件, 使用 [invoke] 将其注册给事件 */ public inner class ListeningFilter @UseNewListenerFilterInstead internal constructor( public val filter: M.(String) -> Boolean ) { /** 进行逻辑 `or`. */ public infix fun or(another: ListeningFilter): ListeningFilter = newListeningFilter { filter.invoke(this, it) || another.filter.invoke(this, it) } /** 进行逻辑 `and`. */ public infix fun and(another: ListeningFilter): ListeningFilter = newListeningFilter { filter.invoke(this, it) && another.filter.invoke(this, it) } /** 进行逻辑 `xor`. */ public infix fun xor(another: ListeningFilter): ListeningFilter = newListeningFilter { filter.invoke(this, it) xor another.filter.invoke(this, it) } /** 进行逻辑 `nand`, 即 `not and`. */ public infix fun nand(another: ListeningFilter): ListeningFilter = newListeningFilter { !filter.invoke(this, it) || !another.filter.invoke(this, it) } /** 进行逻辑 `not` */ public fun not(): ListeningFilter = newListeningFilter { !filter.invoke(this, it) } /** 启动事件监听. */ // do not inline due to kotlin (1.3.61) bug: java.lang.IllegalAccessError public operator fun invoke(onEvent: MessageListener<M, R>): Ret = content(filter, onEvent) } /** 启动监听器, 在 [Bot] 未被禁言且消息满足条件 [this] 时回复原消息 */ @MessageDsl public open infix fun ListeningFilter.reply(toReply: String): Ret = content(filter) { if ((this as? GroupMessageEvent)?.group?.isBotMuted != true) subject.sendMessage(toReply);this@MessageSubscribersBuilder.stub } /** 启动监听器, 在 [Bot] 未被禁言且消息满足条件 [this] 时回复原消息 */ @MessageDsl public open infix fun ListeningFilter.reply(message: Message): Ret = content(filter) { if ((this as? GroupMessageEvent)?.group?.isBotMuted != true) subject.sendMessage(message);this@MessageSubscribersBuilder.stub } /** * 启动监听器, 在 [Bot] 未被禁言且消息满足条件 [this] 时执行 [replier] 并以其返回值回复. * 返回值 [Unit] 将被忽略, [Message] 将被直接回复, 其他内容将会 [Any.toString] 后发送. */ @MessageDsl public open infix fun ListeningFilter.reply( replier: (@MessageDsl suspend M.(String) -> Any?) ): Ret = content(filter) { if ((this as? GroupMessageEvent)?.group?.isBotMuted != true) this@MessageSubscribersBuilder.executeAndReply(this, replier) else this@MessageSubscribersBuilder.stub } /** 启动监听器, 在 [Bot] 未被禁言且消息满足条件 [this] 时引用回复原消息 */ @MessageDsl public open infix fun ListeningFilter.quoteReply(toReply: String): Ret = content(filter) { if ((this as? GroupMessageEvent)?.group?.isBotMuted != true) subject.sendMessage(message.quote() + toReply); this@MessageSubscribersBuilder.stub } /** 启动监听器, 在 [Bot] 未被禁言且消息满足条件 [this] 时引用回复原消息 */ @MessageDsl public open infix fun ListeningFilter.quoteReply(toReply: Message): Ret = content(filter) { if ((this as? GroupMessageEvent)?.group?.isBotMuted != true) subject.sendMessage(message.quote() + toReply);this@MessageSubscribersBuilder.stub } /** * 启动监听器, 在 [Bot] 未被禁言且消息满足条件 [this] 时执行 [replier] 并以其返回值回复原消息 * 返回值 [Unit] 将被忽略, [Message] 将被直接回复, 其他内容将会 [Any.toString] 后发送 */ @MessageDsl public open infix fun ListeningFilter.quoteReply(replier: (@MessageDsl suspend M.(String) -> Any?)): Ret = content(filter) { if ((this as? GroupMessageEvent)?.group?.isBotMuted != true) this@MessageSubscribersBuilder.executeAndQuoteReply(this, replier) else this@MessageSubscribersBuilder.stub } /** 无触发条件, 每次收到消息都执行 [onEvent] */ @MessageDsl public open fun always(onEvent: MessageListener<M, RR>): Ret = subscriber({ true }, onEvent) /** [消息内容][Message.contentToString] `==` [equals] */ @MessageDsl public fun case(equals: String, ignoreCase: Boolean = false, trim: Boolean = true): ListeningFilter = caseImpl(equals, ignoreCase, trim) /** 如果[消息内容][Message.contentToString] `==` [equals] */ @MessageDsl public operator fun String.invoke(block: MessageListener<M, R>): Ret = case(this, onEvent = block) /** 如果[消息内容][Message.contentToString] [matches] */ @MessageDsl @JvmSynthetic @JvmName("matchingExtension") public infix fun Regex.matching(block: MessageListener<M, R>): Ret = content({ it matches this@matching }, block) /** 如果[消息内容][Message.contentToString] [Regex.find] 不为空 */ @MessageDsl @JvmSynthetic @JvmName("findingExtension") public infix fun Regex.finding(block: @MessageDsl suspend M.(MatchResult) -> R): Ret = always { content -> this@finding.find(content)?.let { block(this, it) } ?: this@MessageSubscribersBuilder.stub } /** * [消息内容][Message.contentToString] `==` [equals] * @param trim `true` 则删除首尾空格后比较 * @param ignoreCase `true` 则不区分大小写 */ @MessageDsl public fun case( equals: String, ignoreCase: Boolean = false, trim: Boolean = true, onEvent: MessageListener<M, R> ): Ret = (if (trim) equals.trim() else equals).let { toCheck -> content({ (if (trim) it.trim() else it).equals(toCheck, ignoreCase = ignoreCase) }) { onEvent(this, this.message.contentToString()) } } /** [消息内容][Message.contentToString]包含 [sub] */ @MessageDsl @JvmOverloads // bin comp public fun contains(sub: String, ignoreCase: Boolean = false): ListeningFilter = content { it.contains(sub, ignoreCase) } /** * [消息内容][Message.contentToString]包含 [sub] 中的任意一个元素 */ @MessageDsl public fun contains( sub: String, ignoreCase: Boolean = false, trim: Boolean = true, onEvent: MessageListener<M, R> ): Ret = containsImpl(sub, ignoreCase, trim, onEvent) /** [消息内容][Message.contentToString]包含 [sub] */ @JvmOverloads @MessageDsl public fun containsAny(vararg sub: String, ignoreCase: Boolean = false, trim: Boolean = true): ListeningFilter = containsAnyImpl(*sub, ignoreCase = ignoreCase, trim = trim) /** [消息内容][Message.contentToString]包含 [sub] */ @JvmOverloads @MessageDsl public fun containsAll(vararg sub: String, ignoreCase: Boolean = false, trim: Boolean = true): ListeningFilter = containsAllImpl(sub, ignoreCase = ignoreCase, trim = trim) /** 如果消息的前缀是 [prefix] */ @MessageDsl public fun startsWith(prefix: String, trim: Boolean = true): ListeningFilter { val toCheck = if (trim) prefix.trimStart() else prefix return content { (if (trim) it.trimStart() else it).startsWith(toCheck) } } /** 如果消息的前缀是 [prefix] */ @MessageDsl public fun startsWith( prefix: String, removePrefix: Boolean = true, trim: Boolean = true, onEvent: @MessageDsl suspend M.(String) -> R ): Ret = startsWithImpl(prefix, removePrefix, trim, onEvent) /** 如果消息的结尾是 [suffix] */ @MessageDsl @JvmOverloads // for binary compatibility public fun endsWith(suffix: String, trim: Boolean = true): ListeningFilter { val toCheck = if (trim) suffix.trimEnd() else suffix return content { (if (trim) it.trimEnd() else it).endsWith(toCheck) } } /** 如果消息的结尾是 [suffix] */ @MessageDsl public fun endsWith( suffix: String, removeSuffix: Boolean = true, trim: Boolean = true, onEvent: @MessageDsl suspend M.(String) -> R ): Ret = endsWithImpl(suffix, removeSuffix, trim, onEvent) /** 如果是这个人发的消息. 消息目前只会是群消息 */ @MessageDsl public fun sentBy(name: String): ListeningFilter = content { this is GroupMessageEvent && this.senderName == name } /** 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */ @MessageDsl public fun sentBy(qq: Long): ListeningFilter = content { sender.id == qq } /** 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */ @MessageDsl public fun sentBy(friend: User): ListeningFilter = content { sender.id == friend.id } /** 如果是这个人发的消息. 消息可以是好友消息也可以是群消息 */ @MessageDsl public fun sentBy(qq: Long, onEvent: MessageListener<M, R>): Ret = content { this.sender.id == qq }.invoke(onEvent) /** 如果是好友发来的消息 */ @MessageDsl public fun sentByFriend(onEvent: MessageListener<FriendMessageEvent, R>): Ret = content({ this is FriendMessageEvent }) { onEvent(this as FriendMessageEvent, it) } /** 如果是好友发来的消息 */ @MessageDsl public fun sentByFriend(): ListeningFilter = newListeningFilter { this is FriendMessageEvent } /** 如果是陌生人发来的消息 */ @MessageDsl public fun sentByStranger(onEvent: MessageListener<StrangerMessageEvent, R>): Ret = content({ this is StrangerMessageEvent }) { onEvent(this as StrangerMessageEvent, it) } /** 如果是陌生人发来的消息 */ @MessageDsl public fun sentByStranger(): ListeningFilter = newListeningFilter { this is StrangerMessageEvent } /** 如果是群临时会话消息 */ @MessageDsl @Deprecated("use sentByGroupTemp()", ReplaceWith("sentByGroupTemp()"), DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(hiddenSince = "2.0") // maybe 2.0 public fun sentByTemp(): ListeningFilter = sentByGroupTemp() /** 如果是群临时会话消息 */ @MessageDsl public fun sentByGroupTemp(): ListeningFilter = newListeningFilter { this is GroupTempMessageEvent } /** 如果是管理员或群主发的消息 */ @MessageDsl public fun sentByOperator(): ListeningFilter = content { this is GroupMessageEvent && sender.permission.isOperator() } /** 如果是管理员发的消息 */ @MessageDsl public fun sentByAdministrator(): ListeningFilter = content { this is GroupMessageEvent && sender.permission.isAdministrator() } /** 如果是群主发的消息 */ @MessageDsl public fun sentByOwner(): ListeningFilter = content { this is GroupMessageEvent && sender.isOwner() } /** 如果是来自这个群的消息 */ @MessageDsl public fun sentFrom(groupId: Long): ListeningFilter = content { this is GroupMessageEvent && group.id == groupId } /** 如果是来自这个群的消息 */ @MessageDsl public fun sentFrom(group: Group): ListeningFilter = content { this is GroupMessageEvent && group.id == group.id } /** [消息内容][Message.contentToString]包含目标为 [Bot] 的 [At] */ @MessageDsl public fun atBot(): ListeningFilter = content { message.firstIsInstanceOrNull<At>()?.target == bot.id } /** [消息内容][Message.contentToString]包含 [AtAll] */ @MessageDsl public fun atAll(): ListeningFilter = content { message.firstIsInstanceOrNull<AtAll>() != null } /** [消息内容][Message.contentToString]包含目标为 [target] 的 [At] */ @MessageDsl public fun at(target: Long): ListeningFilter = content { message.firstIsInstanceOrNull<At>()?.target == target } /** [消息内容][Message.contentToString]包含目标为 [target] 的 [At] */ @MessageDsl public fun at(target: User): ListeningFilter = content { message.firstIsInstanceOrNull<At>()?.target == target.id } /** [消息内容][Message.contentToString]包含目标为 [Bot] 的 [At], 就执行 [onEvent] */ @MessageDsl public fun atBot(onEvent: @MessageDsl suspend M.(String) -> R): Ret = content { message.firstIsInstanceOrNull<At>()?.target == bot.id }.invoke { onEvent.invoke(this, message.contentToString()) } @MessageDsl public inline fun <reified N : SingleMessage> has(noinline onEvent: @MessageDsl suspend M.(N) -> R): Ret = content { message.any { it is N } }.invoke { onEvent.invoke(this, message.firstIsInstance()) } /** [消息内容][Message.contentToString]包含 [N] 类型的 [Message] */ @MessageDsl public inline fun <reified N : SingleMessage> has(): ListeningFilter = content { message.any { it is N } } /** 如果 [mapper] 返回值非空, 就执行 [onEvent] */ @MessageDsl public open fun <N : Any> mapping(mapper: M.(String) -> N?, onEvent: @MessageDsl suspend M.(N) -> R): Ret = always { onEvent.invoke( this, mapper(this, message.contentToString()) ?: return@always this@MessageSubscribersBuilder.stub ) } /** 如果 [filter] 返回 `true` */ @MessageDsl public fun content(filter: M.(String) -> Boolean): ListeningFilter = newListeningFilter(filter) /** [消息内容][Message.contentToString]可由正则表达式匹配([Regex.matchEntire]) */ @MessageDsl public fun matching(regex: Regex): ListeningFilter = content { regex.matchEntire(it) != null } /** [消息内容][Message.contentToString]可由正则表达式匹配([Regex.matchEntire]), 就执行 `onEvent` */ @MessageDsl public fun matching(regex: Regex, onEvent: @MessageDsl suspend M.(MatchResult) -> Unit): Ret = always { this@MessageSubscribersBuilder.executeAndReply(this) { onEvent.invoke( this, regex.matchEntire(it) ?: return@always this@MessageSubscribersBuilder.stub ) } } /** [消息内容][Message.contentToString]可由正则表达式查找([Regex.find]) */ @MessageDsl public fun finding(regex: Regex): ListeningFilter = content { regex.find(it) != null } /** [消息内容][Message.contentToString]可由正则表达式查找([Regex.find]), 就执行 `onEvent` */ @MessageDsl public fun finding(regex: Regex, onEvent: @MessageDsl suspend M.(MatchResult) -> Unit): Ret = always { this@MessageSubscribersBuilder.executeAndReply(this) { onEvent.invoke( this, regex.find(it) ?: return@always this@MessageSubscribersBuilder.stub ) } } /** [消息内容][Message.contentToString]包含 [this] 则回复 [reply] */ @MessageDsl public open infix fun String.containsReply(reply: String): Ret = content({ this@containsReply in it }, { subject.sendMessage(reply); this@MessageSubscribersBuilder.stub }) /** * [消息内容][Message.contentToString]包含 [this] 则执行 [replier] 并将其返回值回复给发信对象. * * [replier] 的 `it` 将会是消息内容 string. * * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl public open infix fun String.containsReply(replier: @MessageDsl suspend M.(String) -> Any?): Ret = content({ this@containsReply in it }, { this@MessageSubscribersBuilder.executeAndReply(this, replier) }) /** * [消息内容][Message.contentToString]可由正则表达式匹配([Regex.matchEntire]), 则执行 [replier] 并将其返回值回复给发信对象. * * [replier] 的 `it` 将会是消息内容 string. * * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl public open infix fun Regex.matchingReply(replier: @MessageDsl suspend M.(MatchResult) -> Any?): Ret = always { this@MessageSubscribersBuilder.executeAndReply(this) { replier.invoke( this, matchEntire(it) ?: return@always this@MessageSubscribersBuilder.stub ) } } /** * [消息内容][Message.contentToString]可由正则表达式查找([Regex.find]), 则执行 [replier] 并将其返回值回复给发信对象. * * [replier] 的 `it` 将会是消息内容 string. * * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl public open infix fun Regex.findingReply(replier: @MessageDsl suspend M.(MatchResult) -> Any?): Ret = always { this@MessageSubscribersBuilder.executeAndReply(this) { replier.invoke( this, this@findingReply.find(it) ?: return@always this@MessageSubscribersBuilder.stub ) } } /** * 不考虑空格, [消息内容][Message.contentToString]以 [this] 结尾则执行 [replier] 并将其返回值回复给发信对象. * @param replier 若返回 [Message] 则直接发送; 若返回 [Unit] 则不回复; 其他情况则 [Any.toString] 后回复 */ @MessageDsl public open infix fun String.endsWithReply(replier: @MessageDsl suspend M.(String) -> Any?): Ret { val toCheck = this.trimEnd() return content({ it.trim().endsWith(toCheck) }, { this@MessageSubscribersBuilder.executeAndReply(this) { replier(this, it.trim().removeSuffix(toCheck)) } }) } /** 当发送的消息内容为 [this] 就回复 [reply] */ @MessageDsl public open infix fun String.reply(reply: String): Ret { val toCheck = this.trim() return content({ it.trim() == toCheck }, { subject.sendMessage(reply);this@MessageSubscribersBuilder.stub }) } /** 当发送的消息内容为 [this] 就回复 [reply] */ @MessageDsl public open infix fun String.reply(reply: Message): Ret { val toCheck = this.trim() return content({ it.trim() == toCheck }, { subject.sendMessage(reply);this@MessageSubscribersBuilder.stub }) } /** 当发送的消息内容为 [this] 就执行并回复 [replier] 的返回值 */ @MessageDsl public open infix fun String.reply(replier: @MessageDsl suspend M.(String) -> Any?): Ret { val toCheck = this.trim() return content({ it.trim() == toCheck }, { @Suppress("DSL_SCOPE_VIOLATION_WARNING") this@MessageSubscribersBuilder.executeAndReply(this) { replier(this, it.trim()) } }) } ///////////////////////////////// //// DEPRECATED AND INTERNAL //// ///////////////////////////////// @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE", "UNCHECKED_CAST") // false positive 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()) } return stub } @Suppress("REDUNDANT_INLINE_SUSPEND_FUNCTION_TYPE", "UNCHECKED_CAST") // false positive 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()) } return stub } } /** * DSL 标记. 将能让 IDE 阻止一些错误的方法调用. */ @Retention(AnnotationRetention.SOURCE) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS, AnnotationTarget.TYPE) @DslMarker public annotation class MessageDsl ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEvent.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @file:JvmName("NextEventKt") package net.mamoe.mirai.event import kotlinx.coroutines.* import net.mamoe.mirai.Bot import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.coroutines.resume import kotlin.reflect.KClass /** * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * * ### 已弃用 * * 该函数相当于 [GlobalEventChannel.nextEvent]. * 不一定需要将所有被弃用的 [nextEvent] 都换成 `GlobalEventChannel.nextEvent`, 请根据情况选择合适的 [EventChannel]. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制. * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * * @see EventChannel.subscribe 普通地监听一个事件 * @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值 * * @throws TimeoutCancellationException 在超时后抛出. */ @JvmSynthetic @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @Deprecated( "Use GlobalEventChannel.nextEvent", ReplaceWith( "if (timeoutMillis == -1L) { GlobalEventChannel.nextEvent<E>(priority, filter) } else { withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent<E>(priority, filter) } }", "net.mamoe.mirai.event.GlobalEventChannel", "kotlinx.coroutines.withTimeout", ), level = DeprecationLevel.HIDDEN ) public suspend inline fun <reified E : Event> nextEvent( timeoutMillis: Long = -1, priority: EventPriority = EventPriority.MONITOR, crossinline filter: (E) -> Boolean = { true } ): E = if (timeoutMillis == -1L) { GlobalEventChannel.nextEvent(priority) { filter(it) } } else { withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent(priority) { filter(it) } } } /** * 挂起当前协程, 直到监听到事件 [E] 的广播并通过 [filter], 返回这个事件实例. * * ### 已弃用 * * 该函数相当于 [GlobalEventChannel.nextEvent]. * 不一定需要将所有被弃用的 [nextEvent] 都换成 `GlobalEventChannel.nextEvent`, 请根据情况选择合适的 [EventChannel]. * * @param timeoutMillis 超时. 单位为毫秒. * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * * @see EventChannel.subscribe 普通地监听一个事件 * @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值 * * @return 事件实例, 在超时后返回 `null` */ @JvmSynthetic @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @Deprecated( "Use GlobalEventChannel.nextEvent", ReplaceWith( "withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent<E>(priority, filter) }", "kotlinx.coroutines.withTimeoutOrNull", "net.mamoe.mirai.event.GlobalEventChannel", "net.mamoe.mirai.event.nextEvent" ), level = DeprecationLevel.HIDDEN ) public suspend inline fun <reified E : Event> nextEventOrNull( timeoutMillis: Long, priority: EventPriority = EventPriority.MONITOR, crossinline filter: (E) -> Boolean = { true } ): E? = withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent(priority) { filter(it) } } /////////////////////////////////////////////////////////////////////////// // internals /////////////////////////////////////////////////////////////////////////// /** * @since 2.0 */ @JvmSynthetic @PublishedApi @Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(hiddenSince = "2.10") internal suspend inline fun <E : Event> nextEventImpl( eventClass: KClass<E>, coroutineScope: CoroutineScope, priority: EventPriority, crossinline filter: (E) -> Boolean ): E = suspendCancellableCoroutine { cont -> val listener = coroutineScope.globalEventChannel() .parentJob(coroutineScope.coroutineContext[Job]) .subscribe(eventClass, priority = priority) { if (!filter(this)) return@subscribe ListeningStatus.LISTENING try { cont.resume(this) } catch (_: Exception) { } return@subscribe ListeningStatus.STOPPED } cont.invokeOnCancellation { runCatching { listener.cancel("nextEvent outer scope cancelled", it) } } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "NOTHING_TO_INLINE") @kotlin.internal.HidesMembers @PublishedApi internal inline fun <BaseEvent : Event> EventChannel<BaseEvent>.parentJob(job: Job?): EventChannel<BaseEvent> = if (job != null) parentJob(job) else this @JvmSynthetic @PublishedApi @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) internal suspend inline fun <E : BotEvent> nextBotEventImpl( bot: Bot, eventClass: KClass<E>, coroutineScope: CoroutineScope, priority: EventPriority ): E = suspendCancellableCoroutine { cont -> val listener = coroutineScope.globalEventChannel() .parentJob(coroutineScope.coroutineContext[Job]) .subscribe(eventClass, priority = priority) { try { if (this.bot == bot) cont.resume(this) } catch (_: Exception) { } return@subscribe ListeningStatus.STOPPED } cont.invokeOnCancellation { runCatching { listener.cancel("nextEvent outer scope cancelled", it) } } } @JvmSynthetic @PublishedApi @Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(hiddenSince = "2.10") internal suspend fun <R> withTimeoutOrCoroutineScope( timeoutMillis: Long, useCoroutineScope: CoroutineScope? = null, block: suspend CoroutineScope.() -> R ): R { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } return if (timeoutMillis == -1L) { if (useCoroutineScope == null) coroutineScope(block) else block(useCoroutineScope) } else { withTimeout(timeoutMillis, block) } } @JvmSynthetic @PublishedApi @Deprecated("Kept for binary compatibility", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(hiddenSince = "2.10") internal suspend inline fun <R> withTimeoutOrCoroutineScope( timeoutMillis: Long, noinline block: suspend CoroutineScope.() -> R ): R { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } return if (timeoutMillis == -1L) { coroutineScope(block) } else { withTimeout(timeoutMillis, block) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/deprecated.nextEventAsync.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @file:JvmName("NextEventAsyncKt") package net.mamoe.mirai.event import kotlinx.coroutines.* import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiExperimentalApi import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext /** * 返回一个 [Deferred], 其值为下一个广播并通过 [filter] 的事件 [E] 示例. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制. * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * @param coroutineContext 添加给启动的协程的 [CoroutineContext] * * @see nextEvent 同步版本 * @see EventChannel.subscribe 普通地监听一个事件 * @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值 * * @throws TimeoutCancellationException 在超时后抛出. * @since 2.2 */ @JvmSynthetic @Deprecated( "Please use `async` with `nextEvent` manually.", ReplaceWith( """async(coroutineContext) { if (timeoutMillis == -1L) { this.globalEventChannel(coroutineContext).nextEvent<E>(priority, filter) } else { withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent<E>(priority, filter) } } }""", "kotlinx.coroutines.async", "kotlinx.coroutines.withTimeout", "net.mamoe.mirai.event.globalEventChannel", "net.mamoe.mirai.event.GlobalEventChannel", "net.mamoe.mirai.event.nextEvent" ), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @MiraiExperimentalApi public inline fun <reified E : Event> CoroutineScope.nextEventAsync( timeoutMillis: Long = -1, priority: EventPriority = EventPriority.MONITOR, coroutineContext: CoroutineContext = EmptyCoroutineContext, crossinline filter: (E) -> Boolean = { true } ): Deferred<E> = async(coroutineContext) { val block: suspend (E) -> Boolean = { filter(it) } // inline once. if (timeoutMillis == -1L) { this.globalEventChannel(coroutineContext).nextEvent(priority, block) } else { withTimeout(timeoutMillis) { GlobalEventChannel.nextEvent(priority, block) } } } /** * 返回一个 [Deferred], 其值为下一个广播并通过 [filter] 的事件 [E] 示例. * * @param timeoutMillis 超时. 单位为毫秒. * @param filter 过滤器. 返回 `true` 时表示得到了需要的实例. 返回 `false` 时表示继续监听 * @param coroutineContext 添加给启动的协程的 [CoroutineContext] * * @see nextEvent 同步版本 * @see EventChannel.subscribe 普通地监听一个事件 * @see syncFromEvent 挂起当前协程, 并尝试从事件中同步一个值 * * @return 事件实例, 在超时后返回 `null` * @since 2.2 */ @MiraiExperimentalApi @Deprecated( "Please use `async` with `nextEventOrNull` manually.", ReplaceWith( """async(coroutineContext) { if (timeoutMillis == -1L) { this.globalEventChannel(coroutineContext).nextEvent<E>(priority, filter) } else { withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent<E>(priority, filter) } } }""", "kotlinx.coroutines.async", "kotlinx.coroutines.withTimeoutOrNull", "net.mamoe.mirai.event.globalEventChannel", "net.mamoe.mirai.event.GlobalEventChannel", "net.mamoe.mirai.event.nextEvent" ), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @JvmSynthetic public inline fun <reified E : Event> CoroutineScope.nextEventOrNullAsync( timeoutMillis: Long, priority: EventPriority = EventPriority.MONITOR, coroutineContext: CoroutineContext = EmptyCoroutineContext, crossinline filter: (E) -> Boolean = { true } ): Deferred<E?> { return async(coroutineContext) { val block: suspend (E) -> Boolean = { filter(it) } // inline once. if (timeoutMillis == -1L) { this.globalEventChannel(coroutineContext).nextEvent(priority, block) } else { withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.nextEvent(priority, block) } } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/deprecated.syncFromEvent.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") @file:JvmName("SyncFromEventKt") package net.mamoe.mirai.event import kotlinx.coroutines.* import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.reflect.KClass /** * 挂起当前协程, 监听事件 [E], 并尝试从这个事件中**获取**一个值, 在超时时抛出 [TimeoutCancellationException] * * @param timeoutMillis 超时. 单位为毫秒. * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * * @see asyncFromEvent 本函数的异步版本 * @see EventChannel.subscribe 普通地监听一个事件 * @see nextEvent 挂起当前协程, 并获取下一个事件实例 * * @see syncFromEventOrNull 本函数的在超时后返回 `null` 的版本 * * @throws TimeoutCancellationException 在超时后抛出. * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 */ @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @Deprecated( "Use GlobalEventChannel.syncFromEvent", ReplaceWith( "if (timeoutMillis == -1L) { GlobalEventChannel.syncFromEvent<E, R>(priority) { mapper.invoke(it, it) } } else { withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEvent<E, R>(priority) { mapper.invoke(it, it) } } }", "kotlinx.coroutines.withTimeout", "net.mamoe.mirai.event.GlobalEventChannel", "net.mamoe.mirai.event.syncFromEvent" ), level = DeprecationLevel.HIDDEN ) @JvmSynthetic public suspend inline fun <reified E : Event, R : Any> syncFromEvent( timeoutMillis: Long = -1, priority: EventPriority = EventPriority.MONITOR, crossinline mapper: suspend E.(E) -> R? ): R { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } @Suppress("DEPRECATION") return if (timeoutMillis == -1L) { coroutineScope { GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) } } } else { withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) } } } } /** * 挂起当前协程, 监听这个事件, 并尝试从这个事件中获取一个值, 在超时时返回 `null` * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. * * @return 超时返回 `null`, 否则返回 [mapper] 返回的第一个非 `null` 值. * * @see asyncFromEvent 本函数的异步版本 * @see EventChannel.subscribe 普通地监听一个事件 * @see nextEvent 挂起当前协程, 并获取下一个事件实例 * * @throws Throwable 当 [mapper] 抛出任何异常时, 本函数会抛出该异常 */ @JvmSynthetic @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @Deprecated( "Use GlobalEventChannel.syncFromEvent", ReplaceWith("withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.syncFromEvent<E, R>(priority) { event -> with(event) { mapper(event) } }"), level = DeprecationLevel.HIDDEN ) public suspend inline fun <reified E : Event, R : Any> syncFromEventOrNull( timeoutMillis: Long, priority: EventPriority = EventPriority.MONITOR, crossinline mapper: suspend E.(E) -> R? ): R? { require(timeoutMillis > 0) { "timeoutMillis must be > 0" } return withTimeoutOrNull(timeoutMillis) { @Suppress("DEPRECATION") GlobalEventChannel.syncFromEventImpl(E::class, this, priority) { mapper.invoke(it, it) } } } /** * 异步监听这个事件, 并尝试从这个事件中获取一个值. * * 若 [mapper] 抛出的异常将会被传递给 [Deferred.await] 抛出. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param coroutineContext 额外的 [CoroutineContext] * @param mapper 过滤转换器. 返回非 `null` 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * * @see syncFromEvent * @see asyncFromEvent * @see EventChannel.subscribe 普通地监听一个事件 * @see nextEvent 挂起当前协程, 并获取下一个事件实例 */ @Deprecated( "Please use `async` and `syncFromEvent` manually.", replaceWith = ReplaceWith( """async(coroutineContext) { withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.syncFromEvent<E, R>(priority, filter) } }""", "kotlinx.coroutines.async", "kotlinx.coroutines.withTimeoutOrNull", "net.mamoe.mirai.event.globalEventChannel", "net.mamoe.mirai.event.syncFromEvent" ), level = DeprecationLevel.HIDDEN ) @JvmSynthetic @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @Suppress("DeferredIsResult") public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEventOrNull( timeoutMillis: Long, coroutineContext: CoroutineContext = EmptyCoroutineContext, priority: EventPriority = EventPriority.MONITOR, crossinline mapper: suspend E.(E) -> R? ): Deferred<R?> { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } return this.async(coroutineContext) { withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.syncFromEvent<E, R>(priority) { event -> mapper(event, event) } } } } /** * 异步监听这个事件, 并尝试从这个事件中获取一个值. * * 若 [mapper] 抛出的异常将会被传递给 [Deferred.await] 抛出. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param coroutineContext 额外的 [CoroutineContext] * @param mapper 过滤转换器. 返回非 null 则代表得到了需要的值. [syncFromEvent] 会返回这个值 * * @see syncFromEvent * @see asyncFromEventOrNull * @see EventChannel.subscribe 普通地监听一个事件 * @see nextEvent 挂起当前协程, 并获取下一个事件实例 */ @Deprecated( "Please use `async` and `syncFromEvent` manually.", replaceWith = ReplaceWith( """async(coroutineContext) { if (timeoutMillis == -1L) { this.globalEventChannel(coroutineContext).syncFromEvent<E, R>(priority, filter) } else { withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEvent<E, R>(priority, filter) } } }""", "kotlinx.coroutines.async", "kotlinx.coroutines.withTimeout", "net.mamoe.mirai.event.globalEventChannel", "net.mamoe.mirai.event.syncFromEvent" ), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(warningSince = "2.10", errorSince = "2.12", hiddenSince = "2.13") @JvmSynthetic @Suppress("DeferredIsResult") public inline fun <reified E : Event, R : Any> CoroutineScope.asyncFromEvent( timeoutMillis: Long = -1, coroutineContext: CoroutineContext = EmptyCoroutineContext, priority: EventPriority = EventPriority.MONITOR, crossinline mapper: suspend E.(E) -> R? ): Deferred<R> { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0" } return this.async(coroutineContext) { if (timeoutMillis == -1L) { GlobalEventChannel.syncFromEvent(priority) { it: E -> mapper.invoke(it, it) } } else { withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEvent(priority) { it: E -> mapper.invoke(it, it) } } } } } ////////////// //// internal ////////////// @Deprecated("Deprecated since its usages are deprecated", level = DeprecationLevel.HIDDEN) @DeprecatedSinceMirai(warningSince = "2.10", hiddenSince = "2.14") @JvmSynthetic @PublishedApi internal suspend inline fun <E : Event, R> syncFromEventImpl( eventClass: KClass<E>, coroutineScope: CoroutineScope, priority: EventPriority, crossinline mapper: suspend E.(E) -> R? ): R = suspendCancellableCoroutine { cont -> coroutineScope.globalEventChannel().subscribe(eventClass, priority = priority) { try { cont.resumeWith(kotlin.runCatching { mapper.invoke(this, it) ?: return@subscribe ListeningStatus.LISTENING }) } catch (_: Exception) { } return@subscribe ListeningStatus.STOPPED } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/EventCancelledException.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.event.events @Suppress("unused") public class EventCancelledException : RuntimeException { public constructor() : super() public constructor(message: String?) : super(message) public constructor(message: String?, cause: Throwable?) : super(message, cause) public constructor(cause: Throwable?) : super(cause) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/ImageUploadEvent.kt ================================================ /* * 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("BotEventsKt") package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact.Companion.uploadImage import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.event.events.ImageUploadEvent.Failed import net.mamoe.mirai.event.events.ImageUploadEvent.Succeed import net.mamoe.mirai.internal.event.VerboseEvent import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 图片上传前. 可以阻止上传. * * 此事件总是在 [ImageUploadEvent] 之前广播. * 若此事件被取消, [ImageUploadEvent] 不会广播. * * @see Contact.uploadImage 上传图片. 为广播这个事件的唯一途径 */ @OptIn(MiraiInternalApi::class) public data class BeforeImageUploadEvent @MiraiInternalApi constructor( public val target: Contact, public val source: ExternalResource ) : BotEvent, BotActiveEvent, AbstractEvent(), CancellableEvent, VerboseEvent { public override val bot: Bot get() = target.bot } /** * 图片上传完成. * * 此事件总是在 [BeforeImageUploadEvent] 之后广播. * 若 [BeforeImageUploadEvent] 被取消, 此事件不会广播. * * @see Contact.uploadImage 上传图片. 为广播这个事件的唯一途径 * * @see Succeed * @see Failed */ @OptIn(MiraiInternalApi::class) public sealed class ImageUploadEvent : BotEvent, BotActiveEvent, AbstractEvent(), VerboseEvent { public abstract val target: Contact public abstract val source: ExternalResource public override val bot: Bot get() = target.bot public data class Succeed @MiraiInternalApi constructor( override val target: Contact, override val source: ExternalResource, val image: Image ) : ImageUploadEvent() { override fun toString(): String { return "ImageUploadEvent.Succeed(target=$target, source=$source, image=$image)" } } public data class Failed @MiraiInternalApi constructor( override val target: Contact, override val source: ExternalResource, val errno: Int, val message: String ) : ImageUploadEvent() { override fun toString(): String { return "ImageUploadEvent.Failed(target=$target, source=$source, errno=$errno, message='$message')" } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/MessageEvent.kt ================================================ /* * 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("BotEventsKt") package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.source import net.mamoe.mirai.message.isContextIdenticalWith import net.mamoe.mirai.message.nextMessage import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 一个消息事件. * * ## 实用扩展 * * 在处理事件时, 以下可能有用 (Kotlin). * * ### 挂起协程直到监听下一条事件 * * 通过 [MessageEvent.nextMessage] 可在[全局事件通道][GlobalEventChannel]中监听并获取下一条符合条件的消息事件. * 这可能帮助实现向用户请求一条更详细的输入等功能. * * ### 判断语境相同 * * 使用 [MessageEvent.isContextIdenticalWith] 可判断两个 [MessageEvent] 的语境是否相同. 有关"语境"的定义可查看 [MessageEvent.isContextIdenticalWith]. */ @OptIn(MiraiInternalApi::class) public sealed interface MessageEvent : Event, Packet, BotPassiveEvent { /** * 与这个消息事件相关的 [Bot] */ public override val bot: Bot /** * 消息事件主体. * * - 对于私聊会话, 这个属性与 [sender] 相同; * - 对于群消息, 这个属性为 [Group] 的实例, 与 [GroupMessageEvent.group] 相同. * * 如果在 [GroupMessageEvent] 对 [sender] 发送消息, 将会通过私聊发送给群员, 而不会发送在群内. * 使用 [subject] 作为消息目标则可以确保消息发送到用户所在的场景. * * 在回复消息时, 可通过 [subject] 作为回复对象. */ public val subject: Contact /** * 发送人. * * 在私聊消息时为相关 [User] 的实例, 在群消息时为 [Member] 的实例, 在其他客户端消息时为 [Bot.asFriend] */ public val sender: User /** * 发送人名称. 由群员发送时为群员名片, 由好友发送时为好友昵称. 使用 [User.nameCardOrNick] 也能得到相同的结果. */ public val senderName: String /** * 消息内容. * * 返回的消息链中一定包含 [MessageSource], 存储此消息的发送人, 发送时间, 收信人, 消息 ids 等数据. 随后的元素为拥有顺序的真实消息内容. * * 详细查看 [MessageChain] */ public val message: MessageChain /** 消息发送时间戳, 单位为秒. 由服务器提供, 可能与本地有时差. */ public val time: Int /** * 消息源. 来自 [message]. 相当于对 [message] 以 [MessageSource] 参数调用 [MessageChain.get]. */ public val source: OnlineMessageSource.Incoming get() = message.source as OnlineMessageSource.Incoming } /** * 来自 [User] 的消息 * * @see FriendMessageEvent * @see GroupTempMessageEvent */ public sealed interface UserMessageEvent : MessageEvent { public override val subject: User } /** * 机器人收到的好友消息的事件 * * @see MessageEvent */ @OptIn(MiraiInternalApi::class) public class FriendMessageEvent( public override val sender: Friend, public override val message: MessageChain, public override val time: Int ) : AbstractMessageEvent(), UserMessageEvent, FriendEvent { init { val source = message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromFriend) { "source provided to a FriendMessageEvent must be an instance of OnlineMessageSource.Incoming.FromFriend" } } public override val friend: Friend get() = sender public override val bot: Bot get() = super.bot public override val subject: Friend get() = sender public override val senderName: String get() = sender.nick public override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend public override fun toString(): String = "FriendMessageEvent(sender=${sender.id}, message=$message)" } /** * 机器人收到的其他客户端消息的事件 * * @see MessageEvent */ @OptIn(MiraiInternalApi::class) public class OtherClientMessageEvent( public override val client: OtherClient, public override val message: MessageChain, public override val time: Int ) : AbstractMessageEvent(), MessageEvent, OtherClientEvent { init { val source = message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromFriend) { "source provided to a OtherClientMessageEvent must be an instance of OnlineMessageSource.Incoming.FromFriend" } } public override val sender: User get() = client.bot.asFriend public override val bot: Bot get() = super.bot public override val subject: OtherClient get() = client public override val senderName: String get() = sender.nick /** * 为简化处理, 其他客户端消息的 [MessageSource] 被作为 [OnlineMessageSource.Incoming.FromFriend]. */ public override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend public override fun toString(): String = "OtherClientMessageEvent(client=${client.platform}, message=$message)" } /** * 来自一个可以知道其 [Group] 的用户消息 * * @see FriendMessageEvent * @see GroupTempMessageEvent */ public sealed interface GroupAwareMessageEvent : MessageEvent { public val group: Group } /** * 机器人收到的群消息的事件 * * @see MessageEvent */ @OptIn(MiraiInternalApi::class) public class GroupMessageEvent( public override val senderName: String, /** * 发送方权限. */ public val permission: MemberPermission, /** * 发送人. 可能是 [NormalMember] 或 [AnonymousMember] */ public override val sender: Member, public override val message: MessageChain, public override val time: Int ) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageEvent, GroupEvent { init { val source = message[MessageSource] ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessageEvent must be an instance of OnlineMessageSource.Incoming.FromGroup" } } public override val group: Group get() = sender.group public override val bot: Bot get() = sender.bot public override val subject: Group get() = group public override val source: OnlineMessageSource.Incoming.FromGroup get() = message.source as OnlineMessageSource.Incoming.FromGroup public override fun toString(): String = "GroupMessageEvent(group=${group.id}, senderName=$senderName, sender=${sender.id}, permission=${permission.name}, message=$message)" } /** * 机器人收到的群临时会话消息的事件 * * @see MessageEvent */ @OptIn(MiraiInternalApi::class) @Deprecated( "mirai 正计划支持其他渠道发起的临时会话, 届时此事件会变动. 原 TempMessageEvent 已更改为 GroupTempMessageEvent", replaceWith = ReplaceWith("GroupTempMessageEvent", "net.mamoe.mirai.event.events.GroupTempMessageEvent"), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(hiddenSince = "2.0") // maybe 2.0 public sealed class TempMessageEvent( public override val sender: NormalMember, public override val message: MessageChain, public override val time: Int ) : AbstractMessageEvent(), GroupAwareMessageEvent, UserMessageEvent // support by mirai 2.1 // //public class UserTempMessageEvent( // sender: TempUser, // message: MessageChain, // time: Int //) : @Suppress("DEPRECATION_ERROR") TempMessageEvent(sender, message, time), GroupAwareMessageEvent, UserMessageEvent { //} /** * 群临时会话消息 */ public class GroupTempMessageEvent( public override val sender: NormalMember, public override val message: MessageChain, public override val time: Int ) : @Suppress("DEPRECATION_ERROR") TempMessageEvent(sender, message, time), GroupAwareMessageEvent, UserMessageEvent { init { val source = message[MessageSource] ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a GroupTempMessageEvent must be an instance of OnlineMessageSource.Incoming.FromTemp" } } public override val bot: Bot get() = sender.bot public override val subject: NormalMember get() = sender public override val group: Group get() = sender.group public override val senderName: String get() = sender.nameCardOrNick public override val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp public override fun toString(): String = "GroupTempMessageEvent(sender=${sender.id} from group(${sender.group.id}), message=$message)" } /** * 机器人收到的陌生人消息的事件 * * @see MessageEvent */ @OptIn(MiraiInternalApi::class) public class StrangerMessageEvent( public override val sender: Stranger, public override val message: MessageChain, public override val time: Int ) : AbstractMessageEvent(), UserMessageEvent, StrangerEvent { init { val source = message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromStranger) { "source provided to a StrangerMessageEvent must be an instance of OnlineMessageSource.Incoming.FromStranger" } } public override val stranger: Stranger get() = sender public override val bot: Bot get() = super.bot public override val subject: Stranger get() = sender public override val senderName: String get() = sender.nick public override val source: OnlineMessageSource.Incoming.FromStranger get() = message.source as OnlineMessageSource.Incoming.FromStranger public override fun toString(): String = "StrangerMessageEvent(sender=${sender.id}, message=$message)" } /** * 消息事件的公共抽象父类, 保留将来使用. 这是内部 API, 请不要使用. */ @MiraiInternalApi public abstract class AbstractMessageEvent : MessageEvent, AbstractEvent() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/MessagePostSendEvent.kt ================================================ /* * 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("BotEventsKt") package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.internal.event.VerboseEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 在发送消息后广播的事件, 总是在 [MessagePreSendEvent] 之后广播. * * 只要 [MessagePreSendEvent] 未被 [取消][CancellableEvent.cancel], [MessagePostSendEvent] 就一定会被广播, 并携带 [发送时产生的异常][MessagePostSendEvent.exception] (如果有). * * 在此事件广播前, 消息一定已经发送成功, 或产生一个异常. * * @see Contact.sendMessage 发送消息. 为广播这个事件的唯一途径 * @see MessagePreSendEvent */ @OptIn(MiraiInternalApi::class) public sealed class MessagePostSendEvent<C : Contact> : BotEvent, BotActiveEvent, AbstractEvent(), VerboseEvent { /** 发信目标. */ public abstract val target: C public final override val bot: Bot get() = target.bot /** 待发送的消息. 此为 [MessagePreSendEvent.message] 的最终值. */ public abstract val message: MessageChain /** * 发送消息时抛出的异常. `null` 表示消息成功发送. * @see result */ public abstract val exception: Throwable? /** * 发送消息成功时的回执. `null` 表示消息发送失败. * @see result */ public abstract val receipt: MessageReceipt<C>? } /** * 获取指代这条已经发送的消息的 [MessageSource]. 若消息发送失败, 返回 `null` * @see MessagePostSendEvent.sourceResult */ @get:JvmSynthetic public inline val MessagePostSendEvent<*>.source: MessageSource? get() = receipt?.source /** * 获取指代这条已经发送的消息的 [MessageSource], 并包装为 [kotlin.Result] * @see MessagePostSendEvent.result */ @get:JvmSynthetic @Suppress("RESULT_CLASS_IN_RETURN_TYPE") public inline val MessagePostSendEvent<*>.sourceResult: Result<MessageSource> get() = result.map { it.source } /** * 在此消息发送成功时返回 `true`. * @see MessagePostSendEvent.exception * @see MessagePostSendEvent.result */ @get:JvmSynthetic public inline val MessagePostSendEvent<*>.isSuccess: Boolean get() = exception == null /** * 在此消息发送失败时返回 `true`. * @see MessagePostSendEvent.exception * @see MessagePostSendEvent.result */ @get:JvmSynthetic public inline val MessagePostSendEvent<*>.isFailure: Boolean get() = exception != null /** * 将 [MessagePostSendEvent.exception] 与 [MessagePostSendEvent.receipt] 表示为 [Result] */ @Suppress("RESULT_CLASS_IN_RETURN_TYPE") public inline val <C : Contact> MessagePostSendEvent<C>.result: Result<MessageReceipt<C>> get() = exception.let { exception -> if (exception != null) Result.failure(exception) else Result.success(receipt!!) } /** * 在群消息发送后广播的事件. * @see MessagePostSendEvent */ public data class GroupMessagePostSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Group, /** 待发送的消息. 此为 [MessagePreSendEvent.message] 的最终值. */ public override val message: MessageChain, /** * 发送消息时抛出的异常. `null` 表示消息成功发送. * @see result */ public override val exception: Throwable?, /** * 发送消息成功时的回执. `null` 表示消息发送失败. * @see result */ public override val receipt: MessageReceipt<Group>? ) : MessagePostSendEvent<Group>() /** * 在好友或群临时会话消息发送后广播的事件. * @see MessagePostSendEvent */ public sealed class UserMessagePostSendEvent<C : User> : MessagePostSendEvent<C>() /** * 在好友消息发送后广播的事件. * @see MessagePostSendEvent */ public data class FriendMessagePostSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Friend, /** 待发送的消息. 此为 [MessagePreSendEvent.message] 的最终值. */ public override val message: MessageChain, /** * 发送消息时抛出的异常. `null` 表示消息成功发送. * @see result */ public override val exception: Throwable?, /** * 发送消息成功时的回执. `null` 表示消息发送失败. * @see result */ public override val receipt: MessageReceipt<Friend>? ) : UserMessagePostSendEvent<Friend>() /** * 在群临时会话消息发送后广播的事件. * @see MessagePostSendEvent */ @Deprecated( "mirai 正计划支持其他渠道发起的临时会话, 届时此事件会变动. 原 TempMessagePostSendEvent 已更改为 GroupTempMessagePostSendEvent", replaceWith = ReplaceWith( "GroupTempMessagePostSendEvent", "net.mamoe.mirai.event.events.GroupTempMessagePostSendEvent" ), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(hiddenSince = "2.0") // maybe 2.0 public sealed class TempMessagePostSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Member, /** 待发送的消息. 此为 [MessagePreSendEvent.message] 的最终值. */ public override val message: MessageChain, /** * 发送消息时抛出的异常. `null` 表示消息成功发送. * @see result */ public override val exception: Throwable?, /** * 发送消息成功时的回执. `null` 表示消息发送失败. * @see result */ public override val receipt: MessageReceipt<Member>? ) : UserMessagePostSendEvent<Member>() { public open val group: Group get() = target.group } /** * 在群临时会话消息发送后广播的事件. * @see MessagePostSendEvent */ @OptIn(MiraiInternalApi::class) public data class GroupTempMessagePostSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: NormalMember, /** 待发送的消息. 此为 [MessagePreSendEvent.message] 的最终值. */ public override val message: MessageChain, /** * 发送消息时抛出的异常. `null` 表示消息成功发送. * @see result */ public override val exception: Throwable?, /** * 发送消息成功时的回执. `null` 表示消息发送失败. * @see result */ public override val receipt: MessageReceipt<NormalMember>? ) : @kotlin.Suppress("DEPRECATION_ERROR") TempMessagePostSendEvent(target, message, exception, receipt) { public override val group: Group get() = target.group } /** * 在陌生人消息发送后广播的事件. * @see MessagePostSendEvent */ public data class StrangerMessagePostSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Stranger, /** 待发送的消息. 此为 [MessagePreSendEvent.message] 的最终值. */ public override val message: MessageChain, /** * 发送消息时抛出的异常. `null` 表示消息成功发送. * @see result */ public override val exception: Throwable?, /** * 发送消息成功时的回执. `null` 表示消息发送失败. * @see result */ public override val receipt: MessageReceipt<Stranger>? ) : UserMessagePostSendEvent<Stranger>() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/MessagePreSendEvent.kt ================================================ /* * 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("BotEventsKt") package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.internal.event.VerboseEvent import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 在发送消息前广播的事件. 可被 [取消][CancellableEvent.cancel]. * * 此事件总是在 [MessagePostSendEvent] 之前广播. * * 当 [MessagePreSendEvent] 被 [取消][CancellableEvent.cancel] 后: * - [MessagePostSendEvent] 不会广播 * - 消息不会发送. * - [Contact.sendMessage] 会抛出异常 [EventCancelledException] * * @see Contact.sendMessage 发送消息. 为广播这个事件的唯一途径 */ @OptIn(MiraiInternalApi::class) public sealed class MessagePreSendEvent : BotEvent, BotActiveEvent, AbstractEvent(), CancellableEvent, VerboseEvent { /** 发信目标. */ public abstract val target: Contact public final override val bot: Bot get() = target.bot /** 待发送的消息. 修改后将会同时应用于发送. */ public abstract var message: Message } /** * 在发送群消息前广播的事件. * @see MessagePreSendEvent */ public data class GroupMessagePreSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Group, /** 待发送的消息. 修改后将会同时应用于发送. */ public override var message: Message ) : MessagePreSendEvent() /** * 在发送好友或群临时会话消息前广播的事件. * @see MessagePreSendEvent */ public sealed class UserMessagePreSendEvent : MessagePreSendEvent() { /** 发信目标. */ public abstract override val target: User } /** * 在发送好友消息前广播的事件. * @see MessagePreSendEvent */ public data class FriendMessagePreSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Friend, /** 待发送的消息. 修改后将会同时应用于发送. */ public override var message: Message ) : UserMessagePreSendEvent() /** * 在发送临时会话消息前广播的事件. * @see MessagePreSendEvent */ @Deprecated( "mirai 正计划支持其他渠道发起的临时会话, 届时此事件会变动. 原 TempMessagePreSendEvent 已更改为 GroupTempMessagePreSendEvent", replaceWith = ReplaceWith( "GroupTempMessagePreSendEvent", "net.mamoe.mirai.event.events.GroupTempMessagePreSendEvent" ), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(hiddenSince = "2.0") // maybe 2.0 public sealed class TempMessagePreSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Member, /** 待发送的消息. 修改后将会同时应用于发送. */ public override var message: Message ) : UserMessagePreSendEvent() { public open val group: Group get() = target.group } /** * 在发送群临时会话消息前广播的事件. * @see MessagePreSendEvent */ @OptIn(MiraiInternalApi::class) public data class GroupTempMessagePreSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: NormalMember, /** 待发送的消息. 修改后将会同时应用于发送. */ public override var message: Message ) : @Suppress("DEPRECATION_ERROR") TempMessagePreSendEvent(target, message) { public override val group: Group get() = target.group } /** * 在发送陌生人消息前广播的事件. * @see MessagePreSendEvent */ public data class StrangerMessagePreSendEvent @MiraiInternalApi constructor( /** 发信目标. */ public override val target: Stranger, /** 待发送的消息. 修改后将会同时应用于发送. */ public override var message: Message ) : UserMessagePreSendEvent() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/MessageRecallEvent.kt ================================================ /* * 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("BotEventsKt") package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.isSameClass import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 消息撤回事件. 可是任意消息被任意人撤回. * * @see Contact.recallMessage 撤回消息. 为广播这个事件的唯一途径 */ public sealed class MessageRecallEvent : BotEvent, AbstractEvent() { /** * 消息原发送人 */ public abstract val authorId: Long /** * 消息原发送人. */ public abstract val author: UserOrBot /** * 消息 ids. * @see MessageSource.ids */ public abstract val messageIds: IntArray /** * 消息内部 ids. * @see MessageSource.ids */ public abstract val messageInternalIds: IntArray /** * 原发送时间戳, 单位为秒. */ public abstract val messageTime: Int // seconds /** * 好友消息撤回事件 */ @OptIn(MiraiInternalApi::class) public data class FriendRecall @MiraiInternalApi public constructor( public override val bot: Bot, public override val messageIds: IntArray, public override val messageInternalIds: IntArray, public override val messageTime: Int, /** * 撤回操作人, 好友的 [User.id] */ public val operatorId: Long, public val operator: Friend, ) : MessageRecallEvent(), Packet { /** * 消息原发送人, 等于 [operator] */ override val author: Friend get() = operator public override val authorId: Long get() = operatorId @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is FriendRecall || !isSameClass(this, other)) return false if (bot != other.bot) return false if (!messageIds.contentEquals(other.messageIds)) return false if (!messageInternalIds.contentEquals(other.messageInternalIds)) return false if (messageTime != other.messageTime) return false if (operatorId != other.operatorId) return false return operator == other.operator } override fun hashCode(): Int { var result = bot.hashCode() result = 31 * result + messageIds.contentHashCode() result = 31 * result + messageInternalIds.contentHashCode() result = 31 * result + messageTime result = 31 * result + operatorId.hashCode() result = 31 * result + operator.hashCode() return result } } /** * 群消息撤回事件. */ @OptIn(MiraiInternalApi::class) public data class GroupRecall @MiraiInternalApi constructor( public override val bot: Bot, public override val authorId: Long, public override val messageIds: IntArray, public override val messageInternalIds: IntArray, public override val messageTime: Int, /** * 操作人. 为 null 时则为 [Bot] 操作. */ public override val operator: Member?, public override val group: Group, public override val author: NormalMember, ) : MessageRecallEvent(), GroupOperableEvent, Packet { @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is GroupRecall || !isSameClass(this, other)) return false if (bot != other.bot) return false if (authorId != other.authorId) return false if (!messageIds.contentEquals(other.messageIds)) return false if (!messageInternalIds.contentEquals(other.messageInternalIds)) return false if (messageTime != other.messageTime) return false if (operator != other.operator) return false if (group != other.group) return false return author == other.author } override fun hashCode(): Int { var result = bot.hashCode() result = 31 * result + authorId.hashCode() result = 31 * result + messageIds.contentHashCode() result = 31 * result + messageInternalIds.contentHashCode() result = 31 * result + messageTime result = 31 * result + (operator?.hashCode() ?: 0) result = 31 * result + group.hashCode() result = 31 * result + author.hashCode() return result } } } public val MessageRecallEvent.FriendRecall.isByBot: Boolean get() = this.operatorId == bot.id // val MessageRecallEvent.GroupRecall.isByBot: Boolean get() = (this as GroupOperableEvent).isByBot // no need public val MessageRecallEvent.isByBot: Boolean get() = when (this) { is MessageRecallEvent.FriendRecall -> this.isByBot is MessageRecallEvent.GroupRecall -> (this as GroupOperableEvent).isByBot } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/MessageSyncEvent.kt ================================================ /* * 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("BotEventsKt") package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.message.data.source import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 机器人在其他客户端发送消息同步到这个客户端的事件. * * 本事件发生于**机器人账号**在另一个客户端向一个群或一个好友主动发送消息, 这条消息同步到机器人这个客户端上. * * @see MessageEvent */ public interface MessageSyncEvent : MessageEvent, OtherClientEvent { public override val client: OtherClient override val bot: Bot get() = sender.bot // don't rely on `client`, old version does not have client. } /** * 机器人在其他客户端发送群临时会话消息同步到这个客户端的事件 * * @see MessageSyncEvent */ @OptIn(MiraiInternalApi::class) public class GroupTempMessageSyncEvent private constructor( private val _client: OtherClient?, public override val sender: NormalMember, public override val message: MessageChain, public override val time: Int, @Suppress("UNUSED_PARAMETER", "LocalVariableName") _primaryConstructorMark: Any? ) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageSyncEvent { /** * @since 2.13 */ public override val client: OtherClient get() = _client ?: error("client is not set. Please use the new constructor.") /** * @since 2.13 */ public constructor( client: OtherClient, sender: NormalMember, message: MessageChain, time: Int ) : this(client, sender, message, time, null) @Deprecated( "Please use the new constructor.", replaceWith = ReplaceWith("GroupTempMessageSyncEvent(client, sender, message, time)"), level = DeprecationLevel.WARNING ) @DeprecatedSinceMirai(warningSince = "2.13") public constructor( sender: NormalMember, message: MessageChain, time: Int ) : this(null, sender, message, time, null) init { val source = message[MessageSource] ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromTemp) { "source provided to a GroupTempMessageSyncEvent must be an instance of OnlineMessageSource.Incoming.FromTemp" } } public override val bot: Bot get() = sender.bot public override val subject: NormalMember get() = sender public override val group: Group get() = sender.group public override val senderName: String get() = sender.nameCardOrNick public override val source: OnlineMessageSource.Incoming.FromTemp get() = message.source as OnlineMessageSource.Incoming.FromTemp } /** * 机器人在其他客户端发送好友消息同步到这个客户端的事件 * * @see MessageSyncEvent */ @OptIn(MiraiInternalApi::class) public class FriendMessageSyncEvent private constructor( private val _client: OtherClient?, public override val sender: Friend, public override val message: MessageChain, public override val time: Int, @Suppress("UNUSED_PARAMETER", "LocalVariableName") _primaryConstructorMark: Any? ) : AbstractMessageEvent(), FriendEvent, MessageSyncEvent { /** * @since 2.13 */ public override val client: OtherClient get() = _client ?: error("client is not set. Please use the new constructor.") /** * @since 2.13 */ public constructor( client: OtherClient, sender: Friend, message: MessageChain, time: Int ) : this(client, sender, message, time, null) @Deprecated( "Please use the new constructor.", replaceWith = ReplaceWith("FriendMessageSyncEvent(client, sender, message, time)"), level = DeprecationLevel.WARNING ) @DeprecatedSinceMirai(warningSince = "2.13") public constructor( sender: Friend, message: MessageChain, time: Int ) : this(null, sender, message, time, null) init { val source = message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromFriend) { "source provided to a FriendMessageSyncEvent must be an instance of OnlineMessageSource.Incoming.FromFriend" } } public override val friend: Friend get() = sender public override val bot: Bot get() = sender.bot public override val subject: Friend get() = sender public override val senderName: String get() = sender.nick public override val source: OnlineMessageSource.Incoming.FromFriend get() = message.source as OnlineMessageSource.Incoming.FromFriend } /** * 机器人在其他客户端发送陌生人消息同步到这个客户端的事件 * * @see MessageSyncEvent */ @OptIn(MiraiInternalApi::class) public class StrangerMessageSyncEvent private constructor( private val _client: OtherClient?, public override val sender: Stranger, public override val message: MessageChain, public override val time: Int, @Suppress("UNUSED_PARAMETER", "LocalVariableName") _primaryConstructorMark: Any?, ) : AbstractMessageEvent(), StrangerEvent, MessageSyncEvent { /** * @since 2.13 */ public override val client: OtherClient get() = _client ?: error("client is not set. Please use the new constructor.") /** * @since 2.13 */ public constructor( client: OtherClient, sender: Stranger, message: MessageChain, time: Int ) : this(client, sender, message, time, null) @Deprecated( "Please use the new constructor.", replaceWith = ReplaceWith("StrangerMessageSyncEvent(client, sender, message, time)"), level = DeprecationLevel.WARNING ) @DeprecatedSinceMirai(warningSince = "2.13") public constructor( sender: Stranger, message: MessageChain, time: Int ) : this(null, sender, message, time, null) init { val source = message[MessageSource] ?: throw IllegalArgumentException("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromStranger) { "source provided to a StrangerMessageSyncEvent must be an instance of OnlineMessageSource.Incoming.FromStranger" } } public override val stranger: Stranger get() = sender public override val bot: Bot get() = sender.bot public override val subject: Stranger get() = sender public override val senderName: String get() = sender.nick public override val source: OnlineMessageSource.Incoming.FromStranger get() = message.source as OnlineMessageSource.Incoming.FromStranger } /** * 机器人在其他客户端发送群消息同步到这个客户端的事件 * * @see MessageSyncEvent */ @OptIn(MiraiInternalApi::class) public class GroupMessageSyncEvent private constructor( private val _client: OtherClient?, public override val group: Group, public override val message: MessageChain, public override val sender: Member, public override val senderName: String, public override val time: Int, @Suppress("UNUSED_PARAMETER", "LocalVariableName") _primaryConstructorMark: Any?, ) : AbstractMessageEvent(), GroupAwareMessageEvent, MessageSyncEvent { /** * @since 2.13 */ public override val client: OtherClient get() = _client ?: error("client is not set. Please use the new constructor.") /** * @since 2.13 */ public constructor( client: OtherClient, group: Group, message: MessageChain, sender: Member, senderName: String, time: Int ) : this(client, group, message, sender, senderName, time, null) @Deprecated( "Please use the new constructor.", replaceWith = ReplaceWith("GroupMessageSyncEvent(client, group, message, sender, senderName, time)"), level = DeprecationLevel.WARNING ) @DeprecatedSinceMirai(warningSince = "2.13") public constructor( group: Group, message: MessageChain, sender: Member, senderName: String, time: Int ) : this(null, group, message, sender, senderName, time, null) init { val source = message[MessageSource] ?: error("Cannot find MessageSource from message") check(source is OnlineMessageSource.Incoming.FromGroup) { "source provided to a GroupMessageSyncEvent must be an instance of OnlineMessageSource.Incoming.FromGroup" } } override val bot: Bot get() = sender.bot override val subject: Group get() = group override val source: OnlineMessageSource.Incoming.FromGroup get() = message.source as OnlineMessageSource.Incoming.FromGroup public override fun toString(): String = "GroupMessageSyncEvent(group=${group.id}, senderName=$senderName, sender=${sender.id}, message=$message)" } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/NudgeEvent.kt ================================================ /* * 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("BotEventsKt") @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 戳一戳事件 */ public data class NudgeEvent @MiraiInternalApi constructor( /** * 戳一戳发起人 */ public val from: UserOrBot, /** * 戳一戳目标, 可能与 [from] 相同. */ public val target: UserOrBot, /** * 消息语境, 同 [MessageEvent.subject]. 可能为 [Group], [Stranger], [Friend], [Member]. */ public val subject: Contact, public val action: String, public val suffix: String, ) : AbstractEvent(), BotEvent, Packet { override val bot: Bot get() = from.bot } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/ShortVideoUploadEvent.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("BotEventsKt") package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.CancellableEvent import net.mamoe.mirai.event.events.ShortVideoUploadEvent.Failed import net.mamoe.mirai.event.events.ShortVideoUploadEvent.Succeed import net.mamoe.mirai.internal.event.VerboseEvent import net.mamoe.mirai.message.data.ShortVideo import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.MiraiInternalApi /** * 短视频上传前. 可以阻止上传. * * 此事件总是在 [ShortVideoUploadEvent] 之前广播. * 若此事件被取消, [ShortVideoUploadEvent] 不会广播. * * @see Contact.uploadShortVideo 上传短视频. 为广播这个事件的唯一途径 * @since 2.16 */ @OptIn(MiraiInternalApi::class) public class BeforeShortVideoUploadEvent @MiraiInternalApi constructor( public val target: Contact, public val thumbnailSource: ExternalResource, public val videoSource: ExternalResource ) : BotEvent, BotActiveEvent, AbstractEvent(), CancellableEvent, VerboseEvent { public override val bot: Bot get() = target.bot } /** * 短视频上传完成. * * 此事件总是在 [BeforeImageUploadEvent] 之后广播. * 若 [BeforeImageUploadEvent] 被取消, 此事件不会广播. * * @see Contact.uploadShortVideo 上传短视频. 为广播这个事件的唯一途径 * @see Succeed * @see Failed * @since 2.16 */ @OptIn(MiraiInternalApi::class) public sealed class ShortVideoUploadEvent : BotEvent, BotActiveEvent, AbstractEvent(), VerboseEvent { public abstract val target: Contact public abstract val thumbnailSource: ExternalResource public abstract val videoSource: ExternalResource public override val bot: Bot get() = target.bot public class Succeed @MiraiInternalApi constructor( override val target: Contact, override val thumbnailSource: ExternalResource, override val videoSource: ExternalResource, public val video: ShortVideo ) : ShortVideoUploadEvent() { override fun toString(): String { return "ShortVideoUploadEvent.Succeed(target=$target, " + "thumbnailSource=$thumbnailSource, " + "videoSource=$videoSource, " + "video=$video)" } } public class Failed @MiraiInternalApi constructor( override val target: Contact, override val thumbnailSource: ExternalResource, override val videoSource: ExternalResource, public val errno: Int, public val message: String ) : ShortVideoUploadEvent() { override fun toString(): String { return "ShortVideoUploadEvent.Failed(target=$target, " + "thumbnailSource=$thumbnailSource, " + "videoSource=$videoSource, " + "errno=$errno, message='$message')" } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/SignEvent.kt ================================================ /* * 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("BotEventsKt") @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.UserOrBot import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 打卡事件 * @property user 打卡发起人 * @property sign 打卡标记 * @property hasRank 有排名的打卡,通常是前三名 * @property rank 打卡排名,没有排名或识别失败时为 null * @since 2.14 */ public class SignEvent @MiraiInternalApi constructor( public val user: UserOrBot, public val sign: String, @get:JvmName("hasRank") public val hasRank: Boolean, public val rank: Int? ) : AbstractEvent(), BotEvent, Packet { override val bot: Bot get() = user.bot override fun toString(): String { return when (user) { is Bot -> "SignEvent(bot=${user.id}, sign=${sign})" is Member -> "SignEvent(bot=${user.bot.id}, group=${user.group.id}, member=${user.id}, sign=${sign})" is Friend -> "SignEvent(bot=${user.bot.id}, friend=${user.id} sign=${sign})" else -> "SignEvent(user=${user}, sign=${sign})" } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/bot.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "FunctionName") @file:JvmMultifileClass @file:JvmName("BotEventsKt") @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName // note: 若你使用 IntelliJ IDEA, 按 alt + 7 可打开结构 /** * [Bot] 登录完成, 好友列表, 群组列表初始化完成 */ public data class BotOnlineEvent @MiraiInternalApi public constructor( public override val bot: Bot ) : BotActiveEvent, AbstractEvent() /** * [Bot] 离线时广播的事件. Bot 离线不会 [关闭 Bot][Bot.close], 只会关闭 Bot 的网络层. */ public sealed class BotOfflineEvent : BotEvent, AbstractEvent() { /** * 为 `true` 时会尝试重连. 仅 [BotOfflineEvent.Force] 默认为 `false`, 其他默认为 `true`. */ public open val reconnect: Boolean get() = true /** * 主动离线. * * 在调用 [Bot.close] 时, 如果 Bot 连接正常, 将会广播 [Active]. * * 主动广播这个事件也可以让 [Bot] 离线, 但不建议这么做. 建议调用 [Bot.close]. */ @OptIn(MiraiExperimentalApi::class) public data class Active( public override val bot: Bot, public override val cause: Throwable? ) : BotOfflineEvent(), BotActiveEvent, CauseAware { override val reconnect: Boolean get() = false override fun toString(): String { return "BotOfflineEvent.Active(bot=$bot, cause=$cause, reconnect=$reconnect)" } } /** * 被挤下线. 默认不会自动重连. 可将 [reconnect] 改为 `true` 以重连. */ public data class Force @MiraiInternalApi public constructor( public override val bot: Bot, public val title: String, public val message: String, ) : BotOfflineEvent(), Packet, BotPassiveEvent { override var reconnect: Boolean = bot.configuration.autoReconnectOnForceOffline override fun toString(): String { return "BotOfflineEvent.Force(bot=$bot, title='$title', message='$message', reconnect=$reconnect)" } } /** * 被服务器断开 */ @OptIn(MiraiExperimentalApi::class) @MiraiInternalApi("This is very experimental and might be changed") public data class MsfOffline @MiraiInternalApi public constructor( public override val bot: Bot, public override val cause: Throwable? ) : BotOfflineEvent(), Packet, BotPassiveEvent, CauseAware { override var reconnect: Boolean = true override fun toString(): String { return "BotOfflineEvent.MsfOffline(bot=$bot, cause=$cause, reconnect=$reconnect)" } } /** * 因网络问题而掉线 */ @OptIn(MiraiExperimentalApi::class) public data class Dropped @MiraiInternalApi public constructor( public override val bot: Bot, public override val cause: Throwable? ) : BotOfflineEvent(), Packet, BotPassiveEvent, CauseAware { override var reconnect: Boolean = true override fun toString(): String { return "BotOfflineEvent.Dropped(bot=$bot, cause=$cause, reconnect=$reconnect)" } } /** * 服务器主动要求更换另一个服务器 */ @OptIn(MiraiExperimentalApi::class) @MiraiInternalApi public data class RequireReconnect @MiraiInternalApi public constructor( public override val bot: Bot, override val cause: Throwable?, ) : BotOfflineEvent(), Packet, BotPassiveEvent, CauseAware { override var reconnect: Boolean = true override fun toString(): String { return "BotOfflineEvent.RequireReconnect(bot=$bot, cause=$cause, reconnect=$reconnect)" } } @MiraiExperimentalApi public interface CauseAware { public val cause: Throwable? } } /** * [Bot] 主动或被动重新登录. 在此事件广播前就已经登录完毕. */ public data class BotReloginEvent @MiraiInternalApi public constructor( public override val bot: Bot, public val cause: Throwable? ) : BotEvent, BotActiveEvent, AbstractEvent() /** * [Bot] 头像被修改(通过其他客户端修改了头像). 在此事件广播前就已经修改完毕. * @see FriendAvatarChangedEvent */ public data class BotAvatarChangedEvent( public override val bot: Bot ) : BotEvent, Packet, AbstractEvent() /** * [Bot] 的昵称被改变事件, 在此事件触发时 bot 已经完成改名 * @see FriendNickChangedEvent */ public data class BotNickChangedEvent( public override val bot: Bot, public val from: String, public val to: String ) : BotEvent, Packet, AbstractEvent() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/friend.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("BotEventsKt") @file:Suppress("FunctionName", "unused", "DEPRECATION_ERROR") @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.event.events import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.internal.event.VerboseEvent import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 好友昵称改变事件. 目前仅支持解析 (来自 PC 端的修改). */ public data class FriendRemarkChangeEvent @MiraiInternalApi public constructor( public override val friend: Friend, public val oldRemark: String, public val newRemark: String, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent, BroadcastControllable { override val shouldBroadcast: Boolean get() = oldRemark != newRemark } /** * 成功添加了一个新好友的事件 */ public data class FriendAddEvent @MiraiInternalApi public constructor( /** * 新好友. 已经添加到 [Bot.friends] */ public override val friend: Friend, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent /** * 好友已被删除或主动删除的事件. */ public data class FriendDeleteEvent @MiraiInternalApi public constructor( public override val friend: Friend, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent /** * 一个账号请求添加机器人为好友的事件 */ public data class NewFriendRequestEvent @MiraiInternalApi public constructor( public override val bot: Bot, /** * 事件唯一识别号 */ public val eventId: Long, /** * 申请好友消息 */ public val message: String, /** * 请求人 [User.id] */ public val fromId: Long, /** * 来自群 [Group.id], 其他途径时为 0 */ public val fromGroupId: Long, /** * 群名片或好友昵称 */ public val fromNick: String, ) : BotEvent, Packet, AbstractEvent(), FriendInfoChangeEvent { /** * @return 申请人来自的群. 当申请人来自其他途径申请时为 `null` */ public val fromGroup: Group? = if (fromGroupId == 0L) null else bot.getGroup(fromGroupId) @JvmBlockingBridge public suspend fun accept(): Unit = Mirai.acceptNewFriendRequest(this) @JvmBlockingBridge public suspend fun reject(blackList: Boolean = false): Unit = Mirai.rejectNewFriendRequest(this, blackList) } /** * [Friend] 头像被修改. 在此事件广播前就已经修改完毕. */ public data class FriendAvatarChangedEvent @MiraiInternalApi public constructor( public override val friend: Friend, ) : FriendEvent, Packet, AbstractEvent() /** * [Friend] 昵称改变事件, 在此事件广播时好友已经完成改名 * @see BotNickChangedEvent */ public data class FriendNickChangedEvent @MiraiInternalApi public constructor( public override val friend: Friend, public val from: String, public val to: String, ) : FriendEvent, Packet, AbstractEvent(), FriendInfoChangeEvent /** * 好友输入状态改变的事件,当开始输入文字、退出聊天窗口或清空输入框时会触发此事件 */ public data class FriendInputStatusChangedEvent @MiraiInternalApi public constructor( public override val friend: Friend, public val inputting: Boolean, ) : FriendEvent, Packet, AbstractEvent(), VerboseEvent ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/group.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("BotEventsKt") @file:Suppress( "FunctionName", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "DEPRECATION_ERROR", "MemberVisibilityCanBePrivate" ) @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.event.events import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.event.BroadcastControllable import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic /** * 机器人被踢出群或在其他客户端主动退出一个群. 在事件广播前 [Bot.groups] 就已删除这个群. */ public sealed class BotLeaveEvent : BotEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent { public abstract override val group: Group /** * 机器人主动退出一个群. */ @MiraiExperimentalApi("目前此事件类型不一定正确. 部分被踢出情况也会广播此事件.") public data class Active @MiraiInternalApi constructor( public override val group: Group ) : BotLeaveEvent() { public override fun toString(): String = "BotLeaveEvent.Active(group=${group.id})" } /** * 机器人被管理员或群主踢出群. */ @MiraiExperimentalApi("BotLeaveEvent 的子类可能在将来改动. 使用 BotLeaveEvent 以保证兼容性.") public data class Kick @MiraiInternalApi constructor( public override val operator: NormalMember ) : BotLeaveEvent(), GroupOperableEvent { public override val group: Group get() = operator.group public override val bot: Bot get() = super<BotLeaveEvent>.bot public override fun toString(): String = "BotLeaveEvent.Kick(group=${group.id},operator=${operator.id})" } /** * 机器人因群主解散群而退出群. 操作人一定是群主 * @since 2.8 */ @MiraiExperimentalApi("BotLeaveEvent 的子类可能在将来改动. 使用 BotLeaveEvent 以保证兼容性.") public data class Disband @MiraiInternalApi constructor( public override val group: Group ) : BotLeaveEvent(), GroupOperableEvent { public override val operator: NormalMember = group.owner public override fun toString(): String = "BotLeaveEvent.Disband(group=${group.id})" } public override val bot: Bot get() = group.bot } /** * Bot 在群里的权限被改变. 操作人一定是群主 */ public data class BotGroupPermissionChangeEvent @MiraiInternalApi constructor( public override val group: Group, public val origin: MemberPermission, public val new: MemberPermission ) : BotPassiveEvent, GroupEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent /** * Bot 被禁言 */ public data class BotMuteEvent @MiraiInternalApi constructor( public val durationSeconds: Int, /** * 操作人. */ public val operator: NormalMember ) : GroupEvent, Packet, BotPassiveEvent, AbstractEvent(), GroupMemberInfoChangeEvent { public override val group: Group get() = operator.group } /** * Bot 被取消禁言 */ public data class BotUnmuteEvent @MiraiInternalApi constructor( /** * 操作人. */ public val operator: NormalMember ) : GroupEvent, Packet, BotPassiveEvent, AbstractEvent(), GroupMemberInfoChangeEvent { public override val group: Group get() = operator.group } /** * Bot 成功加入了一个新群 */ public sealed class BotJoinGroupEvent : GroupEvent, BotPassiveEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent { public abstract override val group: Group /** * 不确定, 已知的来源: * - Bot 在其他客户端创建群聊而同步到 Bot 客户端. */ @MiraiExperimentalApi public data class Active @MiraiInternalApi constructor( public override val group: Group ) : BotJoinGroupEvent() { public override fun toString(): String = "BotJoinGroupEvent.Active(group=$group)" } /** * Bot 被一个群内的成员直接邀请加入了群. * * 此时服务器基于 Bot 的 QQ 设置自动同意了请求. */ @MiraiExperimentalApi public data class Invite @MiraiInternalApi constructor( /** * 邀请人 */ public val invitor: NormalMember ) : BotJoinGroupEvent() { public override val group: Group get() = invitor.group public override fun toString(): String { return "BotJoinGroupEvent.Invite(invitor=$invitor)" } } /** * 原群主通过 https://huifu.qq.com/ 恢复原来群主身份并入群, * [Bot] 是原群主 */ @MiraiExperimentalApi public data class Retrieve @MiraiInternalApi constructor( public override val group: Group ) : BotJoinGroupEvent() { override fun toString(): String = "BotJoinGroupEvent.Retrieve(group=${group.id})" } } // region 群设置 /** * 群设置改变. 此事件广播前修改就已经完成. */ public interface GroupSettingChangeEvent<T> : GroupEvent, BotPassiveEvent, BroadcastControllable { public val origin: T public val new: T public override val shouldBroadcast: Boolean get() = origin != new } /** * 群名改变. 此事件广播前修改就已经完成. */ public data class GroupNameChangeEvent @MiraiInternalApi constructor( public override val origin: String, public override val new: String, public override val group: Group, /** * 操作人. 为 null 时则是机器人操作 */ public override val operator: NormalMember? ) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent /** * 入群公告改变. 此事件广播前修改就已经完成. * * ## 已弃用 * * 本事件不会再被触发. 无替代方法获知入群公告改变事件. 可使用 [Announcements] 主动获取所有公告列表. */ @DeprecatedSinceMirai(warningSince = "2.12", errorSince = "2.14") @Deprecated("This event is not being triggered anymore.", level = DeprecationLevel.ERROR) public data class GroupEntranceAnnouncementChangeEvent @MiraiInternalApi constructor( public override val origin: String, public override val new: String, public override val group: Group, /** * 操作人. 为 null 时则是机器人操作 */ public override val operator: NormalMember? ) : GroupSettingChangeEvent<String>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent /** * 群 "全员禁言" 功能状态改变. 此事件广播前修改就已经完成. */ public data class GroupMuteAllEvent @MiraiInternalApi constructor( public override val origin: Boolean, public override val new: Boolean, public override val group: Group, /** * 操作人. 为 null 时则是机器人操作 */ public override val operator: NormalMember? ) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent /** * 群 "匿名聊天" 功能状态改变. 此事件广播前修改就已经完成. */ public data class GroupAllowAnonymousChatEvent @MiraiInternalApi constructor( public override val origin: Boolean, public override val new: Boolean, public override val group: Group, /** * 操作人. 为 null 时则是机器人操作 */ public override val operator: NormalMember? ) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent /** * 群 "坦白说" 功能状态改变. 此事件广播前修改就已经完成. */ public data class GroupAllowConfessTalkEvent @MiraiInternalApi constructor( public override val origin: Boolean, public override val new: Boolean, public override val group: Group, public val isByBot: Boolean // 无法获取操作人 ) : GroupSettingChangeEvent<Boolean>, Packet, AbstractEvent(), GroupMemberInfoChangeEvent /** * 群 "允许群员邀请好友加群" 功能状态改变. 此事件广播前修改就已经完成. */ public data class GroupAllowMemberInviteEvent @MiraiInternalApi constructor( public override val origin: Boolean, public override val new: Boolean, public override val group: Group, /** * 操作人. 为 null 时则是机器人操作 */ public override val operator: NormalMember? ) : GroupSettingChangeEvent<Boolean>, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent // endregion // region 群成员 // region 成员变更 /** * 成员已经加入群的事件 */ public sealed class MemberJoinEvent( public override val member: NormalMember ) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent { /** * 被邀请加入群 */ public data class Invite @MiraiInternalApi constructor( public override val member: NormalMember, /** * 邀请者 */ public val invitor: NormalMember ) : MemberJoinEvent(member) { public override fun toString(): String = "MemberJoinEvent.Invite(member=${member.id}, invitor=${invitor.id})" } /** * 成员主动加入群 */ public data class Active @MiraiInternalApi constructor( public override val member: NormalMember ) : MemberJoinEvent(member) { public override fun toString(): String = "MemberJoinEvent.Active(member=${member.id})" } /** * 原群主通过 https://huifu.qq.com/ 恢复原来群主身份并入群, * 此时 [member] 的 [Member.permission] 肯定是 [MemberPermission.OWNER] */ public data class Retrieve @MiraiInternalApi constructor( public override val member: NormalMember ) : MemberJoinEvent(member) { override fun toString(): String = "MemberJoinEvent.Retrieve(member=${member.id})" } } /** * 成员已经离开群的事件. 在事件广播前成员就已经从 [Group.members] 中删除 */ public sealed class MemberLeaveEvent : GroupMemberEvent, AbstractEvent(), GroupMemberInfoChangeEvent { /** * 成员被踢出群. 成员不可能是机器人自己. */ public data class Kick( public override val member: NormalMember, /** * 操作人. 为 null 则是机器人操作. */ public override val operator: NormalMember? ) : MemberLeaveEvent(), Packet, GroupOperableEvent { public override fun toString(): String = "MemberLeaveEvent.Kick(member=${member.id}, operator=${operator?.id})" } /** * 成员主动离开 */ public data class Quit( public override val member: NormalMember ) : MemberLeaveEvent(), Packet { public override fun toString(): String = "MemberLeaveEvent.Quit(member=${member.id})" } } /** * [Bot] 被邀请加入一个群. */ public data class BotInvitedJoinGroupRequestEvent @MiraiInternalApi constructor( public override val bot: Bot, /** * 事件唯一识别号 */ public val eventId: Long, /** * 邀请入群的账号的 id */ public val invitorId: Long, public override val groupId: Long, public val groupName: String, /** * 邀请人昵称 */ public val invitorNick: String ) : BotEvent, Packet, AbstractEvent(), BaseGroupMemberInfoChangeEvent { /** * 邀请人. 若在事件发生后邀请人已经被删除好友, [invitor] 为 `null`. */ public val invitor: Friend? get() = this.bot.getFriend(invitorId) @JvmBlockingBridge public suspend fun accept(): Unit = Mirai.acceptInvitedJoinGroupRequest(this) @JvmBlockingBridge public suspend fun ignore(): Unit = Mirai.ignoreInvitedJoinGroupRequest(this) } /** * 一个账号请求加入群事件, [Bot] 在此群中是管理员或群主. */ public data class MemberJoinRequestEvent @MiraiInternalApi constructor( override val bot: Bot, /** * 事件唯一识别号 */ val eventId: Long, /** * 入群申请消息 */ val message: String, /** * 申请入群的账号的 id */ val fromId: Long, override val groupId: Long, val groupName: String, /** * 申请人昵称 */ val fromNick: String, /** * 邀请人 id(如果是邀请入群) */ val invitorId: Long? = null ) : BotEvent, Packet, AbstractEvent(), BaseGroupMemberInfoChangeEvent { /** * 相关群. 若在事件发生后机器人退出这个群, [group] 为 `null`. */ public val group: Group? get() = this.bot.getGroup(groupId) /** * 邀请入群的成员. 若在事件发生时机器人或该成员退群, [invitor] 为 `null`. */ public val invitor: NormalMember? by lazy { invitorId?.let { group?.get(it) } } /** * 同意这个请求 */ @JvmBlockingBridge public suspend fun accept(): Unit = Mirai.acceptMemberJoinRequest(this) /** * 拒绝这个请求 */ @JvmBlockingBridge @JvmOverloads public suspend fun reject(blackList: Boolean = false, message: String = ""): Unit = Mirai.rejectMemberJoinRequest(this, blackList, message) /** * 忽略这个请求. */ @JvmBlockingBridge public suspend fun ignore(blackList: Boolean = false): Unit = Mirai.ignoreMemberJoinRequest(this, blackList) @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public constructor( bot: Bot, eventId: Long, message: String, fromId: Long, groupId: Long, groupName: String, fromNick: String ) : this(bot, eventId, message, fromId, groupId, groupName, fromNick, null) @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public fun copy( bot: Bot, eventId: Long, message: String, fromId: Long, groupId: Long, groupName: String, fromNick: String ): MemberJoinRequestEvent = copy( bot = bot, eventId = eventId, message = message, fromId = fromId, groupId = groupId, groupName = groupName, fromNick = fromNick, invitorId = null ) internal companion object { @Suppress("unused") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) @JvmStatic @JvmName("copy\$default") // avoid being mangled fun copy_default( var0: MemberJoinRequestEvent, var1: Bot, var2: Long, var4: String, var5: Long, var7: Long, var9: String, var10: String, var11: Int, @Suppress("UNUSED_PARAMETER") var12: Any ): MemberJoinRequestEvent { var bot = var1 var eventId = var2 var message = var4 var fromId = var5 var groupId = var7 var groupName = var9 var fromNick = var10 if (var11 and 1 != 0) bot = var0.bot if (var11 and 2 != 0) eventId = var0.eventId if (var11 and 4 != 0) message = var0.message if (var11 and 8 != 0) fromId = var0.fromId if (var11 and 16 != 0) groupId = var0.groupId if (var11 and 32 != 0) groupName = var0.groupName if (var11 and 64 != 0) fromNick = var0.fromNick return var0.copy( bot = bot, eventId = eventId, message = message, fromId = fromId, groupId = groupId, groupName = groupName, fromNick = fromNick ) } } } // endregion // region 名片和头衔 /** * 成员群名片改动. 此事件广播前修改就已经完成. * * 由于服务器并不会告知名片变动, 此事件只能由 mirai 在发现变动时才广播. 不要依赖于这个事件. */ public data class MemberCardChangeEvent @MiraiInternalApi constructor( /** * 修改前 */ public val origin: String, /** * 修改后 */ public val new: String, public override val member: NormalMember ) : GroupMemberEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent /** * 成员群特殊头衔改动. 一定为群主操作 * * 由于服务器并不会告知特殊头衔的重置, 因此此事件在特殊头衔重置后只能由 mirai 在发现变动时才广播 */ public data class MemberSpecialTitleChangeEvent @MiraiInternalApi constructor( /** * 修改前 */ public val origin: String, /** * 修改后 */ public val new: String, public override val member: NormalMember, /** * 操作人. * 不为 null 时一定为群主. 可能与 [member] 引用相同, 此时为群员自己修改. * 为 null 时则是机器人操作. */ public override val operator: NormalMember? ) : GroupMemberEvent, GroupOperableEvent, AbstractEvent(), Packet, GroupMemberInfoChangeEvent // endregion // region 成员权限 /** * 成员权限改变的事件. 成员不可能是机器人自己. */ public data class MemberPermissionChangeEvent @MiraiInternalApi constructor( public override val member: NormalMember, public val origin: MemberPermission, public val new: MemberPermission ) : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent(), GroupMemberInfoChangeEvent // endregion // region 禁言 /** * 群成员被禁言事件. 被禁言的成员都不可能是机器人本人 * * @see BotMuteEvent 机器人被禁言的事件 */ public data class MemberMuteEvent @MiraiInternalApi constructor( public override val member: Member, public val durationSeconds: Int, /** * 操作人. 为 null 则为机器人操作 */ public override val operator: Member? ) : GroupMemberEvent, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent /** * 群成员被取消禁言事件. 被禁言的成员都不可能是机器人本人 * * @see BotUnmuteEvent 机器人被取消禁言的事件 */ public data class MemberUnmuteEvent @MiraiInternalApi constructor( public override val member: Member, /** * 操作人. 为 null 则为机器人操作 */ public override val operator: Member? ) : GroupMemberEvent, Packet, GroupOperableEvent, AbstractEvent(), GroupMemberInfoChangeEvent // endregion // region 群荣誉 /** * [Member] 荣誉改变时的事件, 目前只支持龙王 */ @MiraiExperimentalApi public sealed class MemberHonorChangeEvent : GroupMemberEvent, BotPassiveEvent, Packet, AbstractEvent() { /** * 改变荣誉的群成员 */ public abstract override val member: NormalMember /** * 改变的荣誉类型 */ @Suppress("INAPPLICABLE_JVM_NAME") @get:JvmName("getHonorType") public abstract val honorType: GroupHonorType // `public int getHonorType()` on Java's point of view /** * 获得荣誉时的事件 */ public data class Achieve( override val member: NormalMember, @get:JvmName("getHonorType") override val honorType: GroupHonorType ) : MemberHonorChangeEvent() { override fun toString(): String { return "MemberHonorChangeEvent.Achieve(member=$member, honorType=$honorType)" } } /** * 失去荣誉时的事件 */ public data class Lose( override val member: NormalMember, @get:JvmName("getHonorType") override val honorType: GroupHonorType ) : MemberHonorChangeEvent() { override fun toString(): String { return "MemberHonorChangeEvent.Lose(member=$member, honorType=$honorType)" } } } /** * [Group] 龙王改变时的事件 */ public data class GroupTalkativeChangeEvent( /** * 改变的群 */ override val group: Group, /** * 当前龙王 */ public val now: NormalMember, /** * 先前龙王 */ public val previous: NormalMember ) : Packet, GroupEvent, BotPassiveEvent, AbstractEvent() // endregion // endregion ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/otherClient.kt ================================================ /* * 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.event.events import net.mamoe.mirai.contact.ClientKind import net.mamoe.mirai.contact.OtherClient import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.utils.MiraiInternalApi /** * 其他设备上线 */ public data class OtherClientOnlineEvent @MiraiInternalApi constructor( override val client: OtherClient, /** * 详细设备类型,通常非 `null`. */ val kind: ClientKind? ) : OtherClientEvent, AbstractEvent(), BotPassiveEvent /** * 其他设备离线 */ public data class OtherClientOfflineEvent( override val client: OtherClient, ) : OtherClientEvent, AbstractEvent(), BotPassiveEvent ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/stranger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.event.events import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.event.AbstractEvent import net.mamoe.mirai.internal.network.Packet import net.mamoe.mirai.utils.MiraiInternalApi /** * 新增陌生人的事件 * */ public data class StrangerAddEvent @MiraiInternalApi public constructor( /** * 新的陌生人. 已经添加到 [Bot.strangers] */ public override val stranger: Stranger, ) : StrangerEvent, Packet, AbstractEvent() /** * 陌生人关系改变事件 * */ public sealed class StrangerRelationChangeEvent( public override val stranger: Stranger, ) : StrangerEvent, Packet, AbstractEvent() { /** * 主动删除陌生人或陌生人被删除的事件, 不一定能接收到被动删除的事件 */ public class Deleted( /** * 被删除的陌生人 */ stranger: Stranger, ) : StrangerRelationChangeEvent(stranger) /** * 与陌生人成为好友 */ public class Friended( /** * 成为好友的陌生人 * * 成为好友后该陌生人会从陌生人列表中删除 */ public override val stranger: Stranger, /** * 成为好友后的实例 * * 已经添加到Bot的好友列表中 */ public val friend: Friend, ) : StrangerRelationChangeEvent(stranger) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/events/types.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("BotEventsKt") package net.mamoe.mirai.event.events 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 import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 有关一个 [Bot] 的事件 */ public interface BotEvent : Event { public val bot: Bot } /** * [Bot] 被动接收的事件. 这些事件可能与机器人有关 */ public interface BotPassiveEvent : BotEvent /** * 由 [Bot] 主动发起的动作的事件 */ public interface BotActiveEvent : BotEvent /** * 有关群的事件 */ public interface GroupEvent : BotEvent { public val group: Group override val bot: Bot get() = group.bot } /** * 可由 [Member] 或 [Bot] 操作的事件 * @see isByBot * @see operatorOrBot */ public interface GroupOperableEvent : GroupEvent { /** * 操作人, 为 `null` 时为 [Bot] 操作 */ public val operator: Member? } /** * 是否由 [Bot] 操作 */ @get:JvmSynthetic @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @kotlin.internal.HidesMembers public inline val GroupOperableEvent.isByBot: Boolean get() = operator == null /** * 当操作人为 [Member] 时获取这个 [Member], * 当操作人为 [Bot] 时获取 [Group.botAsMember] */ @get:JvmSynthetic public inline val GroupOperableEvent.operatorOrBot: Member get() = this.operator ?: this.group.botAsMember /** * 有关 [User] 的事件 */ public interface UserEvent : BotEvent { public val user: User } /** * 有关好友的事件 */ public interface FriendEvent : BotEvent, UserEvent { public val friend: Friend override val bot: Bot get() = friend.bot override val user: Friend get() = friend } internal interface FriendInfoChangeEvent : BotEvent // for cache /** * 有关陌生人的事件 */ public interface StrangerEvent : BotEvent, UserEvent { public val stranger: Stranger override val bot: Bot get() = stranger.bot override val user: Stranger get() = stranger } /** * 有关群成员的事件 */ public interface GroupMemberEvent : GroupEvent, UserEvent { public val member: Member override val group: Group get() = member.group 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 @OptIn(MiraiInternalApi::class) public interface OtherClientEvent : BotEvent, Packet { public val client: OtherClient override val bot: Bot get() = client.bot } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/select.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("DuplicatedCode") package net.mamoe.mirai.event import kotlinx.coroutines.* import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.isContextIdenticalWith import net.mamoe.mirai.message.nextMessage import net.mamoe.mirai.utils.MiraiExperimentalApi import kotlin.jvm.JvmInline import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 挂起当前协程, 等待任意一个事件监听器返回 `false` 后返回. * * 创建的所有事件监听器都会判断发送人信息 ([isContextIdenticalWith]), 监听之后的所有消息. * * [selectBuilder] DSL 类似于 [EventChannel.subscribeMessages] 的 DSL, 屏蔽了一些 `reply` DSL 以确保类型安全 * * ```kotlin * reply("开启复读模式") * * whileSelectMessages { * "stop" { * reply("已关闭复读") * false // 停止循环 * } * // 也可以使用 startsWith("") { true } 等 DSL * default { * reply(message) * true // 继续循环 * } * timeout(3000) { * // on * true * } * } // 等待直到 `false` * * reply("复读模式结束") * ``` * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * * @see EventChannel.subscribe * @see EventChannel.subscribeMessages * @see nextMessage 挂起协程并等待下一条消息 */ @Suppress("unused") @BuilderInference public suspend inline fun <reified T : MessageEvent> T.whileSelectMessages( timeoutMillis: Long = -1, filterContext: Boolean = true, priority: EventPriority = EventPriority.MONITOR, @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit ): Unit = whileSelectMessagesImpl(timeoutMillis, filterContext, priority, selectBuilder) /** * [selectMessages] 的 [Unit] 返回值捷径 (由于 Kotlin 无法推断 [Unit] 类型) */ @MiraiExperimentalApi @JvmName("selectMessages1") @BuilderInference public suspend inline fun <reified T : MessageEvent> T.selectMessagesUnit( timeoutMillis: Long = -1, filterContext: Boolean = true, priority: EventPriority = EventPriority.MONITOR, @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, Unit>.() -> Unit ): Unit = selectMessagesImpl(timeoutMillis, true, filterContext, priority, selectBuilder) /** * 挂起当前协程, 等待任意一个事件监听器触发后返回其返回值. * * 创建的所有事件监听器都会判断发送人信息 ([isContextIdenticalWith]), 监听之后的所有消息. * * [selectBuilder] DSL 类似于 [EventChannel.subscribeMessages] 的 DSL, 屏蔽了一些 `reply` DSL 以确保类型安全 * * ```kotlin * val value: String = selectMessages { * "hello" { "111" } * "hi" { "222" } * startsWith("/") { it } * default { "default" } * } * ``` * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * * @see nextMessage 挂起协程并等待下一条消息 */ @Suppress("unused") // false positive @BuilderInference public suspend inline fun <reified T : MessageEvent, R> T.selectMessages( timeoutMillis: Long = -1, filterContext: Boolean = true, priority: EventPriority = EventPriority.MONITOR, @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, R>.() -> Unit ): R = selectMessagesImpl( timeoutMillis, false, filterContext, priority ) { selectBuilder.invoke(this as MessageSelectBuilder<T, R>) } /** * [selectMessages] 时的 DSL 构建器. * * 它是特殊化的消息监听 ([EventChannel.subscribeMessages]) DSL, 屏蔽了一些 `reply` DSL 以确保作用域安全性 * * @see MessageSelectBuilderUnit 查看上层 API */ @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") public abstract class MessageSelectBuilder<M : MessageEvent, R> @PublishedApi internal constructor( ownerMessagePacket: M, stub: Any?, subscriber: (M.(String) -> Boolean, MessageListener<M, Any?>) -> Unit ) : MessageSelectBuilderUnit<M, R>(ownerMessagePacket, stub, subscriber) { // 这些函数无法获取返回值. 必须屏蔽. @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun <N : Any> mapping( mapper: M.(String) -> N?, onEvent: @MessageDsl suspend M.(N) -> R ): Nothing = error("prohibited") @Deprecated("Use `default` instead", level = DeprecationLevel.HIDDEN) override fun always(onEvent: MessageListener<M, Any?>): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override infix fun MessageSelectionTimeoutChecker.reply(block: suspend () -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override infix fun MessageSelectionTimeoutChecker.reply(message: String): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override infix fun MessageSelectionTimeoutChecker.reply(message: Message): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override infix fun MessageSelectionTimeoutChecker.quoteReply(block: suspend () -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override infix fun MessageSelectionTimeoutChecker.quoteReply(message: String): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override infix fun MessageSelectionTimeoutChecker.quoteReply(message: Message): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun String.containsReply(reply: String): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun String.containsReply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun Regex.matchingReply(replier: suspend M.(MatchResult) -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun Regex.findingReply(replier: suspend M.(MatchResult) -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun String.endsWithReply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun String.reply(reply: String): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun String.reply(reply: Message): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun String.reply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun ListeningFilter.reply(toReply: String): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun ListeningFilter.reply(message: Message): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun ListeningFilter.reply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun ListeningFilter.quoteReply(toReply: String): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun ListeningFilter.quoteReply(toReply: Message): Nothing = error("prohibited") @Deprecated("Using `reply` DSL in message selection is prohibited", level = DeprecationLevel.HIDDEN) override fun ListeningFilter.quoteReply(replier: suspend M.(String) -> Any?): Nothing = error("prohibited") } @JvmInline @Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") public value class MessageSelectionTimeoutChecker internal constructor(public val timeoutMillis: Long) public class MessageSelectionTimeoutException : RuntimeException() ///////////////////////// //// implementations //// ///////////////////////// @JvmSynthetic @PublishedApi internal suspend inline fun <R> withSilentTimeoutOrCoroutineScope( timeoutMillis: Long, noinline block: suspend CoroutineScope.() -> R ): R { require(timeoutMillis == -1L || timeoutMillis > 0) { "timeoutMillis must be -1 or > 0 " } return withContext(ExceptionHandlerIgnoringCancellationException) { if (timeoutMillis == -1L) { coroutineScope(block) } else { withTimeout(timeoutMillis, block) } } } @PublishedApi internal val SELECT_MESSAGE_STUB: Any = Any() @PublishedApi internal val ExceptionHandlerIgnoringCancellationException: CoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable -> if (throwable !is CancellationException) { throw throwable } } @PublishedApi @BuilderInference internal suspend inline fun <reified T : MessageEvent, R> T.selectMessagesImpl( timeoutMillis: Long = -1, isUnit: Boolean, filterContext: Boolean = true, priority: EventPriority, @BuilderInference crossinline selectBuilder: @MessageDsl MessageSelectBuilderUnit<T, R>.() -> Unit ): R = withSilentTimeoutOrCoroutineScope(timeoutMillis) { var deferred: CompletableDeferred<R>? = CompletableDeferred() coroutineContext[Job]!!.invokeOnCompletion { deferred?.cancel() } // ensure sequential invoking val listeners: MutableList<Pair<T.(String) -> Boolean, MessageListener<T, Any?>>> = mutableListOf() val defaultListeners: MutableList<MessageListener<T, Any?>> = mutableListOf() if (isUnit) { // https://youtrack.jetbrains.com/issue/KT-37716 val outside = { filter: T.(String) -> Boolean, listener: MessageListener<T, Any?> -> listeners += filter to listener } object : MessageSelectBuilderUnit<T, R>( this@selectMessagesImpl, SELECT_MESSAGE_STUB, outside ) { override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred override fun default(onEvent: MessageListener<T, R>) { defaultListeners += onEvent } } } else { // https://youtrack.jetbrains.com/issue/KT-37716 val outside = { filter: T.(String) -> Boolean, listener: MessageListener<T, Any?> -> listeners += filter to listener } object : MessageSelectBuilder<T, R>( this@selectMessagesImpl, SELECT_MESSAGE_STUB, outside ) { override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope override fun obtainCurrentDeferred(): CompletableDeferred<R>? = deferred override fun default(onEvent: MessageListener<T, R>) { defaultListeners += onEvent } } }.apply(selectBuilder) // we don't have any way to reduce duplication yet, // until local functions are supported in inline functions @Suppress("DuplicatedCode") val subscribeAlways = globalEventChannel().subscribeAlways<T>( concurrency = ConcurrencyKind.LOCKED, priority = priority ) { event -> if (filterContext && !this.isContextIdenticalWith(this@selectMessagesImpl)) return@subscribeAlways val toString = event.message.contentToString() listeners.forEach { (filter, listener) -> if (deferred?.isCompleted == true || !isActive) return@subscribeAlways if (filter.invoke(event, toString)) { // same to the one below val value = listener.invoke(event, toString) if (value !== SELECT_MESSAGE_STUB) { @Suppress("UNCHECKED_CAST") deferred?.complete(value as R) return@subscribeAlways } else if (isUnit) { // value === stub // unit mode: we can directly complete this selection @Suppress("UNCHECKED_CAST") deferred?.complete(Unit as R) } } } defaultListeners.forEach { listener -> // same to the one above val value = listener.invoke(event, toString) if (value !== SELECT_MESSAGE_STUB) { @Suppress("UNCHECKED_CAST") deferred?.complete(value as R) return@subscribeAlways } else if (isUnit) { // value === stub // unit mode: we can directly complete this selection @Suppress("UNCHECKED_CAST") deferred?.complete(Unit as R) } } } deferred!!.await().also { subscribeAlways.complete() deferred = null coroutineContext.cancelChildren() } } @Suppress("unused") @PublishedApi internal suspend inline fun <reified T : MessageEvent> T.whileSelectMessagesImpl( timeoutMillis: Long, filterContext: Boolean, priority: EventPriority, crossinline selectBuilder: @MessageDsl MessageSelectBuilder<T, Boolean>.() -> Unit ): Unit = withSilentTimeoutOrCoroutineScope(timeoutMillis) { var deferred: CompletableDeferred<Boolean>? = CompletableDeferred() coroutineContext[Job]!!.invokeOnCompletion { deferred?.cancel() } // ensure sequential invoking val listeners: MutableList<Pair<T.(String) -> Boolean, MessageListener<T, Any?>>> = mutableListOf() val defaultListeners: MutableList<MessageListener<T, Any?>> = mutableListOf() // https://youtrack.jetbrains.com/issue/KT-37716 val outside = { filter: T.(String) -> Boolean, listener: MessageListener<T, Any?> -> listeners += filter to listener } object : MessageSelectBuilder<T, Boolean>( this@whileSelectMessagesImpl, SELECT_MESSAGE_STUB, outside ) { override fun obtainCurrentCoroutineScope(): CoroutineScope = this@withSilentTimeoutOrCoroutineScope override fun obtainCurrentDeferred(): CompletableDeferred<Boolean>? = deferred override fun default(onEvent: MessageListener<T, Boolean>) { defaultListeners += onEvent } }.apply(selectBuilder) // ensure atomic completing val subscribeAlways = globalEventChannel().subscribeAlways<T>( concurrency = ConcurrencyKind.LOCKED, priority = priority ) { event -> if (filterContext && !this.isContextIdenticalWith(this@whileSelectMessagesImpl)) return@subscribeAlways val toString = event.message.contentToString() listeners.forEach { (filter, listener) -> if (deferred?.isCompleted != false || !isActive) return@subscribeAlways if (filter.invoke(event, toString)) { listener.invoke(event, toString).let { value -> if (value !== SELECT_MESSAGE_STUB) { deferred?.complete(value as Boolean) return@subscribeAlways // accept the first value only } } } } defaultListeners.forEach { listener -> listener.invoke(event, toString).let { value -> if (value !== SELECT_MESSAGE_STUB) { deferred?.complete(value as Boolean) return@subscribeAlways // accept the first value only } } } } while (deferred?.await() == true) { deferred = CompletableDeferred() } subscribeAlways.complete() deferred = null coroutineContext.cancelChildren() } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/event/subscribeMessages.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("SubscribeMessagesKt") @file:Suppress("EXPERIMENTAL_API_USAGE", "MemberVisibilityCanBePrivate", "unused") package net.mamoe.mirai.event import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.OtherClient import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.ConcurrencyKind.CONCURRENT import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.data.content import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName public typealias MessageEventSubscribersBuilder = MessageSubscribersBuilder<MessageEvent, Listener<MessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所有联系人的消息事件. * * ``` * eventChannel.subscribeMessages { * "test" { * // 当消息内容为 "test" 时执行 * // this: MessageEvent * reply("test!") * } * * "Hello" reply "Hi" // 当消息内容为 "Hello" 时回复 "Hi" * "quote me" quoteReply "ok" // 当消息内容为 "quote me" 时引用该消息并回复 "ok" * "quote me2" quoteReply { * // lambda 也是允许的: * // 返回值接受 Any? * // 为 Unit 时不发送任何内容; * // 为 Message 时直接发送; * // 为 String 时发送为 PlainText; * // 否则 toString 并发送为 PlainText * * "ok" * } * * case("iGNorECase", ignoreCase=true) reply "OK" // 忽略大小写 * startsWith("-") reply { cmd -> * // 当消息内容以 "-" 开头时执行 * // cmd 为消息去除开头 "-" 的内容 * } * * * val listener: Listener<MessageEvent> = "1" reply "2" * // 每个语句都会被注册为事件监听器,可以这样获取监听器 * * listener.complete() // 停止 "1" reply "2" 这个事件监听 * } * ``` * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 * * @see subscribeFriendMessages * @see subscribeGroupMessages * @see subscribeGroupTempMessages * @see subscribeOtherClientMessages * @see subscribeStrangerMessages */ public fun <R> EventChannel<*>.subscribeMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: MessageEventSubscribersBuilder.() -> R ): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return createBuilder(::MessageEventSubscribersBuilder, coroutineContext, concurrencyKind, priority).run(listeners) } public typealias GroupMessageSubscribersBuilder = MessageSubscribersBuilder<GroupMessageEvent, Listener<GroupMessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所有群会话消息事件. DSL 语法查看 [subscribeMessages]. * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 */ public fun <R> EventChannel<*>.subscribeGroupMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: GroupMessageSubscribersBuilder.() -> R ): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return createBuilder(::GroupMessageSubscribersBuilder, coroutineContext, concurrencyKind, priority).run(listeners) } public typealias FriendMessageSubscribersBuilder = MessageSubscribersBuilder<FriendMessageEvent, Listener<FriendMessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所有好友消息事件. DSL 语法查看 [subscribeMessages]. * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 */ public fun <R> EventChannel<*>.subscribeFriendMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: FriendMessageSubscribersBuilder.() -> R ): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return createBuilder(::FriendMessageSubscribersBuilder, coroutineContext, concurrencyKind, priority).run(listeners) } /** * @since 2.7 */ public typealias UserMessageSubscribersBuilder = MessageSubscribersBuilder<UserMessageEvent, Listener<UserMessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所 [User] 消息事件. DSL 语法查看 [subscribeMessages]. * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 * * @since 2.7 */ public fun <R> EventChannel<*>.subscribeUserMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: UserMessageSubscribersBuilder.() -> R ): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return createBuilder(::UserMessageSubscribersBuilder, coroutineContext, concurrencyKind, priority).run(listeners) } @Deprecated( "mirai 正计划支持其他渠道发起的临时会话, 届时此定义会变动. 请使用 GroupTempMessageSubscribersBuilder", ReplaceWith( "GroupTempMessageSubscribersBuilder", "net.mamoe.mirai.event.GroupTempMessageSubscribersBuilder" ), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(hiddenSince = "2.0") // maybe 2.0 public typealias TempMessageSubscribersBuilder = MessageSubscribersBuilder<GroupTempMessageEvent, Listener<GroupTempMessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所有临时会话消息事件. DSL 语法查看 [subscribeMessages]. * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 */ @Deprecated( "mirai 正计划支持其他渠道发起的临时会话, 届时此方法会变动. 请使用 subscribeGroupTempMessages", ReplaceWith( "subscribeGroupTempMessages(coroutineContext, concurrencyKind, priority, listeners)", "net.mamoe.mirai.event.subscribeGroupTempMessages" ), DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(hiddenSince = "2.0") // maybe 2.0 public fun <R> EventChannel<*>.subscribeTempMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: GroupTempMessageSubscribersBuilder.() -> R ): R = subscribeGroupTempMessages(coroutineContext, concurrencyKind, priority, listeners) public typealias GroupTempMessageSubscribersBuilder = MessageSubscribersBuilder<GroupTempMessageEvent, Listener<GroupTempMessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所有 [GroupTempMessageEvent]. DSL 语法查看 [subscribeMessages]. * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 */ public fun <R> EventChannel<*>.subscribeGroupTempMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: GroupTempMessageSubscribersBuilder.() -> R ): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return createBuilder(::GroupTempMessageSubscribersBuilder, coroutineContext, concurrencyKind, priority) .run(listeners) } public typealias StrangerMessageSubscribersBuilder = MessageSubscribersBuilder<StrangerMessageEvent, Listener<StrangerMessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所有 [Stranger] 消息事件. DSL 语法查看 [subscribeMessages]. * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 */ public fun <R> EventChannel<*>.subscribeStrangerMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: StrangerMessageSubscribersBuilder.() -> R ): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return createBuilder(::StrangerMessageSubscribersBuilder, coroutineContext, concurrencyKind, priority) .run(listeners) } public typealias OtherClientMessageSubscribersBuilder = MessageSubscribersBuilder<OtherClientMessageEvent, Listener<OtherClientMessageEvent>, Unit, Unit> /** * 通过 DSL 订阅来自所有 [Bot] 的所有 [OtherClient] 消息事件. DSL 语法查看 [subscribeMessages]. * * @see EventChannel.subscribe 事件监听基础 * @see EventChannel 事件通道 */ public fun <R> EventChannel<*>.subscribeOtherClientMessages( coroutineContext: CoroutineContext = EmptyCoroutineContext, concurrencyKind: ConcurrencyKind = CONCURRENT, priority: EventPriority = EventPriority.MONITOR, listeners: OtherClientMessageSubscribersBuilder.() -> R ): R { contract { callsInPlace(listeners, InvocationKind.EXACTLY_ONCE) } return createBuilder(::OtherClientMessageSubscribersBuilder, coroutineContext, concurrencyKind, priority) .run(listeners) } private typealias MessageSubscriberBuilderConstructor<E> = ( Unit, (E.(String) -> Boolean, MessageListener<E, Unit>) -> Listener<E> ) -> MessageSubscribersBuilder<E, Listener<E>, Unit, Unit> private inline fun <reified E : MessageEvent> EventChannel<*>.createBuilder( constructor: MessageSubscriberBuilderConstructor<E>, coroutineContext: CoroutineContext, concurrencyKind: ConcurrencyKind, priority: EventPriority ): MessageSubscribersBuilder<E, Listener<E>, Unit, Unit> = constructor(Unit) { filter, listener -> subscribeAlways(coroutineContext, concurrencyKind, priority) { val toString = this.message.content if (filter(this, toString)) listener(this, toString) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/event/JvmMethodListenersInternal.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import net.mamoe.mirai.event.* import net.mamoe.mirai.utils.EventListenerLikeJava import net.mamoe.mirai.utils.castOrNull import java.lang.reflect.Method import kotlin.coroutines.CoroutineContext import kotlin.reflect.KClass import kotlin.reflect.full.IllegalCallableAccessException import kotlin.reflect.full.callSuspend import kotlin.reflect.full.isSubclassOf import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.kotlinFunction internal object JvmMethodListenersInternal { private fun isKotlinFunction(method: Method): Boolean { if (method.getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false if (method.declaringClass.getDeclaredAnnotation(EventListenerLikeJava::class.java) != null) return false @Suppress("RemoveRedundantQualifierName") // for strict return method.declaringClass.getDeclaredAnnotation(Metadata::class.java) != null } @Suppress("UNCHECKED_CAST") internal fun registerEventHandler( method: Method, owner: Any, eventChannel: EventChannel<*>, annotation: EventHandler, coroutineContext: CoroutineContext, ): Listener<Event> { method.isAccessible = true val kotlinFunction = kotlin.runCatching { method.kotlinFunction }.getOrNull() return if (kotlinFunction != null && isKotlinFunction(method)) { // kotlin functions val param = kotlinFunction.parameters when (param.size) { 3 -> { // ownerClass, receiver, event check(param[1].type == param[2].type) { "Illegal kotlin function ${kotlinFunction.name}. Receiver and param must have same type" } check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) { "Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}" } } 2 -> { // ownerClass, event check((param[1].type.classifier as? KClass<*>)?.isSubclassOf(Event::class) == true) { "Illegal kotlin function ${kotlinFunction.name}. First param or receiver must be subclass of Event, but found ${param[1].type.classifier}" } } else -> error("function ${kotlinFunction.name} must have one Event param") } lateinit var listener: Listener<*> kotlin.runCatching { kotlinFunction.isAccessible = true } suspend fun callFunction(event: Event): Any? { try { return when (param.size) { 3 -> { if (kotlinFunction.isSuspend) { kotlinFunction.callSuspend(owner, event, event) } else withContext(Dispatchers.IO) { // for safety kotlinFunction.call(owner, event, event) } } 2 -> { if (kotlinFunction.isSuspend) { kotlinFunction.callSuspend(owner, event) } else withContext(Dispatchers.IO) { // for safety kotlinFunction.call(owner, event) } } else -> error("stub") } } catch (e: IllegalCallableAccessException) { listener.completeExceptionally(e) return ListeningStatus.STOPPED } catch (e: Throwable) { throw ExceptionInEventHandlerException(event, cause = e) } } require(!kotlinFunction.returnType.isMarkedNullable) { "Kotlin event handlers cannot have nullable return type." } require(kotlinFunction.parameters.none { it.type.isMarkedNullable }) { "Kotlin event handlers cannot have nullable parameter type." } when (kotlinFunction.returnType.classifier) { Unit::class, Nothing::class -> { eventChannel.subscribeAlways( param[1].type.classifier as KClass<out Event>, coroutineContext, annotation.concurrency, annotation.priority ) { if (annotation.ignoreCancelled) { if ((this as? CancellableEvent)?.isCancelled != true) { callFunction(this) } } else callFunction(this) }.also { listener = it } } ListeningStatus::class -> { eventChannel.subscribe( param[1].type.classifier as KClass<out Event>, coroutineContext, annotation.concurrency, annotation.priority ) { if (annotation.ignoreCancelled) { if ((this as? CancellableEvent)?.isCancelled != true) { callFunction(this) as ListeningStatus } else ListeningStatus.LISTENING } else callFunction(this) as ListeningStatus }.also { listener = it } } else -> error("Illegal method return type. Required Void, Nothing or ListeningStatus, found ${kotlinFunction.returnType.classifier}") } } else { // java methods val paramType = method.parameterTypes[0] check(method.parameterTypes.size == 1 && Event::class.java.isAssignableFrom(paramType)) { "Illegal method parameter. Required one exact Event subclass. found ${method.parameterTypes.contentToString()}" } suspend fun callMethod(event: Event): Any? { fun Method.invokeWithErrorReport(self: Any?, vararg args: Any?): Any? = try { invoke(self, *args) } catch (exception: IllegalArgumentException) { throw IllegalArgumentException( "Internal Error: $exception, method=${this}, this=$self, arguments=$args, please report to https://github.com/mamoe/mirai", exception ) } catch (e: Throwable) { throw ExceptionInEventHandlerException(event, cause = e) } return if (annotation.ignoreCancelled) { if (event.castOrNull<CancellableEvent>()?.isCancelled != true) { withContext(Dispatchers.IO) { method.invokeWithErrorReport(owner, event) } } else ListeningStatus.LISTENING } else withContext(Dispatchers.IO) { method.invokeWithErrorReport(owner, event) } } when (method.returnType) { Void::class.java, Void.TYPE, Nothing::class.java -> { eventChannel.subscribeAlways( paramType.kotlin as KClass<out Event>, coroutineContext, annotation.concurrency, annotation.priority ) { callMethod(this) } } ListeningStatus::class.java -> { eventChannel.subscribe( paramType.kotlin as KClass<out Event>, coroutineContext, annotation.concurrency, annotation.priority ) { callMethod(this) as ListeningStatus? ?: error("Java method EventHandler cannot return `null`: $this") } } else -> error("Illegal method return type. Required Void or ListeningStatus, but found ${method.returnType.canonicalName}") } } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/event/VerboseEvent.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.event import net.mamoe.mirai.utils.MiraiInternalApi /** * 标记一个事件过于冗长, 默认不显示 */ @MiraiInternalApi public interface VerboseEvent // May be public? ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/event/messageSubscribersInternal.kt ================================================ /* * 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.event import net.mamoe.mirai.event.MessageDsl import net.mamoe.mirai.event.MessageListener import net.mamoe.mirai.event.MessageSubscribersBuilder import net.mamoe.mirai.event.events.MessageEvent /* * 将 internal 移出 MessageSubscribersBuilder.kt 以减小其体积 */ @MessageDsl internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.content( filter: M.(String) -> Boolean, onEvent: MessageListener<M, RR> ): Ret = subscriber(filter) { onEvent(this, it) } internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.endsWithImpl( suffix: String, removeSuffix: Boolean = true, trim: Boolean = true, onEvent: @MessageDsl suspend M.(String) -> R ): Ret { return if (trim) { val toCheck = suffix.trimEnd() content({ it.trimEnd().endsWith(toCheck) }) { if (removeSuffix) this.onEvent(this.message.contentToString().removeSuffix(toCheck).trim()) else onEvent(this, this.message.contentToString().trim()) } } else { content({ it.endsWith(suffix) }) { if (removeSuffix) this.onEvent(this.message.contentToString().removeSuffix(suffix)) else onEvent(this, this.message.contentToString()) } } } internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.startsWithImpl( prefix: String, removePrefix: Boolean = true, trim: Boolean = true, onEvent: @MessageDsl suspend M.(String) -> R ): Ret { return if (trim) { val toCheck = prefix.trimStart() content({ it.trimStart().startsWith(toCheck) }) { if (removePrefix) this.onEvent(this.message.contentToString().substringAfter(toCheck).trim()) else onEvent(this, this.message.contentToString().trim()) } } else content({ it.startsWith(prefix) }) { if (removePrefix) this.onEvent(this.message.contentToString().removePrefix(prefix)) else onEvent(this, this.message.contentToString()) } } internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsAllImpl( sub: Array<out String>, ignoreCase: Boolean = false, trim: Boolean = true ): MessageSubscribersBuilder<M, Ret, R, RR>.ListeningFilter = if (trim) { val list = sub.map { it.trim() } content { list.all { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } } else { content { sub.all { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } } internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsAnyImpl( vararg sub: String, ignoreCase: Boolean = false, trim: Boolean = true ): MessageSubscribersBuilder<M, Ret, R, RR>.ListeningFilter = if (trim) { val list = sub.map { it.trim() } content { list.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } } else content { sub.any { toCheck -> it.contains(toCheck, ignoreCase = ignoreCase) } } internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.caseImpl( equals: String, ignoreCase: Boolean = false, trim: Boolean = true ): MessageSubscribersBuilder<M, Ret, R, RR>.ListeningFilter { return if (trim) { val toCheck = equals.trim() content { it.trim().equals(toCheck, ignoreCase = ignoreCase) } } else { content { it.equals(equals, ignoreCase = ignoreCase) } } } internal fun <M : MessageEvent, Ret, R : RR, RR> MessageSubscribersBuilder<M, Ret, R, RR>.containsImpl( sub: String, ignoreCase: Boolean = false, trim: Boolean = true, onEvent: MessageListener<M, R> ): Ret { return if (trim) { val toCheck = sub.trim() content({ it.contains(toCheck, ignoreCase = ignoreCase) }) { onEvent(this, this.message.contentToString().trim()) } } else { content({ it.contains(sub, ignoreCase = ignoreCase) }) { onEvent(this, this.message.contentToString()) } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/message/AbstractPolymorphicSerializer.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:OptIn(ExperimentalSerializationApi::class) package net.mamoe.mirai.internal.message import kotlinx.serialization.* import kotlinx.serialization.encoding.* import net.mamoe.mirai.utils.cast import kotlin.jvm.JvmName import kotlin.reflect.KClass internal abstract class AbstractPolymorphicSerializer<T : Any> internal constructor() : KSerializer<T> { /** * Base class for all classes that this polymorphic serializer can serialize or deserialize. */ abstract val baseClass: KClass<T> final override fun serialize(encoder: Encoder, value: T) { val actualSerializer = findPolymorphicSerializer(encoder, value) encoder.encodeStructure(descriptor) { encodeStringElement(descriptor, 0, actualSerializer.descriptor.serialName) encodeSerializableElement(descriptor, 1, actualSerializer.cast(), value) } } final override fun deserialize(decoder: Decoder): T = decoder.decodeStructure(descriptor) { var klassName: String? = null var value: Any? = null if (decodeSequentially()) { return@decodeStructure decodeSequentially(this) } mainLoop@ while (true) { when (val index = decodeElementIndex(descriptor)) { CompositeDecoder.DECODE_DONE -> { break@mainLoop } 0 -> { klassName = decodeStringElement(descriptor, index) } 1 -> { klassName = requireNotNull(klassName) { "Cannot read polymorphic value before its type token" } val serializer = findPolymorphicSerializer(this, klassName) value = decodeSerializableElement(descriptor, index, serializer) } else -> throw SerializationException( "Invalid index in polymorphic deserialization of " + (klassName ?: "unknown class") + "\n Expected 0, 1 or DECODE_DONE(-1), but found $index" ) } } @Suppress("UNCHECKED_CAST") requireNotNull(value) { "Polymorphic value has not been read for class $klassName" } as T } private fun decodeSequentially(compositeDecoder: CompositeDecoder): T { val klassName = compositeDecoder.decodeStringElement(descriptor, 0) val serializer = findPolymorphicSerializer(compositeDecoder, klassName) return compositeDecoder.decodeSerializableElement(descriptor, 1, serializer) } /** * Lookups an actual serializer for given [klassName] withing the current [base class][baseClass]. * May use context from the [decoder]. */ open fun findPolymorphicSerializerOrNull( decoder: CompositeDecoder, klassName: String? ): DeserializationStrategy<T>? = decoder.serializersModule.getPolymorphic(baseClass, klassName) /** * Lookups an actual serializer for given [value] within the current [base class][baseClass]. * May use context from the [encoder]. */ open fun findPolymorphicSerializerOrNull( encoder: Encoder, value: T ): SerializationStrategy<T>? = encoder.serializersModule.getPolymorphic(baseClass, value) } internal fun <T : Any> AbstractPolymorphicSerializer<T>.findPolymorphicSerializer( decoder: CompositeDecoder, klassName: String? ): DeserializationStrategy<T> = findPolymorphicSerializerOrNull(decoder, klassName) ?: throwSubtypeNotRegistered(klassName, baseClass) internal fun <T : Any> AbstractPolymorphicSerializer<T>.findPolymorphicSerializer( encoder: Encoder, value: T ): SerializationStrategy<T> = findPolymorphicSerializerOrNull(encoder, value) ?: throwSubtypeNotRegistered(value::class, baseClass) @JvmName("throwSubtypeNotRegistered") internal fun throwSubtypeNotRegistered(subClassName: String?, baseClass: KClass<*>): Nothing { val scope = "in the scope of '${baseClass.simpleName}'" throw SerializationException( if (subClassName == null) "Class discriminator was missing and no default polymorphic serializers were registered $scope" else "Class '$subClassName' is not registered for polymorphic serialization $scope.\n" + "Mark the base class as 'sealed' or register the serializer explicitly." ) } @JvmName("throwSubtypeNotRegistered") internal fun throwSubtypeNotRegistered(subClass: KClass<*>, baseClass: KClass<*>): Nothing = throwSubtypeNotRegistered(subClass.simpleName ?: "$subClass", baseClass) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/message/MessageSerializersImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.message import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.overwriteWith import kotlinx.serialization.modules.polymorphic import net.mamoe.mirai.Mirai import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.* import kotlin.reflect.KClass import kotlin.reflect.full.allSuperclasses import kotlin.reflect.full.isSubclassOf @MiraiInternalApi public open class MessageSourceSerializerImpl(serialName: String) : KSerializer<MessageSource> by SerialData.serializer().map( resultantDescriptor = buildClassSerialDescriptor(serialName) { takeElementsFrom(SerialData.serializer().descriptor) }, serialize = { SerialData(kind, botId, ids, internalIds, time, fromId, targetId, originalMessage) }, deserialize = { Mirai.constructMessageSource(botId, kind, fromId, targetId, ids, time, internalIds, originalMessage) } ) { @MiraiInternalApi public companion object { public fun serialDataSerializer(): KSerializer<*> = SerialData.serializer() } @SerialName(MessageSource.SERIAL_NAME) @Serializable internal class SerialData( val kind: MessageSourceKind, val botId: Long, val ids: IntArray, val internalIds: IntArray, val time: Int, val fromId: Long, val targetId: Long, val originalMessage: MessageChain, ) } // Tests: // net.mamoe.mirai.internal.message.data.MessageSerializationTest @OptIn(MiraiExperimentalApi::class) internal object MessageSerializersImpl : MessageSerializers { private var serializersModuleField: SerializersModule by lateinitMutableProperty { SerializersModule { } } override val serializersModule: SerializersModule get() { Mirai // ensure registered, for tests return serializersModuleField } @Synchronized override fun <M : SingleMessage> registerSerializer(type: KClass<M>, serializer: KSerializer<M>) { serializersModuleField = serializersModule.overwritePolymorphicWith(type, serializer) } @Synchronized override fun registerSerializers(serializersModule: SerializersModule) { serializersModuleField = this.serializersModule.overwriteWith(serializersModule) } } internal fun <M : Any> SerializersModule.overwritePolymorphicWith( type: KClass<M>, serializer: KSerializer<M> ): SerializersModule { return overwriteWith(SerializersModule { // 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) } } }) } //private inline fun <reified M : SingleMessage> SerializersModuleBuilder.hierarchicallyPolymorphic(serializer: KSerializer<M>) = // hierarchicallyPolymorphic(M::class, serializer) // //private fun <M : SingleMessage> SerializersModuleBuilder.hierarchicallyPolymorphic( // type: KClass<M>, // serializer: KSerializer<M> //) { // // 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) // } // } //} ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/network/Packet.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.internal.network import net.mamoe.mirai.event.Event import net.mamoe.mirai.utils.MiraiInternalApi /** * 从服务器收到的包解析之后的结构化数据. * 它是一个数据包工厂的处理的返回值. * * **InternalAPI**: 这是内部 API, 它随时都有可能被修改 */ @MiraiInternalApi public interface Packet { /** * 实现这个接口的包将不会被记录到日志中 */ @MiraiInternalApi public interface NoLog /** * 实现这个接口的 [Event] 不会被作为事件记录到日志中 */ @MiraiInternalApi public interface NoEventLog } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/utils/ExternalResourceImpls.kt ================================================ /* * 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.utils import io.ktor.utils.io.core.Input import io.ktor.utils.io.streams.* import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import net.mamoe.mirai.utils.* import java.io.Closeable import java.io.InputStream import java.io.RandomAccessFile import kotlin.io.use internal fun InputStream.detectFileTypeAndClose(): String? { val buffer = ByteArray(COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE) return use { kotlin.runCatching { it.read(buffer) }.onFailure { return null } getFileType(buffer) } } internal class ExternalResourceImplByFileWithMd5( private val file: RandomAccessFile, override val md5: ByteArray, formatName: String? ) : ExternalResourceInternal { internal class ResourceHolder( @JvmField internal val file: RandomAccessFile, ) : ExternalResourceHolder() { override val closed: CompletableDeferred<Unit> = CompletableDeferred() override fun closeImpl() { file.close() } } override var origin: Any? = null internal set override val holder: ResourceHolder = ResourceHolder(file) override val sha1: ByteArray by lazy { inputStream().sha1() } override val size: Long = file.length() override val formatName: String by lazy { formatName ?: inputStream().detectFileTypeAndClose() ?: ExternalResource.DEFAULT_FORMAT_NAME } override fun inputStream(): InputStream { check(file.filePointer == 0L) { "RandomAccessFile.inputStream cannot be opened simultaneously." } return file.inputStream() } override val closed: CompletableDeferred<Unit> get() = holder.closed override fun close() = holder.close() init { registerToLeakObserver(this) } } internal abstract class ExternalResourceHolder : Closeable { /** * Mirror of [ExternalResource.closed] */ abstract val closed: Deferred<Unit> val isClosed: Boolean get() = _closed.value val createStackTrace: Array<StackTraceElement>? = if (isExternalResourceCreationStackEnabled) { Thread.currentThread().stackTrace } else null private val _closed = atomic(false) protected abstract fun closeImpl() override fun close() { if (!_closed.compareAndSet(false, true)) return try { closeImpl() } finally { kotlin.runCatching { val closed = this.closed if (closed is CompletableDeferred<Unit>) { closed.complete(Unit) } else { closed.cancel() } } } } } internal interface ExternalResourceInternal : ExternalResource { val holder: ExternalResourceHolder @MiraiInternalApi override fun input(): Input = inputStream().asInput() } internal class ExternalResourceImplByFile( private val file: RandomAccessFile, formatName: String?, closeOriginalFileOnClose: Boolean = true, ) : ExternalResourceInternal { override var origin: Any? = null internal set internal class ResourceHolder( @JvmField internal val closeOriginalFileOnClose: Boolean, @JvmField internal val file: RandomAccessFile, ) : ExternalResourceHolder() { override val closed: CompletableDeferred<Unit> = CompletableDeferred() override fun closeImpl() { if (closeOriginalFileOnClose) file.close() } } override val holder: ResourceHolder = ResourceHolder( closeOriginalFileOnClose, file, ) override val size: Long = file.length() override val md5: ByteArray by lazy { inputStream().md5() } override val sha1: ByteArray by lazy { inputStream().sha1() } override val formatName: String by lazy { formatName ?: inputStream().detectFileTypeAndClose() ?: ExternalResource.DEFAULT_FORMAT_NAME } override fun inputStream(): InputStream { check(file.filePointer == 0L) { "RandomAccessFile.inputStream cannot be opened simultaneously." } return file.inputStream() } override val closed: CompletableDeferred<Unit> get() = holder.closed override fun close() = holder.close() init { registerToLeakObserver(this) } } internal class ExternalResourceImplByByteArray( private val data: ByteArray, formatName: String? ) : ExternalResource { override val size: Long = data.size.toLong() override val md5: ByteArray by lazy { data.md5() } override val sha1: ByteArray by lazy { data.sha1() } override val formatName: String by lazy { formatName ?: getFileType(data.copyOf(COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE)) ?: ExternalResource.DEFAULT_FORMAT_NAME } override val closed: CompletableDeferred<Unit> = CompletableDeferred() override val origin: Any get() = data//.clone() override fun inputStream(): InputStream = data.inputStream() @MiraiInternalApi override fun input(): Input { return data.inputStream().asInput() } override fun close() { kotlin.runCatching { closed.complete(Unit) } } } private fun RandomAccessFile.inputStream(): InputStream { val file = this return object : InputStream() { override fun read(): Int = file.read() override fun read(b: ByteArray, off: Int, len: Int): Int = file.read(b, off, len) override fun close() { file.seek(0) } // don't close file on stream.close. stream may be obtained at multiple times. }.buffered() } private fun registerToLeakObserver(resource: ExternalResourceInternal) { ExternalResourceLeakObserver.register(resource) } internal const val isExternalResourceCreationStackEnabledName = "mirai.resource.creation.stack.enabled" internal val isExternalResourceCreationStackEnabled by lazy { systemProp(isExternalResourceCreationStackEnabledName, false) } /* * ImgType: * JPG: 1000 * PNG: 1001 * WEBP: 1002 * BMP: 1005 * GIG: 2000 // gig? gif? * APNG: 2001 * SHARPP: 1004 */ ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/utils/ExternalResourceLeakObserver.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.error import net.mamoe.mirai.utils.warning import java.lang.ref.ReferenceQueue import java.lang.ref.WeakReference import java.util.concurrent.ConcurrentLinkedDeque internal object ExternalResourceLeakObserver : Runnable { private val queue = ReferenceQueue<Any>() private val references = ConcurrentLinkedDeque<ERReference>() private val logger by lazy { MiraiLogger.Factory.create(ExternalResourceLeakObserver::class, "ExternalResourceLeakObserver") } internal class ERReference : WeakReference<Any> { constructor(resource: ExternalResourceInternal) : super(resource, queue) { this.holder = resource.holder } constructor(resource: ExternalResource, holder: ExternalResourceHolder) : super(resource, queue) { this.holder = holder } @JvmField internal val holder: ExternalResourceHolder } class ExternalResourceCreateStackTrace : Throwable() { override fun fillInStackTrace(): Throwable { return this } } @JvmStatic fun register(resource: ExternalResource) { if (resource !is ExternalResourceInternal) return references.add(ERReference(resource)) } @JvmStatic fun register(resource: ExternalResource, holder: ExternalResourceHolder) { references.add(ERReference(resource, holder)) } init { val thread = Thread(this, "Mirai ExternalResource Leak Observer Thread") thread.isDaemon = true thread.start() } override fun run() { while (true) { try { loop@ while (true) { val reference = queue.poll() ?: break@loop if (reference !is ERReference) { logger.warning { "Unknown reference $reference (#${reference.javaClass}) was entered queue. Skipping" } reference.clear() continue@loop } val holder = reference.holder reference.clear() references.remove(reference) if (holder.isClosed) { continue@loop } val stackException = holder.createStackTrace?.let { stack -> ExternalResourceCreateStackTrace().also { it.stackTrace = stack } } kotlin.runCatching { // Observer should avoid all possible errors logger.error( { "A resource leak occurred, use ExternalResource.close to avoid it!! (holder=$holder)" + if (isExternalResourceCreationStackEnabled) { "" } else ". Add jvm option `-D$isExternalResourceCreationStackEnabledName=true` to show creation stack track" }, stackException ) } try { holder.close() } catch (exceptionInClose: Throwable) { kotlin.runCatching { // Observer should avoid all possible errors logger.error( { "Exception in closing a leaked resource (holder=$holder)" }, exceptionInClose.also { if (stackException != null) { it.addSuppressed(stackException) } } ) } } } } catch (throwable: Throwable) { kotlin.runCatching { // Observer should avoid all possible errors logger.error( "Exception in queue loop", throwable ) } } Thread.sleep(60 * 1000L) } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/utils/LoggerAdapterImpls.kt ================================================ /* * 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.utils import net.mamoe.mirai.utils.MiraiLoggerPlatformBase import org.apache.logging.log4j.Marker import org.apache.logging.log4j.MarkerManager import org.apache.logging.log4j.message.Message import org.apache.logging.log4j.message.ReusableMessageFactory import org.apache.logging.log4j.message.ReusableSimpleMessage import java.util.logging.Level as JulLevel import java.util.logging.Logger as JulLogger private inline fun ReusableMessageFactory.takeMessage(message: String?, crossinline block: (message: Message) -> Unit) { val msg = this.newMessage(message) as ReusableSimpleMessage block(msg) msg.clear() } internal class Log4jLoggerAdapter( private val logger: org.apache.logging.log4j.Logger, override val marker: Marker?, ) : MiraiLoggerPlatformBase(), MarkedMiraiLogger { val factory: ReusableMessageFactory = ReusableMessageFactory.INSTANCE override fun verbose0(message: String?, e: Throwable?) { factory.takeMessage(message) { logger.trace(marker, it, e) } } override fun debug0(message: String?, e: Throwable?) { factory.takeMessage(message) { logger.debug(marker, it, e) } } override fun info0(message: String?, e: Throwable?) { factory.takeMessage(message) { logger.info(marker, it, e) } } override fun warning0(message: String?, e: Throwable?) { factory.takeMessage(message) { logger.warn(marker, it, e) } } override fun error0(message: String?, e: Throwable?) { factory.takeMessage(message) { logger.error(marker, it, e) } } override val isVerboseEnabled: Boolean get() = logger.isTraceEnabled override val isDebugEnabled: Boolean get() = logger.isDebugEnabled override val isInfoEnabled: Boolean get() = logger.isInfoEnabled override val isWarningEnabled: Boolean get() = logger.isWarnEnabled override val isErrorEnabled: Boolean get() = logger.isErrorEnabled override val identity: String? get() = logger.name override fun subLogger(name: String): MarkedMiraiLogger { return Log4jLoggerAdapter(logger, Marker(name, marker)) } } internal val MARKER_MIRAI by lazy { MarkerManager.getMarker("mirai") } internal class Slf4jLoggerAdapter(private val logger: org.slf4j.Logger, private val marker: org.slf4j.Marker?) : MiraiLoggerPlatformBase() { override fun verbose0(message: String?, e: Throwable?) { if (marker == null) logger.trace(message, e) else logger.trace(marker, message, e) } override fun debug0(message: String?, e: Throwable?) { if (marker == null) logger.debug(message, e) else logger.debug(marker, message, e) } override fun info0(message: String?, e: Throwable?) { if (marker == null) logger.info(message, e) else logger.info(marker, message, e) } override fun warning0(message: String?, e: Throwable?) { if (marker == null) logger.warn(message, e) else logger.warn(marker, message, e) } override fun error0(message: String?, e: Throwable?) { if (marker == null) logger.error(message, e) else logger.error(marker, message, e) } override val isVerboseEnabled: Boolean get() = logger.isTraceEnabled override val isDebugEnabled: Boolean get() = logger.isDebugEnabled override val isInfoEnabled: Boolean get() = logger.isInfoEnabled override val isWarningEnabled: Boolean get() = logger.isWarnEnabled override val isErrorEnabled: Boolean get() = logger.isErrorEnabled override val identity: String? get() = logger.name } internal class JdkLoggerAdapter(private val logger: JulLogger) : MiraiLoggerPlatformBase() { override fun verbose0(message: String?, e: Throwable?) { logger.log(JulLevel.FINEST, message, e) } override fun debug0(message: String?, e: Throwable?) { logger.log(JulLevel.FINER, message, e) } override fun info0(message: String?, e: Throwable?) { logger.log(JulLevel.INFO, message, e) } override fun warning0(message: String?, e: Throwable?) { logger.log(JulLevel.WARNING, message, e) } override fun error0(message: String?, e: Throwable?) { logger.log(JulLevel.SEVERE, message, e) } override val isVerboseEnabled: Boolean get() = logger.isLoggable(JulLevel.FINE) override val isDebugEnabled: Boolean get() = logger.isLoggable(JulLevel.FINEST) override val isInfoEnabled: Boolean get() = logger.isLoggable(JulLevel.INFO) override val isWarningEnabled: Boolean get() = logger.isLoggable(JulLevel.WARNING) override val isErrorEnabled: Boolean get() = logger.isLoggable(JulLevel.SEVERE) override val identity: String? get() = logger.name } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/utils/MarkedMiraiLogger.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import net.mamoe.mirai.utils.MiraiLogger /** * 内部添加 [Marker] 支持, 并兼容旧 [MiraiLogger] API. */ internal interface MarkedMiraiLogger : MiraiLogger { val marker: Marker? /** * Create an implementation-specific [MarkedMiraiLogger]. * * Do not call the extension `MiraiLogger.subLogger` inside the function body. */ fun subLogger(name: String): MarkedMiraiLogger } internal fun Marker(name: String, parents: Marker?): Marker { return MarkerManager.getMarker(name).apply { if (parents != null) addParents(parents) } } internal fun Marker(name: String, vararg parents: Marker?): Marker { return MarkerManager.getMarker(name).apply { parents.forEach { if (it != null) addParents(it) } } } internal val MiraiLogger.markerOrNull get() = (this as? MarkedMiraiLogger)?.marker // This clashes with the same declaration in mirai-core (same package), for native. // ///** // * Create a marked logger whose marker is a child of this' marker. // * // * Calling [MarkedMiraiLogger.subLogger] if possible, and creating [MiraiLoggerMarkedWrapper] otherwise. // */ //@JvmName("subLoggerImpl2") //@CName("", "subLogger2") //internal fun MiraiLogger.subLogger(name: String): MiraiLogger { // return subLoggerImpl(this, name) //} // used by mirai-core internal fun subLoggerImpl(origin: MiraiLogger, name: String): MiraiLogger { return if (origin is MarkedMiraiLogger) { // origin can be Log4JAdapter or MiraiLoggerMarkedWrapper which delegates a non-Log4JAdapter. origin.subLogger(name) // Log4JAdapter natively supports Markers. } else { return origin // origin will never use the MiraiLoggerMarkedWrapper.marker so wrapping it is meaningless. } } /** * 仅当日志系统使用的不是 Log4J 时才会构造 [MiraiLoggerMarkedWrapper]. */ private class MiraiLoggerMarkedWrapper( val origin: MiraiLogger, override val marker: Marker ) : MiraiLogger by origin, MarkedMiraiLogger { override fun subLogger(name: String): MarkedMiraiLogger = MiraiLoggerMarkedWrapper(origin, Marker(name, marker)) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/utils/Marker.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.internal.utils import org.apache.logging.log4j.MarkerManager internal typealias Marker = org.apache.logging.log4j.Marker internal object MarkerManager { fun getMarker(name: String): Marker { return MarkerManager.getMarker(name) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/internal/utils/StdoutLogger.kt ================================================ /* * 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.utils import net.mamoe.mirai.utils.* /** * JVM 控制台日志实现 * * * 单条日志格式 (正则) 为: * ```regex * ^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$ * ``` * 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容. * * 示例: * ```log * 2020-05-21 19:51:09 V/Bot 123456789: Send: OidbSvc.0x88d_7 * ``` * * 日期时间格式为 `yyyy-MM-dd HH:mm:ss`, * * 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error * * @param isColored 是否添加 ANSI 颜色 * * @see MiraiLogger.create * @see SingleFileLogger 使用单一文件记录日志 * @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志 */ @OptIn(MiraiExperimentalApi::class) internal open class StdoutLogger constructor( override val identity: String? = "Mirai", /** * 日志输出. 不会自动添加换行 */ open val output: (String) -> Unit, val isColored: Boolean = true ) : MiraiLoggerPlatformBase() { constructor(identity: String?) : this(identity, ::println) constructor(identity: String?, output: (String) -> Unit) : this(identity, output, true) /** * 输出一条日志. [message] 末尾可能不带换行符. */ protected open fun printLog(message: String?, priority: SimpleLogger.LogPriority) { if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message${Color.RESET}") else output("$currentTimeFormatted ${priority.simpleName}/$identity: $message") } /** * 获取指定 [SimpleLogger.LogPriority] 的颜色 */ protected open val SimpleLogger.LogPriority.color: Color get() = when (this) { SimpleLogger.LogPriority.VERBOSE -> Color.RESET SimpleLogger.LogPriority.INFO -> Color.LIGHT_GREEN SimpleLogger.LogPriority.WARNING -> Color.LIGHT_YELLOW SimpleLogger.LogPriority.ERROR -> Color.RED SimpleLogger.LogPriority.DEBUG -> Color.LIGHT_CYAN } public override fun verbose0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.VERBOSE) public override fun verbose0(message: String?, e: Throwable?) { if (e != null) verbose((message ?: e.toString()) + "\n${e.stackTraceToString()}") else verbose(message.toString()) } public override fun info0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.INFO) public override fun info0(message: String?, e: Throwable?) { if (e != null) info((message ?: e.toString()) + "\n${e.stackTraceToString()}") else info(message.toString()) } public override fun warning0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.WARNING) public override fun warning0(message: String?, e: Throwable?) { if (e != null) warning((message ?: e.toString()) + "\n${e.stackTraceToString()}") else warning(message.toString()) } public override fun error0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.ERROR) public override fun error0(message: String?, e: Throwable?) { if (e != null) error((message ?: e.toString()) + "\n${e.stackTraceToString()}") else error(message.toString()) } public override fun debug0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.DEBUG) public override fun debug0(message: String?, e: Throwable?) { if (e != null) debug((message ?: e.toString()) + "\n${e.stackTraceToString()}") else debug(message.toString()) } private val currentTimeFormatted get() = currentTimeFormatted(null) @MiraiExperimentalApi("This is subject to change.") protected enum class Color(private val format: String) { RESET("\u001b[0m"), WHITE("\u001b[30m"), RED("\u001b[31m"), EMERALD_GREEN("\u001b[32m"), GOLD("\u001b[33m"), BLUE("\u001b[34m"), PURPLE("\u001b[35m"), GREEN("\u001b[36m"), GRAY("\u001b[90m"), LIGHT_RED("\u001b[91m"), LIGHT_GREEN("\u001b[92m"), LIGHT_YELLOW("\u001b[93m"), LIGHT_BLUE("\u001b[94m"), LIGHT_PURPLE("\u001b[95m"), LIGHT_CYAN("\u001b[96m") ; override fun toString(): String = format } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/MessageReceipt.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "FunctionName", "unused") @file:JvmBlockingBridge package net.mamoe.mirai.message import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.action.AsyncRecallResult import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.MessageSource.Key.recallIn import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance /** * 发送消息后得到的回执. 可用于撤回, 引用回复等. * * @param source 指代发送出去的消息 * @param target 消息发送对象 * * @see quote 引用这条消息. 即引用机器人自己发出去的消息 * @see quoteReply 引用并回复这条消息. * @see recallMessage 撤回这条消息 * * @see Group.sendMessage 发送群消息, 返回回执(此对象) * @see User.sendMessage 发送群消息, 返回回执(此对象) * @see Member.sendMessage 发送临时消息, 返回回执(此对象) * * @see MessageReceipt.sourceIds 源 ids * @see MessageReceipt.sourceTime 源时间 */ @NotStableForInheritance public open class MessageReceipt<out C : Contact> @MiraiInternalApi @Deprecated("Do not call it directly", level = DeprecationLevel.ERROR) constructor( /** * 指代发送出去的消息. */ public val source: OnlineMessageSource.Outgoing, /** * 发送目标, 为 [Group] 或 [Friend] 或 [Member] */ public val target: C, ) { /** * 是否为发送给群的消息的回执 */ public val isToGroup: Boolean get() = target is Group /** * 撤回这条消息. * * @see IMirai.recallMessage */ public suspend inline fun recall() { return Mirai.recallMessage(target.bot, source) } /** * 在一段时间后撤回这条消息. * * @see IMirai.recallMessage */ @Suppress("DeferredIsResult") public fun recallIn(millis: Long): AsyncRecallResult = this.source.recallIn(millis) /** * 引用这条消息. * @see MessageSource.quote 引用一条消息 */ public fun quote(): QuoteReply = this.source.quote() /** * 引用这条消息并回复. * @see MessageSource.quote 引用一条消息 */ public suspend inline fun quoteReply(message: Message): MessageReceipt<C> { @Suppress("UNCHECKED_CAST") return target.sendMessage(this.quote() + message) as MessageReceipt<C> } /** * 引用这条消息并回复. * @see MessageSource.quote 引用一条消息 */ public suspend inline fun quoteReply(message: String): MessageReceipt<C> { return this.quoteReply(PlainText(message)) } public companion object } /** * 获取相关 [Bot] */ public inline val MessageReceipt<*>.bot: Bot get() = target.bot /** * 获取源消息 [MessageSource.ids] */ public inline val MessageReceipt<*>.sourceIds: IntArray get() = this.source.ids /** * 获取源消息 [MessageSource.internalIds] */ public inline val MessageReceipt<*>.sourceInternalIds: IntArray get() = this.source.internalIds /** * 获取源消息 [MessageSource.time] */ public inline val MessageReceipt<*>.sourceTime: Int get() = this.source.time /** * 获取源消息 [MessageSource.originalMessage] */ public inline val MessageReceipt<*>.sourceMessage: MessageChain get() = this.source.originalMessage ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/MessageSerializers.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ 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.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 import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.message.data.SingleMessage import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.NotStableForInheritance import kotlin.reflect.KClass /** * 消息序列化器. * * [MessageSerializers] 存放 [SerializersModule], 用于协助 [SingleMessage] [PolymorphicSerializer] 的多态序列化. * * 要序列化一个 [MessageChain], 请使用内建的 [MessageChain.serializeToJsonString] * * @see serializersModule * * * @see MessageChain.Serializer * * @see MessageSerializers.INSTANCE */ @NotStableForInheritance public interface MessageSerializers { /** * 包含 [SingleMessage] 多态序列化和 [Message] [ContextualSerializer] 信息的 [SerializersModule]. * * 在序列化消息时都需要提供给相关 [Json] 配置的 [Json.serializersModule]. 如通过: * ``` * val json = Json { * serializesModule = MessageSerializers.serializersModule * } * ``` */ public val serializersModule: SerializersModule /** * 注册 [serializer] 到 [type] 的所有为 [SingleMessage] 子类型的超类型的多态域 [PolymorphicModuleBuilder.subclass] * * 实现: * ``` * 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( type: KClass<M>, serializer: KSerializer<M> ) // not supported on native. /** * 合并 [serializersModule] 到 [MessageSerializers.serializersModule] 并覆盖. */ @MiraiExperimentalApi public fun registerSerializers(serializersModule: SerializersModule) // supported on all platforms. public companion object INSTANCE : MessageSerializers by MessageSerializersImpl } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/action/AsyncRecallResult.kt ================================================ /* * 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:JvmBlockingBridge package net.mamoe.mirai.message.action import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.future.asCompletableFuture import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.message.data.MessageSource.Key.recallIn import java.util.concurrent.CompletableFuture /** * 异步撤回结果. * * 可由 [MessageSource.recallIn] 返回得到. * * ## Kotlin 用法示例 * * ### 获取撤回失败时的异常 * * ``` * val exception = result.exception.await() // 挂起协程并等待撤回的结果. * if (exception == null) { * // 撤回成功 * } else { * // 撤回失败 * } * ``` * * 若仅需要了解撤回是否成功而不需要获取详细异常实例, 可使用 [isSuccess] * * ## Java 用法示例 * * ```java * Throwable exception = result.exceptionFuture.get(); // 阻塞线程并等待撤回的结果. * if (exception == null) { * // 撤回成功 * } else { * // 撤回失败 * } * ``` * * @see MessageSource.recallIn */ public class AsyncRecallResult internal constructor( /** * 撤回时产生的异常, 当撤回成功时为 `null`. Kotlin [Deferred] API. */ public val exception: Deferred<Throwable?>, ) { /** * 撤回时产生的异常, 当撤回成功时为 `null`. Java [CompletableFuture] API. */ public val exceptionFuture: CompletableFuture<Throwable?> by lazy { exception.asCompletableFuture() } /** * 撤回是否成功. Kotlin [Deferred] API. */ public val isSuccess: Deferred<Boolean> by lazy { CompletableDeferred<Boolean>().apply { exception.invokeOnCompletion { complete(it == null) } } } /** * 撤回是否成功. Java [CompletableFuture] API. */ public val isSuccessFuture: CompletableFuture<Boolean> by lazy { isSuccess.asCompletableFuture() } /** * 挂起协程 (在 Java 为阻塞线程) 直到撤回完成, 返回撤回时产生的异常. 当撤回成功时返回 `null`. */ public suspend fun awaitException(): Throwable? { return exception.await() } /** * 挂起协程 (在 Java 为阻塞线程) 直到撤回完成, 返回撤回的结果. */ public suspend fun awaitIsSuccess(): Boolean { return isSuccess.await() } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/action/Nudge.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.action import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.NudgeEvent import net.mamoe.mirai.message.data.PokeMessage import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.BotConfiguration.MiraiProtocol import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * 一个 "戳一戳" 动作. * * 备注: 这类似微信拍一拍. 消息对话框中显示的 "一个手指" 的戳一戳是 [PokeMessage] * * 仅在手机 QQ 8.4.0 左右版本才受支持. 其他客户端会忽略这些消息. * * 示例,要机器人戳一个群员并发送到群里,使用 `member.nudge().sendTo(group)`. * 要机器人戳一个好友并发送给该好友,使用 `friend.nudge().sendTo(friend)`. * * @see UserOrBot.nudge 创建 [Nudge] 对象 */ public sealed class Nudge { /** * 戳的对象. 即 "A 戳了 B" 中的 "B". */ public abstract val target: UserOrBot /** * 发送戳一戳消息到 [receiver]. * * 需要使用支持的[协议][BotConfiguration.protocol] [MiraiProtocol.ANDROID_PHONE], [ANDROID_PAD 协议][MiraiProtocol.ANDROID_PAD] 或 [MiraiProtocol.IPAD]. * + 自 2.10 起才支持使用 IPAD 协议发送. * + 自 2.16.0-RC 起才支持使用 PAD 协议发送. * * @param receiver 这条 "戳一戳" 消息的接收对象. (不是 "戳" 动作的对象, 而是接收 "A 戳了 B" 这条消息的对象) * @return 成功发送时为 `true`. 若对方禁用 "戳一戳" 功能, 返回 `false`. * @throws UnsupportedOperationException 当未使用 [ANDROID_PHONE 协议][MiraiProtocol.ANDROID_PHONE], [ANDROID_PAD 协议][MiraiProtocol.ANDROID_PAD] 或 [IPAD 协议][MiraiProtocol.IPAD] 时抛出 * * @see NudgeEvent 事件 * @see Contact.sendNudge */ @JvmBlockingBridge public suspend fun sendTo(receiver: Contact): Boolean { @Suppress("DEPRECATION_ERROR") return Mirai.sendNudge(receiver.bot, this, receiver) } public companion object { /** * 发送戳一戳消息. * * 需要使用支持的[协议][BotConfiguration.protocol] [MiraiProtocol.ANDROID_PHONE], [ANDROID_PAD 协议][MiraiProtocol.ANDROID_PAD] 或 [MiraiProtocol.IPAD]. * + 自 2.10 起才支持使用 IPAD 协议发送. * + 自 2.16.0-RC 起才支持使用 PAD 协议发送. * * @return 成功发送时为 `true`. 若对方禁用 "戳一戳" 功能或今日 "戳一戳" 次数已达到上限, 返回 `false`. * * @throws UnsupportedOperationException 当未使用 [ANDROID_PHONE 协议][MiraiProtocol.ANDROID_PHONE], [ANDROID_PAD 协议][MiraiProtocol.ANDROID_PAD] 或 [IPAD 协议][MiraiProtocol.IPAD] 时抛出 * * @see NudgeEvent 事件 */ @JvmSynthetic @JvmStatic public suspend fun Contact.sendNudge(nudge: Nudge): Boolean = nudge.sendTo(this) } } /** * @see Bot.nudge * @see Nudge */ public data class BotNudge( public override val target: Bot ) : Nudge() /** * @see User.nudge * @see Nudge */ public sealed class UserNudge : Nudge() { public abstract override val target: UserOrBot } /** * @see Member.nudge * @see Nudge */ public data class MemberNudge( public override val target: NormalMember ) : UserNudge() /** * @see Friend.nudge * @see Nudge */ public data class FriendNudge( public override val target: Friend ) : UserNudge() /** * @see Stranger.nudge * @see Nudge */ public data class StrangerNudge( public override val target: Stranger ) : UserNudge() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/code/CodableMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.message.code import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.NotStableForInheritance /** * 可以使用 mirai 码表示的 [Message] 类型. * * 从字符串解析 mirai 码:[MiraiCode.deserializeMiraiCode] * * ### 不适合第三方实现 * 即使自定义消息类型实现了接口 [CodableMessage], 在 [MiraiCode.deserializeMiraiCode] 时也无法解析消息类型. 因此实现没有意义. * * @see MiraiCode */ @NotStableForInheritance public interface CodableMessage : Message { /** * 转换为 mirai 码. */ public fun serializeToMiraiCode(): String = buildString { @OptIn(MiraiExperimentalApi::class) appendMiraiCodeTo(this) } // Using StringBuilder faster than direct plus objects @MiraiExperimentalApi public fun appendMiraiCodeTo(builder: StringBuilder) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/code/MiraiCode.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.message.code import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.code.internal.parseMiraiCodeImpl import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain.Companion.deserializeFromMiraiCode import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * Mirai 码相关操作. * * 可在 GitHub 查看 [详细文档](https://github.com/mamoe/mirai/blob/dev/docs/Messages.md#mirai-%E7%A0%81). */ public object MiraiCode { /** * 解析形如 "[mirai:]" 的 mirai 码, 即 [CodableMessage.serializeToMiraiCode] 返回的内容. * @see MessageChain.deserializeFromMiraiCode */ @JvmName("parseMiraiCode1") @JvmSynthetic public fun String.deserializeMiraiCode(contact: Contact? = null): MessageChain = deserializeMiraiCode(this, contact) /** * 解析形如 "[mirai:]" 的 mirai 码, 即 [CodableMessage.serializeToMiraiCode] 返回的内容. * @see MessageChain.deserializeFromMiraiCode */ @JvmOverloads @JvmStatic public fun deserializeMiraiCode(code: String, contact: Contact? = null): MessageChain = code.parseMiraiCodeImpl(contact) /** * 转换得到 mirai 码. */ @JvmStatic public fun Iterable<Message>.serializeToMiraiCode(): String = iterator().serializeToMiraiCode() /** * 转换得到 mirai 码. */ @JvmStatic public fun Sequence<Message>.serializeToMiraiCode(): String = iterator().serializeToMiraiCode() /** * 转换得到 mirai 码. */ @JvmStatic public fun Array<out Message>.serializeToMiraiCode(): String = iterator().serializeToMiraiCode() /** * 转换得到 mirai 码. */ @JvmStatic public fun Iterator<Message>.serializeToMiraiCode(): String = buildString { @OptIn(MiraiExperimentalApi::class) this@serializeToMiraiCode.forEach { it.safeCast<CodableMessage>()?.appendMiraiCodeTo(this) } } /** * 转换得到 mirai 码. * @see CodableMessage.serializeToMiraiCode */ @Suppress("EXTENSION_SHADOWED_BY_MEMBER")// for better Java API. @JvmStatic public fun CodableMessage.serializeToMiraiCode(): String = this.serializeToMiraiCode() // member function } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/code/internal/impl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER") package net.mamoe.mirai.message.code.internal import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi internal fun String.parseMiraiCodeImpl(contact: Contact?): MessageChain = buildMessageChain { forEachMiraiCode { origin, name, args -> if (name == null) { add(PlainText(origin.decodeMiraiCode())) return@forEachMiraiCode } val parser = MiraiCodeParsers[name] ?: kotlin.run { add(PlainText(origin.decodeMiraiCode())) return@forEachMiraiCode } parser.parse(contact, args) ?.let(::add) ?: add(PlainText(origin.decodeMiraiCode())) } } private fun String.forEachMiraiCode(block: (origin: String, name: String?, args: String) -> Unit) { var pos = 0 var lastPos = 0 val len = length - 7 // [mirai: fun findEnding(start: Int): Int { var pos0 = start while (pos0 < length) { when (get(pos0)) { '\\' -> pos0 += 2 ']' -> return pos0 else -> pos0++ } } return -1 } while (pos < len) { when (get(pos)) { '\\' -> { pos += 2 } '[' -> { if (get(pos + 1) == 'm' && get(pos + 2) == 'i' && get(pos + 3) == 'r' && get(pos + 4) == 'a' && get(pos + 5) == 'i' && get(pos + 6) == ':' ) { val begin = pos pos += 7 val ending = findEnding(pos) if (ending == -1) { block(substring(lastPos), null, "") return } else { if (lastPos < begin) { block(substring(lastPos, begin), null, "") } val v = substring(begin, ending + 1) val splitter = v.indexOf(':', 7) block( v, if (splitter == -1) v.substring(7, v.length - 1) else v.substring(7, splitter), if (splitter == -1) { "" } else v.substring(splitter + 1, v.length - 1) ) lastPos = ending + 1 pos = lastPos } } else pos++ } else -> { pos++ } } } if (lastPos < length) { block(substring(lastPos), null, "") } } @OptIn(MiraiInternalApi::class, MiraiExperimentalApi::class) @Suppress("DELEGATED_MEMBER_HIDES_SUPERTYPE_OVERRIDE") private object MiraiCodeParsers : AbstractMap<String, MiraiCodeParser>(), Map<String, MiraiCodeParser> by mapOf( "at" to MiraiCodeParser(Regex("""(\d*)""")) { (target) -> At(target.toLong()) }, "atall" to MiraiCodeParser(Regex("")) { AtAll }, "poke" to MiraiCodeParser(Regex("(.*)?,(\\d*),(-?\\d*)")) { (name, type, id) -> PokeMessage(name, type.toInt(), id.toInt()) }, "vipface" to MiraiCodeParser(Regex("""(\d*),(.*),(\d*)""")) { (id, name, count) -> VipFace(VipFace.Kind(id.toInt(), name), count.toInt()) }, "face" to MiraiCodeParser(Regex("""(\d*)""")) { (id) -> Face(id.toInt()) }, "superface" to MiraiCodeParser(Regex("""(\d*),(.*),(\d*)""")) { (face, id, type) -> SuperFace(face.toInt(), id, type.toInt()) }, "image" to MiraiCodeParser(Regex("""(.*)""")) { (id) -> Image(id) }, "flash" to MiraiCodeParser(Regex("""(.*)""")) { (id) -> Image(id).flash() }, "service" to MiraiCodeParser(Regex("""(\d*),(.*)""")) { (id, content) -> SimpleServiceMessage(id.toInt(), content.decodeMiraiCode()) }, "app" to MiraiCodeParser(Regex("""(.*)""")) { (content) -> LightApp(content.decodeMiraiCode()) }, "dice" to MiraiCodeParser(Regex("""([1-6])""")) { (value) -> Dice(value.toInt()) }, "rps" to MiraiCodeParser(Regex("""(\w+)""")) { (value) -> RockPaperScissors.valueOf(value.uppercase()) }, "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) }, "file" to MiraiCodeParser(Regex("""(.*?),(.*?),(.*?),(.*?)""")) { (id, internalId, name, size) -> FileMessage(id, internalId.toInt(), name, size.toLong()) }, ) // 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 -> when (char) { '[', ']', ':', ',', '\\', -> append("\\").append(char) '\n' -> append("\\n") '\r' -> append("\\r") else -> append(char) } } } private val DECODE_MIRAI_CODE_REGEX = """\\.""".toRegex() private val DECODE_MIRAI_CODE_TRANSLATOR: (MatchResult) -> String = { when (it.value[1]) { 'n' -> "\n" 'r' -> "\r" '\n' -> "" else -> it.value.substring(1) } } private fun String.decodeMiraiCode() = replace(DECODE_MIRAI_CODE_REGEX, DECODE_MIRAI_CODE_TRANSLATOR) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/At.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("EXPERIMENTAL_API_USAGE", "NOTHING_TO_INLINE") package net.mamoe.mirai.message.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.UserOrBot import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi /** * At 一个群成员. 只能发送给一个群. * * 使用时直接构造 At 实例即可. * * ## mirai 码支持 * 格式: &#91;mirai:at:*[target]*&#93; * * @see AtAll 全体成员 */ @SerialName(At.SERIAL_NAME) @Serializable public data class At( public val target: Long, ) : MessageContent, CodableMessage { public override fun toString(): String = "[mirai:at:$target]" public override fun contentToString(): String = "@$target" /** * 获取 [At] 发送于指定 [Group] 时会显示的内容. * * 若 [group] 非 `null` 且包含成员 [target], 返回 `"@成员名片或昵称"`. 否则返回 `"@123456"` 其中 123456 表示 [target] */ public fun getDisplay(group: Group?): String { val member = group?.get(this.target) ?: return "@$target" return "@${member.nameCardOrNick}" } @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:at:").append(target).append(']') } public companion object { public const val SERIAL_NAME: String = "At" } // 自动为消息补充 " " public override fun followedBy(tail: Message): MessageChain { if (tail is PlainText && tail.content.startsWith(' ')) { return super<MessageContent>.followedBy(tail) } return super<MessageContent>.followedBy(PlainText(" ")) + tail } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitAt(this, data) } } /** * 构造 [At] * * @see At * @see Member.at */ @JvmSynthetic public inline fun At(user: UserOrBot): At = At(user.id) /** * At 这个成员 */ @JvmSynthetic public inline fun Member.at(): At = At(this) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/AtAll.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("MemberVisibilityCanBePrivate") 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.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * "@全体成员". * * 非会员每天只能发送 10 次 [AtAll]. 超出部分会被以普通文字看待. * * ## 使用 [AtAll] * * [AtAll] 是单例, 将 [AtAll] 实例[添加][Message.plus]到消息链中即可. * ``` * // Kotlin * contact.sendMessage(AtAll + "test") * * // Java * contact.sendMessage(MessageUtils.newChain(AtAll.INSTANCE, new PlainText("test"))); * ``` * * ## mirai 码支持 * 格式: &#91;mirai:atall&#93; * * @see At at 单个群成员 */ @SerialName(AtAll.SERIAL_NAME) @Serializable public object AtAll : MessageContent, CodableMessage { public const val display: String = "@全体成员" public const val SERIAL_NAME: String = "AtAll" @Suppress("SpellCheckingInspection") override fun contentToString(): String = display override fun toString(): String = "[mirai:atall]" override fun serializeToMiraiCode(): String = toString() override fun hashCode(): Int = display.hashCode() override fun equals(other: Any?): Boolean = other === this @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append(toString()) } // 自动为消息补充 " " @JvmSynthetic public override fun followedBy(tail: Message): MessageChain { if (tail is PlainText && tail.content.startsWith(' ')) { return super<MessageContent>.followedBy(tail) } return super<MessageContent>.followedBy(PlainText(" ")) + tail } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitAtAll(this, data) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/Audio.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "unused") @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.contact.AudioSupported import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds /** * 语音消息. * * [Audio] 分为 [OnlineAudio] 与 [OfflineAudio]. 在本地上传的, 或手动构造的语音为 [OfflineAudio]. 从服务器接收的语音为 [OnlineAudio]. * * ## 上传和发送语音 * * 使用 [AudioSupported.uploadAudio] 上传语音到服务器并取得 [Audio] 消息实例, 然后通过 [Contact.sendMessage] 发送. * * Java 示例: * ``` * Audio audio; * try { * audio = group.uploadAudio(resource); // 上传文件得到语音实例 * } finally { * resource.close(); // 保证资源正常关闭 * } * group.sendMessage(audio); // 发送语音消息 * ``` * * ## 下载语音 * * 使用 [OnlineAudio.urlForDownload] 获取文件下载链接. * * ## [Audio] 与 [Voice] 的转换 * * 原 [Voice] 已弃用故不推荐进行兼容转换. [Audio] 将有稳定性保证, 请尽量使用新的 [Audio]. * * 将 [Audio] 转为 [Voice]: [Voice.fromAudio] * 将 [Voice] 转为 [Audio]: [Voice.toAudio] * * @since 2.7 */ public sealed interface Audio : MessageContent, ConstrainSingle { public companion object Key : AbstractPolymorphicMessageKey<MessageContent, Audio>(MessageContent, { it.safeCast() }) /** * 文件名称. 通常为 `XXX.amr`. 服务器要求文件名后缀必须为 ".amr", 但其[编码方式][codec]也有可能是非 [AudioCodec.AMR]. */ public val filename: String /** * 文件 MD5. 16 bytes. */ public val fileMd5: ByteArray /** * 文件大小 bytes. 官方客户端支持最大文件大小约为 1MB, 过大的文件**可能**可以正常上传, 但在官方客户端无法收听 (显示文件损坏). */ public val fileSize: Long /** * 编码方式. * * - 若语音文件真实编码方式为 [AudioCodec.SILK], 而该属性为 [AudioCodec.AMR], 语音文件将会被服务器压缩为低音质 [AudioCodec.AMR] 格式. * - 若语音文件真实编码方式为 [AudioCodec.AMR], 而该属性为 [AudioCodec.SILK], 语音也可以正常发送并在客户端收听, 音质随文件真实格式而决定. * * 因此在发送时 [codec] 通常可以总是使用 [AudioCodec.SILK] (这也是 [AudioSupported.uploadAudio] 的默认行为). */ public val codec: AudioCodec /** * 文件的额外数据. 该数据由服务器提供, 可能会影响语音音质等属性. * [extraData] 为 `null` 时也可以发送语音, 但不确定发送和客户端收听是否会正常. * * [extraData] 可能随服务器更新而更新, 因此请不要尝试解析该数据. * * [extraData] 向下兼容, 即旧版本的 [extraData] 可以在新版本构造 [OfflineAudio] ([OfflineAudio.Factory.create]). */ public val extraData: ByteArray? /** * @return `"[mirai:audio:${filename}]"` */ public override fun toString(): String public override fun contentToString(): String = "[语音消息]" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitAudio(this, data) } override val key: MessageKey<*> get() = Key } /** * 在线语音消息, 即从消息事件中接收到的语音消息. * * [OnlineAudio] 可以获取[语音长度][length]以及[下载链接][urlForDownload]. * * [OnlineAudio] 仅可以从事件中的[消息链][MessageChain]接收, 不可手动构造. 若需要手动构造, 请使用 [OfflineAudio.Factory.create] 构造 [离线语音][OfflineAudio]. * * ### 序列化支持 * * [OnlineAudio] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString]. * 也可以在 [MessageSerializers.serializersModule] 获取到 [OnlineAudio] 的 [KSerializer]. * * 要获取更多有关序列化的信息, 参阅 [MessageSerializers]. * * ### 不建议自行实现该接口 * * [OnlineAudio] 不稳定, 将来可能会增加新的抽象属性或方法而导致不兼容. 仅可以使用该接口而不能继承或实现它. * * @since 2.7 * @see OfflineAudio */ @NotStableForInheritance public interface OnlineAudio : Audio { // 协议实现 /** * 下载链接 HTTP URL. * @return `"http://xxx"` */ public val urlForDownload: String /** * 语音长度秒数 */ public val length: Long public companion object Key : AbstractPolymorphicMessageKey<Audio, OnlineAudio>(Audio, { it.safeCast() }) { public const val SERIAL_NAME: String = "OnlineAudio" } } /** * 获取语音长度秒数, 作为 [Duration]. * @since 2.11 */ @get:JvmSynthetic public inline val OnlineAudio.lengthDuration: Duration get() = length.seconds /** * 离线语音消息. * * [OfflineAudio] 仅拥有协议上必要的五个属性: * - 文件名 [filename] * - 文件 MD5 [fileMd5] * - 文件大小 [fileSize] * - 编码方式 [codec] * - 额外数据 [extraData] * * [OfflineAudio] 可由本地 [ExternalResource] 经过 [AudioSupported.uploadAudio] 上传到服务器得到, 故无[下载链接][OnlineAudio.urlForDownload]. * * [OfflineAudio] 同时还可以用做自定义构造 [Audio] 实例, 使用 [OfflineAudio.Factory.create] 可通过上述五个必要参数获得 [OfflineAudio] 实例. * * ### 序列化支持 * * [OfflineAudio] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString]. * 也可以在 [MessageSerializers.serializersModule] 获取到 [OfflineAudio] 的 [KSerializer]. * * 要获取更多有关序列化的信息, 参阅 [MessageSerializers]. * * ### 不建议自行实现该接口 * * [OfflineAudio] 不稳定, 将来可能会增加新的抽象属性或方法而导致不兼容. 仅可以使用该接口而不能继承或实现它. * * @since 2.7 */ @NotStableForInheritance public interface OfflineAudio : Audio { public companion object Key : AbstractPolymorphicMessageKey<Audio, OfflineAudio>(Audio, { it.safeCast() }) { public const val SERIAL_NAME: String = "OfflineAudio" } public interface Factory { /** * 构造 [OfflineAudio]. 有关参数的含义, 参考 [Audio]. * * 在 Kotlin 可以使用类构造器的函数 [OfflineAudio]: `OfflineAudio(...)` */ public fun create( filename: String, fileMd5: ByteArray, fileSize: Long, codec: AudioCodec, extraData: ByteArray?, ): OfflineAudio /** * 使用 [OnlineAudio] 的信息构造 [OfflineAudio]. * * 在 Kotlin 可以使用类构造器的函数 [OfflineAudio]: `OfflineAudio(...)` */ public fun from(onlineAudio: OnlineAudio): OfflineAudio = onlineAudio.run { create(filename, fileMd5, fileSize, codec, extraData) } public companion object INSTANCE : Factory by loadService("net.mamoe.mirai.internal.message.data.OfflineAudioFactoryImpl") } } /** * 构造 [OfflineAudio]. 有关参数的含义, 参考 [Audio]. * @since 2.7 */ @JvmSynthetic public inline fun OfflineAudio( filename: String, fileMd5: ByteArray, fileSize: Long, codec: AudioCodec, extraData: ByteArray?, ): OfflineAudio = OfflineAudio.Factory.create(filename, fileMd5, fileSize, codec, extraData) /** * 使用 [OnlineAudio] 的信息构造 [OfflineAudio]. * @since 2.7 */ @JvmSynthetic public inline fun OfflineAudio( onlineAudio: OnlineAudio ): OfflineAudio = OfflineAudio.Factory.from(onlineAudio) /** * 语音编码方式. * * @since 2.7 */ @Serializable(AudioCodec.AsIntSerializer::class) public enum class AudioCodec( public val id: Int, public val formatName: String, ) { /** * 低音质编码格式 */ AMR(0, "amr"), /** * 高音质编码格式 */ SILK(1, "silk"); public companion object { private val VALUES = values() @JvmStatic public fun fromId(id: Int): AudioCodec = VALUES.first { it.id == id } @JvmStatic public fun fromFormatName(formatName: String): AudioCodec = VALUES.first { it.formatName == formatName } @JvmStatic public fun fromIdOrNull(id: Int): AudioCodec? = VALUES.find { it.id == id } @JvmStatic public fun fromFormatNameOrNull(formatName: String): AudioCodec? = VALUES.find { it.formatName == formatName } } internal object AsIntSerializer : KSerializer<AudioCodec> by Int.serializer().map( Int.serializer().descriptor, deserialize = { fromId(it) }, serialize = { id } ) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/CombinedMessage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor import net.mamoe.mirai.message.data.visitor.accept import net.mamoe.mirai.utils.MiraiInternalApi /** * 树状消息链的一个节点. [element] 为当前元素, [tail] 为下一个元素. * * **注意:** 这是内部 API. 不要在任何情况下使用它. * [Message.plus] 等连接消息链的 API 已经有性能优化, 会在合适的时机创建 [CombinedMessage], 不需要自行考虑创建 [CombinedMessage]. * * @since 2.12 */ @MiraiInternalApi @Suppress("EXPOSED_SUPER_CLASS") public class CombinedMessage @MessageChainConstructor constructor( @MiraiInternalApi public val element: Message, // element 和 tail 必须提前处理 constrain single. 见 Message.followedBy @MiraiInternalApi public val tail: Message, @MiraiInternalApi public override val hasConstrainSingle: Boolean ) : AbstractMessageChain(), List<SingleMessage> { override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitCombinedMessage(this, data) } override fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) { element.accept(visitor, data) tail.accept(visitor, data) } override val size: Int by lazy { if (slowList.isInitialized()) return@lazy slowList.value.size var size = 0 val visitor = object : RecursiveMessageVisitor<Unit>() { override fun visitMessageChain(messageChain: MessageChain, data: Unit) { if (messageChain is DirectSizeAccess) { size += messageChain.size return } return super.visitMessageChain(messageChain, data) } override fun visitSingleMessage(message: SingleMessage, data: Unit) { size++ } } element.accept(visitor) tail.accept(visitor) size } override fun isEmpty(): Boolean { if (slowList.isInitialized()) return slowList.value.isEmpty() return element is MessageChain && element.isEmpty() && tail is MessageChain && tail.isEmpty() } override fun contains(element: SingleMessage): Boolean { if (slowList.isInitialized()) return slowList.value.contains(element) if (this.element == element) return true if (this.tail == element) return true if (this.element is MessageChain && this.element.contains(element)) return true if (this.tail is MessageChain && this.tail.contains(element)) return true return false } private val toStringTemp: String by lazy { if (slowList.isInitialized()) return@lazy slowList.value.toString() buildString { accept(object : RecursiveMessageVisitor<Unit>() { override fun visitSingleMessage(message: SingleMessage, data: Unit) { append(message.toString()) } override fun visitMessageChain(messageChain: MessageChain, data: Unit) { if (messageChain is DirectToStringAccess) { append(messageChain.toString()) } else { super.visitMessageChain(messageChain, data) } } }) } } private val contentToStingTemp: String by lazy { if (slowList.isInitialized()) return@lazy slowList.value.contentToString() buildString { accept(object : RecursiveMessageVisitor<Unit>() { override fun visitSingleMessage(message: SingleMessage, data: Unit) { append(message.contentToString()) } override fun visitMessageChain(messageChain: MessageChain, data: Unit) { if (messageChain is DirectToStringAccess) { append(messageChain.contentToString()) } else { super.visitMessageChain(messageChain, data) } } }) } } override fun toString(): String = toStringTemp override fun contentToString(): String = contentToStingTemp override fun containsAll(elements: Collection<SingleMessage>): Boolean { if (slowList.isInitialized()) return slowList.value.containsAll(elements) if (elements.isEmpty()) return true if (this.isEmpty()) return false val remaining = elements.toMutableList() accept(object : RecursiveMessageVisitor<Unit>() { override fun isFinished(): Boolean = remaining.isEmpty() override fun visitSingleMessage(message: SingleMessage, data: Unit) { remaining.remove(message) } }) return remaining.isEmpty() } override fun iterator(): Iterator<SingleMessage> { if (slowList.isInitialized()) return slowList.value.iterator() suspend fun SequenceScope<SingleMessage>.yieldMessage(element: Message) { when (element) { is SingleMessage -> { yield(element) } is MessageChain -> { yieldAll(element.iterator()) } } } return iterator { yieldMessage(element) yieldMessage(tail) } } override fun subList(fromIndex: Int, toIndex: Int): List<SingleMessage> { if (slowList.isInitialized()) return slowList.value.subList(fromIndex, toIndex) if (fromIndex < 0 || fromIndex > toIndex) throw IndexOutOfBoundsException("fromIndex: $fromIndex, toIndex: $toIndex") return buildList { accept(object : RecursiveMessageVisitor<Unit>() { private var currentIndex = 0 override fun isFinished(): Boolean = currentIndex >= toIndex override fun visitSingleMessage(message: SingleMessage, data: Unit) { if (isFinished()) return if (currentIndex >= fromIndex) { add(message) } currentIndex++ } }) } } /////////////////////////////////////////////////////////////////////////// // slow operations /////////////////////////////////////////////////////////////////////////// /** * 仅在必要时初始化来优化性能. 基于 [ConstrainSingle] 的情况, 它可能会有立方级别的时间复杂度. */ internal val slowList: Lazy<MessageChain> = lazy { sequenceOf(element, tail).toMessageChain() } // [MessageChain] implements [RandomAccess] so we should ensure that property here. override fun get(index: Int): SingleMessage = slowList.value[index] override fun indexOf(element: SingleMessage): Int = slowList.value.indexOf(element) override fun lastIndexOf(element: SingleMessage): Int = slowList.value.lastIndexOf(element) override fun listIterator(): ListIterator<SingleMessage> = slowList.value.listIterator() override fun listIterator(index: Int): ListIterator<SingleMessage> = slowList.value.listIterator(index) } internal interface DirectSizeAccess : MessageChain internal interface DirectToStringAccess : MessageChain ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/ConstrainSingle.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 约束一个 [MessageChain] 中只存在这一种类型的元素. 新元素将会替换旧元素, 保持原顺序. * * 实现此接口的元素将会在连接时自动处理替换. * * - 要获取有关键的信息, 查看 [MessageKey]. * - 要获取有关约束的处理方式, 查看 [AbstractPolymorphicMessageKey]. */ public interface ConstrainSingle : SingleMessage { /** * 用于判断是否为同一种元素的 [MessageKey]. 使用多态类型 [MessageKey] 最上层的 [MessageKey]. * @see MessageKey 查看更多信息 */ public val key: MessageKey<*> } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/CustomMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.message.data import io.ktor.utils.io.core.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoNumber import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.ConcurrentLinkedDeque import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi /** * 自定义消息 * * 它不会显示在消息文本中, 也不会被其他客户端识别. * 只有 mirai 才能识别这些消息. * * 目前在回复时无法通过 [MessageSource.originalMessage] 获取自定义类型消息 * * ## 序列化 * 若要支持序列化, 需 [MessageSerializers.registerSerializer] * * @sample samples.CustomMessageIdentifier 实现示例 * * @see CustomMessageMetadata 自定义消息元数据 * @see MessageSerializers */ @Serializable @MiraiExperimentalApi public sealed class CustomMessage : SingleMessage { /** * 获取这个消息的工厂 */ public abstract fun getFactory(): Factory<out CustomMessage> /** * 序列化和反序列化此消息的工厂, 将会自动注册. * 应实现为 `object`. * * @see JsonSerializerFactory 使用 [Json] 作为序列模式的 [Factory] * @see ProtoBufSerializerFactory 使用 [ProtoBuf] 作为序列模式的 [Factory] */ @MiraiExperimentalApi public abstract class Factory<M : CustomMessage>( /** * 此类型消息的名称. * 在发往服务器时使用此名称. * 应确保唯一且不变. */ public val typeName: String ) { init { @Suppress("LeakingThis") register(this) } /** * 序列化此消息. */ @Throws(Exception::class) public abstract fun dump(message: @UnsafeVariance M): ByteArray /** * 从 [input] 读取此消息. */ @Throws(Exception::class) public abstract fun load(input: ByteArray): @UnsafeVariance M } /** * 使用 [ProtoBuf] 作为序列模式的 [Factory]. * 推荐使用此工厂 */ public abstract class ProtoBufSerializerFactory<M : CustomMessage>(typeName: String) : Factory<M>(typeName) { /** * 得到 [M] 的 [KSerializer]. */ public abstract fun serializer(): KSerializer<M> @OptIn(ExperimentalSerializationApi::class) public override fun dump(message: M): ByteArray = ProtoBuf.encodeToByteArray(serializer(), message) @OptIn(ExperimentalSerializationApi::class) public override fun load(input: ByteArray): M = ProtoBuf.decodeFromByteArray(serializer(), input) } /** * 使用 [Json] 作为序列模式的 [Factory] * 推荐在调试时使用此工厂 */ public abstract class JsonSerializerFactory<M : CustomMessage>(typeName: String) : Factory<M>(typeName) { /** * 得到 [M] 的 [KSerializer]. */ public abstract fun serializer(): KSerializer<M> public open val json: Json = Json.Default public override fun dump(message: M): ByteArray = json.encodeToString(serializer(), message).toByteArray() public override fun load(input: ByteArray): M = json.decodeFromString(serializer(), String(input)) } public companion object { private val factories: MutableCollection<Factory<*>> = ConcurrentLinkedDeque() internal fun register(factory: Factory<out CustomMessage>) { factories.removeAll { it::class == factory::class } val exist = factories.firstOrNull { it.typeName == factory.typeName } if (exist != null) { error("CustomMessage.Factory typeName ${factory.typeName} is already registered by ${exist::class.qualifiedName}") } factories.add(factory) } @Serializable private class CustomMessageFullData @OptIn(ExperimentalSerializationApi::class) constructor( @ProtoNumber(1) val miraiVersionFlag: Int, @ProtoNumber(2) val typeName: String, @ProtoNumber(3) val data: ByteArray ) public class CustomMessageFullDataDeserializeInternalException(cause: Throwable?) : RuntimeException(cause) public class CustomMessageFullDataDeserializeUserException(public val body: ByteArray, cause: Throwable?) : RuntimeException(cause) @OptIn(ExperimentalSerializationApi::class) @MiraiInternalApi public fun load(fullData: ByteReadPacket): CustomMessage? { val msg = kotlin.runCatching { val length = fullData.readInt() if (fullData.remaining != length.toLong()) { return null } ProtoBuf.decodeFromByteArray(CustomMessageFullData.serializer(), fullData.readBytes(length)) }.getOrElse { throw CustomMessageFullDataDeserializeInternalException(it) } return kotlin.runCatching { when (msg.miraiVersionFlag) { 1 -> factories.firstOrNull { it.typeName == msg.typeName }?.load(msg.data) else -> null } }.getOrElse { throw CustomMessageFullDataDeserializeUserException(msg.data, it) } } @OptIn(ExperimentalSerializationApi::class) @MiraiInternalApi public fun <M : CustomMessage> dump(factory: Factory<M>, message: M): ByteArray = buildPacket { ProtoBuf.encodeToByteArray( CustomMessageFullData.serializer(), CustomMessageFullData( miraiVersionFlag = 1, typeName = factory.typeName, data = factory.dump(message) ) ).let { data -> writeInt(data.size) writeFully(data) } }.readBytes() } } /** * 序列化这个消息 */ @MiraiExperimentalApi public fun <T : CustomMessage> T.toByteArray(): ByteArray { @Suppress("UNCHECKED_CAST") return (this.getFactory() as CustomMessage.Factory<T>).dump(this) } /** * 自定义消息元数据. * * **实现方法**: * 1. 实现一个类继承 [CustomMessageMetadata], 添加 `@Serializable` (来自 `kotlinx.serialization`) * 2. 添加伴生对象, 继承 [CustomMessage.ProtoBufSerializerFactory] 或 [CustomMessage.JsonSerializerFactory], 或 [CustomMessage.Factory] * 3. 在需要解析消息前调用一次伴生对象以注册 * * 注意: 这是实验性 API. 可能会在未来发生变动. * * @see CustomMessage 查看更多信息 * @see ConstrainSingle 可实现此接口以保证消息链中只存在一个元素 */ @Serializable @MiraiExperimentalApi public abstract class CustomMessageMetadata : CustomMessage(), MessageMetadata { public open fun customToString(): ByteArray = customToStringImpl(this.getFactory()) final override fun toString(): String = "[mirai:custom:${getFactory().typeName}:${String(customToString())}]" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitCustomMessageMetadata(this, data) } public companion object } @OptIn(MiraiExperimentalApi::class) internal fun <T : CustomMessageMetadata> T.customToStringImpl(factory: CustomMessage.Factory<*>): ByteArray { @Suppress("UNCHECKED_CAST") return (factory as CustomMessage.Factory<T>).dump(this) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/Deprecated.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("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.DeprecatedSinceMirai import net.mamoe.mirai.utils.isSameClass import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 兼容 2.6 以下的 [MessageOrigin]. 请使用 [MessageOrigin] * @since 2.3 * @suppress Deprecated since 2.6 */ @Suppress("DEPRECATION_ERROR") @Serializable @SerialName(RichMessageOrigin.SERIAL_NAME) @Deprecated( "Use MessageOrigin instead.", replaceWith = ReplaceWith( "MessageOrigin", "net.mamoe.mirai.message.data.MessageOrigin", ), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.6", hiddenSince = "2.8") public class RichMessageOrigin @Deprecated( "Use MessageOrigin instead.", replaceWith = ReplaceWith( "MessageOrigin(origin, resourceId, kind)", "net.mamoe.mirai.message.data.MessageOrigin", ), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.6", hiddenSince = "2.8") constructor( /** * 原 [RichMessage]. */ public val origin: @Polymorphic RichMessage, /** * 如果来自长消息或转发消息, 则会有 [resourceId], 否则为 `null`. * * - 下载长消息 [IMirai.downloadLongMessage] * - 下载合并转发消息 [IMirai.downloadForwardMessage] */ public val resourceId: String?, /** * 来源类型 */ @Suppress("DEPRECATION_ERROR") public val kind: RichMessageKind, ) : MessageMetadata, ConstrainSingle { @Suppress("DEPRECATION_ERROR") 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 (other !is RichMessageOrigin || !isSameClass(this, other)) return false 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 } @Deprecated( "Use MessageOrigin instead.", replaceWith = ReplaceWith( "MessageOrigin", "net.mamoe.mirai.message.data.MessageOrigin", ), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.6", hiddenSince = "2.8") @Suppress("DEPRECATION_ERROR") public companion object Key : AbstractMessageKey<RichMessageOrigin>({ it.safeCast() }) { public const val SERIAL_NAME: String = "RichMessageOrigin" } } /** * 消息来源 * @since 2.3 * @suppress Deprecated since 2.6 */ @Deprecated( "Use MessageOriginKind", ReplaceWith("MessageOriginKind", "net.mamoe.mirai.message.data.MessageOriginKind"), level = DeprecationLevel.HIDDEN ) @DeprecatedSinceMirai(errorSince = "2.6", hiddenSince = "2.8") public enum class RichMessageKind { /** * 长消息 */ LONG, /** * 合并转发 * @see ForwardMessage */ FORWARD, /** * 音乐分享 * @see MusicShare * @since 2.4 */ MUSIC_SHARE, } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/Dice.kt ================================================ /* * Copyright 2019-2022 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/dev/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.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.annotations.Range import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic import kotlin.random.Random import kotlin.random.nextInt /** * 骰子. * * 构造 [Dice] 实例即可使用. 也可以通过 [Dice.random] 获得一个随机点数的实例. * * @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." } } 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]" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitDice(this, data) } 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)) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/Face.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("MemberVisibilityCanBePrivate") 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.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmField import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * QQ 自带表情 * * 使用时通过 [Face.JING_YA] 等静态字段得到 ID 然后构造 [Face] 实例. * * ## mirai 码支持 * 格式: &#91;mirai:face:*[id]*&#93; */ @Serializable @SerialName(Face.SERIAL_NAME) public data class Face(public val id: Int) : // used in delegation MessageContent, CodableMessage { public val name: String get() = contentToString().let { it.substring(1, it.length - 1) } override fun toString(): String = contentToString() override fun contentToString(): String = names.getOrElse(id) { "[表情]" } @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:face:").append(id).append(']') } override fun equals(other: Any?): Boolean = other is Face && other.id == this.id override fun hashCode(): Int = id @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitFace(this, data) } //Auto generated @Suppress("NonAsciiCharacters", "unused", "SpellCheckingInspection", "all", "ObjectPropertyName") public companion object { public const val SERIAL_NAME: String = "Face" public const val JING_YA: Int = 0 public const val 惊讶: Int = JING_YA public const val PIE_ZUI: Int = 1 public const val 撇嘴: Int = PIE_ZUI public const val SE: Int = 2 public const val 色: Int = SE public const val FA_DAI: Int = 3 public const val 发呆: Int = FA_DAI public const val DE_YI: Int = 4 public const val 得意: Int = DE_YI public const val LIU_LEI: Int = 5 public const val 流泪: Int = LIU_LEI public const val HAI_XIU: Int = 6 public const val 害羞: Int = HAI_XIU public const val BI_ZUI: Int = 7 public const val 闭嘴: Int = BI_ZUI public const val SHUI: Int = 8 public const val 睡: Int = SHUI public const val DA_KU: Int = 9 public const val 大哭: Int = DA_KU public const val GAN_GA: Int = 10 public const val 尴尬: Int = GAN_GA public const val FA_NU: Int = 11 public const val 发怒: Int = FA_NU public const val TIAO_PI: Int = 12 public const val 调皮: Int = TIAO_PI public const val ZI_YA: Int = 13 public const val 呲牙: Int = ZI_YA public const val WEI_XIAO: Int = 14 public const val 微笑: Int = WEI_XIAO public const val NAN_GUO: Int = 15 public const val 难过: Int = NAN_GUO public const val KU: Int = 16 public const val 酷: Int = KU public const val ZHUA_KUANG: Int = 18 public const val 抓狂: Int = ZHUA_KUANG public const val TU: Int = 19 public const val 吐: Int = TU public const val TOU_XIAO: Int = 20 public const val 偷笑: Int = TOU_XIAO public const val KE_AI: Int = 21 public const val 可爱: Int = KE_AI public const val BAI_YAN: Int = 22 public const val 白眼: Int = BAI_YAN public const val AO_MAN: Int = 23 public const val 傲慢: Int = AO_MAN public const val JI_E: Int = 24 public const val 饥饿: Int = JI_E public const val KUN: Int = 25 public const val 困: Int = KUN public const val JING_KONG: Int = 26 public const val 惊恐: Int = JING_KONG public const val LIU_HAN: Int = 27 public const val 流汗: Int = LIU_HAN public const val HAN_XIAO: Int = 28 public const val 憨笑: Int = HAN_XIAO public const val YOU_XIAN: Int = 29 public const val 悠闲: Int = YOU_XIAN public const val FEN_DOU: Int = 30 public const val 奋斗: Int = FEN_DOU public const val ZHOU_MA: Int = 31 public const val 咒骂: Int = ZHOU_MA public const val YI_WEN: Int = 32 public const val 疑问: Int = YI_WEN public const val XU: Int = 33 public const val 嘘: Int = XU public const val YUN: Int = 34 public const val 晕: Int = YUN public const val ZHE_MO: Int = 35 public const val 折磨: Int = ZHE_MO public const val SHUAI: Int = 36 public const val 衰: Int = SHUAI public const val KU_LOU: Int = 37 public const val 骷髅: Int = KU_LOU public const val QIAO_DA: Int = 38 public const val 敲打: Int = QIAO_DA public const val ZAI_JIAN: Int = 39 public const val 再见: Int = ZAI_JIAN public const val FA_DOU: Int = 41 public const val 发抖: Int = FA_DOU public const val AI_QING: Int = 42 public const val 爱情: Int = AI_QING public const val TIAO_TIAO: Int = 43 public const val 跳跳: Int = TIAO_TIAO public const val ZHU_TOU: Int = 46 public const val 猪头: Int = ZHU_TOU public const val YONG_BAO: Int = 49 public const val 拥抱: Int = YONG_BAO public const val DAN_GAO: Int = 53 public const val 蛋糕: Int = DAN_GAO public const val SHAN_DIAN: Int = 54 public const val 闪电: Int = SHAN_DIAN public const val ZHA_DAN: Int = 55 public const val 炸弹: Int = ZHA_DAN public const val DAO: Int = 56 public const val 刀: Int = DAO public const val ZU_QIU: Int = 57 public const val 足球: Int = ZU_QIU public const val BIAN_BIAN: Int = 59 public const val 便便: Int = BIAN_BIAN public const val KA_FEI: Int = 60 public const val 咖啡: Int = KA_FEI public const val FAN: Int = 61 public const val 饭: Int = FAN public const val MEI_GUI: Int = 63 public const val 玫瑰: Int = MEI_GUI public const val DIAO_XIE: Int = 64 public const val 凋谢: Int = DIAO_XIE public const val AI_XIN: Int = 66 public const val 爱心: Int = AI_XIN public const val XIN_SUI: Int = 67 public const val 心碎: Int = XIN_SUI public const val LI_WU: Int = 69 public const val 礼物: Int = LI_WU public const val TAI_YANG: Int = 74 public const val 太阳: Int = TAI_YANG public const val YUE_LIANG: Int = 75 public const val 月亮: Int = YUE_LIANG public const val ZAN: Int = 76 public const val 赞: Int = ZAN public const val CAI: Int = 77 public const val 踩: Int = CAI public const val WO_SHOU: Int = 78 public const val 握手: Int = WO_SHOU public const val SHENG_LI: Int = 79 public const val 胜利: Int = SHENG_LI public const val FEI_WEN: Int = 85 public const val 飞吻: Int = FEI_WEN public const val OU_HUO: Int = 86 public const val 怄火: Int = OU_HUO public const val XI_GUA: Int = 89 public const val 西瓜: Int = XI_GUA public const val LENG_HAN: Int = 96 public const val 冷汗: Int = LENG_HAN public const val CA_HAN: Int = 97 public const val 擦汗: Int = CA_HAN public const val KOU_BI: Int = 98 public const val 抠鼻: Int = KOU_BI public const val GU_ZHANG: Int = 99 public const val 鼓掌: Int = GU_ZHANG public const val QIU_DA_LE: Int = 100 public const val 糗大了: Int = QIU_DA_LE public const val HUAI_XIAO: Int = 101 public const val 坏笑: Int = HUAI_XIAO public const val ZUO_HENG_HENG: Int = 102 public const val 左哼哼: Int = ZUO_HENG_HENG public const val YOU_HENG_HENG: Int = 103 public const val 右哼哼: Int = YOU_HENG_HENG public const val HA_QIAN: Int = 104 public const val 哈欠: Int = HA_QIAN public const val BI_SHI: Int = 105 public const val 鄙视: Int = BI_SHI public const val WEI_QU: Int = 106 public const val 委屈: Int = WEI_QU public const val KUAI_KU_LE: Int = 107 public const val 快哭了: Int = KUAI_KU_LE public const val YIN_XIAN: Int = 108 public const val 阴险: Int = YIN_XIAN public const val QIN_QIN: Int = 109 public const val 亲亲: Int = QIN_QIN public const val ZUO_QIN_QIN: Int = QIN_QIN public const val 左亲亲: Int = QIN_QIN public const val XIA: Int = 110 public const val 吓: Int = XIA public const val KE_LIAN: Int = 111 public const val 可怜: Int = KE_LIAN public const val CAI_DAO: Int = 112 public const val 菜刀: Int = CAI_DAO public const val PI_JIU: Int = 113 public const val 啤酒: Int = PI_JIU public const val LAN_QIU: Int = 114 public const val 篮球: Int = LAN_QIU public const val PING_PANG: Int = 115 public const val 乒乓: Int = PING_PANG public const val SHI_AI: Int = 116 public const val 示爱: Int = SHI_AI public const val PIAO_CHONG: Int = 117 public const val 瓢虫: Int = PIAO_CHONG public const val BAO_QUAN: Int = 118 public const val 抱拳: Int = BAO_QUAN public const val GOU_YIN: Int = 119 public const val 勾引: Int = GOU_YIN public const val QUAN_TOU: Int = 120 public const val 拳头: Int = QUAN_TOU public const val CHA_JIN: Int = 121 public const val 差劲: Int = CHA_JIN public const val AI_NI: Int = 122 public const val 爱你: Int = AI_NI public const val NO: Int = 123 public const val 不: Int = NO public const val BU: Int = NO public const val OK: Int = 124 public const val 好: Int = OK public const val HAO: Int = OK public const val ZHUAN_QUAN: Int = 125 public const val 转圈: Int = ZHUAN_QUAN public const val KE_TOU: Int = 126 public const val 磕头: Int = KE_TOU public const val HUI_TOU: Int = 127 public const val 回头: Int = HUI_TOU public const val TIAO_SHENG: Int = 128 public const val 跳绳: Int = TIAO_SHENG public const val HUI_SHOU: Int = 129 public const val 挥手: Int = HUI_SHOU public const val JI_DONG: Int = 130 public const val 激动: Int = JI_DONG public const val JIE_WU: Int = 131 public const val 街舞: Int = JIE_WU public const val XIAN_WEN: Int = 132 public const val 献吻: Int = XIAN_WEN public const val ZUO_TAI_JI: Int = 133 public const val 左太极: Int = ZUO_TAI_JI public const val YOU_TAI_JI: Int = 134 public const val 右太极: Int = YOU_TAI_JI public const val SHUANG_XI: Int = 136 public const val 双喜: Int = SHUANG_XI public const val BIAN_PAO: Int = 137 public const val 鞭炮: Int = BIAN_PAO public const val DENG_LONG: Int = 138 public const val 灯笼: Int = DENG_LONG public const val K_GE: Int = 140 public const val K歌: Int = K_GE public const val HE_CAI: Int = 144 public const val 喝彩: Int = HE_CAI public const val QI_DAO: Int = 145 public const val 祈祷: Int = QI_DAO public const val BAO_JIN: Int = 146 public const val 爆筋: Int = BAO_JIN public const val BANG_BANG_TANG: Int = 147 public const val 棒棒糖: Int = BANG_BANG_TANG public const val HE_NAI: Int = 148 public const val 喝奶: Int = HE_NAI public const val FEI_JI: Int = 151 public const val 飞机: Int = FEI_JI public const val CHAO_PIAO: Int = 158 public const val 钞票: Int = CHAO_PIAO public const val YAO: Int = 168 public const val 药: Int = YAO public const val SHOU_QIANG: Int = 169 public const val 手枪: Int = SHOU_QIANG public const val CHA: Int = 171 public const val 茶: Int = CHA public const val ZHA_YAN_JING: Int = 172 public const val 眨眼睛: Int = ZHA_YAN_JING public const val LEI_BEN: Int = 173 public const val 泪奔: Int = LEI_BEN public const val WU_NAI: Int = 174 public const val 无奈: Int = WU_NAI public const val MAI_MENG: Int = 175 public const val 卖萌: Int = MAI_MENG public const val XIAO_JIU_JIE: Int = 176 public const val 小纠结: Int = XIAO_JIU_JIE public const val PEN_XIE: Int = 177 public const val 喷血: Int = PEN_XIE public const val XIE_YAN_XIAO: Int = 178 public const val 斜眼笑: Int = XIE_YAN_XIAO public const val doge: Int = 179 public const val JING_XI: Int = 180 public const val 惊喜: Int = JING_XI public const val SAO_RAO: Int = 181 public const val 骚扰: Int = SAO_RAO public const val XIAO_KU: Int = 182 public const val 笑哭: Int = XIAO_KU public const val WO_ZUI_MEI: Int = 183 public const val 我最美: Int = WO_ZUI_MEI public const val HE_XIE: Int = 184 public const val 河蟹: Int = HE_XIE public const val YANG_TUO: Int = 185 public const val 羊驼: Int = YANG_TUO public const val YOU_LING: Int = 187 public const val 幽灵: Int = YOU_LING public const val DAN: Int = 188 public const val 蛋: Int = DAN public const val JU_HUA: Int = 190 public const val 菊花: Int = JU_HUA public const val HONG_BAO: Int = 192 public const val 红包: Int = HONG_BAO public const val DA_XIAO: Int = 193 public const val 大笑: Int = DA_XIAO public const val BU_KAI_XIN: Int = 194 public const val 不开心: Int = BU_KAI_XIN public const val LENG_MO: Int = 197 public const val 冷漠: Int = LENG_MO public const val E: Int = 198 public const val 呃: Int = E public const val HAO_BANG: Int = 199 public const val 好棒: Int = HAO_BANG public const val BAI_TUO: Int = 200 public const val 拜托: Int = BAI_TUO public const val DIAN_ZAN: Int = 201 public const val 点赞: Int = DIAN_ZAN public const val WU_LIAO: Int = 202 public const val 无聊: Int = WU_LIAO public const val TUO_LIAN: Int = 203 public const val 托脸: Int = TUO_LIAN public const val CHI: Int = 204 public const val 吃: Int = CHI public const val SONG_HUA: Int = 205 public const val 送花: Int = SONG_HUA public const val HAI_PA: Int = 206 public const val 害怕: Int = HAI_PA public const val HUA_CHI: Int = 207 public const val 花痴: Int = HUA_CHI public const val XIAO_YANG_ER: Int = 208 public const val 小样儿: Int = XIAO_YANG_ER public const val BIAO_LEI: Int = 210 public const val 飙泪: Int = BIAO_LEI public const val WO_BU_KAN: Int = 211 public const val 我不看: Int = WO_BU_KAN public const val TUO_SAI: Int = 212 public const val 托腮: Int = TUO_SAI public const val BO_BO: Int = 214 public const val 啵啵: Int = BO_BO public const val HU_LIAN: Int = 215 public const val 糊脸: Int = HU_LIAN public const val PAI_TOU: Int = 216 public const val 拍头: Int = PAI_TOU public const val CHE_YI_CHE: Int = 217 public const val 扯一扯: Int = CHE_YI_CHE public const val TIAN_YI_TIAN: Int = 218 public const val 舔一舔: Int = TIAN_YI_TIAN public const val CENG_YI_CENG: Int = 219 public const val 蹭一蹭: Int = CENG_YI_CENG public const val ZHUAI_ZHA_TIAN: Int = 220 public const val 拽炸天: Int = ZHUAI_ZHA_TIAN public const val DING_GUA_GUA: Int = 221 public const val 顶呱呱: Int = DING_GUA_GUA public const val BAO_BAO: Int = 222 public const val 抱抱: Int = BAO_BAO public const val BAO_JI: Int = 223 public const val 暴击: Int = BAO_JI public const val KAI_QIANG: Int = 224 public const val 开枪: Int = KAI_QIANG public const val LIAO_YI_LIAO: Int = 225 public const val 撩一撩: Int = LIAO_YI_LIAO public const val PAI_ZHUO: Int = 226 public const val 拍桌: Int = PAI_ZHUO public const val PAI_SHOU: Int = 227 public const val 拍手: Int = PAI_SHOU public const val GONG_XI: Int = 228 public const val 恭喜: Int = GONG_XI public const val GAN_BEI: Int = 229 public const val 干杯: Int = GAN_BEI public const val CHAO_FENG: Int = 230 public const val 嘲讽: Int = CHAO_FENG public const val HENG: Int = 231 public const val 哼: Int = HENG public const val FO_XI: Int = 232 public const val 佛系: Int = FO_XI public const val QIA_YI_QIA: Int = 233 public const val 掐一掐: Int = QIA_YI_QIA public const val JING_DAI: Int = 234 public const val 惊呆: Int = JING_DAI public const val CHAN_DOU: Int = 235 public const val 颤抖: Int = CHAN_DOU public const val KEN_TOU: Int = 236 public const val 啃头: Int = KEN_TOU public const val TOU_KAN: Int = 237 public const val 偷看: Int = TOU_KAN public const val SHAN_LIAN: Int = 238 public const val 扇脸: Int = SHAN_LIAN public const val YUAN_LIANG: Int = 239 public const val 原谅: Int = YUAN_LIANG public const val PEN_LIAN: Int = 240 public const val 喷脸: Int = PEN_LIAN public const val SHENG_RI_KUAI_LE: Int = 241 public const val 生日快乐: Int = SHENG_RI_KUAI_LE public const val TOU_ZHUANG_JI: Int = 242 public const val 头撞击: Int = TOU_ZHUANG_JI public const val SHUAI_TOU: Int = 243 public const val 甩头: Int = SHUAI_TOU public const val RENG_GOU: Int = 244 public const val 扔狗: Int = RENG_GOU public const val JIA_YOU_BI_SHENG: Int = 245 public const val 加油必胜: Int = JIA_YOU_BI_SHENG public const val JIA_YOU_BAO_BAO: Int = 246 public const val 加油抱抱: Int = JIA_YOU_BAO_BAO public const val KOU_ZHAO_HU_TI: Int = 247 public const val 口罩护体: Int = KOU_ZHAO_HU_TI public const val BAN_ZHUAN_ZHONG: Int = 260 public const val 搬砖中: Int = BAN_ZHUAN_ZHONG public const val MANG_DAO_FEI_QI: Int = 261 public const val 忙到飞起: Int = MANG_DAO_FEI_QI public const val NAO_KUO_TENG: Int = 262 public const val 脑阔疼: Int = NAO_KUO_TENG public const val CANG_SANG: Int = 263 public const val 沧桑: Int = CANG_SANG public const val WU_LIAN: Int = 264 public const val 捂脸: Int = WU_LIAN public const val LA_YAN_JING: Int = 265 public const val 辣眼睛: Int = LA_YAN_JING public const val O_YO: Int = 266 public const val 哦哟: Int = O_YO public const val TOU_TU: Int = 267 public const val 头秃: Int = TOU_TU public const val WEN_HAO_LIAN: Int = 268 public const val 问号脸: Int = WEN_HAO_LIAN public const val AN_ZHONG_GUAN_CHA: Int = 269 public const val 暗中观察: Int = AN_ZHONG_GUAN_CHA public const val emm: Int = 270 public const val CHI_GUA: Int = 271 public const val 吃瓜: Int = CHI_GUA public const val HE_HE_DA: Int = 272 public const val 呵呵哒: Int = HE_HE_DA public const val WO_SUAN_LE: Int = 273 public const val 我酸了: Int = WO_SUAN_LE public const val TAI_NAN_LE: Int = 274 public const val 太南了: Int = TAI_NAN_LE public const val LA_JIAO_JIANG: Int = 276 public const val 辣椒酱: Int = LA_JIAO_JIANG public const val WANG_WANG: Int = 277 public const val 汪汪: Int = WANG_WANG public const val HAN: Int = 278 public const val 汗: Int = HAN public const val DA_LIAN: Int = 279 public const val 打脸: Int = DA_LIAN public const val JI_ZHANG: Int = 280 public const val 击掌: Int = JI_ZHANG public const val WU_YAN_XIAO: Int = 281 public const val 无眼笑: Int = WU_YAN_XIAO public const val JING_LI: Int = 282 public const val 敬礼: Int = JING_LI public const val KUANG_XIAO: Int = 283 public const val 狂笑: Int = KUANG_XIAO public const val MIAN_WU_BIAO_QING: Int = 284 public const val 面无表情: Int = MIAN_WU_BIAO_QING public const val MO_YU: Int = 285 public const val 摸鱼: Int = MO_YU public const val MO_GUI_XIAO: Int = 286 public const val 魔鬼笑: Int = MO_GUI_XIAO public const val O: Int = 287 public const val 哦: Int = O public const val QING: Int = 288 public const val 请: Int = QING public const val ZHENG_YAN: Int = 289 public const val 睁眼: Int = ZHENG_YAN public const val QIAO_KAI_XIN: Int = 290 public const val 敲开心: Int = QIAO_KAI_XIN public const val ZHEN_JING: Int = 291 public const val 震惊: Int = ZHEN_JING public const val RANG_WO_KANG_KANG: Int = 292 public const val 让我康康: Int = RANG_WO_KANG_KANG public const val MO_JIN_LI: Int = 293 public const val 摸锦鲤: Int = MO_JIN_LI public const val QI_DAI: Int = 294 public const val 期待: Int = QI_DAI public const val NA_DAO_HONG_BAO: Int = 295 public const val 拿到红包: Int = NA_DAO_HONG_BAO public const val ZHEN_HAO: Int = 296 public const val 真好: Int = ZHEN_HAO public const val BAI_XIE: Int = 297 public const val 拜谢: Int = BAI_XIE public const val YUAN_BAO: Int = 298 public const val 元宝: Int = YUAN_BAO public const val NIU_A: Int = 299 public const val 牛啊: Int = NIU_A public const val PANG_SAN_JIN: Int = 300 public const val 胖三斤: Int = PANG_SAN_JIN public const val HAO_SHAN: Int = 301 public const val 好闪: Int = HAO_SHAN public const val ZUO_BAI_NIAN: Int = 302 public const val 左拜年: Int = ZUO_BAI_NIAN public const val YOU_BAI_NIAN: Int = 303 public const val 右拜年: Int = YOU_BAI_NIAN public const val HONG_BAO_BAO: Int = 304 public const val 红包包: Int = HONG_BAO_BAO public const val YOU_QIN_QIN: Int = 305 public const val 右亲亲: Int = YOU_QIN_QIN public const val NIU_QI_CHONG_TIAN: Int = 306 public const val 牛气冲天: Int = NIU_QI_CHONG_TIAN public const val MIAO_MIAO: Int = 307 public const val 喵喵: Int = MIAO_MIAO public const val QIU_HONG_BAO: Int = 308 public const val 求红包: Int = QIU_HONG_BAO public const val XIE_HONG_BAO: Int = 309 public const val 谢红包: Int = XIE_HONG_BAO public const val XIN_NIAN_YAN_HUA: Int = 310 public const val 新年烟花: Int = XIN_NIAN_YAN_HUA public const val DA_CALL: Int = 311 public const val 打call: Int = DA_CALL public const val BIAN_XING: Int = 312 public const val 变形: Int = BIAN_XING public const val KE_DAO_LE: Int = 313 public const val 嗑到了: Int = KE_DAO_LE public const val ZI_XI_FEN_XI: Int = 314 public const val 仔细分析: Int = ZI_XI_FEN_XI public const val JIA_YOU: Int = 315 public const val 加油: Int = JIA_YOU public const val WO_MEI_SHI: Int = 316 public const val 我没事: Int = WO_MEI_SHI public const val CAI_GOU: Int = 317 public const val 菜狗: Int = CAI_GOU public const val CHONG_BAI: Int = 318 public const val 崇拜: Int = CHONG_BAI public const val BI_XIN: Int = 319 public const val 比心: Int = BI_XIN public const val QING_ZHU: Int = 320 public const val 庆祝: Int = QING_ZHU public const val LAO_SE_PI: Int = 321 public const val 老色痞: Int = LAO_SE_PI public const val JU_JUE: Int = 322 public const val 拒绝: Int = JU_JUE public const val XIAN_QI: Int = 323 public const val 嫌弃: Int = XIAN_QI public const val CHI_TANG: Int = 324 public const val 吃糖: Int = CHI_TANG public const val JING_XIA: Int = 325 public const val 惊吓: Int = JING_XIA public const val SHENG_QI: Int = 326 public const val 生气: Int = SHENG_QI public const val JIA_YI: Int = 327 public const val 加一: Int = JIA_YI public const val CUO_HAO: Int = 328 public const val 错号: Int = CUO_HAO public const val DUI_HAO: Int = 329 public const val 对号: Int = DUI_HAO public const val WAN_CHENG: Int = 330 public const val 完成: Int = WAN_CHENG public const val MING_BAI: Int = 331 public const val 明白: Int = MING_BAI public const val JU_PAI_PAI: Int = 332 public const val 举牌牌: Int = JU_PAI_PAI public const val YAN_HUA: Int = 333 public const val 烟花: Int = YAN_HUA public const val HU_HU_SHENG_WEI: Int = 334 public const val 虎虎生威: Int = HU_HU_SHENG_WEI public const val BAO_FU: Int = 336 public const val 豹富: Int = BAO_FU public const val HUA_DUO_LIAN : Int = 337 public const val 花朵脸: Int = HUA_DUO_LIAN public const val WO_XIANG_KAI_LE: Int = 338 public const val 我想开了: Int = WO_XIANG_KAI_LE public const val TIAN_PING : Int = 339 public const val 舔屏: Int = TIAN_PING public const val RE_HUA_LE: Int = 340 public const val 热化了: Int = RE_HUA_LE public const val DA_ZHAO_HU : Int = 341 public const val 打招呼: Int = DA_ZHAO_HU public const val SUAN_Q: Int = 342 public const val 酸Q: Int = SUAN_Q public const val WO_FANG_LE : Int = 343 public const val 我方了: Int = WO_FANG_LE public const val DA_YUAN_ZHONG: Int = 344 public const val 大怨种: Int = DA_YUAN_ZHONG public const val HONG_BAO_DUO_DUO: Int = 345 public const val 红包多多: Int = HONG_BAO_DUO_DUO public const val NI_ZHEN_BANG_BANG: Int = 346 public const val 你真棒棒: Int = NI_ZHEN_BANG_BANG public const val DA_ZHAN_HONG_TU: Int = 347 public const val 大展宏兔: Int = DA_ZHAN_HONG_TU public const val FU_LUO_BO: Int = 348 public const val 福萝卜: Int = 348 @JvmField public val names: Array<String> = Array(349) { "[表情]" } init { names[JING_YA] = "[惊讶]" names[PIE_ZUI] = "[撇嘴]" names[SE] = "[色]" names[FA_DAI] = "[发呆]" names[DE_YI] = "[得意]" names[LIU_LEI] = "[流泪]" names[HAI_XIU] = "[害羞]" names[BI_ZUI] = "[闭嘴]" names[SHUI] = "[睡]" names[DA_KU] = "[大哭]" names[GAN_GA] = "[尴尬]" names[FA_NU] = "[发怒]" names[TIAO_PI] = "[调皮]" names[ZI_YA] = "[呲牙]" names[WEI_XIAO] = "[微笑]" names[NAN_GUO] = "[难过]" names[KU] = "[酷]" names[ZHUA_KUANG] = "[抓狂]" names[TU] = "[吐]" names[TOU_XIAO] = "[偷笑]" names[KE_AI] = "[可爱]" names[BAI_YAN] = "[白眼]" names[AO_MAN] = "[傲慢]" names[JI_E] = "[饥饿]" names[KUN] = "[困]" names[JING_KONG] = "[惊恐]" names[LIU_HAN] = "[流汗]" names[HAN_XIAO] = "[憨笑]" names[YOU_XIAN] = "[悠闲]" names[FEN_DOU] = "[奋斗]" names[ZHOU_MA] = "[咒骂]" names[YI_WEN] = "[疑问]" names[XU] = "[嘘]" names[YUN] = "[晕]" names[ZHE_MO] = "[折磨]" names[SHUAI] = "[衰]" names[KU_LOU] = "[骷髅]" names[QIAO_DA] = "[敲打]" names[ZAI_JIAN] = "[再见]" names[FA_DOU] = "[发抖]" names[AI_QING] = "[爱情]" names[TIAO_TIAO] = "[跳跳]" names[ZHU_TOU] = "[猪头]" names[YONG_BAO] = "[拥抱]" names[DAN_GAO] = "[蛋糕]" names[SHAN_DIAN] = "[闪电]" names[ZHA_DAN] = "[炸弹]" names[DAO] = "[刀]" names[ZU_QIU] = "[足球]" names[BIAN_BIAN] = "[便便]" names[KA_FEI] = "[咖啡]" names[FAN] = "[饭]" names[MEI_GUI] = "[玫瑰]" names[DIAO_XIE] = "[凋谢]" names[AI_XIN] = "[爱心]" names[XIN_SUI] = "[心碎]" names[LI_WU] = "[礼物]" names[TAI_YANG] = "[太阳]" names[YUE_LIANG] = "[月亮]" names[ZAN] = "[赞]" names[CAI] = "[踩]" names[WO_SHOU] = "[握手]" names[SHENG_LI] = "[胜利]" names[FEI_WEN] = "[飞吻]" names[OU_HUO] = "[怄火]" names[XI_GUA] = "[西瓜]" names[LENG_HAN] = "[冷汗]" names[CA_HAN] = "[擦汗]" names[KOU_BI] = "[抠鼻]" names[GU_ZHANG] = "[鼓掌]" names[QIU_DA_LE] = "[糗大了]" names[HUAI_XIAO] = "[坏笑]" names[ZUO_HENG_HENG] = "[左哼哼]" names[YOU_HENG_HENG] = "[右哼哼]" names[HA_QIAN] = "[哈欠]" names[BI_SHI] = "[鄙视]" names[WEI_QU] = "[委屈]" names[KUAI_KU_LE] = "[快哭了]" names[YIN_XIAN] = "[阴险]" names[ZUO_QIN_QIN] = "[左亲亲]" names[XIA] = "[吓]" names[KE_LIAN] = "[可怜]" names[CAI_DAO] = "[菜刀]" names[PI_JIU] = "[啤酒]" names[LAN_QIU] = "[篮球]" names[PING_PANG] = "[乒乓]" names[SHI_AI] = "[示爱]" names[PIAO_CHONG] = "[瓢虫]" names[BAO_QUAN] = "[抱拳]" names[GOU_YIN] = "[勾引]" names[QUAN_TOU] = "[拳头]" names[CHA_JIN] = "[差劲]" names[AI_NI] = "[爱你]" names[NO] = "[NO]" names[OK] = "[OK]" names[ZHUAN_QUAN] = "[转圈]" names[KE_TOU] = "[磕头]" names[HUI_TOU] = "[回头]" names[TIAO_SHENG] = "[跳绳]" names[HUI_SHOU] = "[挥手]" names[JI_DONG] = "[激动]" names[JIE_WU] = "[街舞]" names[XIAN_WEN] = "[献吻]" names[ZUO_TAI_JI] = "[左太极]" names[YOU_TAI_JI] = "[右太极]" names[SHUANG_XI] = "[双喜]" names[BIAN_PAO] = "[鞭炮]" names[DENG_LONG] = "[灯笼]" names[K_GE] = "[K歌]" names[HE_CAI] = "[喝彩]" names[QI_DAO] = "[祈祷]" names[BAO_JIN] = "[爆筋]" names[BANG_BANG_TANG] = "[棒棒糖]" names[HE_NAI] = "[喝奶]" names[FEI_JI] = "[飞机]" names[CHAO_PIAO] = "[钞票]" names[YAO] = "[药]" names[SHOU_QIANG] = "[手枪]" names[CHA] = "[茶]" names[ZHA_YAN_JING] = "[眨眼睛]" names[LEI_BEN] = "[泪奔]" names[WU_NAI] = "[无奈]" names[MAI_MENG] = "[卖萌]" names[XIAO_JIU_JIE] = "[小纠结]" names[PEN_XIE] = "[喷血]" names[XIE_YAN_XIAO] = "[斜眼笑]" names[doge] = "[doge]" names[JING_XI] = "[惊喜]" names[SAO_RAO] = "[骚扰]" names[XIAO_KU] = "[笑哭]" names[WO_ZUI_MEI] = "[我最美]" names[HE_XIE] = "[河蟹]" names[YANG_TUO] = "[羊驼]" names[YOU_LING] = "[幽灵]" names[DAN] = "[蛋]" names[JU_HUA] = "[菊花]" names[HONG_BAO] = "[红包]" names[DA_XIAO] = "[大笑]" names[BU_KAI_XIN] = "[不开心]" names[LENG_MO] = "[冷漠]" names[E] = "[呃]" names[HAO_BANG] = "[好棒]" names[BAI_TUO] = "[拜托]" names[DIAN_ZAN] = "[点赞]" names[WU_LIAO] = "[无聊]" names[TUO_LIAN] = "[托脸]" names[CHI] = "[吃]" names[SONG_HUA] = "[送花]" names[HAI_PA] = "[害怕]" names[HUA_CHI] = "[花痴]" names[XIAO_YANG_ER] = "[小样儿]" names[BIAO_LEI] = "[飙泪]" names[WO_BU_KAN] = "[我不看]" names[TUO_SAI] = "[托腮]" names[BO_BO] = "[啵啵]" names[HU_LIAN] = "[糊脸]" names[PAI_TOU] = "[拍头]" names[CHE_YI_CHE] = "[扯一扯]" names[TIAN_YI_TIAN] = "[舔一舔]" names[CENG_YI_CENG] = "[蹭一蹭]" names[ZHUAI_ZHA_TIAN] = "[拽炸天]" names[DING_GUA_GUA] = "[顶呱呱]" names[BAO_BAO] = "[抱抱]" names[BAO_JI] = "[暴击]" names[KAI_QIANG] = "[开枪]" names[LIAO_YI_LIAO] = "[撩一撩]" names[PAI_ZHUO] = "[拍桌]" names[PAI_SHOU] = "[拍手]" names[GONG_XI] = "[恭喜]" names[GAN_BEI] = "[干杯]" names[CHAO_FENG] = "[嘲讽]" names[HENG] = "[哼]" names[FO_XI] = "[佛系]" names[QIA_YI_QIA] = "[掐一掐]" names[JING_DAI] = "[惊呆]" names[CHAN_DOU] = "[颤抖]" names[KEN_TOU] = "[啃头]" names[TOU_KAN] = "[偷看]" names[SHAN_LIAN] = "[扇脸]" names[YUAN_LIANG] = "[原谅]" names[PEN_LIAN] = "[喷脸]" names[SHENG_RI_KUAI_LE] = "[生日快乐]" names[TOU_ZHUANG_JI] = "[头撞击]" names[SHUAI_TOU] = "[甩头]" names[RENG_GOU] = "[扔狗]" names[JIA_YOU_BI_SHENG] = "[加油必胜]" names[JIA_YOU_BAO_BAO] = "[加油抱抱]" names[KOU_ZHAO_HU_TI] = "[口罩护体]" names[BAN_ZHUAN_ZHONG] = "[搬砖中]" names[MANG_DAO_FEI_QI] = "[忙到飞起]" names[NAO_KUO_TENG] = "[脑阔疼]" names[CANG_SANG] = "[沧桑]" names[WU_LIAN] = "[捂脸]" names[LA_YAN_JING] = "[辣眼睛]" names[O_YO] = "[哦哟]" names[TOU_TU] = "[头秃]" names[WEN_HAO_LIAN] = "[问号脸]" names[AN_ZHONG_GUAN_CHA] = "[暗中观察]" names[emm] = "[emm]" names[CHI_GUA] = "[吃瓜]" names[HE_HE_DA] = "[呵呵哒]" names[WO_SUAN_LE] = "[我酸了]" names[TAI_NAN_LE] = "[太南了]" names[LA_JIAO_JIANG] = "[辣椒酱]" names[WANG_WANG] = "[汪汪]" names[HAN] = "[汗]" names[DA_LIAN] = "[打脸]" names[JI_ZHANG] = "[击掌]" names[WU_YAN_XIAO] = "[无眼笑]" names[JING_LI] = "[敬礼]" names[KUANG_XIAO] = "[狂笑]" names[MIAN_WU_BIAO_QING] = "[面无表情]" names[MO_YU] = "[摸鱼]" names[MO_GUI_XIAO] = "[魔鬼笑]" names[O] = "[哦]" names[QING] = "[请]" names[ZHENG_YAN] = "[睁眼]" names[QIAO_KAI_XIN] = "[敲开心]" names[ZHEN_JING] = "[震惊]" names[RANG_WO_KANG_KANG] = "[让我康康]" names[MO_JIN_LI] = "[摸锦鲤]" names[QI_DAI] = "[期待]" names[NA_DAO_HONG_BAO] = "[拿到红包]" names[ZHEN_HAO] = "[真好]" names[BAI_XIE] = "[拜谢]" names[YUAN_BAO] = "[元宝]" names[NIU_A] = "[牛啊]" names[PANG_SAN_JIN] = "[胖三斤]" names[HAO_SHAN] = "[好闪]" names[ZUO_BAI_NIAN] = "[左拜年]" names[YOU_BAI_NIAN] = "[右拜年]" names[HONG_BAO_BAO] = "[红包包]" names[YOU_QIN_QIN] = "[右亲亲]" names[NIU_QI_CHONG_TIAN] = "[牛气冲天]" names[MIAO_MIAO] = "[喵喵]" names[QIU_HONG_BAO] = "[求红包]" names[XIE_HONG_BAO] = "[谢红包]" names[XIN_NIAN_YAN_HUA] = "[新年烟花]" names[DA_CALL] = "[打call]" names[BIAN_XING] = "[变形]" names[KE_DAO_LE] = "[嗑到了]" names[ZI_XI_FEN_XI] = "[仔细分析]" names[JIA_YOU] = "[加油]" names[WO_MEI_SHI] = "[我没事]" names[CAI_GOU] = "[菜狗]" names[CHONG_BAI] = "[崇拜]" names[BI_XIN] = "[比心]" names[QING_ZHU] = "[庆祝]" names[LAO_SE_PI] = "[老色痞]" names[JU_JUE] = "[拒绝]" names[XIAN_QI] = "[嫌弃]" names[CHI_TANG] = "[吃糖]" names[JING_XIA] = "[惊吓]" names[SHENG_QI] = "[生气]" names[JIA_YI] = "[加一]" names[CUO_HAO] = "[错号]" names[DUI_HAO] = "[对号]" names[WAN_CHENG] = "[完成]" names[MING_BAI] = "[明白]" names[JU_PAI_PAI] = "[举牌牌]" names[YAN_HUA] = "[烟花]" names[YAN_HUA] = "[烟花]" names[HU_HU_SHENG_WEI] = "[虎虎生威]" names[BAO_FU] = "[豹富]" names[HUA_DUO_LIAN] = "[花朵脸]" names[WO_XIANG_KAI_LE] = "[我想开了]" names[TIAN_PING] = "[舔屏]" names[RE_HUA_LE] = "[热化了]" names[DA_ZHAO_HU] = "[打招呼]" names[SUAN_Q] = "[酸Q]" names[WO_FANG_LE] = "[我方了]" names[DA_YUAN_ZHONG] = "[大怨种]" names[HONG_BAO_DUO_DUO] = "[红包多多]" names[NI_ZHEN_BANG_BANG] = "[你真棒棒]" names[DA_ZHAN_HONG_TU] = "[大展宏兔]" names[FU_LUO_BO] = "[福萝卜]" } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/FileMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* /** * 文件消息. * * [name] 与 [size] 只供本地使用, 发送消息时只会使用 [id] 和 [internalId]. * * 注: [FileMessage] 不可二次发送 * * ### 文件操作 * 要下载这个文件, 可通过 [toAbsoluteFile] 获取到 [AbsoluteFile] 然后操作. * * 要获取到 [FileMessage], 可以通过 [MessageEvent.message] 获取, 或通过 [AbsoluteFile.toMessage] 得到. * * @since 2.5 * @suppress [FileMessage] 的使用是稳定的, 但自行实现不稳定. */ @Serializable(FileMessage.Serializer::class) @SerialName(FileMessage.SERIAL_NAME) @NotStableForInheritance @JvmBlockingBridge public interface FileMessage : MessageContent, ConstrainSingle, CodableMessage { /** * 服务器需要的某种 ID. */ public val id: String /** * 服务器需要的某种 ID. */ public val internalId: Int /** * 文件名 */ public val name: String /** * 文件大小 bytes */ public val size: Long override fun contentToString(): String = "[文件]$name" // orthodox @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:file:") builder.appendStringAsMiraiCode(id).append(",") builder.append(internalId).append(",") builder.appendStringAsMiraiCode(name).append(",") builder.append(size).append("]") } /** * 获取一个对应的 [RemoteFile]. 当目标群或好友不存在这个文件时返回 `null`. */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Please use toAbsoluteFile", ReplaceWith("this.toAbsoluteFile(contact)"), level = DeprecationLevel.ERROR ) // deprecated since 2.8.0-RC @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.14") public suspend fun toRemoteFile(contact: FileSupported): RemoteFile? { return contact.filesRoot.resolveById(id) } /** * 获取一个对应的 [AbsoluteFile]. 当目标群或好友不存在这个文件时返回 `null`. * * @since 2.8 */ public suspend fun toAbsoluteFile(contact: FileSupported): AbsoluteFile? override val key: Key get() = Key @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitFileMessage(this, data) } /** * 注意, baseKey [MessageContent] 不稳定. 未来可能会有变更. */ public companion object Key : AbstractPolymorphicMessageKey<MessageContent, FileMessage>( MessageContent, { it.safeCast() }) { public const val SERIAL_NAME: String = "FileMessage" /** * 构造 [FileMessage] * @since 2.5 */ @JvmStatic public fun create(id: String, internalId: Int, name: String, size: Long): FileMessage = Mirai.createFileMessage(id, internalId, name, size) } public object Serializer : KSerializer<FileMessage> by @OptIn(MiraiInternalApi::class) FallbackFileMessageSerializer() } @MiraiInternalApi internal open class FallbackFileMessageSerializer : KSerializer<FileMessage> by Delegate.serializer().map( Delegate.serializer().descriptor, serialize = { Delegate(id, internalId, name, size) }, deserialize = { Mirai.createFileMessage(id, internalId, name, size) }, ) { @Suppress("ANNOTATION_ARGUMENT_MUST_BE_CONST") @SerialName(FileMessage.SERIAL_NAME) @Serializable data class Delegate( val id: String, val internalId: Int, val name: String, val size: Long, ) } /** * 构造 [FileMessage] * @since 2.5 */ @JvmSynthetic public fun FileMessage(id: String, internalId: Int, name: String, size: Long): FileMessage = FileMessage.create(id, internalId, name, size) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/FlashImage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") 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.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast /** * 闪照. 闪照的内容取决于 [image] 代表的图片. * * ## 构造闪照 * * 需要首先获得普通图片才能构造闪照. 详见 [Image]. * * - 使用 [FlashImage.from] 将普通图片转换为闪照. * - 在 Kotlin 使用类构造器顶层函数 `FlashImage(image)`. * - 在 Kotlin 使用扩展 [Image.flash]. * * ## 获得闪照代表的原图片 * * 访问属性 [FlashImage.image] * * ## mirai 码支持 * 格式: &#91;mirai:flash:*[Image.imageId]*&#93; * * @see Image 查看图片相关信息 */ @OptIn(MiraiExperimentalApi::class) @Serializable @SerialName(FlashImage.SERIAL_NAME) public data class FlashImage( /** * 闪照的内容图片, 即一个普通图片. */ @SerialName("imageId") @Serializable(Image.AsStringSerializer::class) public val image: Image ) : MessageContent, HummerMessage, CodableMessage, ConstrainSingle { override val key: MessageKey<FlashImage> get() = Key private val stringValue: String by lazy(LazyThreadSafetyMode.NONE) { "[mirai:flash:${image.imageId}]" } @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append(stringValue) } override fun serializeToMiraiCode(): String = stringValue override fun toString(): String = stringValue override fun contentToString(): String = "[闪照]" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitFlashImage(this, data) } public companion object Key : AbstractPolymorphicMessageKey<HummerMessage, FlashImage>(HummerMessage, { it.safeCast() }) { public const val SERIAL_NAME: String = "FlashImage" /** * 将普通图片转换为闪照. * * @param imageId 图片 id, 详见 [Image.imageId] */ @JvmStatic public fun from(imageId: String): FlashImage = FlashImage(Image(imageId)) /** * 将普通图片转换为闪照. * * @see Image.flash */ @JvmStatic public inline fun from(image: Image): FlashImage = FlashImage(image) } } /** * 将普通图片转换为闪照. */ @JvmSynthetic public inline fun FlashImage(imageId: String): FlashImage = FlashImage.from(imageId) /** * 将普通图片转换为闪照. */ @JvmSynthetic public inline fun Image.flash(): FlashImage = FlashImage(this) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/ForwardMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate", "INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "unused") package net.mamoe.mirai.message.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.data.ForwardMessage.DisplayStrategy import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic /** * 未通过 [DisplayStrategy] 渲染的合并转发消息. [RawForwardMessage] 仅作为一个中间件, 用于 [ForwardMessageBuilder]. * * [RawForwardMessage] 可以序列化保存, 也可以被多次[渲染][RawForwardMessage.render]产生不同格式的 [ForwardMessage]. */ @Serializable public data class RawForwardMessage( /** * 消息列表 */ val nodeList: List<ForwardMessage.Node> ) { /** * 渲染这个 [RawForwardMessage] 并产生可以发送的 [ForwardMessage] */ public fun render(displayStrategy: DisplayStrategy): ForwardMessage = ForwardMessage( preview = displayStrategy.generatePreview(this), title = displayStrategy.generateTitle(this), brief = displayStrategy.generateBrief(this), source = displayStrategy.generateSource(this), summary = displayStrategy.generateSummary(this), nodeList = nodeList, ) } /** * 合并转发消息 * * * * ## 显示方案 * * ### 移动端 * 在移动客户端将会显示为卡片 * * - `<title>`: [DisplayStrategy.generateTitle] * - `<preview>`: [DisplayStrategy.generatePreview] * - `<summary>`: [DisplayStrategy.generateSummary] * * ``` * |-------------------------| * | <title> | * | <preview> | * |-------------------------| * | <summary> | * |-------------------------| * ``` * * 默认显示方案: * ``` * |-------------------------| * | 群聊的聊天记录 | * | <消息 1> | * | <消息 2> | * | <消息 3> | * |-------------------------| * | 查看3条转发消息 | * |-------------------------| * ``` * * ### PC 端 * 在部分 PC 端显示为类似移动端的卡片, 在其他 PC 端显示为以下格式 * ``` * 鸽子 A 2020/04/23 11:27:54 * 咕 * 鸽子 B 2020/04/23 11:27:55 * 咕 * 鸽子 C 1970/01/01 08:00:00 * 咕咕咕 * ``` * * * ## 构造 * - 使用构建器 [ForwardMessageBuilder] * - 使用 [DSL][buildForwardMessage] * - 通过 [MessageEvent] 集合转换: [toForwardMessage] * * @see buildForwardMessage */ @SerialName(ForwardMessage.SERIAL_NAME) @Serializable public data class ForwardMessage( val preview: List<String>, val title: String, val brief: String, val source: String, val summary: String, val nodeList: List<Node>, ) : MessageContent, ConstrainSingle { override val key: MessageKey<ForwardMessage> get() = Key override fun contentToString(): String = "[转发消息]" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitForwardMessage(this, data) } // use data-class generated toString() /** * 合并转发卡片展示策略. 用于 [RawForwardMessage] 的 [渲染][RawForwardMessage.render]. * * @see ForwardMessage */ public interface DisplayStrategy { /** * 修改后卡片标题会变为 "转发的聊天记录", 而此函数的返回值会显示在 preview 前 */ public fun generateTitle(forward: RawForwardMessage): String = "群聊的聊天记录" /** * 显示在消息列表中的预览. */ public fun generateBrief(forward: RawForwardMessage): String = "[聊天记录]" /** * 目前未发现在哪能显示 */ public fun generateSource(forward: RawForwardMessage): String = "聊天记录" /** * 显示在卡片 body 中, 只会显示 sequence 前四个元素. */ public fun generatePreview(forward: RawForwardMessage): List<String> = forward.nodeList.map { it.senderName + ": " + it.messageChain.contentToString() } /** * 显示在卡片底部 */ public fun generateSummary(forward: RawForwardMessage): String = "查看${forward.nodeList.size}条转发消息" /** * 默认的, 与官方客户端相似的展示方案. */ public companion object Default : DisplayStrategy } /** * 消息节点 */ @OptIn(MiraiExperimentalApi::class) @Serializable public data class Node( /** * 发送人 [User.id] */ override val senderId: Long, /** * 时间戳 秒 */ override val time: Int, /** * 发送人昵称 */ override val senderName: String, /** * 消息内容 */ override val messageChain: MessageChain ) : INode { public constructor( senderId: Long, time: Int, senderName: String, message: Message ) : this(senderId, time, senderName, message.toMessageChain()) } /** * 请构造 [Node] */ @MiraiExperimentalApi public interface INode { /** * 发送人 [User.id] */ public val senderId: Long /** * 时间戳秒 */ public val time: Int /** * 发送人名称 */ public val senderName: String /** * 消息内容 */ public val messageChain: MessageChain } public companion object Key : AbstractPolymorphicMessageKey<MessageContent, ForwardMessage>(MessageContent, { it.safeCast() }) { public const val SERIAL_NAME: String = "ForwardMessage" } } /** * 转换为 [ForwardMessage] */ @JvmOverloads public fun Iterable<MessageEvent>.toForwardMessage(displayStrategy: DisplayStrategy = DisplayStrategy): ForwardMessage { val iterator = this.iterator() if (!iterator.hasNext()) return RawForwardMessage(emptyList()).render(displayStrategy) return RawForwardMessage( this.map { ForwardMessage.Node(it.sender.id, it.time, it.senderName, it.message) } ).render(displayStrategy) } /** * 转换为 [ForwardMessage] */ @JvmOverloads public fun Message.toForwardMessage( sender: User, time: Int = currentTimeSeconds().toInt(), displayStrategy: DisplayStrategy = DisplayStrategy ): ForwardMessage = this.toForwardMessage(sender.id, sender.nameCardOrNick, time, displayStrategy) /** * 转换为 [ForwardMessage] */ @JvmOverloads public fun Message.toForwardMessage( senderId: Long, senderName: String, time: Int = currentTimeSeconds().toInt(), displayStrategy: DisplayStrategy = DisplayStrategy ): ForwardMessage = RawForwardMessage(listOf(ForwardMessage.Node(senderId, time, senderName, this))).render(displayStrategy) /** * 构造一条 [ForwardMessage] * * @see ForwardMessageBuilder 查看 DSL 帮助 * @see ForwardMessage 查看转发消息说明 */ @JvmSynthetic public inline fun buildForwardMessage( context: Contact, displayStrategy: DisplayStrategy = DisplayStrategy, block: ForwardMessageBuilder.() -> Unit ): ForwardMessage = ForwardMessageBuilder(context).apply { this.displayStrategy = displayStrategy }.apply(block).build() /** * 使用 DSL 构建一个 [ForwardMessage]. * * @see ForwardMessageBuilder 查看 DSL 帮助 * @see ForwardMessage 查看转发消息说明 */ @JvmSynthetic public inline fun MessageEvent.buildForwardMessage( context: Contact = this.subject, displayStrategy: DisplayStrategy = DisplayStrategy, block: ForwardMessageBuilder.() -> Unit ): ForwardMessage = ForwardMessageBuilder(context).apply { this.displayStrategy = displayStrategy }.apply(block).build() /** * 标记转发消息 DSL */ @Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) @DslMarker public annotation class ForwardMessageDsl /** * 转发消息 DSL 构建器. * * # 总览 * * * 在 Java 使用一般方法添加消息构建转发: * ``` * ForwardMessageBuilder builder = new ForwardMessageBuilder(group); * * builder.add(123456L, "群员A", new PlainText("消息")) * builder.add(user, new PlainText("msg")); * builder.add(user, (chain) -> { * chain.add(new At(user)); * chain.add(new PlainText("Hello")) * }); // 使用 Java 8 lambda 语法, MessageChainBuilder 快速构建消息链 * builder.add(event); // 可添加 MessageEvent * * builder.setDisplayStrategy(new ForwardMessage.DisplayStrategy() { * }); // 可选, 修改显示策略 * * ForwardMessage forward = builder.build(); * group.sendMessage(forward); * ``` * * 在 Kotlin 使用一般方法添加消息构建转发: * ``` * val forward: ForwardMessage = buildForwardMessage { * add(123456L, "群员A", PlainText("消息")) * add(user, PlainText("msg")) * add(user) { * // this: MessageChainBuilder * add(At(user)) * add(PlainText("msg")) * }; * add(event) // 可添加 MessageEvent * } * * group.sendMessage(forward); * friend.sendMessage(forward); * ``` * * 在 Kotlin 也可以使用 DSL 构建一个转发: * ``` * buildForwardMessage { * 123456789 named "鸽子 A" says "咕" // 意为 名为 "鸽子 A" 的用户 123456789 发送了一条内容为 "咕" 的消息 * 100200300 named "鸽子 C" at 1582315452 says "咕咕咕" // at 设置时间 (在 PC 端显示, 在手机端不影响顺序) * 987654321 named "鸽子 B" says "咕" // 未指定时间, 则自动顺序安排时间 * myFriend says "咕" // User.says * bot says { // 构造消息链, 同 `buildMessageChain` * add("发个图片试试") * add(Image("{90CCED1C-2D64-313B-5D66-46625CAB31D7}.jpg")) * } * val member: Member = ... * member says "我是幸运群员" // 使用 `User says` 则会同时设置发送人名称 * } * ``` * DSL 语法在下文详细解释. * * 上述示例效果一样, 根据个人偏好选择. * * # Kotlin DSL 语法 * * 下文中 `S` 代表消息发送人. 可接受: 发送人账号 id([Long] 或 [Int]) 或 [User] * * 下文中 `M` 代表消息内容. 可接受: [String], [Message], 或 [构造消息链][MessageChainBuilder] 的 DSL 代码块 * * ## 陈述一条消息 * 使用 [`infix fun S.says(M)`][ForwardMessageBuilder.says] * * 语句 `123456789 named "鸽子 A" says "咕"` 创建并添加了一条名为 "鸽子 A" 的用户 123456789 发送的内容为 "咕" 的消息 * * * ### 陈述 * 一条 '陈述' 必须包含以下属性: * - 发送人. 只可以作为 infix 函数的接收者 (receiver) 设置, 如 `sender says M`, `sender named "xxx"`, `sender at 123` * - 消息内容. 只可以通过 `says` 函数的参数设置, 即 `says M`. * * ### 组合陈述 * 现支持的可选属性为 `named`, `at` * * * 最基础的陈述为 `S says M`. 可在 `says` 前按任意顺序添加组合属性: * * `S named "xxx" says M`; * * `S at 123456 says M`; 其中 `123456` 为发信时间 * * * 属性的顺序并不重要. 如下两句陈述效果相同. * * `S named "xxx" at 123456 says M`; * * `S at 123456 named "xxx" says M`; * * ### 重复属性 * 若属性有重复, **新属性会替换旧属性**. * * `S named "name1" named "name2" says M` 最终的发送人名称为 `"name2"` */ @OptIn(MiraiExperimentalApi::class) public class ForwardMessageBuilder private constructor( /** * 消息语境. 可为 [Group] 或 [User]. 用来确定某 ID 的用户的昵称. */ public val context: Contact, private val container: MutableList<ForwardMessage.INode> ) : MutableList<ForwardMessage.INode> by container { /** * @see RawForwardMessage.render */ public var displayStrategy: DisplayStrategy = DisplayStrategy private var built: Boolean = false private fun checkBuilt() { check(!built) { "ForwardMessageBuilder is already built therefore can't be modified" } } public constructor(context: Contact) : this(context, mutableListOf()) public constructor(context: Contact, initialSize: Int) : this(context, ArrayList<ForwardMessage.INode>(initialSize)) /** * 当前时间. * 在使用 [says] 时若不指定时间, 则会使用 [currentTime] 自增 1 的时间. */ public var currentTime: Int = currentTimeSeconds().toInt() public inner class BuilderNode internal constructor() : ForwardMessage.INode { /** * 发送人 [User.id] */ public override var senderId: Long = 0 /** * 时间戳秒 */ public override var time: Int = currentTime++ /** * 发送人名称 */ public override var senderName: String = "" /** * 消息内容 */ public override lateinit var messageChain: MessageChain /** * 指定发送人 id 和名称. */ @ForwardMessageDsl public infix fun sender(user: User): BuilderNode = apply { this.senderId(user.id); this.named(user.nameCardOrNick) } /** * 指定发送人 id 和名称. * @since 2.6 */ @ForwardMessageDsl public infix fun sender(user: UserOrBot): BuilderNode = apply { this.senderId(user.id); this.named(user.nameCardOrNick) } /** * 指定发送人 id. */ @ForwardMessageDsl public infix fun senderId(id: Int): BuilderNode = apply { this.senderId = id.toLongUnsigned() } /** * 指定发送人 id. */ @ForwardMessageDsl public infix fun senderId(id: Long): BuilderNode = apply { this.senderId = id } /** * 指定发送人名称. */ @ForwardMessageDsl public infix fun named(name: String): BuilderNode = apply { this.senderName = name } /** * 指定发送人名称. */ @ForwardMessageDsl public infix fun senderName(name: String): BuilderNode = apply { this.senderName = name } /** * 指定时间. * @time 时间戳, 单位为秒 */ @ForwardMessageDsl public infix fun at(time: Int): BuilderNode = this.apply { this.time = time } /** * 指定时间. * @time 时间戳, 单位为秒 */ @ForwardMessageDsl public infix fun time(time: Int): BuilderNode = this.apply { this.time = time } /** * 指定消息内容 */ @ForwardMessageDsl public infix fun message(message: Message): BuilderNode = this.apply { this.messageChain = message.toMessageChain() } /** * 指定消息内容 */ @ForwardMessageDsl public infix fun message(message: String): BuilderNode = this.apply { this.messageChain = PlainText(message).toMessageChain() } /** 添加一条消息 */ @ForwardMessageDsl public infix fun says(message: Message): ForwardMessageBuilder = this@ForwardMessageBuilder.apply { checkBuilt() this@BuilderNode.messageChain = message.toMessageChain() add(this@BuilderNode) } /** 添加一条消息 */ @ForwardMessageDsl public infix fun says(message: String): ForwardMessageBuilder = this.says(PlainText(message)) /** 构造并添加一个 [MessageChain] */ @ForwardMessageDsl public inline infix fun says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder = says(MessageChainBuilder().apply(chain).asMessageChain()) override fun toString(): String { return "BuilderNode(senderId=$senderId, time=$time, senderName='$senderName', messageChain=$messageChain)" } } // region general `says` /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun Long.says(message: String): ForwardMessageBuilder = says(PlainText(message)) /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun Int.says(message: String): ForwardMessageBuilder = this.toLong().and(0xFFFF_FFFF).says(PlainText(message)) /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun Long.says(message: Message): ForwardMessageBuilder = this@ForwardMessageBuilder.apply { checkBuilt() add(BuilderNode().apply { senderId = this@says this.messageChain = message.toMessageChain() }) } /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun Int.says(message: Message): ForwardMessageBuilder = this.toLong().and(0xFFFF_FFFF).says(message) /** 构造并添加一个 [MessageChain], 自动按顺序调整时间 */ @ForwardMessageDsl public inline infix fun Long.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder = says(MessageChainBuilder().apply(chain).asMessageChain()) /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public inline infix fun Int.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder = this.toLong().and(0xFFFF_FFFF).says(chain) /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun Bot.says(message: String): ForwardMessageBuilder = this.id named this.smartName() says message /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun User.says(message: String): ForwardMessageBuilder = this.id named this.nameCardOrNick says message /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun User.says(message: Message): ForwardMessageBuilder = this.id named this.nameCardOrNick says message /** * 添加一条消息, 自动按顺序调整时间 * @since 2.6 */ @ForwardMessageDsl public infix fun UserOrBot.says(message: Message): ForwardMessageBuilder = this.id named this.nameCardOrNick says message /** 添加一条消息, 自动按顺序调整时间 */ @ForwardMessageDsl public infix fun Bot.says(message: Message): ForwardMessageBuilder = this.id named this.smartName() says message /** 构造并添加一个 [MessageChain], 自动按顺序调整时间 */ @ForwardMessageDsl public inline infix fun User.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder = this says (MessageChainBuilder().apply(chain).asMessageChain()) /** * 构造并添加一个 [MessageChain], 自动按顺序调整时间 * @since 2.6 */ @ForwardMessageDsl public inline infix fun UserOrBot.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder = this says (MessageChainBuilder().apply(chain).asMessageChain()) /** 构造并添加一个 [MessageChain], 自动按顺序调整时间 */ @ForwardMessageDsl public inline infix fun Bot.says(chain: @ForwardMessageDsl MessageChainBuilder.() -> Unit): ForwardMessageBuilder = this says (MessageChainBuilder().apply(chain).asMessageChain()) // endregion // region timed /** * 为一条消息指定时间. * @time 时间戳, 单位为秒 */ @ForwardMessageDsl public infix fun Int.at(time: Int): BuilderNode = this.toLongUnsigned() at time /** * 为一条消息指定时间. * @time 时间戳, 单位为秒 */ @ForwardMessageDsl public infix fun Long.at(time: Int): BuilderNode = BuilderNode().apply { senderId = this@at;this.time = time } /** * 为一条消息指定时间和发送人名称. * @time 时间戳, 单位为秒 */ @ForwardMessageDsl public infix fun User.at(time: Int): BuilderNode = this.id named this.nameCardOrNick at time /** * 为一条消息指定时间和发送人名称. * @time 时间戳, 单位为秒 * @since 2.6 */ @ForwardMessageDsl public infix fun UserOrBot.at(time: Int): BuilderNode = this.id named this.nameCardOrNick at time // endregion // region named /** 为一条消息指定发送人名称. */ @ForwardMessageDsl public infix fun Int.named(name: String): BuilderNode = this.toLongUnsigned().named(name) /** 为一条消息指定发送人名称. */ @ForwardMessageDsl public infix fun Long.named(name: String): BuilderNode = BuilderNode().apply { senderId = this@named;this.senderName = name } /** 为一条消息指定发送人名称. */ @ForwardMessageDsl public infix fun User.named(name: String): BuilderNode = this.id.named(name) /** * 为一条消息指定发送人名称. * @since 2.6 */ @ForwardMessageDsl public infix fun UserOrBot.named(name: String): BuilderNode = this.id.named(name) @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution // due to inference problem, `add(MessageEvent)` results in resolution ambiguity override fun add(element: ForwardMessage.INode): Boolean = container.add(element) /** * 从消息事件添加一个消息 * * `event.sender named event.senderName at event.time says event.message` * * @since 2.1 */ public fun add(event: MessageEvent): ForwardMessageBuilder { return event.sender named event.senderName at event.time says event.message } // endregion // region common /** * 添加一个消息. 若不指定 [time], 将会使用 [currentTime] 自增 1 的时间. * @since 2.6 */ @JvmOverloads public fun add(sender: User, message: Message, time: Int = -1): ForwardMessageBuilder { return if (time == -1) sender says message else sender at time says message } /** * 添加一个消息. 若不指定 [time], 将会使用 [currentTime] 自增 1 的时间. * @since 2.6 */ @JvmOverloads public fun add(sender: UserOrBot, message: Message, time: Int = -1): ForwardMessageBuilder { return if (time == -1) sender says message else sender at time says message } /** * 添加一个消息. 若不指定 [time], 将会使用 [currentTime] 自增 1 的时间. * @since 2.6 */ @JvmOverloads public fun add(senderId: Long, senderName: String, message: Message, time: Int = -1): ForwardMessageBuilder { return if (time == -1) senderId named senderName says message else senderId named senderName at time says message } /** * 构建并添加一个消息. 若不指定 [time], 将会使用 [currentTime] 自增 1 的时间. * @since 2.6 * @see buildMessageChain */ @JvmOverloads public inline fun add( senderId: Long, senderName: String, time: Int = -1, builderAction: MessageChainBuilder.() -> Unit ): ForwardMessageBuilder { return add(senderId, senderName, time = time, message = buildMessageChain(builderAction)) } /** * 构建并添加一个消息. 若不指定 [time], 将会使用 [currentTime] 自增 1 的时间. * @since 2.6 * @see buildMessageChain */ @JvmOverloads public inline fun add( sender: UserOrBot, time: Int = -1, builderAction: MessageChainBuilder.() -> Unit ): ForwardMessageBuilder { return add(sender, time = time, message = buildMessageChain(builderAction)) } // endregion /** * 构造 [RawForwardMessage]. [RawForwardMessage] 可以被多个 [DisplayStrategy] [渲染][RawForwardMessage.render]. * @since 2.6 */ public fun toRawForwardMessage(): RawForwardMessage = RawForwardMessage(container.map { ForwardMessage.Node(it.senderId, it.time, it.senderName, it.messageChain) }) /** * 使用 [displayStrategy] 渲染并构造可以发送的 [ForwardMessage]. */ public fun build(): ForwardMessage = toRawForwardMessage().render(this.displayStrategy) internal fun Bot.smartName(): String = when (val c = this@ForwardMessageBuilder.context) { is Group -> c.botAsMember.nameCardOrNick else -> nick } } @OptIn(MiraiExperimentalApi::class) private fun ForwardMessage.INode.toNode(): ForwardMessage.Node { return ForwardMessage.Node(senderId, time, senderName, messageChain) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/HummerMessage.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("unused", "NOTHING_TO_INLINE") @file:JvmMultifileClass @file:JvmName("MessageUtils") // since 0.39.1 package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.castOrNull import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 一些特殊的消息 * * 注意, [HummerMessage] 类型不稳定, 但它的子类如 [PokeMessage] 是稳定的. * * @see PokeMessage 戳一戳 * @see FlashImage 闪照 * @see MarketFace 商城表情 * @see VipFace VIP 表情 */ @MiraiExperimentalApi @NotStableForInheritance public interface HummerMessage : MessageContent, ConstrainSingle { @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitHummerMessage(this, data) } public companion object Key : AbstractPolymorphicMessageKey<MessageContent, HummerMessage>(MessageContent, { it.castOrNull() }) // has service type etc. } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/Image.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress( "EXPERIMENTAL_API_USAGE", "unused", "UnusedImport", "DEPRECATION_ERROR", "MemberVisibilityCanBePrivate" ) package net.mamoe.mirai.message.data import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.CompositeDecoder import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.decodeStructure import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact.Companion.uploadImage import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.data.Image.Builder import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1 import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2 import net.mamoe.mirai.message.data.Image.Key.isUploaded import net.mamoe.mirai.message.data.Image.Key.queryUrl import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage /** * 自定义表情 (收藏的表情) 和普通图片. * * * 最推荐的存储方式是存储图片原文件, 每次发送图片时都使用文件上传. * 在上传时服务器会根据其缓存情况回复已有的图片 ID 或要求客户端上传. * * # 获取 [Image] 实例 * * ## 根据 ID 构造图片 * * ID 格式查看 [Image.imageId]. 通过 ID 构造的图片若未存在于服务器中, 发送后在客户端将不能正常显示. 可通过 [Image.isUploaded] 检查图片是否存在于服务器. * [Image.isUploaded] 需要除了 ID 以外其他参数, 如 [width], [size] 等, 因此不建议通过 ID 构造图片, 而是使用 [Builder] 构建, 以提供详细参数. * * 使用 [Image.fromId]. 在 Kotlin, 也可以使用顶层函数 `val image = Image("id")`. * * ### 在 Kotlin 通过 ID 构造图片 * ``` * // 根据喜好选择 * val image = Image.fromId("id") * val image2 = Image("id") * ``` * * ### 在 Java 通过 ID 构造图片 * ```java * Image image = Image.fromId("id") * ``` * * ## 使用 [Builder] 构建图片 * * [Image] 提供 [Builder] 构建方式, 可以指定 [width], [height] 等额外参数. 请尽可能提供这些参数以提升图片发送的成功率和 [Image.isUploaded] 的准确性. * * ## 上传图片资源获得 [Image] * * 使用 [Contact.uploadImage], 将 [ExternalResource] 上传得到 [Image]. * * 也可以使用 [ExternalResource.uploadAsImage] 扩展. * * # 发送图片消息 * * 在获取实例后, 将图片元素连接到[消息链][MessageChain]即可发送. 图片可以与[纯文本][PlainText]等其他 [MessageContent] 混合使用 (在同一[消息链][MessageChain]中). * * # 下载图片 * * 通过[事件][MessageEvent]等方式获取到 [Image] 实例后, 使用 [Image.queryUrl] 查询得到图片的下载地址, 自行使用 HTTP 客户端下载. * * # 其他查询 * * ## 查询图片是否已存在于服务器 * * 使用 [Image.isUploaded]. 当图片在服务器上存在时返回 `true`, 这意味着图片可以直接发送. * * 服务器通过 [Image.md5] 鉴别图片. 当图片已经存在于服务器时, [Contact.uploadImage] 会更快返回 (仍然需要进行网络请求), 不会上传图片数据. * * ## 检测图片 ID 合法性 * * 使用 [Image.IMAGE_ID_REGEX]. * * ## mirai 码支持 * 格式: &#91;mirai:image:*[Image.imageId]*&#93; * * @see FlashImage 闪照 * @see Image.flash 转换普通图片为闪照 */ @Suppress("DEPRECATION", "DEPRECATION_ERROR") @Serializable(Image.Serializer::class) @NotStableForInheritance public interface Image : Message, MessageContent, CodableMessage { /** * 图片的 id. * * 图片 id 不一定会长时间保存, 也可能在将来改变格式, 因此不建议使用 id 发送图片. * * ### 格式 * 所有图片的 id 都满足正则表达式 [IMAGE_ID_REGEX]. 示例: `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.ext` (ext 为文件后缀, 如 png) * * @see Image 使用 id 构造图片 */ public val imageId: String /** * 图片的宽度 (px), 当无法获取时为 0 * * @since 2.8.0 */ public val width: Int /** * 图片的高度 (px), 当无法获取时为 0 * * @since 2.8.0 */ public val height: Int /** * 图片的大小(字节), 当无法获取时为 0. 可用于 [isUploaded]. * * @since 2.8.0 */ public val size: Long /** * 图片的类型, 当无法获取时为未知 [ImageType.UNKNOWN] * * @since 2.8.0 * * @see ImageType */ public val imageType: ImageType /** * 判断该图片是否为 `动画表情` * * @since 2.8.0 */ public val isEmoji: Boolean get() = false /** * 图片文件 MD5. 可用于 [isUploaded]. * * @return 16 bytes * @see isUploaded * @since 2.9.0 */ // was an extension on Image before 2.9.0-M1. public val md5: ByteArray get() = calculateImageMd5ByImageId(imageId) @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitImage(this, data) } public object AsStringSerializer : KSerializer<Image> by String.serializer().mapPrimitive( SERIAL_NAME, serialize = { imageId }, deserialize = { Image(it) }, ) @OptIn(MiraiInternalApi::class) @Deprecated( message = "For internal use only. Deprecated for removal. Please retrieve serializer from MessageSerializers.serializersModule.", level = DeprecationLevel.WARNING ) @DeprecatedSinceMirai(warningSince = "2.13") // error since 2.15, hidden since 2.16 public object Serializer : KSerializer<Image> by FallbackSerializer(SERIAL_NAME) // move to mirai-core in 2.16. Delegate Serializer to the implementation from MessageSerializers. @MiraiInternalApi public open class FallbackSerializer(serialName: String) : KSerializer<Image> { override val descriptor: SerialDescriptor = buildClassSerialDescriptor(serialName) { element("imageId", String.serializer().descriptor) element("size", Long.serializer().descriptor) element("imageType", ImageType.serializer().descriptor) element("width", Int.serializer().descriptor) element("height", Int.serializer().descriptor) element("isEmoji", Boolean.serializer().descriptor) } // Note: Manually written to overcome discriminator issues. // Without this implementation you will need `ignoreUnknownKeys` on deserialization. override fun deserialize(decoder: Decoder): Image { return decoder.decodeStructure(descriptor) { @OptIn(ExperimentalSerializationApi::class) if (runCatching { this.decodeSequentially() }.getOrElse { false }) { val imageId = this.decodeStringElement(descriptor, 0) val size = this.decodeLongElement(descriptor, 1) val type = this.decodeSerializableElement(descriptor, 2, ImageType.serializer()) val width = this.decodeIntElement(descriptor, 3) val height = this.decodeIntElement(descriptor, 4) val isEmoji = this.decodeBooleanElement(descriptor, 5) return@decodeStructure Image(imageId) { this.size = size this.type = type this.width = width this.height = height this.isEmoji = isEmoji } } else { return@decodeStructure Image("") { while (true) { when (val index = this@decodeStructure.decodeElementIndex(descriptor)) { 0 -> imageId = this@decodeStructure.decodeStringElement(descriptor, index) 1 -> size = this@decodeStructure.decodeLongElement(descriptor, index) 2 -> type = this@decodeStructure.decodeSerializableElement( descriptor, index, ImageType.serializer() ) 3 -> width = this@decodeStructure.decodeIntElement(descriptor, index) 4 -> height = this@decodeStructure.decodeIntElement(descriptor, index) 5 -> isEmoji = this@decodeStructure.decodeBooleanElement(descriptor, index) CompositeDecoder.DECODE_DONE -> break } } check(imageId.isNotEmpty()) { "imageId must not empty" } } } } } override fun serialize(encoder: Encoder, value: Image) { Delegate.serializer().serialize( encoder, Delegate( value.imageId, value.size, value.imageType, value.width, value.height, value.isEmoji ) ) } @SerialName(SERIAL_NAME) @Serializable private data class Delegate( val imageId: String, val size: Long, val imageType: ImageType, val width: Int, val height: Int, val isEmoji: Boolean ) } /** * [Image] 构建器. * * 示例: * * ```java * Builder builder = Image.Builder.newBuilder("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.jpg") * builder.setSize(123); * builder.setType(ImageType.PNG); * * Image image = builder.build(); * ``` * * @since 2.9.0 */ public class Builder private constructor( /** * @see Image.imageId */ public var imageId: String, ) { /** * 图片大小字节数. 如果不提供该属性, 将无法 [Image.Key.isUploaded] * * @see Image.size */ public var size: Long = 0 /** * @see Image.imageType */ public var type: ImageType = ImageType.UNKNOWN /** * @see Image.width */ public var width: Int = 0 /** * @see Image.height */ public var height: Int = 0 /** * @see Image.isEmoji */ public var isEmoji: Boolean = false public fun build(): Image { if (type == ImageType.UNKNOWN) { type = ImageType.match(imageId.split(".").last()) } @OptIn(MiraiInternalApi::class) return InternalImageProtocol.instance.createImage( imageId = imageId, size = size, type = type, width = width, height = height, isEmoji = isEmoji, ) } public companion object { /** * 创建一个 [Builder] */ @JvmStatic public fun newBuilder(imageId: String): Builder = Builder(imageId) } } @JvmBlockingBridge public companion object Key : AbstractMessageKey<Image>({ it.safeCast() }) { public const val SERIAL_NAME: String = "Image" /** * 通过 [Image.imageId] 构造一个 [Image] 以便发送. * * 图片 ID 不一定会长时间保存, 因此不建议使用 ID 发送图片. 建议使用 [Builder], 可以指定更多参数 (以及用于查询图片是否存在于服务器的必要参数 size). * * @see Image 获取更多说明 * @see Image.imageId 获取更多说明 * @see Builder */ @JvmStatic public fun fromId(imageId: String): Image = Mirai.createImage(imageId) /** * 构造一个 [Image.Builder] 实例. * * @since 2.9.0 */ @JvmStatic public fun newBuilder(imageId: String): Builder = Builder.newBuilder(imageId) /** * 查询原图下载链接. * * - 当图片为从服务器接收的消息中的图片时, 可以直接获取下载链接, 本函数不会挂起协程. * - 其他情况下协程可能会挂起并向服务器查询下载链接, 或不挂起并拼接一个链接. * * @return 原图 HTTP 下载链接 * @throws IllegalStateException 当无任何 [Bot] 在线时抛出 (因为无法获取相关协议) */ @JvmStatic public suspend fun Image.queryUrl(): String { val bot = Bot.instancesSequence.firstOrNull() ?: error("No Bot available to query image url") return Mirai.queryImageUrl(bot, this) } /** * 当图片在服务器上存在时返回 `true`, 这意味着图片可以直接发送给 [contact]. * * 若返回 `false`, 则图片需要用 [ExternalResource] 重新上传 ([Contact.uploadImage]). * * @since 2.9.0 */ @JvmStatic public suspend fun Image.isUploaded(bot: Bot): Boolean { @OptIn(MiraiInternalApi::class) return InternalImageProtocol.instance.isUploaded(bot, md5, size, null, imageType, width, height) } /** * 当图片在服务器上存在时返回 `true`, 这意味着图片可以直接发送给 [contact]. * * 若返回 `false`, 则图片需要用 [ExternalResource] 重新上传 ([Contact.uploadImage]). * * @param md5 图片文件 MD5. 可通过 [Image.md5] 获得. * @param size 图片文件大小. * * @since 2.9.0 */ @JvmStatic public suspend fun isUploaded( bot: Bot, md5: ByteArray, size: Long, ): Boolean { @OptIn(MiraiInternalApi::class) return InternalImageProtocol.instance.isUploaded(bot, md5, size, null) } /** * 由 [Image.imageId] 计算 [Image.md5]. * * @since 2.9.0 */ public fun calculateImageMd5ByImageId(imageId: String): ByteArray { @OptIn(MiraiInternalApi::class) return when { imageId matches IMAGE_ID_REGEX -> imageId.imageIdToMd5(1) imageId matches IMAGE_RESOURCE_ID_REGEX_2 -> imageId.imageIdToMd5(imageId.skipToSecondHyphen() + 1) imageId matches IMAGE_RESOURCE_ID_REGEX_1 -> imageId.imageIdToMd5(1) else -> throw IllegalArgumentException( "Illegal imageId: '$imageId'. $ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE" ) } } /** * 统一 ID 正则表达式 * * `{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.ext` */ @Suppress("RegExpRedundantEscape") // This is required on Android @JvmStatic @get:JvmName("getImageIdRegex") public val IMAGE_ID_REGEX: Regex = Regex("""\{[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}\}\..{3,5}""") /** * 图片资源 ID 正则表达式 1. mirai 内部使用. * * `/f8f1ab55-bf8e-4236-b55e-955848d7069f` * @see IMAGE_RESOURCE_ID_REGEX_2 */ @JvmStatic @MiraiInternalApi @get:JvmName("getImageResourceIdRegex1") public val IMAGE_RESOURCE_ID_REGEX_1: Regex = Regex("""/[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}""") /** * 图片资源 ID 正则表达式 2. mirai 内部使用. * * `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` * @see IMAGE_RESOURCE_ID_REGEX_1 */ @JvmStatic @MiraiInternalApi @get:JvmName("getImageResourceIdRegex2") public val IMAGE_RESOURCE_ID_REGEX_2: Regex = Regex("""/[0-9]*-[0-9]*-[0-9a-fA-F]{32}""") } } /** * 通过 [Image.imageId] 构造一个 [Image] 以便发送. * * 图片 ID 不一定会长时间保存, 因此不建议使用 ID 发送图片. 建议使用 [Image.Builder], 可以指定更多参数 (以及用于查询图片是否存在于服务器的必要参数 size). * * @see Image 获取更多关于 [Image] 的说明 * @see Image.Builder 获取更多关于构造 [Image] 的方法 * * @see IMirai.createImage */ @JvmSynthetic public fun Image(imageId: String): Image = Builder.newBuilder(imageId).build() /** * 使用 [Image.Builder] 构建一个 [Image]. * * @see Image.Builder * @since 2.9.0 */ @JvmSynthetic public inline fun Image(imageId: String, builderAction: Builder.() -> Unit = {}): Image = Builder.newBuilder(imageId).apply(builderAction).build() @Serializable public enum class ImageType( /** * @since 2.9.0 */ @MiraiInternalApi public val formatName: String, ) { PNG("png"), BMP("bmp"), JPG("jpg"), GIF("gif"), //WEBP, //Unsupported by pc client APNG("png"), UNKNOWN("gif"); // bad design, should use `null` to represent unknown, but we cannot change it anymore. public companion object { private val IMAGE_TYPE_ENUM_LIST = values() @JvmStatic public fun match(str: String): ImageType { return matchOrNull(str) ?: UNKNOWN } @JvmStatic public fun matchOrNull(str: String): ImageType? { val input = str.uppercase() return IMAGE_TYPE_ENUM_LIST.firstOrNull { it.name == input } } } } /////////////////////////////////////////////////////////////////////////// // Internals /////////////////////////////////////////////////////////////////////////// @Deprecated("Use member function", level = DeprecationLevel.HIDDEN) // safe since it was internal @Suppress("EXTENSION_SHADOWED_BY_MEMBER") @MiraiInternalApi @get:JvmName("calculateImageMd5") @DeprecatedSinceMirai(hiddenSince = "2.9") public val Image.md5: ByteArray get() = Image.calculateImageMd5ByImageId(imageId) /** * 内部图片协议实现 * @since 2.9.0-M1 */ @MiraiInternalApi public interface InternalImageProtocol { // naming it Internal* to assign it a lower priority when resolving Image* public fun createImage( imageId: String, size: Long, type: ImageType = ImageType.UNKNOWN, width: Int = 0, height: Int = 0, isEmoji: Boolean = false ): Image /** * @param context 用于检查的 [Contact]. 群图片与好友图片是两个通道, 建议使用欲发送到的 [Contact] 对象作为 [contact] 参数, 但目前不提供此参数时也可以检查. */ public suspend fun isUploaded( bot: Bot, md5: ByteArray, size: Long, context: Contact? = null, type: ImageType = ImageType.UNKNOWN, width: Int = 0, height: Int = 0 ): Boolean @MiraiInternalApi public companion object { public val instance: InternalImageProtocol by lazy { Mirai // initialize MiraiImpl first loadService( InternalImageProtocol::class, "net.mamoe.mirai.internal.message.InternalImageProtocolImpl" ) } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MarketFace.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.safeCast /** * 商城表情 * * 除 [Dice] 可以发送外, 目前不支持直接构造和发送,可保存接收到的来自官方客户端的商城表情然后转发. * * @see Dice */ @OptIn(MiraiExperimentalApi::class) @NotStableForInheritance public interface MarketFace : HummerMessage { /** * 如 `[开心]` */ public val name: String /** * 内部 id. */ @MiraiExperimentalApi public val id: Int override val key: MessageKey<MarketFace> get() = Key override fun contentToString(): String = name.ifEmpty { "[商城表情]" } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitMarketFace(this, data) } @OptIn(MiraiExperimentalApi::class) public companion object Key : AbstractPolymorphicMessageKey<HummerMessage, MarketFace>(HummerMessage, { it.safeCast() }) { // Notice that for MarketFaceImpl, its serial name is 'MarketFace'; // while for Dice, that is 'Dice' instead of 'MarketFace' again. (Dice extends MarketFace) public const val SERIAL_NAME: String = "MarketFace" } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/Message.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.fold import kotlinx.serialization.* import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.code.MiraiCode import net.mamoe.mirai.message.code.MiraiCode.serializeToMiraiCode import net.mamoe.mirai.message.data.Message.Companion.toString import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 可发送的或从服务器接收的消息. * * [Message] 派生为 [SingleMessage] 和 [MessageChain]. * * [SingleMessage] 分为: * - [MessageMetadata] 消息元数据, 即消息的属性. 包括: [消息来源][MessageSource], [引用回复][QuoteReply] 等. * - [MessageContent] 含内容的消息, 包括: [纯文本][PlainText], [@群员][At], [@全体成员][AtAll] 等. * * [MessageChain] 是链表形式链接的多个 [SingleMessage] 实例, 类似 [List]. * * ## [Message] 是不可变的 * * 所有类型的 [Message] 都是不可变的 (immutable), 它们被构造后其所有属性的值就已经固定了. 因此在多线程环境使用是安全的. * 因此 [contentToString], [serializeToJsonString], [MiraiCode.serializeToMiraiCode] 的返回都是不变的. * * [MessageChain] 的 [contentToString] 会被缓存, 只会在第一次调用时计算. * * ## 获得 [Message] * * 查看 [Message] 子类. 或在 GitHub 查看 [表格](https://github.com/mamoe/mirai/blob/dev/docs/Messages.md#%E6%B6%88%E6%81%AF%E5%85%83%E7%B4%A0). * * ## 使用 [Message] * * ### 转换为 [MessageChain] * * [MessageChain] 为多个 [SingleMessage] 的集合. [Message] 可能表示 single 也可能表示 chain. 可以通过 [toMessageChain] 将 [Message] 转换为 [MessageChain] 统一处理. * * ### 连接两个或多个 [Message] * * 在 Kotlin, 使用操作符 [Message.plus]: * ``` * val text = PlainText("Hello ") + PlainText("world") + "!" * friend.sendMessage(text) // "Hello world!" * ``` * * 在 Java, 使用 [plus]: * ``` * MessageChain text = new PlainText("Hello ") * .plus(new PlainText("world")) * .plus("!"); * friend.sendMessage(text); // "Hello world!" * ``` * * 注: 若需要拼接较多 [Message], 推荐使用 [MessageChainBuilder] 加快拼接效率 * * ### 使用 [MessageChainBuilder] 来构建消息 * * 查看 [MessageChainBuilder]. * * ### 发送消息 * * - [Contact.sendMessage] 接收 [Message] 参数 * - [Message.sendTo] 是发送的扩展 * * ### 处理消息 * * 除了直接访问 [Message] 子类的对象外, 有时候可能需要将 [Message] 作为字符串处理, * 通常可以使用 [contentToString] 方法或 [content] 扩展得到与官方客户端显示格式相同的内容字符串. * * #### 文字处理示例 * * 本示例实现处理以 `#` 开头的消息: * * Kotlin: * ``` * val msg = event.message * val content = msg.content.trim() * if (content.startsWith("#")) { * val name = content.substringAfter("#", "") * when(name) { * "mute" -> event.sender.mute(60000) // 发 #mute 就把自己禁言 1 分钟 * } * } * ``` * * Java: * ``` * MessageChain msg = event.message; * String content = msg.contentToString(); * if (!content.equals("#") && content.startsWith("#")) { * String name = content.substring(content.indexOf('#') + 1); // `#` 之后的内容 * switch(name) { * case "mute": event.sender.mute(60000) // 发 #mute 就把自己禁言 1 分钟 * } * } * ``` * * 若使用 Java 对象的 [toString], 会得到包含更多信息. 因此 [toString] 结果可能会随着 mirai 更新变化. [toString] 不适合用来处理消息. 只适合用来调试输出. * * [Message] 还提供了 [Mirai 码][MiraiCode] 和 [JSON][MessageChain.serializeToJsonString] 序列化方式. 可在 [MessageChain] 文档详细了解它们. * * ### 发送消息 * - 通过 [Contact] 中的成员函数: [Contact.sendMessage] * - 通过 [Message] 的扩展函数: [Message.sendTo] * * @see MessageChain 消息链(即 `List<Message>`) * @see buildMessageChain 构造一个 [MessageChain] * * @see Contact.sendMessage 发送消息 * * @suppress **注意:** [Message] 类型大多有隐藏的协议实现, 不能被第三方应用继承. */ public interface Message { /** * 得到包含 mirai 消息元素代码的, 易读的字符串. 如 `At(member) + "test"` 将转为 `"[mirai:at:qqId]test"`. * * 在使用消息相关 DSL 和扩展时, 一些内容比较的实现均使用的是 [contentToString] 而不是 [toString]. * * **注意: ** 即使 [toString] 输出的格式看起来像 [MiraiCode] 的表示, 但它们实际上是不同的. * [toString] 会返回更随意的和更适合开发者阅读的信息, 通常不能被 [MiraiCode] 解析. * * 各个消息类型的转换示例: * - [PlainText] : `"Hello"` * - [Image] : `"[mirai:image:{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai]"` * - [PokeMessage] : `"[mirai:poke:1,-1]"` * - [MessageChain] : 无间隔地连接所有元素 (`joinToString("")`) * - ... * * @see contentToString 转为最接近官方格式的字符串 */ public override fun toString(): String /** * 转为接近官方格式的字符串, 即 "内容". 如 `At(member) + "test"` 将转为 `"@QQ test"`. * * (对于 [At],应使用 [At.getDisplay] 将其转为最接近官方格式的字符串 `"@群名片"`) * * 在使用消息相关 DSL 和扩展时, 一些内容比较的实现均使用 [contentToString] 而不是 [toString]. * * 由于消息元素都是不可变的, [contentToString] 的返回也是不变的. * [MessageChain] 的 [contentToString] 会被缓存, 只会在第一次调用时计算. * * 各个消息类型的转换示例: * - [PlainText] : `"Hello"` * - [Image] : `"[图片]"` * - [PokeMessage] : `"[戳一戳]"` * - [MessageChain] : 无间隔地连接所有元素 (`joinToString("", transformer=Message::contentToString)`) * - ... * * **注意: ** 即使 [toString] 输出的格式看起来像 [MiraiCode] 的表示, 但它们实际上是不同的. * [toString] 会返回更随意的和更适合开发者阅读的信息, 通常不能被 [MiraiCode] 解析. * * @see toString 得到包含 mirai 消息元素代码的, 易读的字符串 * @see contentEquals * @see Message.content Kotlin 扩展 */ public fun contentToString(): String /** * 判断内容是否与 [another] 相等即 `this` 与 [another] 的 [contentToString] 相等. * * @param ignoreCase 为 `true` 时忽略大小写 * @param strict 为 `true` 时表示执行严格匹配, 即会额外判断每个消息元素的类型, 顺序和属性. 如 [Image] 会判断 [Image.imageId]. * 每个 [Image] 的[内容][contentToString]都是 `"[图片]"`, * 进行非严格匹配时 [contentEquals] 会返回 `true`. * 而为进行严格匹配时会额外比较 [Image.imageId], 两张不同的图片的 [contentEquals] 会返回 `false`. */ public fun contentEquals(another: Message, ignoreCase: Boolean = false, strict: Boolean = false): Boolean { return if (strict) this.contentEqualsStrictImpl(another, ignoreCase) else this.contentToString().equals(another.contentToString(), ignoreCase = ignoreCase) } /** * 判断内容是否与 [another] 相等即 `this` 与 [another] 的 [contentToString] 相等. * * 单个消息的顺序和内容不会被检查, 即只要比较两个 [Image], 总是会得到 `true`, 因为 [Image] 的 [contentToString] 都是 `"[图片]"`. * * * 相当于 * ``` * this.contentToString().equals(another.contentToString(), ignoreCase = ignoreCase) * ``` * * @param ignoreCase 为 `true` 时忽略大小写 */ @kotlin.internal.LowPriorityInOverloadResolution @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") public fun contentEquals(another: Message, ignoreCase: Boolean = false): Boolean = contentEquals(another, ignoreCase, false) /** * 判断内容是否与 [another] 相等. * * * 相当于 * ``` * this.contentToString().equals(another, ignoreCase = ignoreCase) * ``` * * * 若本函数返回 `true`, 则表明: * - `this` 与 [another] 的 [contentToString] 相等 */ public fun contentEquals(another: String, ignoreCase: Boolean = false): Boolean { return this.contentToString().equals(another, ignoreCase = ignoreCase) } /** * 将 `this` 和 [tail] 连接. * * 连接后可以保证 [ConstrainSingle] 的元素单独存在. * * 例: * ``` * val a = PlainText("Hello ") * val b = PlainText("world!") * val c: MessageChain = a + b * println(c) // "Hello world!" * ``` * * 在 Java 使用 [plus] * * @see plus `+` 操作符重载 */ @JvmSynthetic // in Java they should use `plus` instead public fun followedBy(tail: Message): MessageChain { var constrainSingleCount = 0 if (this.hasConstrainSingle) constrainSingleCount++ if (tail.hasConstrainSingle) constrainSingleCount++ return if (constrainSingleCount == 0) { // Future optimize: // When constrainSingleCount == 1, see if we can connect by CombinedMessage, // this need some kind of replacement of `hasConstrainSingle` with more information about MessageKeys. @OptIn(MessageChainConstructor::class, MiraiInternalApi::class) CombinedMessage(this, tail, false) } else { LinearMessageChainImpl.combineCreate(this, tail) } } /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: MessageChain): MessageChain = this + another as Message /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: Message): MessageChain = this.followedBy(another) /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: SingleMessage): MessageChain = this.followedBy(another) /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: String): MessageChain = this.followedBy(PlainText(another)) /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: CharSequence): MessageChain = this.followedBy(PlainText(another.toString())) /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: Iterable<Message>): MessageChain = another.fold(this, Message::plus).toMessageChain() /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: Array<out Message>): MessageChain = another.fold(this, Message::plus).toMessageChain() /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ @JvmName("plusIterableString") @Suppress("INAPPLICABLE_JVM_NAME") public operator fun plus(another: Iterable<String>): MessageChain = another.fold(this, Message::plus).toMessageChain() /** * 创建一个[消息链][MessageChain], 将 [another] 连接到这个消息的尾部. * 这不会改变本 [Message], 而是会创建新的 [MessageChain] 实例. * 返回的 [MessageChain] 实例的第一个元素为本 [Message], 随后为按顺序的 [another] 中的元素. */ public operator fun plus(another: Sequence<Message>): MessageChain = another.fold(this, Message::plus).toMessageChain() /** * @suppress 这是内部 API, 不要在任何情况下调用 * @since 2.12 */ @MiraiInternalApi public fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessage(this, data) /** * @suppress 这是内部 API, 不要在任何情况下调用 * @since 2.12 */ @MiraiInternalApi public fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) { } public companion object // 用于"注册"扩展 } /** 将 [another] 按顺序连接到这个消息的尾部. */ @JvmSynthetic public suspend operator fun Message.plus(another: Flow<Message>): MessageChain = another.fold(this) { acc, it -> acc + it }.toMessageChain() /** * [Message.contentToString] 的捷径 */ @get:JvmSynthetic public val Message.content: String get() = contentToString() /** * 将 [this] 发送给指定联系人 */ @JvmSynthetic @Suppress("UNCHECKED_CAST") public suspend inline fun <C : Contact> Message.sendTo(contact: C): MessageReceipt<C> = contact.sendMessage(this) as MessageReceipt<C> /** * 当消息内容为空时返回 `true`. * @see String.isEmpty */ public fun Message.isContentEmpty(): Boolean { return when (this) { is MessageChain -> this.all { it.isContentEmpty() } else -> this.content.isEmpty() } } /** * 当消息内容为空白时返回 `true`. * @see String.isBlank */ public fun Message.isContentBlank(): Boolean { return when (this) { is MessageChain -> this.all { it.isContentBlank() } else -> this.content.isBlank() } } /** * 将此消息元素按顺序重复 [count] 次. */ public fun Message.repeat(count: Int): MessageChain { if (this is ConstrainSingle) { // fast-path return this.toMessageChain() } return buildMessageChain(count) { repeat(count) l@{ add(this@repeat) } } } /** * 将此消息元素按顺序重复 [count] 次. */ @JvmSynthetic public operator fun Message.times(count: Int): MessageChain = this.repeat(count) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MessageChain.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("unused", "NOTHING_TO_INLINE", "INAPPLICABLE_JVM_NAME") package net.mamoe.mirai.message.data import kotlinx.coroutines.flow.Flow import kotlinx.serialization.* import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.json.Json import net.mamoe.mirai.console.compiler.common.ResolveContext import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.code.MiraiCode import net.mamoe.mirai.message.code.MiraiCode.deserializeMiraiCode import net.mamoe.mirai.message.data.MessageChain.Companion.deserializeFromMiraiCode import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.MessageSource.Key.recall import net.mamoe.mirai.message.data.MessageSource.Key.recallIn import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* import java.util.stream.Stream import kotlin.reflect.KProperty import kotlin.streams.asSequence import net.mamoe.mirai.console.compiler.common.ResolveContext.Kind.RESTRICTED_ABSTRACT_MESSAGE_KEYS as RAMK /** * 消息链, `List<SingleMessage>`, 即 [单个消息元素][SingleMessage] 的有序集合. * * [MessageChain] 代表一条完整的聊天中的消息, 可包含 [带内容的消息 `MessageContent`][MessageContent] * 和 [不带内容的元数据 `MessageMetadata`][MessageMetadata]. * * # 元素类型 * * [MessageContent] 如 [纯文字][PlainText], [图片][Image], [语音][Voice], 是能被用户看到的内容. * * * [MessageMetadata] 是用来形容这条消息的状态的数据, 因此称为 *元数据 (metadata)*. * 元数据目前只分为 [消息来源 `MessageSource`][MessageSource] 和 [引用回复 `QuoteReply`][QuoteReply]. * * [MessageSource] 存储这条消息的发送人, 接收人, 识别 ID (服务器提供), 发送时间等信息. * **[MessageSource] 是精确的**. 凭 [MessageSource] 就可以在服务器上定位一条消息, 因此可以用来 [撤回消息][MessageSource.recall]. * * [QuoteReply] 是一个标记, 表示这条消息引用了另一条消息 (在官方客户端中可通过 "回复" 功能发起引用). [QuoteReply.source] 则指代那条被引用的消息. * 由于 [MessageSource] 是精确的, 如果对 [QuoteReply.source] 使用 [MessageSource.recall], 则可以撤回那条被引用的消息. * * * # 获得消息链 * * 在消息事件中可以获得消息内容作为 [MessageChain]: [MessageEvent.message] * * 在主动发送消息时, 可使用如下方案. * * ## 在 Kotlin 构造消息链 * - 获取不包含任何元素的消息链: [EmptyMessageChain] * - [messageChainOf][messageChainOf]: 类似 [listOf], 将多个 [Message] 构造为 [MessageChain]: * ``` * val chain = messageChainOf(PlainText("..."), Image("..."), ...) * ``` * - [buildMessageChain][buildMessageChain]: 使用 DSL 构建器. * ``` * val chain = buildMessageChain { * +"你想要的图片是:" * +Image("...") * } * ``` * - [Message.plus][Message.plus]: 将两个消息相连成为一个消息链: * ``` * val chain = PlainText("Hello ") + PlainText("Mirai!") // chain: MessageChain * ``` * - [toMessageChain][toMessageChain] 将 [Iterable], [Array], [Sequence], [Iterator], [Flow], [Stream] 转换为 [MessageChain]. * 相关定义为: * ``` * public fun Sequence<Message>.toMessageChain(): MessageChain * public fun Iterable<Message>.toMessageChain(): MessageChain * public fun Iterator<Message>.toMessageChain(): MessageChain * public fun Stream<Message>.toMessageChain(): MessageChain * public fun Flow<Message>.toMessageChain(): MessageChain * public fun Array<Message>.toMessageChain(): MessageChain * ``` * - [Message.toMessageChain][Message.toMessageChain] 将单个 [Message] 包装成一个单元素的 [MessageChain] * * ## 在 Java 构造消息链 * - `MessageUtils.newChain`: 有多个重载, 相关定义如下: * ```java * public static MessageChain newChain(Message messages...) * public static MessageChain newChain(Iterable<Message> iterable) * public static MessageChain newChain(Iterator<Message> iterator) * public static MessageChain newChain(Stream<Message> stream) * public static MessageChain newChain(Message[] array) * ``` * - [Message.plus][Message.plus]: 将两个消息相连成为一个消息链: * ```java * MessageChain chain = new PlainText("Hello ").plus(new PlainText("Mirai!")) * ``` * - [MessageChainBuilder][MessageChainBuilder]: * ```java * MessageChainBuilder builder = MessageChainBuilder.create(); * builder.append(new PlainText("Hello ")); * builder.append(new PlainText(" Mirai!")); * MessageChain chain = builder.build(); * ``` * * # 元素唯一性 * * 部分消息类型如 [语音][Voice], [小程序][LightApp] 在官方客户端限制中只允许单独存在于一条消息. 在创建 [MessageChain] 时这种限制会被体现. * * 当添加只允许单独存在的消息元素到一个消息链时, 已有的元素可能会被删除或替换. 详见 [AbstractPolymorphicMessageKey] 和 [ConstrainSingle]. * * # 操作消息链 * * [MessageChain] 继承 `List<SingleMessage>`. 可以以 [List] 的方式处理 [MessageChain]. * * 额外地, 若要获取一个 [ConstrainSingle] 的元素, 可以通过 [ConstrainSingle.key]: * ``` * val quote = chain[QuoteReply] // Kotlin * * QuoteReply quote = chain.get(QuoteReply.Key) // Java * ``` * * 相关地还可以使用 [MessageChain.contains] 和 [MessageChain.getOrFail] * * ## 直接索引访问 * * [MessageChain] 实现接口 [List], 可以通过索引 `get(index)` 来访问. 由于 [MessageChain] 是稳定的, 这种访问操作也是稳定的. * * 但在处理来自服务器的 [MessageChain] 时, 请尽量避免这种直接索引访问. 来自服务器的消息的组成有可能会变化, 可能会有新的 [MessageMetadata] 加入. * 例如用户发送了两条内容相同的消息, 但其中一条带有引用回复而另一条没有, 则两条消息的索引可能有变化 (当然内容顺序不会改变, 只是 [QuoteReply] 的位置有可能会变化). * 因此在使用直接索引访问时要格外注意兼容性, 故不推荐这种访问方案. * * ### 避免索引访问以提高性能 * * 自 2.12 起, [MessageChain] 内部结构有性能优化. 该优化大幅降低元素数量多的 [MessageChain] 的连接的时间复杂度. * 性能优化默认生效, 但若使用 [get], [subList] 等 [List] 于 [Collection] 之外的方法时则会让该优化失效 (相比 2.12 以前不会丢失性能, 只是没有优化). * * ## 撤回和引用 * * 要撤回消息, 查看 [MessageSource] * * - [MessageSource.quote] * - [MessageSource.recall] * - [MessageSource.recallIn] * - `MessageChain.quote` * - `MessageChain.recall` * - `MessageChain.recallIn` * * ## Kotlin 扩展 * * ### 属性委托 * ``` * val at: At? by chain.orNull() * val at: At by chain.orElse { /* 返回一个 At */ } * val at: At by chain * ``` * * ### 筛选得到 [Sequence] 与 [List] * - [MessageChain.contentsSequence] * - [MessageChain.metadataSequence] * - [MessageChain.contentsList] * - [MessageChain.metadataList] * * # 序列化 * * ## kotlinx-serialization 序列化 * * - 使用 [MessageChain.serializeToJsonString] 将 [MessageChain] 序列化为 JSON [String]. * - 使用 [MessageChain.deserializeFromJsonString] 将 JSON [String] 反序列化为 [MessageChain]. * * ## Mirai Code 序列化 * * 详见 [MiraiCode] * * - 使用 [MessageChain.serializeToMiraiCode] 将 [MessageChain] 序列化为 Mirai Code [String]. * - 使用 [MessageChain.deserializeFromMiraiCode] 将 Mirai Code [String] 反序列化为 [MessageChain]. * */ @Serializable(MessageChain.Serializer::class) @NotStableForInheritance public sealed interface MessageChain : Message, List<SingleMessage>, RandomAccess, CodableMessage { // 写给 mirai 内部实现者: 优先考虑使用 visitor API 而不是 List 的 API. 这将会提升性能, 对于巨大的通过 `plus` 方式连接的消息链有重大区别. // 即使 [MessageChain] 实现 [RandomAccess], 使用基于 index 的 API 也可能会产生重大性能下降. (不过这本来就是没法避免的, 避免使用 index 属于一种性能优化). // 若你感兴趣原理, 阅读 [Message.followedBy] 和 [CombinedMessage] /** * 获取第一个类型为 [key] 的 [Message] 实例. 若不存在此实例, 返回 `null`. * * 此方法目前仅适用于 [ConstrainSingle] 的消息类型, 如 [MessageSource]. * * ### Kotlin 使用方法 * ``` * val chain: MessageChain = ... * * val source = chain[MessageSource] // MessageSource 为伴生对象 * ``` * * ### Java 使用方法 * ```java * MessageChain chain = ... * chain.get(MessageSource.Key) * ``` * * @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key] * * @see MessageChain.getOrFail 在找不到此类型的元素时抛出 [NoSuchElementException] */ public operator fun <M : SingleMessage> get(@ResolveContext(RAMK) key: MessageKey<M>): M? { @Suppress("UNCHECKED_CAST") return firstOrNull { key.safeCast.invoke(it) != null } as M? } /** * 当存在 [ConstrainSingle.key] 为 [key] 的 [SingleMessage] 实例时返回 `true`. * * 此方法目前仅适用于 [ConstrainSingle] 的消息类型, 如 [MessageSource]. * * ### Kotlin 使用方法 * ``` * val chain: MessageChain = ... * * if (chain.contains(QuoteReply)) { * // 包含引用回复 * } * ``` * * ### Java 使用方法 * ```java * MessageChain chain = ... * if (chain.contains(QuoteReply.Key)) { * // 包含引用回复 * } * ``` * * @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key] * * @see MessageChain.getOrFail 在找不到此类型的元素时抛出 [NoSuchElementException] */ public operator fun <M : SingleMessage> contains(@ResolveContext(RAMK) key: MessageKey<M>): Boolean = any { key.safeCast.invoke(it) != null } @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { forEach { it.safeCast<CodableMessage>()?.appendMiraiCodeTo(builder) } } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessageChain(this, data) /** * 将 [MessageChain] 作为 `List<SingleMessage>` 序列化. 使用 [多态序列化][Polymorphic]. * * 在实践时请提供 [MessageSerializers.serializersModule] 到指定 [SerialFormat]. * * @see ListSerializer * @see MessageSerializers */ public object Serializer : KSerializer<MessageChain> { @Suppress("DEPRECATION_ERROR") private val delegate = ListSerializer(PolymorphicSerializer(SingleMessage::class)) override val descriptor: SerialDescriptor = delegate.descriptor override fun deserialize(decoder: Decoder): MessageChain = delegate.deserialize(decoder).toMessageChain() override fun serialize(encoder: Encoder, value: MessageChain): Unit = delegate.serialize(encoder, value) } public companion object { private fun getDefaultJson() = Json { serializersModule = MessageSerializers.serializersModule // don't convert to property, serializersModule is volatile. ignoreUnknownKeys = true } /** * 从 JSON 字符串解析 [MessageChain] * @param json 需要包含 [MessageSerializers.serializersModule] * @see serializeToJsonString */ @JvmOverloads @JvmStatic public fun deserializeFromJsonString( string: String, json: Json = getDefaultJson() ): MessageChain { return json.decodeFromString(Serializer, string) } /** * 从 JSON 字符串解析 [MessageChain] * @param json 需要包含 [MessageSerializers.serializersModule] * @see deserializeFromJsonString * @see serializeToJsonString */ @JvmSynthetic @JvmStatic public inline fun String.deserializeJsonToMessageChain(json: Json): MessageChain = deserializeFromJsonString(this, json) /** * 从 JSON 字符串解析 [MessageChain] * @see serializeToJsonString * @see deserializeFromJsonString */ @JvmSynthetic @JvmStatic public inline fun String.deserializeJsonToMessageChain(): MessageChain = deserializeFromJsonString(this) /** * 将 [MessageChain] 序列化为 JSON 字符串. * @see deserializeFromJsonString */ @JvmOverloads @JvmStatic public fun MessageChain.serializeToJsonString( json: Json = getDefaultJson() ): String = json.encodeToString(Serializer, this) /** * 将 [MessageChain] 序列化为指定格式的字符串. * * @see serializeToJsonString * @see StringFormat.encodeToString */ @ExperimentalSerializationApi @JvmStatic public fun MessageChain.serializeToString(format: StringFormat): String = format.encodeToString(Serializer, this) /** * 解析形如 "[mirai:]" 的 mirai 码, 即 [CodableMessage.serializeToMiraiCode] 返回的内容. * @param contact 解析语境 * @see MiraiCode.deserializeMiraiCode * @since 2.13 */ @JvmStatic public fun deserializeFromMiraiCode(miraiCode: String, contact: Contact? = null): MessageChain = miraiCode.deserializeMiraiCode(contact) /** * 解析形如 "[mirai:]" 的 mirai 码, 即 [CodableMessage.serializeToMiraiCode] 返回的内容. * @see MiraiCode.deserializeMiraiCode */ @Deprecated( "Parameter MessageChain is redundant, use the overload instead.", ReplaceWith( "MessageChain.deserializeFromMiraiCode(miraiCode, contact)", "net.mamoe.mirai.message.data.MessageChain" ) ) @Suppress("UnusedReceiverParameter") @JvmStatic public fun MessageChain.deserializeFromMiraiCode(miraiCode: String, contact: Contact? = null): MessageChain = MessageChain.deserializeFromMiraiCode(miraiCode, contact) } } /** * 返回一个不含任何元素的 [MessageChain]. * * @since 2.12 */ // Java: MessageUtils.emptyMessageChain() @Suppress("DEPRECATION", "DEPRECATION_ERROR") public fun emptyMessageChain(): MessageChain = EmptyMessageChain /** * 不含任何元素的 [MessageChain]. 已弃用, 请使用 [emptyMessageChain]. */ //@Serializable(MessageChain.Serializer::class) @Deprecated( "Please use emptyMessageChain()", replaceWith = ReplaceWith("emptyMessageChain()", "net.mamoe.mirai.message.data.emptyMessageChain"), level = DeprecationLevel.ERROR ) @DeprecatedSinceMirai( warningSince = "2.12", errorSince = "2.14" ) // make internal after deprecation cycle, but keep as @PublishedApi! @Suppress("EXPOSED_SUPER_CLASS") public object EmptyMessageChain : MessageChain, List<SingleMessage> by emptyList(), AbstractMessageChain(), DirectSizeAccess, DirectToStringAccess { override val size: Int get() = 0 override fun toString(): String = "" override fun contentToString(): String = "" override fun serializeToMiraiCode(): String = "" @MiraiInternalApi override val hasConstrainSingle: Boolean get() = false @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { } override fun iterator(): Iterator<SingleMessage> = EmptyMessageChainIterator @Suppress("DeprecatedCallableAddReplaceWith") @Deprecated( "Serializers for EmptyMessageChain is not provided any more. " + "Please specify your serial property as MessageChain and use contextual and polymorphic serializers from MessageSerializers.serializersModule.", level = DeprecationLevel.WARNING ) // deprecated since 2.8-M1 @DeprecatedSinceMirai(warningSince = "2.8") public fun serializer(): KSerializer<MessageChain> = MessageChain.Serializer private object EmptyMessageChainIterator : Iterator<SingleMessage> { override fun hasNext(): Boolean = false override fun next(): Nothing = throw NoSuchElementException("EmptyMessageChain is empty.") } } // region accessors /** * 获取第一个类型为 [key] 的 [Message] 实例, 在找不到此类型的元素时抛出 [NoSuchElementException] * * @param key 由各个类型消息的伴生对象持有. 如 [MessageSource.Key] */ @JvmSynthetic public inline fun <M : SingleMessage> MessageChain.getOrFail( @ResolveContext(RAMK) key: MessageKey<M>, crossinline lazyMessage: (key: MessageKey<M>) -> String = { key.toString() } ): M = get(key) ?: throw NoSuchElementException(lazyMessage(key)) /** * 获取 `Sequence<MessageContent>` * 相当于 `this.asSequence().filterIsInstance<MessageContent>()` */ @JvmSynthetic public fun MessageChain.contentsSequence(): Sequence<MessageContent> = this.asSequence().filterIsInstance<MessageContent>() /** * 获取 `Sequence<MessageMetadata>` * 相当于 `this.asSequence().filterIsInstance<MessageMetadata>()` */ @JvmSynthetic public fun MessageChain.metadataSequence(): Sequence<MessageMetadata> = this.asSequence().filterIsInstance<MessageMetadata>() /** * 筛选 [MessageMetadata] */ public fun MessageChain.metadataList(): List<MessageMetadata> = this.filterIsInstance<MessageMetadata>() /** * 筛选 [MessageContent] */ public fun MessageChain.contentsList(): List<MessageContent> = this.filterIsInstance<MessageContent>() /** * 获取第一个 [M] 实例. 在不存在时返回 `null`. */ @JvmSynthetic public inline fun <reified M : SingleMessage?> MessageChain.findIsInstance(): M? = this.find { it is M } as M? /** * 获取第一个 [M] 实例. 在不存在时返回 `null`. * @see findIsInstance */ @JvmSynthetic public inline fun <reified M : SingleMessage?> MessageChain.firstIsInstanceOrNull(): M? = this.find { it is M } as M? /** * 获取第一个 [M] 实例. 在不存在时抛出 [NoSuchElementException]. * @see findIsInstance */ @JvmSynthetic public inline fun <reified M : SingleMessage> MessageChain.firstIsInstance(): M = this.first { it is M } as M /** * 当 [this] 中存在 [M] 的实例时返回 `true`. */ @JvmSynthetic public inline fun <reified M : SingleMessage> MessageChain.anyIsInstance(): Boolean = this.any { it is M } // endregion accessors // region toMessageChain /** * 返回一个包含 [messages] 所有元素的消息链, 保留顺序. * * ``` * val chain = messageChainOf(messageChainOf(AtAll, new PlainText("")), messageChainOf(Image(""), QuoteReply())) * ``` * 将会得到 `chain` 为 `[AtAll, PlainText, Image, QuoteReply]` * * @see buildMessageChain */ @JvmName("newChain") public inline fun messageChainOf(vararg messages: Message): MessageChain = messages.toMessageChain() /** * 扁平化 [this] 并创建一个 [MessageChain]. */ @JvmName("newChain") public fun Sequence<Message>.toMessageChain(): MessageChain = LinearMessageChainImpl.create(ConstrainSingleHelper.constrainSingleMessages(this)) /** * 扁平化 [this] 并创建一个 [MessageChain]. */ @JvmName("newChain") public suspend fun Flow<Message>.toMessageChain(): MessageChain = buildMessageChain { collect { add(it) } } /** * 扁平化 [this] 并创建一个 [MessageChain]. */ @JvmName("newChain") public inline fun Iterable<Message>.toMessageChain(): MessageChain = this.asSequence().toMessageChain() /** * 扁平化 [this] 并创建一个 [MessageChain]. */ @JvmName("newChain") public inline fun Iterator<Message>.toMessageChain(): MessageChain = this.asSequence().toMessageChain() /** * 扁平化 [this] 并创建一个 [MessageChain]. */ @JvmSynthetic // no JvmName because 'fun messageChainOf(vararg messages: Message)' public inline fun Array<out Message>.toMessageChain(): MessageChain = this.asSequence().toMessageChain() @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNCHECKED_CAST") @kotlin.internal.LowPriorityInOverloadResolution // prefer Iterable<Message>.toMessageChain() for MessageChain @JvmName("newChain") public fun Message.toMessageChain(): MessageChain = when (this) { is MessageChain -> (this as List<SingleMessage>).toMessageChain() is SingleMessage -> LinearMessageChainImpl.create(listOf(this), this is ConstrainSingle) else -> error("Message is either MessageChain nor SingleMessage: $this") } /** * 扁平化 [this] 并创建一个 [MessageChain]. */ @JvmName("newChain") public fun Stream<Message>.toMessageChain(): MessageChain = this.asSequence().toMessageChain() // region delegate /** * 提供一个类型的值的委托. 若不存在则会抛出异常 [NoSuchElementException] * * 用法: * ``` * val message: MessageChain * * val at: At by message * val image: Image by message * ``` */ @JvmSynthetic public inline operator fun <reified T : SingleMessage> MessageChain.getValue(thisRef: Any?, property: KProperty<*>): T = this.firstIsInstance() /** * 可空的委托 * @see orNull */ @JvmInline @Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS") public value class OrNullDelegate<out R> @PublishedApi internal constructor(@JvmField @PublishedApi internal val value: Any?) { @Suppress("UNCHECKED_CAST") // don't inline, IC error public operator fun getValue(thisRef: Any?, property: KProperty<*>): R = value as R } /** * 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null` * * 用法: * ``` * val message: MessageChain * * val at: At? by message.orNull() * ``` * @see orNull 提供一个不存在则 null 的委托 * @see orElse 提供一个不存在则使用默认值的委托 */ @JvmSynthetic public inline fun <reified T : SingleMessage> MessageChain.orNull(): OrNullDelegate<T?> = OrNullDelegate(this.firstIsInstanceOrNull<T>()) /** * 提供一个类型的 [Message] 的委托, 若不存在这个类型的 [Message] 则委托会提供 `null` * * 用法: * ``` * val message: MessageChain * * val at: At by message.orElse { /* 返回一个 At */ } * val atNullable: At? by message.orElse { /* 返回一个 At? */ } * ``` * @see orNull 提供一个不存在则 null 的委托 */ @Suppress("RemoveExplicitTypeArguments") @JvmSynthetic public inline fun <reified T : R, R : SingleMessage?> MessageChain.orElse( lazyDefault: () -> R ): OrNullDelegate<R> = OrNullDelegate<R>(this.firstIsInstanceOrNull<T>() ?: lazyDefault()) // endregion delegate ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MessageChainBuilder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("unused") package net.mamoe.mirai.message.data import kotlin.contracts.InvocationKind.EXACTLY_ONCE import kotlin.contracts.contract import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 构建一个 [MessageChain]. 用法查看 [MessageChainBuilder]. * * @see MessageChainBuilder */ public inline fun buildMessageChain(block: MessageChainBuilder.() -> Unit): MessageChain { contract { callsInPlace(block, EXACTLY_ONCE) } return MessageChainBuilder().apply(block).asMessageChain() } /** * 使用特定的容器大小构建一个 [MessageChain]. 用法查看 [MessageChainBuilder]. * * @see MessageChainBuilder */ public inline fun buildMessageChain(initialSize: Int, block: MessageChainBuilder.() -> Unit): MessageChain { contract { callsInPlace(block, EXACTLY_ONCE) } return MessageChainBuilder(initialSize).apply(block).asMessageChain() } /** * [MessageChain] 构建器. * * **注意:** 无并发安全性. * * ### 连续 String 优化 * * 多个连续的 [String] 会被连接为单个 [PlainText] 以优化性能。 * * ```java * MessageChain chain = new MessageChainBuilder() * .append("Hello ") * .append("mirai!") * .build(); * * // chain 将会只包含一个 [PlainText], 其内容为 "Hello mirai!". * ``` * * ## Kotlin 示例 * * ``` * val chain = buildMessageChain { * +PlainText("a") * +AtAll * +Image("/f8f1ab55-bf8e-4236-b55e-955848d7069f") * add(At(123456)) * } * ``` * * 该示例中 `+` 是 [MessageChainBuilder.unaryPlus]. 使用 `+` 和使用 `add` 是相等的. * * ## Java 示例 * ```java * MessageChain chain = new MessageChainBuilder() * .append(new PlainText("string")) * .append("string") // 会被构造成 PlainText 再添加, 相当于上一行 * .append(AtAll.INSTANCE) * .append(Image.fromId("{f8f1ab55-bf8e-4236-b55e-955848d7069f}.png")) * .build(); * ``` * * @see buildMessageChain 推荐使用 * @see asMessageChain 完成构建 */ public class MessageChainBuilder private constructor( private val container: MutableList<SingleMessage> ) : MutableList<SingleMessage> by container, Appendable { public constructor() : this(mutableListOf()) public constructor(initialSize: Int) : this(ArrayList<SingleMessage>(initialSize)) public override fun add(element: SingleMessage): Boolean { flushCache() return container.add(element) } public fun add(element: Message): Boolean { flushCache() @Suppress("UNCHECKED_CAST") return when (element) { // is ConstrainSingle -> container.add(element) is SingleMessage -> container.add(element) // no need to constrain is MessageChain -> addAll(element) is Iterable<*> -> this.addAll(element.toMessageChain()) else -> error("stub") } } public override fun addAll(elements: Collection<SingleMessage>): Boolean { flushCache() return addAll(elements.asIterable()) } public fun addAll(elements: Iterable<SingleMessage>): Boolean { flushCache() var result = false for (item in elements) { if (add(item)) result = true } return result } @JvmName("addAllFlatten") // erased generic type cause declaration clash public fun addAll(elements: Iterable<Message>): Boolean { flushCache() var result = false for (item in elements) { if (add(item)) result = true } return result } @JvmSynthetic public operator fun Message.unaryPlus() { flushCache() add(this) } @JvmSynthetic public operator fun String.unaryPlus() { add(this) } @JvmSynthetic // they should use add public operator fun plusAssign(plain: String) { withCache { append(plain) } } @JvmSynthetic // they should use add public operator fun plusAssign(message: Message) { flushCache() this.add(message) } @JvmSynthetic // they should use add public operator fun plusAssign(message: SingleMessage) { // avoid resolution ambiguity flushCache() this.add(message) } public fun add(plain: String) { withCache { append(plain) } } @JvmSynthetic // they should use add public operator fun plusAssign(charSequence: CharSequence) { withCache { append(charSequence) } } public override fun append(value: Char): MessageChainBuilder = withCache { append(value) } public override fun append(value: CharSequence?): MessageChainBuilder = withCache { append(value) } public override fun append(value: CharSequence?, startIndex: Int, endIndex: Int): MessageChainBuilder = withCache { append(value, startIndex, endIndex) } public fun append(message: Message): MessageChainBuilder = apply { add(message) } public fun append(message: SingleMessage): MessageChainBuilder = apply { add(message) } // avoid resolution to extensions public fun asMessageChain(): MessageChain { this.flushCache() return this.toMessageChain() } /** 同 [asMessageChain] */ public fun build(): MessageChain = asMessageChain() /** * 将所有已有元素引用复制到一个新的 [MessageChainBuilder] */ public fun copy(): MessageChainBuilder { return MessageChainBuilder(container.toMutableList()).also { it.cache.append(this.cache) } } public override fun remove(element: SingleMessage): Boolean { return container.remove(element) } public override fun removeAll(elements: Collection<SingleMessage>): Boolean { return container.removeAll(elements) } public override fun removeAt(index: Int): SingleMessage { return container.removeAt(index) } public override fun clear() { cache.setLength(0) return container.clear() } public override fun set(index: Int, element: SingleMessage): SingleMessage { return container.set(index, element) } /** * 缓存通过 `add(String)` 添加的字符串, 将连续的字符串连接为一个 [PlainText] */ private val cache: StringBuilder = StringBuilder() private fun flushCache() { if (cache.isNotEmpty()) { container.add(PlainText(cache.toString())) cache.setLength(0) } } private inline fun withCache(block: StringBuilder.() -> Unit): MessageChainBuilder { contract { callsInPlace(block, EXACTLY_ONCE) } cache.apply(block) return this } private var firstConstrainSingleIndex = -1 private fun addAndCheckConstrainSingle(element: SingleMessage): Boolean { return container.add(element) /* if (element is ConstrainSingle) { if (firstConstrainSingleIndex == -1) { firstConstrainSingleIndex = container.size return container.add(element) } val key = element.key val index = container.indexOfFirst(firstConstrainSingleIndex) { it is ConstrainSingle && it.key.isSubKeyOf(key) } if (index != -1) { container[index] = element } else { container.add(element) } return true } else { return container.add(element) }*/ } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MessageKey.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import kotlin.jvm.JvmField /** * 类型 Key. 由伴生对象实现, 表示一个 [Message] 对象的类型. * * 每个 [ConstrainSingle] 类型都拥有一个伴生对象来持有 [MessageKey] 以允许 `val source = chain[MessageSource]` 的用法. * * #### 用例 * [MessageChain.get][MessageChain.get]: 允许使用数组访问操作符获取指定类型的消息元素 * ``` * val source: MessageSource = chain[MessageSource] * ``` * * @param M 指代持有这个 Key 的消息类型 */ public interface MessageKey<out M : SingleMessage> { /** * 将一个 [SingleMessage] 强转为 [M] 类型. 在类型不符合时返回 `null` */ public val safeCast: (SingleMessage) -> M? } /** * 独立的 [MessageKey] 的实现. '独立' 即 `final`, 不支持多态类型. 适用于作为最顶层的 [MessageKey], 如 [MessageSource]. * * @see AbstractPolymorphicMessageKey */ public abstract class AbstractMessageKey<out M : SingleMessage>( override val safeCast: (SingleMessage) -> M?, ) : MessageKey<M> /** * 多态 [MessageKey]. * * 示例: [HummerMessage] * ``` * MessageContent * ↑ * HummerMessage * ↑ * +------------+-------------+------------+ * | | | | * PokeMessage VipFace FlashImage ... * * ``` * * 当 [连接][Message.plus] 一个 [VipFace] 到一个 [MessageChain] 时, * 由于 [VipFace] 最上层为 [MessageContent], 消息链中第一个 [MessageContent] 会被 (保留顺序地) 替换为 [VipFace], 其他所有 [MessageContent] 都会被删除. * * 如: * ``` * val source: MessageSource = ... * * val result = messageChainOf(PlainText("a"), PlainText("b"), source, AtAll) + VipFace.LiuLian * // result 为 [VipFace.LiuLian, source] * * val result = source1 + source2 * // result 为 [source2], 总是右侧替换左侧 * ``` */ public abstract class AbstractPolymorphicMessageKey<out B : SingleMessage, out M : B>( baseKey: MessageKey<B>, safeCast: (SingleMessage) -> M?, ) : MessageKey<M>, AbstractMessageKey<M>(safeCast) { @JvmField internal val topmostKey: MessageKey<*> = if (baseKey is AbstractPolymorphicMessageKey<*, *>) baseKey.topmostKey else baseKey } /** * 尝试 [MessageKey.safeCast], 成功时返回 `true` */ public fun MessageKey<*>.isInstance(message: SingleMessage): Boolean = this.safeCast(message) != null /** * 获取最上层 [MessageKey]. * @see AbstractPolymorphicMessageKey */ public val <A : SingleMessage> MessageKey<A>.topmostKey: MessageKey<*> get() = when (this) { is AbstractPolymorphicMessageKey<*, *> -> this.topmostKey else -> this } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MessageOrigin.kt ================================================ /* * 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.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.isSameClass import net.mamoe.mirai.utils.safeCast /** * 标识来源 [RichMessage], 存在于接收的 [MessageChain] 中. 在发送消息时会被忽略. * * 一些 [RichMessage] 会被 mirai 解析成特定的更易使用的类型, 如: * - 长消息会被协议内部转化为 [ServiceMessage] `serviceId=35` 通过独立通道上传和下载并获得一个 [resourceId]. mirai 会自动下载长消息并把他们解析为 [MessageChain]. * - 合并转发也使用长消息通道传输, 拥有 [resourceId], mirai 解析为 [ForwardMessage] * - [MusicShare] 也有特殊通道上传, 但会作为 [LightApp] 接收. * * 这些经过转换的类型的来源 [RichMessage] 会被包装为 [MessageOrigin] 并加入消息链中. * * 如一条被 mirai 解析的长消息的消息链组成为, 第一个元素为 [MessageSource], 第二个元素为 [MessageOrigin], 随后为长消息内容. * * 又如一条被 mirai 解析的 [MusicShare] 的消息链组成为, 第一个元素为 [MessageSource], 第二个元素为 [MessageOrigin], 第三个元素为 [MusicShare]. * * @suppress **注意**: 这是实验性 API: 可能会在未来任意时刻变更. * * @since 2.6 */ @Serializable @SerialName(MessageOrigin.SERIAL_NAME) @MiraiExperimentalApi public class MessageOrigin( // [2.3, 2.6-M1) 类名为 RichMessageOrigin /** * 原 [SingleMessage]. */ public val origin: @Polymorphic SingleMessage, /** * 如果来自长消息或转发消息, 则会有 [resourceId], 否则为 `null`. * * - 下载长消息 [IMirai.downloadLongMessage] * - 下载合并转发消息 [IMirai.downloadForwardMessage] */ public val resourceId: String?, /** * 来源类型 */ public val kind: MessageOriginKind, ) : 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 (other !is MessageOrigin || !isSameClass(this, other)) return false 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 } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitMessageOrigin(this, data) public companion object Key : AbstractMessageKey<MessageOrigin>({ it.safeCast() }) { public const val SERIAL_NAME: String = "MessageOrigin" } } /** * [MessageOrigin] 来源 * @see MessageOrigin.kind * @since 2.6 */ @Serializable public enum class MessageOriginKind { // [2.3, 2.6-M1) 类名为 RichMessageKind /** * 长消息 */ LONG, /** * 合并转发 * @see ForwardMessage */ FORWARD, /** * 音乐分享 * @see MusicShare */ MUSIC_SHARE, } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MessageSource.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR", "UnUsedImport") package net.mamoe.mirai.message.data import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.json.JsonConfiguration import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.action.AsyncRecallResult import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.MessageSource.Key.recall import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * 表示聊天中的一条消息的定位信息, 即消息源. * * 一个[消息源][MessageSource]可用于定位一条存在于服务器中的消息, 因此可用来[撤回][recall]或[引用][quote]该消息 * * 消息源可存在于 [MessageChain] 中, 用于表示这个消息的来源, 也可以用来分辨 [MessageChain]. * * 消息源分为在线消息源 [OnlineMessageSource] 和离线消息源 [OfflineMessageSource]. * * ## 属性组成 * [MessageSource] 由以下属性组成: * - 三个*定位属性* [ids], [internalId], [time] * - 发送人 ID [fromId] * - 收信人 ID [targetId] * - 原消息内容 [originalMessage] * * 官方客户端通过这三个*定位属性*来准确定位消息, 撤回和引用回复都是如此 (有这三个属性才可以精确撤回和引用某个消息). * * 即使三个*定位属性*就可以知道原消息是哪一条, 但服务器和官方客户端都实现为读取 [originalMessage] 的内容. * 也就是说, 如果[引用][quote]一个 [MessageSource], *定位属性*只会被用来支持跳转到原消息, 引用中显示的被引用消息内容只取决于 [originalMessage]. * 可以通过修改 [originalMessage] 来达到显示的内容与跳转内容不符合的效果. 但一般没有必要这么做. * * ## 获取消息源实例 * - 来自 [MessageEvent.message] 的 [MessageChain] 总是包含在线消息源 [OnlineMessageSource]. 可通过 [MessageChain.get] 获取 [MessageSource]: * ```kotlin * // Kotlin * val source: MessageSource? = chain[MessageSource] * val notNull: MessageSource = chain.source // 可能抛出 NoSuchElementException * ``` * ```java * // Java * MessageSource source = chain.get(MessageSource.Key); * ``` * - 构造离线消息源: [IMirai.constructMessageSource] * - 使用构建器构造: [MessageSourceBuilder] * * 参阅 [OnlineMessageSource] 或 [OfflineMessageSource] 可获得更详细的获取实例的方式. * * ### "修改" 一个 [MessageSource] * [MessageSource] 是不可变的. 因此不能修改其中属性, 但可以通过 [MessageSource.copyAmend] 或者 [MessageSourceBuilder.allFrom] 来复制一个. * ```java * MessageSource newSource = new MessageSourceBuilder() * .allFrom(source) // 从 source 继承所有数据 * .message(new PlainText("aaa")) // 覆盖消息 * .build(); * ``` * * ## 使用 * * 消息源可用于 [引用回复][MessageSource.quote] 或 [撤回][MessageSource.recall]. * * 对于来自 [MessageEvent.message] 的 [MessageChain], 总是包含 [MessageSource]. * 因此也可以对这样的 [MessageChain] 进行 [引用回复][MessageChain.quote] 或 [撤回][MessageChain.recall]. * * ### 获取有关 [Bot] 实例 * * 调用 [MessageSource.bot] 或 [MessageSource.botOrNull] 来获取有关 [Bot] 实例. * * ### Kotlin 示例 * ```kotlin * val source: MessageSource = ... * source.recall() // 通过 MessageSource 撤回 * * val event: MessageEvent = ... * event.message.recall() // 也可以通过来自服务器的 [MessageChain] 撤回, 因为这些 chain 包含 [MessageSource] * ``` * * ### Java 示例 * ```java * MessageSource source = ... * MessageSource.recall(source); // 通过 MessageSource 撤回 * * MessageEvent event = ... * MessageSource.recall(event.message); // 也可以通过来自服务器的 [MessageChain] 撤回, 因为这些 chain 包含 [MessageSource] * ``` * * * @see MessageSource.quote 引用这条消息, 创建 [MessageChain] * * @see OnlineMessageSource 在线消息的 [MessageSource] * @see OfflineMessageSource 离线消息的 [MessageSource] * * @see buildMessageSource 构建一个 [OfflineMessageSource] */ @Suppress("DEPRECATION") @Serializable(MessageSource.Serializer::class) public sealed class MessageSource : Message, MessageMetadata, ConstrainSingle { public final override val key: MessageKey<MessageSource> get() = Key /** * 所属 [Bot.id] */ public abstract val botId: Long /** * 消息 ids (序列号). 在获取失败时 (概率很低) 为空数组. * * ### 顺序 * 群消息的 id 由服务器维护. 好友消息的 id 由 mirai 维护. * 此 id 不一定从 0 开始. * * - 在同一个群的消息中此值随每条消息递增 1, 但此行为由服务器决定, mirai 不保证自增顺序. * - 在好友消息中无法保证每次都递增 1. 也可能会产生大幅跳过的情况. * * ### 多 ID 情况 * 对于单条消息, [ids] 为单元素数组. 对于分片 (一种长消息处理机制) 消息, [ids] 将包含多元素. * * [internalIds] 与 [ids] 以数组下标对应. */ public abstract val ids: IntArray /** * 内部 ids. **仅用于协议模块使用** * * 值没有顺序, 也可能为 0, 取决于服务器是否提供. * * 在事件中和在引用中无法保证同一条消息的 [internalIds] 相同. * * [internalIds] 与 [ids] 以数组下标对应. * * @see ids */ public abstract val internalIds: IntArray /** * 发送时间时间戳, 单位为秒. * * 自 2.8.0 起, 时间戳为服务器时区 (UTC+8). * 在 2.8.0 以前, 时间戳可能来自服务器 (UTC+8), 也可能来自 mirai (本地), 且无法保证两者时间同步. */ public abstract val time: Int /** * 发送人用户 ID. * * - 当 [OnlineMessageSource.Outgoing] 时为 [机器人][Bot.id] * - 当 [OnlineMessageSource.Incoming] 时为发信 [来源用户][User.id] 或 [群][Group.id] * - 当 [OfflineMessageSource] 时取决于 [OfflineMessageSource.kind] */ public abstract val fromId: Long /** * 消息发送目标用户或群号码. * * - 当 [OnlineMessageSource.Outgoing] 时为发信 [目标用户][User.id] 或 [群][Group.id] * - 当 [OnlineMessageSource.Incoming] 时为 [机器人][Bot.id] * - 当 [OfflineMessageSource] 时取决于 [OfflineMessageSource.kind] */ public abstract val targetId: Long // groupCode / friendUin / memberUin /** * 该消息源指向的原消息的内容. * * ## 内容不一定完整 * 如果消息源是来自一条引用回复, 即 [QuoteReply.source], 那么原消息内容不一定完整. * * 此属性是惰性初始化的: 它只会在第一次调用时初始化, 因为需要反序列化服务器发来的整个包, 相当于接收了一条新消息. */ public abstract val originalMessage: MessageChain // see OutgoingMessageSourceInternal.originalMessage /** * 当 [originalMessage] 已被初始化后返回 `true`. * * @since 2.12 */ public abstract val isOriginalMessageInitialized: Boolean /** * 消息种类 * * @since 2.15 */ public abstract val kind: MessageSourceKind public abstract override fun toString(): String @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitMessageSource(this, data) } @OptIn(MiraiInternalApi::class) @Deprecated("Do not use this serializer. Retrieve from `MessageSerializers.serializersModule`.") @DeprecatedSinceMirai(warningSince = "2.13") public object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("MessageSource") public companion object Key : AbstractMessageKey<MessageSource>({ it.safeCast() }) { /** * 从 [MessageSerializers] 获取到的对应[序列化器][KSerializer]在参与多态序列化时的[类型标识符][JsonConfiguration.classDiscriminator]的值. * * [OnlineMessageSource] 的部分属性无法通过序列化保存. 所有 [MessageSource] 子类型在序列化时都会序列化为 [OfflineMessageSource]. 反序列化时会得到 [OfflineMessageSource] 而不是原类型. */ public const val SERIAL_NAME: String = "MessageSource" /** * 以 [Bot] 身份撤回该[消息源][this]指向的存在于服务器上的消息. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. * * *提示: 若要撤回一条机器人自己发出的消息, 使用 [Contact.sendMessage] 返回的 [MessageReceipt] 中的 [MessageReceipt.recall]* * * ## 需求的权限 * * - [Bot] 撤回自己的消息不需要权限. * - [Bot] 撤回群员的消息需要管理员权限. * * @throws PermissionDeniedException 当 [Bot] 无权限操作时 * @throws IllegalStateException 当这条消息已经被撤回时抛出 * * @see IMirai.recallMessage */ @JvmStatic @JvmBlockingBridge public suspend fun MessageSource.recall() { // don't inline, compilation error Mirai.recallMessage(bot, this) } /** * 以 [Bot] 身份撤回[该消息][this]. 可撤回自己 2 分钟内发出的消息, 和任意时间的群成员的消息. * * **注意:** 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以撤回. * * *提示: 若要撤回一条机器人自己发出的消息, 使用 [Contact.sendMessage] 返回的 [MessageReceipt] 中的 [MessageReceipt.recall]* * * ## 需求的权限 * * - [Bot] 撤回自己的消息不需要权限. * - [Bot] 撤回群员的消息需要管理员权限. * * @throws PermissionDeniedException 当 [Bot] 无权限操作时 * @throws IllegalStateException 当这条消息已经被撤回时抛出 * * @see IMirai.recallMessage */ @JvmStatic @JvmBlockingBridge public suspend fun MessageChain.recall() { this[MessageSource]?.let { it.recall() return } throw NoSuchElementException(tipsForNoMessageSource) } private const val tipsForNoMessageSource = "No MessageSource found from input MessageChain. Tips: " + "You can't recall a MessageChain which is built by you, " + "as it lacks ids of the message on the server. " + "If you want to recall a message after sending it, " + "you can call `recallIn` method on the `MessageReceipt` returned by `sendMessage`." /** * 在一段时间后撤回这条消息. * * **注意:** 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以撤回. * * *提示: 若要撤回一条机器人自己发出的消息, 使用 [Contact.sendMessage] 返回的 [MessageReceipt] 中的 [MessageReceipt.recall]* * * ## 需求的权限 * * - [Bot] 撤回自己的消息不需要权限. * - [Bot] 撤回群员的消息需要管理员权限. * * @return 返回撤回的异步结果. 参考 [AsyncRecallResult]. * @see MessageChain.recall */ @JvmStatic public fun MessageChain.recallIn(millis: Long): AsyncRecallResult { this[MessageSource]?.let { return it.recallIn(millis) } throw NoSuchElementException(tipsForNoMessageSource) } /** * 在一段时间后撤回这条消息. * * **注意:** 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以撤回. * * *提示: 若要撤回一条机器人自己发出的消息, 使用 [Contact.sendMessage] 返回的 [MessageReceipt] 中的 [MessageReceipt.recall]* * * ## 需求的权限 * * - [Bot] 撤回自己的消息不需要权限. * - [Bot] 撤回群员的消息需要管理员权限. * * @return 返回撤回的异步结果. 参考 [AsyncRecallResult]. * * @see MessageSource.recall */ @JvmStatic public fun MessageSource.recallIn(millis: Long): AsyncRecallResult { return AsyncRecallResult(bot.async { try { delay(millis) Mirai.recallMessage(bot, this@recallIn) null } catch (e: Throwable) { e } }) } /** * 引用这条消息. * @see QuoteReply */ @JvmStatic public fun MessageSource.quote(): QuoteReply = QuoteReply(this) /** * 引用这条消息. 仅从服务器接收的消息 (即来自 [MessageEvent]) 才可以通过这个方式被引用. * @see QuoteReply */ @JvmStatic public fun MessageChain.quote(): QuoteReply = QuoteReply(this.source) } } /** * 消息来源类型 */ @Serializable public enum class MessageSourceKind { /** * 群消息 */ GROUP, /** * 好友消息 */ FRIEND, /** * 来自群成员的临时会话消息 */ TEMP, /** * 来自陌生人的消息 */ STRANGER } /* public static final net.mamoe.mirai.message.data.MessageSourceKind getKind(net.mamoe.mirai.message.data.MessageSource); public static final net.mamoe.mirai.message.data.MessageSourceKind getKind(net.mamoe.mirai.message.data.OnlineMessageSource); */ @JvmName("getKind") @Deprecated("For ABI compatibility", level = DeprecationLevel.HIDDEN) public fun getKindLegacy(source: MessageSource): MessageSourceKind = source.kind @JvmName("getKind") @Deprecated("For ABI compatibility", level = DeprecationLevel.HIDDEN) public fun getKindLegacy(source: OnlineMessageSource): MessageSourceKind = source.kind // For MessageChain, no need to expose to Java. /** * 消息 ids. * * 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以获取消息源. * * @see MessageSource.ids */ @get:JvmSynthetic public inline val MessageChain.ids: IntArray get() = this.source.ids /** * 消息内部 ids. * * 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以获取消息源. * * @see MessageSource.ids */ @get:JvmSynthetic public inline val MessageChain.internalId: IntArray get() = this.source.internalIds /** * 消息时间. * * 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以获取消息源. * * @see MessageSource.ids */ @get:JvmSynthetic public inline val MessageChain.time: Int get() = this.source.time /** * 消息内部 ids. * * 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以获取. 否则将抛出异常 [NoSuchElementException] * * @see MessageSource.ids */ @get:JvmSynthetic public inline val MessageChain.bot: Bot get() = this.source.bot /** * 获取这条消息的 [消息源][MessageSource]. * * 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以获取消息源, 否则将抛出异常 [NoSuchElementException] * * @see sourceOrNull */ @get:JvmSynthetic public inline val MessageChain.source: MessageSource get() = this.getOrFail(MessageSource) /** * 获取这条消息的 [消息源][MessageSource]. * * 仅从服务器接收的消息 (即来自 [MessageEvent.message]), 或手动添加了 [MessageSource] 元素的 [MessageChain] 才可以获取消息源, 否则返回 `null` * * @see source */ @get:JvmSynthetic public inline val MessageChain.sourceOrNull: MessageSource? get() = this[MessageSource] /** * 获取此消息源的相关 [Bot]. * * 对于 [OnlineMessageSource], 此操作总是会成功. * 但对于 [OfflineMessageSource], 若此时该 [ID][Bot.id] 的 [Bot] 不存在, 则会抛出 [NoSuchElementException]. * * @throws NoSuchElementException 当目标 [Bot] 不存在时抛出 */ public inline val MessageSource.bot: Bot get() = when (this) { is OnlineMessageSource -> bot is OfflineMessageSource -> Bot.getInstance(botId) } /** * 获取此消息源的相关 [Bot]. * * 对于 [OnlineMessageSource], 此操作总是返回非 `null`. * 但对于 [OfflineMessageSource], 若此时该 [ID][Bot.id] 的 [Bot] 不存在, 则会返回 `null`. */ public inline val MessageSource.botOrNull: Bot? get() = when (this) { is OnlineMessageSource -> bot is OfflineMessageSource -> Bot.getInstanceOrNull(botId) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MessageSourceBuilder.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("NOTHING_TO_INLINE", "unused", "INAPPLICABLE_JVM_NAME", "INVISIBLE_MEMBER") package net.mamoe.mirai.message.data import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.MessageSource.Key.recall import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 将在线消息源转换为离线消息源. */ @JvmName("toOfflineMessageSource") public fun OnlineMessageSource.toOffline(): OfflineMessageSource = Mirai.constructMessageSource(botId, kind, fromId, targetId, ids, time, internalIds, originalMessage) /////////////// //// AMEND //// /////////////// /** * 复制这个消息源, 并以 [block] 修改 * * @see buildMessageSource 查看更多说明 */ @JvmName("copySource") public fun MessageSource.copyAmend( block: MessageSourceAmender.() -> Unit ): OfflineMessageSource = MessageSourceAmender(this).apply(block).run { Mirai.constructMessageSource(botId, kind, fromId, targetId, ids, time, internalIds, originalMessage) } /** * [MessageSource] 复制修改器. 不会修改原 [MessageSource], 而是会创建一个新的 [MessageSource]. * * @see copyAmend Kotlin DSL * @see MessageSourceBuilder */ public class MessageSourceAmender public constructor( origin: MessageSource, ) : MessageSourceBuilder() { public var kind: MessageSourceKind = origin.kind public var originalMessage: MessageChain = origin.originalMessage public override var fromId: Long = origin.fromId public override var targetId: Long = origin.targetId public override var ids: IntArray = origin.ids public override var time: Int = origin.time public override var internalIds: IntArray = origin.internalIds } /////////////// //// BUILD //// /////////////// /** * 使用 DSL 构建一个 [OfflineMessageSource]. 用法参考 [MessageSourceBuilder]. * * @see copyAmend */ @JvmSynthetic public inline fun IMirai.buildMessageSource( botId: Long, kind: MessageSourceKind, block: MessageSourceBuilder.() -> Unit ): OfflineMessageSource = MessageSourceBuilder().apply(block).build(botId, kind) /** * 使用 DSL 构建一个 [OfflineMessageSource]. 用法参考 [MessageSourceBuilder]. * * @see buildMessageSource */ @JvmSynthetic public inline fun Bot.buildMessageSource( kind: MessageSourceKind, block: MessageSourceBuilder.() -> Unit ): OfflineMessageSource = Mirai.buildMessageSource(this.id, kind, block) /** * 离线消息源构建器. * * ### 参数 * 一个 [OfflineMessageSource] 需要以下参数: * - 发送人和发送目标: 通过 [MessageSourceBuilder.sender], [MessageSourceBuilder.target] 设置 * - 消息元数据 (即 [MessageSource.ids], [MessageSource.internalIds], [MessageSource.time]) * 元数据用于 [撤回][MessageSource.recall], [引用回复][MessageSource.quote], 和官方客户端定位原消息. * 可通过 [MessageSourceBuilder.ids], [MessageSourceBuilder.time], [MessageSourceBuilder.internalIds] 设置 * 可通过 [MessageSourceBuilder.metadata] 从另一个 [MessageSource] 复制 * - 消息内容: 通过 [MessageSourceBuilder.messages] 设置 * * ### 性质 * - 当两个消息的元数据相同时, 它们在群中会是同一条消息. 可通过此特性决定官方客户端 "定位原消息" 的目标 * - 发送人的信息和消息内容会在官方客户端显示在引用回复中. * * ### 实例 * Kotlin: * ``` * bot.buildMessageSource(MessageSourceKind.GROUP) { * from(bot) * target(target) * metadata(source) // 从另一个消息源复制 ids, internalIds, time * * time(System.currentTimeMillis()) * // 也可以不设置 time, 则会使用当前系统时间 * * messages { // 指定消息内容 * +"hi" * } * * messages(messageChain) // 也可以赋值一个 MessageChain * } * ``` * * Kotlin 也可以使用 * * Java: * ```java * new MessageSourceBuilder() * .from(bot) * .target(target) * .metadata(source) // 从另一个消息源复制 ids, internalIds, time * .time(System.currentTimeMillis()) // 也可以不设置, 则会使用当前系统时间 * .messages(new PlainText("hi")) * .build(botId, MessageSourceKind.FRIEND); * ``` * * @see buildMessageSource */ public open class MessageSourceBuilder public constructor() { public open var fromId: Long = 0 public open var targetId: Long = 0 public open var ids: IntArray = intArrayOf() /** * seconds * @see MessageSource.time */ public open var time: Int = currentTimeSeconds().toInt() public open var internalIds: IntArray = intArrayOf() @PublishedApi internal val originalMessages: MessageChainBuilder = MessageChainBuilder() public fun time(from: MessageSource): MessageSourceBuilder = apply { this.time = from.time } public fun time(value: Int): MessageSourceBuilder = apply { this.time = value } public fun internalId(from: MessageSource): MessageSourceBuilder = apply { this.internalIds = from.internalIds } public fun internalId(vararg value: Int): MessageSourceBuilder = apply { this.internalIds = value } public fun id(from: MessageSource): MessageSourceBuilder = apply { this.ids = from.ids } public fun id(vararg value: Int): MessageSourceBuilder = apply { this.ids = value } /** * 从另一个 [MessageSource] 复制 [ids], [time], [internalIds]. * 这三个数据决定官方客户端能 "定位" 到的原消息 */ public fun metadata(from: MessageSource): MessageSourceBuilder = apply { id(from) internalId(from) time(from) } /** * 从另一个 [MessageSource] 复制所有信息, 包括消息内容. 不会清空已有消息. */ public fun allFrom(source: MessageSource): MessageSourceBuilder { this.ids = source.ids this.time = source.time this.fromId = source.fromId this.targetId = source.targetId this.internalIds = source.internalIds this.originalMessages.addAll(source.originalMessage) return this } /** * 从另一个 [MessageSource] 复制 [消息内容][MessageSource.originalMessage]. 不会清空已有消息. */ public fun messagesFrom(source: MessageSource): MessageSourceBuilder = apply { this.originalMessages.addAll(source.originalMessage) } /** * 添加消息. 不会清空已有消息. */ public fun messages(messages: Iterable<Message>): MessageSourceBuilder = apply { this.originalMessages.addAll(messages) } /** * 添加消息. 不会清空已有消息. */ public fun messages(vararg message: Message): MessageSourceBuilder = apply { for (it in message) { this.originalMessages.add(it) } } @JvmSynthetic public inline fun messages(block: MessageChainBuilder.() -> Unit): MessageSourceBuilder = apply { this.originalMessages.apply(block) } public fun clearMessages(): MessageSourceBuilder = apply { this.originalMessages.clear() } /** * 设置发信人. */ public fun sender(sender: ContactOrBot): MessageSourceBuilder = apply { this.fromId = sender.id } /** * 设置发信人. 需使用 uin. * @see IMirai.getUin */ public fun sender(uin: Long): MessageSourceBuilder = apply { this.fromId = uin } /** * 设置发信目标 */ public fun target(target: ContactOrBot): MessageSourceBuilder = apply { this.targetId = target.id } /** * 设置发信目标. 需使用 uin. * @see IMirai.getUin */ public fun target(uin: Long): MessageSourceBuilder = apply { this.targetId = uin } /** * 同时设置 [sender] 和 [target] */ public fun setSenderAndTarget(sender: ContactOrBot, target: ContactOrBot): MessageSourceBuilder = sender(sender).target(target) /** * 构建生成 [OfflineMessageSource] */ public fun build(botId: Long, kind: MessageSourceKind): OfflineMessageSource { return Mirai.constructMessageSource( botId, kind, fromId, targetId, ids, time, internalIds, originalMessages.build() ) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/MusicShare.kt ================================================ /* * 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("unused") 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.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast /** * QQ 互联通道音乐分享. * * 构造实例即可使用. * * @since 2.1 */ @Serializable @SerialName(MusicShare.SERIAL_NAME) public data class MusicShare( /** * 音乐应用类型 */ public val kind: MusicKind, // 'type' is reserved by serialization /** * 消息卡片标题. 例如 `"ファッション"` */ public val title: String, /** * 消息卡片内容. 例如 `"rinahamu/Yunomi"` */ public val summary: String, /** * 点击卡片跳转网页 URL. 例如 `"http://music.163.com/song/1338728297/?userid=324076307"` */ public val jumpUrl: String, /** * 消息卡片图片 URL. 例如 `"http://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg"` */ public val pictureUrl: String, /** * 音乐文件 URL. 例如 `"http://music.163.com/song/media/outer/url?id=1338728297&userid=324076307"` */ public val musicUrl: String, /** * 在消息列表显示. 例如 `"[分享]ファッション"` */ public val brief: String, ) : MessageContent, ConstrainSingle, CodableMessage { /* * 想试试? 可以构造: // Kotlin MusicShare( kind = NeteaseCloudMusic, title = "ジェリーフィッシュ", summary = "Yunomi/ローラーガール", jumpUrl = "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46", pictureUrl = "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg", musicUrl = "http://music.163.com/song/media/outer/url?id=562591636&&sc=wmv&tn=", brief = "[分享]ジェリーフィッシュ", ) // Java new MusicShare( NeteaseCloudMusic, "ジェリーフィッシュ", "Yunomi/ローラーガール", "https://y.music.163.com/m/song?id=562591636&uct=QK0IOc%2FSCIO8gBNG%2Bwcbsg%3D%3D&app_version=8.7.46", "http://p1.music.126.net/KaYSb9oYQzhl2XBeJcj8Rg==/109951165125601702.jpg", "http://music.163.com/song/media/outer/url?id=562591636&&sc=wmv&tn=", "[分享]ジェリーフィッシュ", ); */ public constructor( /** * 音乐应用类型 */ kind: MusicKind, /** * 消息卡片标题 */ title: String, /** * 消息卡片内容 */ summary: String, /** * 点击卡片跳转网页 URL */ jumpUrl: String, /** * 消息卡片图片 URL */ pictureUrl: String, /** * 音乐文件 URL */ musicUrl: String, ) : this(kind, title, summary, jumpUrl, pictureUrl, musicUrl, "[分享]$title") // kotlinx serialization doesn't support default arguments. override val key: MessageKey<*> get() = Key override fun contentToString(): String = brief.takeIf { it.isNotBlank() } ?: "[分享]$title" // empty content is not accepted by `sendMessage` @MiraiExperimentalApi 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(']') } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitMusicShare(this, data) } /** * 注意, baseKey [MessageContent] 不稳定. 未来可能会有变更. */ public companion object Key : AbstractPolymorphicMessageKey<MessageContent, MusicShare> (MessageContent, { it.safeCast() }) { /** * @since 2.3 */ public const val SERIAL_NAME: String = "MusicShare" } } /** * @see MusicShare.kind * @since 2.1 */ public enum class MusicKind constructor( @MiraiInternalApi public val appId: Long, @MiraiInternalApi public val platform: Int, @MiraiInternalApi public val sdkVersion: String, @MiraiInternalApi public val packageName: String, @MiraiInternalApi public val signature: String ) { NeteaseCloudMusic( 100495085, 1, "0.0.0", "com.netease.cloudmusic", "da6b069da1e2982db3e386233f68d76d" ), QQMusic( 100497308, 1, "0.0.0", "com.tencent.qqmusic", "cbd27cd7c861227d013a25b2d10f0799" ), MiguMusic( 1101053067, 1, "0.0.0", "cmccwm.mobilemusic", "6cdc72a439cef99a3418d2a78aa28c73" ), /** * @since 2.7 */ KugouMusic( 205141, 1, "0.0.0", "com.kugou.android", "fe4a24d80fcf253a00676a808f62c2c6" ), /** * @since 2.7 */ KuwoMusic( 100243533, 1, "0.0.0", "cn.kuwo.player", "bf9ff4ffb4c558a34ee3fd52c223ebf5" ) // add more? https://github.com/mamoe/mirai/issues/new/choose } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/OfflineMessageSource.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.safeCast /** * 一条在本地构建的, 或其他不保证指向一条服务器上存在的消息的消息源. * * ## 来源 * * 离线消息源可从[引用回复][QuoteReply]中获得, 因为协议上引用回复中的被引用的消息是由客户端自己提供的. * 离线消息源也可通过 [MessageSourceBuilder], [MessageSource.copyAmend] 等方法构建得到. * * 离线消息源可能来自一条与机器人无关的消息, 因此缺少相关发送环境信息, 无法提供 `sender` 或 `target` 的 [ContactOrBot] 对象的获取. * * ## 构建 * * - 使用 [MessageSourceBuilder] 可使用相关属性构建实例. * - 使用 [MessageSource.copyAmend] 复制另外一个 [MessageSource] 得到. * - 使用 [OnlineMessageSource.toOffline] 可将 [OfflineMessageSource] 转换得到 [OfflineMessageSource]. 但这一般没有意义. */ @NotStableForInheritance public abstract class OfflineMessageSource : MessageSource() { public companion object Key : AbstractPolymorphicMessageKey<MessageSource, OfflineMessageSource>(MessageSource, { it.safeCast() }) /** * 消息种类 */ public abstract override val kind: MessageSourceKind final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from $fromId to $targetId at $time]" } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/OnlineMessageSource.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("unused", "INAPPLICABLE_JVM_NAME", "DEPRECATION_ERROR", "UnUsedImport") package net.mamoe.mirai.message.data import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.MessageSource.Key.recall import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 从服务器接收的在线消息的 [MessageSource]. * * 对比 [OfflineMessageSource], [OnlineMessageSource] 拥有完整的信息: * - 可获取 [sender] 和 [target] 的 [ContactOrBot] 对象 * - 可获取有关 [bot] 对象. * * 此消息源一定 "指向" 一条存在于服务器上的消息, 但由于服务器消息可能已经被撤回, 对此消息源执行[撤回][MessageSource.recall] 仍然可能会失败. * * ### 来源 * - 当 bot 主动发送消息时, 产生 (由协议模块主动构造) [OnlineMessageSource.Outgoing] * - 当 bot 接收消息时, 产生 (由协议模块根据服务器的提供的信息构造) [OnlineMessageSource.Incoming] * * #### 机器人主动发送消息 * 当机器人 [主动发出消息][Member.sendMessage], 将会得到一个 [消息回执][MessageReceipt]. * 此回执的 [消息源][MessageReceipt.source] 即为一个 [外向消息源][OnlineMessageSource.Outgoing], 代表着刚刚发出的那条消息的来源. * * #### 机器人接受消息 * 当机器人接收一条消息 [MessageEvent], 这条消息包含一个 [内向消息源][OnlineMessageSource.Incoming], 代表着接收到的这条消息的来源. * * * ### 实现 * 此类的所有子类都有协议模块实现. 不要自行实现它们, 否则将无法发送 * * @see OnlineMessageSource.toOffline 转为 [OfflineMessageSource] */ public sealed class OnlineMessageSource : MessageSource() { public companion object Key : AbstractMessageKey<OnlineMessageSource>({ it.safeCast() }) /** * @see botId */ public abstract val bot: Bot final override val botId: Long get() = bot.id /** * 消息发送人. 可能为 [机器人][Bot] 或 [好友][Friend] 或 [群员][Member]. * 即类型必定为 [Bot], [Friend] 或 [Member] */ public abstract val sender: ContactOrBot /** * 消息发送目标. 可能为 [机器人][Bot] 或 [好友][Friend] 或 [群][Group]. * 即类型必定为 [Bot], [Friend] 或 [Group] */ public abstract val target: ContactOrBot /** * 消息主体. 群消息时为 [Group]. 好友消息时为 [Friend], 临时消息为 [Member] * 不论是机器人接收的消息还是发送的消息, 此属性都指向机器人能进行回复的目标. */ public abstract val subject: Contact /* * 以下子类型仅是覆盖了 [target], [subject], [sender] 等的类型 */ /** * 由 [机器人主动发送消息][Contact.sendMessage] 产生的 [MessageSource], 可通过 [MessageReceipt] 获得. */ public sealed class Outgoing : OnlineMessageSource() { public companion object Key : AbstractPolymorphicMessageKey<OnlineMessageSource, Outgoing>(OnlineMessageSource, { it.safeCast() }) public abstract override val sender: Bot public abstract override val target: Contact public final override val fromId: Long get() = sender.id public final override val targetId: Long get() = target.id @NotStableForInheritance public abstract class ToFriend @MiraiInternalApi constructor() : Outgoing() { public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToFriend>(Outgoing, { it.safeCast() }) public abstract override val target: Friend public final override val subject: Friend get() = target final override val kind: MessageSourceKind get() = MessageSourceKind.FRIEND final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from $fromId to friend $targetId at $time]" } } @NotStableForInheritance public abstract class ToStranger @MiraiInternalApi constructor() : Outgoing() { public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToStranger>(Outgoing, { it.safeCast() }) public abstract override val target: Stranger public final override val subject: Stranger get() = target final override val kind: MessageSourceKind get() = MessageSourceKind.STRANGER final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from $fromId to stranger $targetId at $time]" } } @NotStableForInheritance public abstract class ToTemp @MiraiInternalApi constructor() : Outgoing() { public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToTemp>(Outgoing, { it.safeCast() }) public abstract override val target: Member public val group: Group get() = target.group public final override val subject: Member get() = target final override val kind: MessageSourceKind get() = MessageSourceKind.TEMP final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from $fromId to group temp $targetId at $time]" } } @NotStableForInheritance public abstract class ToGroup @MiraiInternalApi constructor() : Outgoing() { public companion object Key : AbstractPolymorphicMessageKey<Outgoing, ToGroup>(Outgoing, { it.safeCast() }) public abstract override val target: Group public final override val subject: Group get() = target final override val kind: MessageSourceKind get() = MessageSourceKind.GROUP final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from $fromId to group $targetId at $time]" } } } /** * 接收到的一条消息的 [MessageSource] */ public sealed class Incoming : OnlineMessageSource() { /** * 当 [sender] 为 [bot] 自身时为 bot 的对应表示 (如: [Bot.asFriend], [Bot.asStranger], [Group.botAsMember]) */ public abstract override val sender: User /// NOTE: DONT use final to avoid contact not available public override val fromId: Long get() = sender.id public override val targetId: Long get() = target.id @NotStableForInheritance public abstract class FromFriend @MiraiInternalApi constructor() : Incoming() { public companion object Key : AbstractPolymorphicMessageKey<Incoming, FromFriend>(Incoming, { it.safeCast() }) public abstract override val subject: Friend /** * 当 [sender] 为 [bot] 自身时为 [Bot.asFriend] */ public abstract override val sender: Friend public abstract override val target: ContactOrBot @JvmName("getTarget") @Deprecated("For ABI compatibility", level = DeprecationLevel.HIDDEN) public fun getTargetLegacy(): Bot { if (targetId == bot.id) return subject.bot error("Message target isn't bot; $this") } final override val kind: MessageSourceKind get() = MessageSourceKind.FRIEND final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from friend $fromId to $targetId at $time]" } } @NotStableForInheritance public abstract class FromTemp @MiraiInternalApi constructor() : Incoming() { public companion object Key : AbstractPolymorphicMessageKey<Incoming, FromTemp>(Incoming, { it.safeCast() }) /** * 当 [sender] 为 [bot] 自身时为 [Group.botAsMember] */ public abstract override val sender: Member public abstract override val subject: Member public abstract override val target: ContactOrBot public inline val group: Group get() = subject.group @JvmName("getTarget") @Deprecated("For ABI compatibility", level = DeprecationLevel.HIDDEN) public fun getTargetLegacy(): Bot { if (targetId == bot.id) return subject.bot error("Message target isn't bot; $this") } final override val kind: MessageSourceKind get() = MessageSourceKind.TEMP final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from group temp $fromId to $targetId at $time]" } } @NotStableForInheritance public abstract class FromStranger @MiraiInternalApi constructor() : Incoming() { public companion object Key : AbstractPolymorphicMessageKey<Incoming, FromStranger>(Incoming, { it.safeCast() }) /** * 当 [sender] 为 [bot] 自身时为 [Bot.asStranger] */ public abstract override val sender: Stranger public abstract override val subject: Stranger public abstract override val target: ContactOrBot @JvmName("getTarget") @Deprecated("For ABI compatibility", level = DeprecationLevel.HIDDEN) public fun getTargetLegacy(): Bot { if (targetId == bot.id) return subject.bot error("Message target isn't bot; $this") } final override val kind: MessageSourceKind get() = MessageSourceKind.STRANGER final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from stranger $fromId to $targetId at $time]" } } @NotStableForInheritance public abstract class FromGroup @MiraiInternalApi constructor() : Incoming() { public companion object Key : AbstractPolymorphicMessageKey<Incoming, FromGroup>(Incoming, { it.safeCast() }) /** * 当 [sender] 为 [bot] 自身时为 [Group.botAsMember] */ public abstract override val sender: Member public override val subject: Group get() = sender.group public final override val target: Group get() = subject public inline val group: Group get() = subject final override val kind: MessageSourceKind get() = MessageSourceKind.GROUP final override fun toString(): String { return "[mirai:source:ids=${ids.contentToString()}, internalIds=${internalIds.contentToString()}, from group $fromId to $targetId at $time]" } } public companion object Key : AbstractPolymorphicMessageKey<OnlineMessageSource, FromTemp>(OnlineMessageSource, { it.safeCast() }) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/PlainText.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("NOTHING_TO_INLINE") 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.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 纯文本. * * 使用时直接构造即可. [Message] 也可以直接与 [String] 相加, 详见 [Message.plus]. * * ## mirai 码支持 * 将 [content] 转义. 而没有 `[mirai:`. * * @see String.toPlainText */ @Serializable @SerialName(PlainText.SERIAL_NAME) public data class PlainText( /** * 消息内容 */ public val content: String ) : MessageContent, CodableMessage { @Suppress("unused") public constructor(charSequence: CharSequence) : this(charSequence.toString()) public override fun toString(): String = content public override fun contentToString(): String = content @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.appendStringAsMiraiCode(content) } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitPlainText(this, data) } public companion object { public const val SERIAL_NAME: String = "PlainText" } } /** * 构造 [PlainText] */ @JvmSynthetic @Suppress("NOTHING_TO_INLINE") public inline fun String.toPlainText(): PlainText = PlainText(this) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/PokeMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.message.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.code.internal.appendStringAsMiraiCode import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.castOrNull import kotlin.jvm.JvmField /** * 戳一戳. 可以发送给好友或群. * * 备注: 这是消息对话框中显示的 "一个手指" 的戳一戳. 类似微信拍一拍的是 [Nudge]. * * 使用 [PokeMessage] 的静态字段, 而不要手动构造 [PokeMessage] 实例. * * ## mirai 码支持 * 格式: &#91;mirai:poke:*[name]*,*[pokeType]*,*[id]*&#93; * * @see PokeMessage.Companion 使用伴生对象中的常量 */ @OptIn(MiraiExperimentalApi::class) @SerialName(PokeMessage.SERIAL_NAME) @Serializable public data class PokeMessage @MiraiInternalApi constructor( /** * 仅 mirai, 显示的名称 */ public val name: String, public val pokeType: Int, // 'type' is used by serialization public val id: Int ) : HummerMessage, CodableMessage { override val key: MessageKey<HummerMessage> get() = Key @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:poke:").appendStringAsMiraiCode(name) .append(',').append(pokeType).append(',').append(id) .append(']') } override fun toString(): String = "[mirai:poke:$name,$pokeType,$id]" override fun contentToString(): String = "[戳一戳]" //businessType=0x00000001(1) //pbElem=08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00 //serviceType=0x00000002(2) @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitPokeMessage(this, data) } @OptIn(MiraiExperimentalApi::class, MiraiInternalApi::class) public companion object Key : AbstractPolymorphicMessageKey<HummerMessage, PokeMessage>(HummerMessage, { it.castOrNull() }) { public const val SERIAL_NAME: String = "PokeMessage" /** 戳一戳 */ @JvmField public val ChuoYiChuo: PokeMessage = PokeMessage("戳一戳", 1, -1) /** 比心 */ @JvmField public val BiXin: PokeMessage = PokeMessage("比心", 2, -1) /** 点赞 */ @JvmField public val DianZan: PokeMessage = PokeMessage("点赞", 3, -1) /** 心碎 */ @JvmField public val XinSui: PokeMessage = PokeMessage("心碎", 4, -1) /** 666 */ @JvmField public val LiuLiuLiu: PokeMessage = PokeMessage("666", 5, -1) /** 放大招 */ @JvmField public val FangDaZhao: PokeMessage = PokeMessage("放大招", 6, -1) /** 宝贝球 (SVIP) */ @JvmField public val BaoBeiQiu: PokeMessage = PokeMessage("宝贝球", 126, 2011) /** 玫瑰花 (SVIP) */ @JvmField public val Rose: PokeMessage = PokeMessage("玫瑰花", 126, 2007) /** 召唤术 (SVIP) */ @JvmField public val ZhaoHuanShu: PokeMessage = PokeMessage("召唤术", 126, 2006) /** 让你皮 (SVIP) */ @JvmField public val RangNiPi: PokeMessage = PokeMessage("让你皮", 126, 2009) /** 结印 (SVIP) */ @JvmField public val JieYin: PokeMessage = PokeMessage("结印", 126, 2005) /** 手雷 (SVIP) */ @JvmField public val ShouLei: PokeMessage = PokeMessage("手雷", 126, 2004) /** 勾引 */ @JvmField public val GouYin: PokeMessage = PokeMessage("勾引", 126, 2003) /** 抓一下 (SVIP) */ @JvmField public val ZhuaYiXia: PokeMessage = PokeMessage("抓一下", 126, 2001) /** 碎屏 (SVIP) */ @JvmField public val SuiPing: PokeMessage = PokeMessage("碎屏", 126, 2002) /** 敲门 (SVIP) */ @JvmField public val QiaoMen: PokeMessage = PokeMessage("敲门", 126, 2000) /** * 所有类型数组 */ @JvmField public val values: Array<PokeMessage> = arrayOf( ChuoYiChuo, BiXin, DianZan, XinSui, LiuLiuLiu, FangDaZhao, BaoBeiQiu, Rose, ZhaoHuanShu, RangNiPi, JieYin, ShouLei, GouYin, ZhuaYiXia, SuiPing, QiaoMen ) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/QuoteReply.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:Suppress("NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.message.data import kotlinx.serialization.Polymorphic import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.message.data.MessageSource.Key.quote import net.mamoe.mirai.message.data.MessageSource.Key.recall import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 引用回复. [QuoteReply] 被作为 [MessageMetadata], 因为它不包含实际的消息内容. * * 支持引用任何一条消息发送给任何人. * * 引用回复的原消息内容完全由 [source] 中 [MessageSource.originalMessage] 控制, 客户端不会自行寻找原消息. * 可通过 [MessageSource.copyAmend] 修改引用的消息内容. * * 客户端通过 [MessageSource.ids] 等数据定位源消息, 在修改时使用 [MessageSourceBuilder.metadata] 可以修改定位结果. * * ## 创建引用回复 * - 直接构造 [QuoteReply]: `new QuoteReply(source)` * - 在 Kotlin 使用扩展 [MessageSource.quote] * * @see MessageSource 获取有关消息源的更多信息 */ @Serializable @SerialName(QuoteReply.SERIAL_NAME) public data class QuoteReply( /** * 指代被引用的消息. 其中 [MessageSource.originalMessage] 可以控制客户端显示的消息内容. */ public val source: @Polymorphic MessageSource ) : Message, MessageMetadata, ConstrainSingle { /** * 从消息链中获取 [MessageSource] 并构造. */ public constructor(sourceMessage: MessageChain) : this(sourceMessage.getOrFail(MessageSource)) public override val key: MessageKey<QuoteReply> get() = Key public override fun toString(): String = "[mirai:quote:$source, content=${if (source.isOriginalMessageInitialized) source.originalMessage.toString() else "<not yet initialized>"}]" public override fun equals(other: Any?): Boolean = other is QuoteReply && other.source == this.source public override fun hashCode(): Int = source.hashCode() @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitQuoteReply(this, data) } public companion object Key : AbstractMessageKey<QuoteReply>({ it.safeCast() }) { public const val SERIAL_NAME: String = "QuoteReply" } } /** * 撤回引用的源消息 */ @JvmSynthetic public suspend inline fun QuoteReply.recallSource(): Unit = this.source.recall() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/README.md ================================================ # mirai Message 消息对象. 查看 [Message] 源码内注释 [Message]: Message.kt#L35 ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/RichMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmName("MessageUtils") @file:JvmMultifileClass @file:Suppress("MemberVisibilityCanBePrivate", "unused") 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.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.safeCast import kotlin.annotation.AnnotationTarget.* import kotlin.jvm.* /** * XML, JSON 消息等富文本消息. * * 通常构造 [LightApp] 和 [ServiceMessage] * * **注意**: 富文本消息的 [RichMessage.contentEquals] 和 [RichMessage.toString] 都不稳定. 将来可能在没有任何警告的情况下改变格式. * * @see ServiceMessage 服务消息 (XML, JSON) * @see LightApp 小程序 (JSON) */ // not using sealed class for customized implementations // using polymorphic serializer from Message.Serializer @NotStableForInheritance public interface RichMessage : MessageContent, ConstrainSingle { @OptIn(MiraiExperimentalApi::class) override val key: MessageKey<RichMessage> get() = Key /** * **注意**: 富文本消息的 [RichMessage.contentEquals] 和 [RichMessage.toString] 都不稳定. 将来可能在没有任何警告的情况下改变格式. */ public override fun contentToString(): String = this.content /** * 消息内容. 可为 JSON 文本或 XML 文本 */ public val content: String @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitRichMessage(this, data) } /** * 一些模板 * @suppress 此 API 不稳定, 可能在任意时刻被删除 */ @MiraiExperimentalApi public companion object Key : AbstractPolymorphicMessageKey<MessageContent, RichMessage>(MessageContent, { it.safeCast() }) { /** * @suppress 此 API 不稳定, 可能在任意时刻被删除 */ @MiraiExperimentalApi @JvmStatic public fun share( url: String, title: String? = null, content: String? = null, coverUrl: String? = null ): ServiceMessage = buildXmlMessage(60) { templateId = 12345 serviceId = 1 action = "web" brief = "[分享] " + (title.orEmpty()) this.url = url item { layout = 2 if (coverUrl != null) { picture(coverUrl) } if (title != null) { title(title) } if (content != null) { summary(content) } } } } } /** * 小程序. * * 大部分 JSON 消息为此类型, 另外一部分为 [ServiceMessage] * * @param content 一般是 json * * @see ServiceMessage 服务消息 */ @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" } public override fun toString(): String = "[mirai:app:$content]" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitLightApp(this, data) } @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:app:").appendStringAsMiraiCode(content).append(']') } } /** * 服务消息, 可以是 JSON 消息或 XML 消息. * * JSON 消息更多情况下通过 [LightApp] 发送. * * @param serviceId 目前未知, XML 一般为 60, JSON 一般为 1 * @param content 消息内容. 可为 JSON 文本或 XML 文本 * * @see LightApp 小程序类型消息 */ @MiraiExperimentalApi @Serializable @SerialName(SimpleServiceMessage.SERIAL_NAME) public class SimpleServiceMessage( public override val serviceId: Int, public override val content: String ) : ServiceMessage { public override fun toString(): String = "[mirai:service:$serviceId,$content]" public override fun equals(other: Any?): Boolean { if (other == null) return false if (other::class != this::class) return false other as ServiceMessage return other.serviceId == this.serviceId && other.content == this.content } public override fun hashCode(): Int { var result = serviceId result = 31 * result + content.hashCode() return result } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitSimpleServiceMessage(this, data) } public companion object { public const val SERIAL_NAME: String = "SimpleServiceMessage" } } /** * 服务消息, 可以是 JSON 消息或 XML 消息. * * XML 消息有时候是 [SimpleServiceMessage], 有时候是 [LightApp]. * JSON 消息更多情况下通过 [LightApp] 发送. * * 建议使用官方客户端发送来确定具体是哪种类型. * * @see LightApp 小程序类型消息 * @see SimpleServiceMessage */ @NotStableForInheritance public interface ServiceMessage : RichMessage, CodableMessage { @OptIn(MiraiExperimentalApi::class) public companion object Key : AbstractPolymorphicMessageKey<RichMessage, ServiceMessage>(RichMessage, { it.safeCast() }) /** * 目前未知, XML 一般为 60, JSON 一般为 1 */ public val serviceId: Int @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitServiceMessage(this, data) } @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:service:").append(serviceId).append(',').appendStringAsMiraiCode(content).append(']') } } @MiraiExperimentalApi @Serializable public abstract class AbstractServiceMessage : ServiceMessage { public override fun toString(): String = "[mirai:service:$serviceId,$content]" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitAbstractServiceMessage(this, data) } } /* commonElem=CommonElem#750141174 { businessType=0x00000001(1) pbElem=08 01 18 00 20 FF FF FF FF 0F 2A 00 32 00 38 00 50 00 serviceType=0x00000002(2) } */ /** * 构造一条 XML 消息 * @suppress 此 API 不稳定 */ @Suppress("DEPRECATION_ERROR") @JvmSynthetic @MiraiExperimentalApi public inline fun buildXmlMessage(serviceId: Int, block: @XmlMessageDsl XmlMessageBuilder.() -> Unit): ServiceMessage = SimpleServiceMessage(serviceId, XmlMessageBuilder().apply(block).text) @MiraiExperimentalApi @Target(CLASS, FUNCTION, TYPE) @DslMarker public annotation class XmlMessageDsl /** * @suppress 此 API 不稳定 */ @MiraiExperimentalApi @XmlMessageDsl public class XmlMessageBuilder( public var templateId: Int = 1, public var serviceId: Int = 1, public var action: String = "plugin", /** * 一般为点击这条消息后跳转的链接 */ public var actionData: String = "", /** * 摘要, 在官方客户端内消息列表中显示 */ public var brief: String = "", public var flag: Int = 3, public var url: String = "", public var sourceName: String = "", public var sourceIconURL: String = "" ) { @PublishedApi internal val builder: StringBuilder = StringBuilder() public val text: String get() = "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>" + "<msg templateID='$templateId' serviceID='$serviceId' action='$action' actionData='$actionData' brief='$brief' flag='$flag' url='$url'>" + builder.toString() + "<source name='$sourceName' icon='$sourceIconURL'/>" + "</msg>" @JvmOverloads @XmlMessageDsl public inline fun item(bg: Int = 0, layout: Int = 4, block: @XmlMessageDsl ItemBuilder.() -> Unit) { builder.append(ItemBuilder(bg, layout).apply(block).text) } public fun source(name: String, iconURL: String = "") { sourceName = name sourceIconURL = iconURL } @XmlMessageDsl public class ItemBuilder @PublishedApi internal constructor( public var bg: Int = 0, public var layout: Int = 4 ) { @PublishedApi internal val builder: StringBuilder = StringBuilder() public val text: String get() = "<item bg='$bg' layout='$layout'>$builder</item>" public fun summary(text: String, color: String = "#000000") { this.builder.append("<summary color='$color'>$text</summary>") } public fun title(text: String, size: Int = 25, color: String = "#000000") { this.builder.append("<title size='$size' color='$color'>$text</title>") } public fun picture(coverUrl: String) { this.builder.append("<picture cover='$coverUrl'/>") } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/RockPaperScissors.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.message.code.CodableMessage import net.mamoe.mirai.message.data.RockPaperScissors.* import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* import kotlin.jvm.* import kotlin.random.Random /** * 石头剪刀布. * * 可以通过 [RockPaperScissors.random] 获得一个随机手势的实例. * * @property ROCK 石头 `[mirai:rps:rock]` * @property SCISSORS 剪刀 `[mirai:rps:scissors]` * @property PAPER 布(纸)`[mirai:rps:paper]` * * @since 2.14 */ @kotlin.Suppress("RemoveRedundantQualifierName") @Serializable(RockPaperScissors.Serializer::class) @SerialName(RockPaperScissors.SERIAL_NAME) public enum class RockPaperScissors( public val content: String, internalId: Int, ) : MarketFace, CodableMessage { ROCK("[石头]", 48), SCISSORS("[剪刀]", 49), PAPER("[布]", 50) ; @MiraiExperimentalApi override val id: Int get() = 11415 @MiraiInternalApi @JvmSynthetic public val internalId: Byte = internalId.toByte() @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:rps:").append(name.lowercase()).append(']') } override fun toString(): String = serializeToMiraiCode() override fun contentToString(): String = content @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitRockPaperScissors(this, data) } /** * 判断 当前手势 (`this`) 能否淘汰对手 ([other]) * * @return 赢返回 `true`,输返回 `false`,平局时返回 `null` */ public infix fun eliminates(other: RockPaperScissors): Boolean? { return when { this == other -> null this == ROCK && other == SCISSORS -> true this == SCISSORS && other == PAPER -> true this == PAPER && other == ROCK -> true else -> false } } public companion object Key : AbstractPolymorphicMessageKey<MarketFace, RockPaperScissors>(MarketFace, { it.safeCast() }) { public const val SERIAL_NAME: String = "RockPaperScissors" private val values = values() /** * 获取随机手势的 [石头剪刀布][RockPaperScissors] * * Java 可通过 `kotlin.random.PlatformRandomKt.asKotlinRandom()` 来传入一个 random */ @JvmStatic @JvmOverloads public fun random(random: Random = Random): RockPaperScissors = RockPaperScissors.values.random(random) } internal object Serializer : KSerializer<RockPaperScissors> by Surrogate.serializer().map( resultantDescriptor = Surrogate.serializer().descriptor, deserialize = { valueOf(it.name) }, serialize = { Surrogate(name) }, ) { @Serializable @SerialName(RockPaperScissors.SERIAL_NAME) private class Surrogate( val name: String, ) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/ShortVideo.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.message.data import kotlinx.serialization.KSerializer import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.AudioSupported import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* /** * 短视频消息, 指的是可在聊天界面在线播放的视频消息, 而非在群文件上传的视频文件. * * 短视频消息分为 [OnlineShortVideo] 与 [OfflineShortVideo]. 在本地上传的短视频为 [OfflineShortVideo]. 从服务器接收的短视频为 [OnlineShortVideo]. * * 最推荐存储的方式是下载视频文件, 每次都通过上传该文件获取视频消息. * 在上传视频时服务器会根据缓存情况选择回复已有视频 ID 或要求客户端上传. * * # 获取短视频消息示例 * * ## 上传短视频 * 使用 [Contact.uploadShortVideo], 将视频缩略图和视频[资源][ExternalResource] 上传以得到 [OfflineShortVideo]. * * ## 使用 [OfflineShortVideo.Builder] 构建短视频 * [OfflineShortVideo] 提供 [Builder][OfflineShortVideo.Builder] 构建方式, 必须指定 [videoId], [filename], [fileMd5], [fileSize] 和 [fileFormat] 参数. * 可选指定 [thumbnailMd5][OfflineShortVideo.Builder.thumbnailMd5] 和 [thumbnailSize][OfflineShortVideo.Builder.thumbnailSize]. 若不提供, 可能会影响服务器判断缓存. * * ## 从服务器接收 * 通过监听消息接收的短视频消息可直接转换为 [OnlineShortVideo]. * * kotlin 示例: * ```kotlin * val video: OnlineShortVideo = event.message[OnlineShortVideo] * ``` * * # 下载视频 * 通过 [OnlineShortVideo.urlForDownload] 获取下载链接. * 该下载链接不包含短视频的文件信息, 可以使用 [videoId] 或 [filename] 作为文件名, [fileFormat] 作为文件拓展名. * * @since 2.16 */ @NotStableForInheritance public interface ShortVideo : MessageContent, ConstrainSingle { /** * 视频 ID. */ public val videoId: String /** * 视频文件 MD5. 16 bytes. */ public val fileMd5: ByteArray /** * 视频大小 */ public val fileSize: Long /** * 视频文件类型(拓展名) */ public val fileFormat: String /** * 视频文件名, 不包括拓展名 */ public val filename: String @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitShortVideo(this, data) } override val key: MessageKey<*> get() = Key public companion object Key : AbstractPolymorphicMessageKey<MessageContent, ShortVideo>(MessageContent, { it.safeCast() }) { } } /** * 在线短视频消息, 即从消息事件中接收到的视频消息. * * [OnlineShortVideo] 仅可以从事件中的[消息链][MessageChain]接收, 不可手动构造. * * ### 序列化支持 * * [OnlineShortVideo] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString]. * 也可以在 [MessageSerializers.serializersModule] 获取到 [OnlineShortVideo] 的 [KSerializer]. * * 要获取更多有关序列化的信息, 参阅 [MessageSerializers]. * * @since 2.16 */ @NotStableForInheritance public interface OnlineShortVideo : ShortVideo { /** * 下载链接 */ public val urlForDownload: String public companion object Key : AbstractPolymorphicMessageKey<ShortVideo, OnlineShortVideo>(ShortVideo, { it.safeCast() }) { public const val SERIAL_NAME: String = "OnlineShortVideo" } } /** * 离线短视频消息. * * [OfflineShortVideo] 拥有协议上必要的五个属性: * - 视频 ID [videoId] * - 视频文件名 [filename] * - 视频 MD5 [fileMd5] * - 视频大小 [fileSize] * - 视频格式 [fileFormat] * * 和非必要属性: * - 缩略图 MD5 `thumbnailMd5` * - 缩略图大小 `thumbnailSize` * * [OfflineShortVideo] 可由本地 [ExternalResource] 经过 [AudioSupported.uploadShortVideo] 上传到服务器得到, 故无[下载链接][OnlineShortVideo.urlForDownload]. * * [OfflineShortVideo] 支持使用 [OfflineShortVideo.Builder] 可通过上述七个必要参数和两个非必要参数构造 [OfflineShortVideo] 实例. * * ### 序列化支持 * * [OfflineShortVideo] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString]. * 也可以在 [MessageSerializers.serializersModule] 获取到 [OfflineShortVideo] 的 [KSerializer]. * * 要获取更多有关序列化的信息, 参阅 [MessageSerializers]. * @since 2.16 */ @NotStableForInheritance public interface OfflineShortVideo : ShortVideo { public companion object Key : AbstractPolymorphicMessageKey<ShortVideo, OfflineShortVideo>(ShortVideo, { it.safeCast() }) { public const val SERIAL_NAME: String = "OfflineShortVideo" } public class Builder internal constructor( public var videoId: String, public var fileMd5: ByteArray, public var fileSize: Long, public var fileFormat: String, public var fileName: String ) { /** * 缩略图文件 MD5 * * 传入此处的缩略图 MD5 应该仅有以下来源: * * *已通过 [Contact.uploadShortVideo] 上传完成的*缩略图[资源][ExternalResource], 可由 [ExternalResource.md5] 获得. */ public var thumbnailMd5: ByteArray = EMPTY_BYTE_ARRAY /** * 缩略图文件大小 * * 传入此处的缩略图文件大小应该仅有以下来源: * * *已通过 [Contact.uploadShortVideo] 上传完成的*缩略图[资源][ExternalResource], 可由 [ExternalResource.size] 获得. */ public var thumbnailSize: Long = 0 public fun build(): OfflineShortVideo { @OptIn(MiraiInternalApi::class) return InternalShortVideoProtocol.instance.createOfflineShortVideo( videoId, fileMd5, fileSize, fileFormat, fileName, thumbnailMd5, thumbnailSize ) } public companion object { /** * 创建一个 [OfflineShortVideo.Builder] * * 在 Kotlin 可以使用类构造器的函数 [OfflineShortVideo]: `OfflineShortVideo(...)` */ @JvmStatic public fun newBuilder( videoId: String, fileName: String, fileFormat: String, fileMd5: ByteArray, fileSize: Long ): Builder = Builder(videoId, fileMd5, fileSize, fileFormat, fileName) } } } /** * 构造 [OfflineShortVideo]. 有关参数的含义, 参考 [ShortVideo]. * @since 2.16 */ @Suppress("NOTHING_TO_INLINE") @JvmSynthetic public inline fun OfflineShortVideo( videoId: String, fileName: String, fileFormat: String, fileMd5: ByteArray, fileSize: Long, thumbnailMd5: ByteArray = byteArrayOf(), thumbnailSize: Long = 0, ): OfflineShortVideo = OfflineShortVideo.Builder.newBuilder(videoId, fileName, fileFormat, fileMd5, fileSize).apply { this@apply.thumbnailMd5 = thumbnailMd5 this@apply.thumbnailSize = thumbnailSize }.build() /** * 内部短视频协议实现, 请不要使用此接口 * @since 2.16.0 */ @MiraiInternalApi public interface InternalShortVideoProtocol { public fun createOfflineShortVideo( videoId: String, fileMd5: ByteArray, fileSize: Long, fileFormat: String, fileName: String, thumbnailMd5: ByteArray, thumbnailSize: Long ): OfflineShortVideo @MiraiInternalApi public companion object { public val instance: InternalShortVideoProtocol by lazy { Mirai // initialize MiraiImpl first loadService( InternalShortVideoProtocol::class, "net.mamoe.mirai.internal.message.InternalShortVideoProtocolImpl" ) } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/ShowImageFlag.kt ================================================ /* * Copyright 2019-2022 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/dev/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.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast /** * [MessageChain] 中包含秀图时的标记. * * 秀图已被 QQ 弃用, 仅作识别处理 * * * ``` * MessageEvent event; * * if (event.message.contains(ShowImageFlag.INSTANCE)) { * // event.message 包含的图片是作为 '秀图' 发送 * } * ``` * * 发送 [ShowImageFlag] 不会有任何效果. * * @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" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitShowImageFlag(this, data) } /** * @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) {} } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/SingleMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlinx.serialization.KSerializer import kotlinx.serialization.PolymorphicSerializer import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 单个消息元素. 与之相对的是 [MessageChain], 是多个 [SingleMessage] 的集合. */ // @Serializable(SingleMessage.Serializer::class) public interface SingleMessage : Message { @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R = visitor.visitSingleMessage(this, data) /** * @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.HIDDEN // ERROR since 2.8 ) // error since 2.8 @DeprecatedSinceMirai(warningSince = "2.4", errorSince = "2.8", hiddenSince = "2.10") public object Serializer : KSerializer<SingleMessage> by PolymorphicSerializer(SingleMessage::class) } /** * 消息元数据, 即不含内容的元素. * * 这种类型的 [Message] 只表示一条消息的属性. 其子类如 [MessageSource], [QuoteReply], [MessageOrigin] 和 [CustomMessageMetadata] * * 所有子类的 [contentToString] 都应该返回空字符串. * * 要获取详细信息, 查看 [MessageChain]. * * @see MessageSource 消息源 * @see QuoteReply 引用回复 * @see CustomMessageMetadata 自定义元数据 * @see ShowImageFlag 秀图标识 * * @see ConstrainSingle 约束一个 [MessageChain] 中只存在这一种类型的元素 */ public interface MessageMetadata : SingleMessage { /** * 返回空字符串 */ override fun contentToString(): String = "" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitMessageMetadata(this, data) } } /** * 带内容的消息. * * @see PlainText 纯文本 * @see At At 一个群成员. * @see AtAll At 全体成员 * @see HummerMessage 一些特殊消息: [戳一戳][PokeMessage], [闪照][FlashImage] * @see Image 图片 * @see RichMessage 富文本 * @see ServiceMessage 服务消息, 如 JSON/XML * @see Face 原生表情 * @see SuperFace 超级表情 * @see ForwardMessage 合并转发 * @see Voice 语音 * @see MarketFace 商城表情 * @see MusicShare 音乐分享 */ public interface MessageContent : SingleMessage { @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitMessageContent(this, data) } public companion object Key : AbstractMessageKey<MessageContent>({ it.safeCast() }) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/SuperFace.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ 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.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance import net.mamoe.mirai.utils.safeCast /** * 表示一个超级表情. * * 超级表情由[普通表情][Face]转换而来. 不是所有的普通表情都有对应的超级表情. * * 要构造超级表情, 使用 [SuperFace.from] 或 [SuperFace.fromOrNull]. * 在 Kotlin 还可以使用对应扩展函数: [Face.toSuperFace] 或 [Face.toSuperFaceOrNull]. * * @see Face * @since 2.16 */ @OptIn(MiraiExperimentalApi::class) @Serializable @SerialName(SuperFace.SERIAL_NAME) @NotStableForInheritance public class SuperFace @MiraiInternalApi constructor( public val face: Int, public val id: String, @SerialName("sticker_type") public val type: Int ) : HummerMessage, CodableMessage { public companion object Key : AbstractPolymorphicMessageKey<MessageContent, SuperFace>( MessageContent, { it.safeCast() }) { public const val SERIAL_NAME: String = "SuperFace" /** * 将普通表情转换为超级表情. * * @throws IllegalArgumentException 无法转换时抛出 **/ @JvmStatic public fun from(face: Face): SuperFace { return fromOrNull(face = face) ?: throw IllegalArgumentException("No SuperFace mapping from Face(id=${face.id}, name='${face.name}')") } /** * 将普通表情转换为超级表情. * * @return 无法转换时返回 null **/ @JvmStatic @OptIn(MiraiInternalApi::class) public fun fromOrNull(face: Face): SuperFace? { val stickerId = when (face.id) { Face.DA_CALL -> "1" Face.BIAN_XING -> "2" Face.KE_DAO_LE -> "3" Face.ZI_XI_FEN_XI -> "4" Face.JIA_YOU -> "5" Face.WO_MEI_SHI -> "6" Face.CAI_GOU -> "7" Face.CHONG_BAI -> "8" Face.BI_XIN -> "9" Face.QING_ZHU -> "10" Face.LAO_SE_PI -> "11" Face.CHI_TANG -> "12" Face.LAN_QIU -> "13" Face.JING_XIA -> "14" Face.SHENG_QI -> "15" Face.LIU_LEI -> "16" Face.DAN_GAO -> "17" Face.BIAN_PAO -> "18" Face.YAN_HUA -> "19" Face.WO_XIANG_KAI_LE -> "20" Face.TIAN_PING -> "21" Face.HUA_DUO_LIAN -> "22" Face.RE_HUA_LE -> "23" Face.DA_ZHAO_HU -> "24" Face.NI_ZHEN_BANG_BANG -> "25" Face.SUAN_Q -> "26" Face.WO_FANG_LE -> "27" Face.DA_YUAN_ZHONG -> "28" Face.HONG_BAO_DUO_DUO -> "29" else -> return null } val stickerType = when (face.id) { Face.LAN_QIU -> 2 else -> 1 } return SuperFace(face = face.id, id = stickerId, type = stickerType) } } override val key: MessageKey<SuperFace> get() = Key public val name: String get() = contentToString().let { it.substring(1, it.length - 1) } override fun toString(): String = "[mirai:superface:$face,$id,$type]" override fun contentToString(): String = Face.names.getOrElse(face) { "[超级表情]" } @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:superface:").append(face).append(',').append(id).append(',').append(type).append(']') } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitSuperFace(this, data) } override fun hashCode(): Int { var result = face.hashCode() result = 31 * result + id.hashCode() result = 31 * result + type.hashCode() return result } override fun equals(other: Any?): Boolean { if (other !is SuperFace) return false return face == other.face && id == other.id && type == other.type } } /** * 将超级表情转换为普通表情 * * @since 2.16 */ @JvmSynthetic public fun SuperFace.toFace(): Face = Face(id = face) /** * 将普通表情转换为超级表情 * * @since 2.16 * @throws IllegalArgumentException 无法转换时抛出 */ @JvmSynthetic public fun Face.toSuperFace(): SuperFace = SuperFace.from(this) /** * 将普通表情转换为超级表情, 在无法转换时返回 `null` * * @since 2.16 */ @JvmSynthetic public fun Face.toSuperFaceOrNull(): SuperFace? = SuperFace.fromOrNull(this) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/UnsupportedMessage.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.IMirai import net.mamoe.mirai.Mirai import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* /** * mirai 尚未支持的消息类型. * * [UnsupportedMessage] 可以发送, 接收, 或序列化保存 * @since 2.6 */ @SerialName(UnsupportedMessage.SERIAL_NAME) @Serializable(UnsupportedMessage.Serializer::class) @NotStableForInheritance public interface UnsupportedMessage : MessageContent { override fun contentToString(): String = "[不支持的消息#${struct.contentHashCode()}]" // to produce 'stable' and reliable text /** * 原生消息数据 */ public val struct: ByteArray @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitUnsupportedMessage(this, data) } public companion object { public const val SERIAL_NAME: String = "UnsupportedMessage" /** * 创建 [UnsupportedMessage] * @see IMirai.createUnsupportedMessage */ @JvmStatic public fun create(struct: ByteArray): UnsupportedMessage = Mirai.createUnsupportedMessage(struct) } public object Serializer : KSerializer<UnsupportedMessage> by Surrogate.serializer().map( resultantDescriptor = Surrogate.serializer().descriptor, deserialize = { Mirai.createUnsupportedMessage(struct.hexToBytes()) }, serialize = { Surrogate(struct.toUHexString("")) } ) { @Suppress("RemoveRedundantQualifierName") @Serializable @SerialName(UnsupportedMessage.SERIAL_NAME) private class Surrogate( val struct: String // hex ) } } /** * 创建 [UnsupportedMessage] * @since 2.6 * @see UnsupportedMessage.create */ @JvmSynthetic public inline fun UnsupportedMessage(struct: ByteArray): UnsupportedMessage = UnsupportedMessage.create(struct) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/VipFace.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ 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.data.VipFace.Kind import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.safeCast import kotlin.jvm.JvmField /** * VIP 表情. * * 不支持发送, 在发送时会变为纯文本. * * ## mirai 码支持 * 格式: &#91;mirai:vipface:*[Kind.id]*,*[Kind.name]*,*[count]*&#93; * * @see VipFace.Key 使用伴生对象中的常量 */ @OptIn(MiraiExperimentalApi::class) @Serializable @SerialName(VipFace.SERIAL_NAME) public data class VipFace @MiraiInternalApi constructor( /** * 使用 [Companion] 中常量. */ public val kind: Kind, public val count: Int ) : HummerMessage, CodableMessage { override val key: MessageKey<VipFace> get() = Key @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:vipface:").append(kind).append(',').append(count).append(']') } override fun toString(): String = "[mirai:vipface:$kind,$count]" override fun contentToString(): String = "[${kind.name}]x$count" @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitVipFace(this, data) } @Serializable public data class Kind( val id: Int, val name: String ) { public override fun toString(): String { return "$id,$name" } } public companion object Key : AbstractPolymorphicMessageKey<HummerMessage, VipFace>(HummerMessage, { it.safeCast() }) { public const val SERIAL_NAME: String = "VipFace" @JvmField public val LiuLian: Kind = 9 to "榴莲" @JvmField public val PingDiGuo: Kind = 1 to "平底锅" @JvmField public val ChaoPiao: Kind = 12 to "钞票" @JvmField public val LueLueLue: Kind = 10 to "略略略" @JvmField public val ZhuTou: Kind = 4 to "猪头" @JvmField public val BianBian: Kind = 6 to "便便" @JvmField public val ZhaDan: Kind = 5 to "炸弹" @JvmField public val AiXin: Kind = 2 to "爱心" @JvmField public val HaHa: Kind = 3 to "哈哈" @JvmField public val DianZan: Kind = 1 to "点赞" @JvmField public val QinQin: Kind = 7 to "亲亲" @JvmField public val YaoWan: Kind = 8 to "药丸" @JvmField public val values: Array<Kind> = arrayOf( LiuLian, PingDiGuo, ChaoPiao, LueLueLue, ZhuTou, BianBian, ZhaDan, AiXin, HaHa, DianZan, QinQin, YaoWan ) private infix fun Int.to(name: String): Kind = Kind(this, name) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/Voice.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageUtils") package net.mamoe.mirai.message.data import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import net.mamoe.mirai.contact.Group import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.utils.* import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmStatic import kotlin.jvm.JvmSynthetic /** * 需要通过上传到服务器的消息,如语音、文件. * * @suppress 不要使用这个接口. 目前只应该使用 [Voice]. */ @Serializable @MiraiExperimentalApi @NotStableForInheritance public abstract class PttMessage : MessageContent { public companion object Key : AbstractPolymorphicMessageKey<MessageContent, PttMessage>(MessageContent, { it.safeCast() }) @MiraiExperimentalApi public abstract val fileName: String @MiraiExperimentalApi public abstract val md5: ByteArray @MiraiExperimentalApi public abstract val fileSize: Long /* * **internal impl note** * 用于中转 ImMsgBody.Ptt, 在接受到其他用户发送的语音时能按照原样发回, * 并且便于未来修改 (对 api 修改最小化) */ @MiraiInternalApi @Transient public var pttInternalInstance: Any? = null } /** * 已弃用的旧版本语音消息. * * [Voice] 由于有设计缺陷已弃用且可能会在将来版本删除, 请使用 [Audio]. * * ## 迁移指南 * * - 将使用的 [Voice] 类型替换为 [Audio] 类型 * - 将 [Group.uploadVoice] 替换为 [Group.uploadAudio] * - 如果有必须使用旧 [Voice] 类型的情况, 请使用 [Audio.toVoice] */ @OptIn(MiraiExperimentalApi::class) @Suppress("DuplicatedCode", "DEPRECATION_ERROR", "PropertyName", "HttpUrlsUsage") @Serializable @SerialName(Voice.SERIAL_NAME) @Deprecated( "Please use Audio instead.", replaceWith = ReplaceWith("Audio", "net.mamoe.mirai.message.data.Audio"), level = DeprecationLevel.HIDDEN ) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public open class Voice @MiraiInternalApi constructor( @MiraiExperimentalApi public override val fileName: String, @MiraiExperimentalApi public override val md5: ByteArray, @MiraiExperimentalApi public override val fileSize: Long, @SerialName("codec") @MiraiInternalApi public val _codec: Int = 0, private val _url: String ) : PttMessage() { @Deprecated( "Please use Audio instead.", replaceWith = ReplaceWith("Audio.Key", "net.mamoe.mirai.message.data.Audio.Key"), level = DeprecationLevel.HIDDEN ) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public companion object Key : AbstractPolymorphicMessageKey<PttMessage, Voice>(PttMessage, { it.safeCast() }) { public const val SERIAL_NAME: String = "Voice" /** * 将 2.7 新增的 [Audio] 转为旧版本的 [Voice], 以兼容某些情况. * * @see Audio.toVoice * @since 2.7 */ @OptIn(MiraiInternalApi::class) @Deprecated( "Please consider migrating to Audio", level = DeprecationLevel.ERROR ) // deprecated since 2.7 @DeprecatedSinceMirai( warningSince = "2.7", errorSince = "2.10" ) // if HIDDEN, it cannot be resolved by Audio.toVoice @JvmStatic public fun fromAudio(audio: Audio): Voice { audio.run { return Voice( filename, fileMd5, fileSize, codec.id, if (this is OnlineAudio) kotlin.runCatching { urlForDownload }.getOrElse { "" } else "" ) } } } /** * 下载链接 HTTP URL. */ public open val url: String? get() = when { _url.isBlank() -> null _url.startsWith("http") -> _url else -> "http://grouptalk.c2c.qq.com$_url" } private var _stringValue: String? = null get() = field ?: kotlin.run { field = "[mirai:voice:$fileName]" field } public override fun toString(): String = _stringValue!! public override fun contentToString(): String = "[语音消息]" /** * 转换为 2.7 新增的 [Audio], 以兼容某些无法迁移的情况. * * @since 2.7 */ @OptIn(MiraiExperimentalApi::class, MiraiInternalApi::class) public fun toAudio(): Audio { val voice = this return OfflineAudio( voice.fileName, voice.md5, voice.fileSize, AudioCodec.fromIdOrNull(voice._codec) ?: AudioCodec.SILK, byteArrayOf() ) } @OptIn(MiraiInternalApi::class) @MiraiExperimentalApi override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Voice) return false if (fileName != other.fileName) return false if (!md5.contentEquals(other.md5)) return false if (fileSize != other.fileSize) return false if (_codec != other._codec) return false return _url == other._url } @OptIn(MiraiInternalApi::class) @MiraiExperimentalApi override fun hashCode(): Int { var result = fileName.hashCode() result = 12 * result + md5.contentHashCode() result = 54 * result + fileSize.hashCode() result = 33 * result + _codec result = 15 * result + _url.hashCode() return result } @MiraiInternalApi override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R { return visitor.visitVoice(this, data) } } /** * 将 2.7 新增的 [Audio] 转为旧版本的 [Voice], 以兼容某些情况. * * @since 2.7 */ @Suppress("DeprecatedCallableAddReplaceWith", "DEPRECATION_ERROR") @Deprecated( "Please migrate to Audio", level = DeprecationLevel.ERROR ) // deprecated since 2.7 @JvmSynthetic @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public fun Audio.toVoice(): Voice = Voice.fromAudio(this) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/impl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE") @file:JvmMultifileClass @file:JvmName("MessageUtils") @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.message.data import kotlinx.serialization.Serializable import net.mamoe.mirai.message.data.Image.Key.IMAGE_ID_REGEX import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1 import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2 import net.mamoe.mirai.message.data.visitor.MessageVisitor import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor import net.mamoe.mirai.message.data.visitor.acceptChildren import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.asImmutable import net.mamoe.mirai.utils.castOrNull import net.mamoe.mirai.utils.replaceAllKotlin import kotlin.jvm.JvmField import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic // region image ///////////////////////// //// IMPLEMENTATIONS //// ///////////////////////// @JvmSynthetic internal fun Message.contentEqualsStrictImpl(another: Message, ignoreCase: Boolean): Boolean { if (!this.contentToString().equals(another.contentToString(), ignoreCase = ignoreCase)) return false return when { this is SingleMessage && another is SingleMessage -> true this is SingleMessage && another is MessageChain -> another.all { it is MessageMetadata || it is PlainText } this is MessageChain && another is SingleMessage -> this.all { it is MessageMetadata || it is PlainText } this is MessageChain && another is MessageChain -> { val anotherIterator = another.iterator() /** * 逐个判断非 [PlainText] 的 [Message] 是否 [equals] */ this.contentsSequence().forEach { thisElement -> if (thisElement is PlainText) return@forEach for (it in anotherIterator) { if (it is PlainText || it !is MessageContent) continue if (thisElement != it) return false } } return true } else -> error("shouldn't be reached") } } internal sealed class AbstractMessageChain : MessageChain { /** * 去重算法 v1 - 2.12: * 在连接时若只有 0-1 方包含 [ConstrainSingle], 则使用 [CombinedMessage] 优化性能. 否则使用旧版复杂去重算法构造 [LinearMessageChainImpl]. */ @MiraiInternalApi abstract val hasConstrainSingle: Boolean @OptIn(MiraiInternalApi::class) override fun hashCode(): Int { var result = 1 acceptChildren(object : RecursiveMessageVisitor<Unit>() { // override fun visitMessageChain(messageChain: MessageChain, data: Unit) { // result = 31 * result + messageChain.hashCode() // // do not call children // } // ensure `messageChainOf(messageChainOf(AtAll))` and `messageChainOf(AtAll)` get same hash code. override fun visitSingleMessage(message: SingleMessage, data: Unit) { result = 31 * result + message.hashCode() super.visitSingleMessage(message, data) } }) return result } override fun equals(other: Any?): Boolean { if (other === null) return false if (other !is MessageChain) return false return chainEquals(this, other) } private companion object { private fun chainEquals(a: MessageChain, b: MessageChain): Boolean { if (a.size != b.size) return false // Averagely faster even if we may end up counting size. val itr1 = a.iterator() val itr2 = b.iterator() for (singleMessage in itr1) { if (!itr2.hasNext()) return false val n = itr2.next() if (singleMessage != n) return false } return true } } } @OptIn(MiraiInternalApi::class) internal val Message.hasConstrainSingle: Boolean get() { if (this is SingleMessage) return this is ConstrainSingle // now `this` is MessageChain return this.castOrNull<AbstractMessageChain>()?.hasConstrainSingle ?: true // for external type, assume they do } /** * @see ConstrainSingleHelper.constrainSingleMessages */ internal data class ConstrainSingleData( val value: List<SingleMessage>, val hasConstrainSingle: Boolean, ) internal object ConstrainSingleHelper { @JvmName("constrainSingleMessages_Sequence") internal fun constrainSingleMessages(sequence: Sequence<Message>): ConstrainSingleData = constrainSingleMessages(sequence.flatMap { it.toMessageChain() }) internal fun constrainSingleMessages(sequence: Sequence<SingleMessage>): ConstrainSingleData = constrainSingleMessagesImpl(sequence) /** * - [Sequence.toMutableList] * - Replace in-place with marker null */ private fun constrainSingleMessagesImpl(sequence: Sequence<SingleMessage>): ConstrainSingleData { val list: MutableList<SingleMessage?> = sequence.toMutableList() var hasConstrainSingle = false for (singleMessage in list.asReversed()) { if (singleMessage is ConstrainSingle) { hasConstrainSingle = true val key = singleMessage.key.topmostKey val firstOccurrence = list.first { it != null && key.isInstance(it) } // may be singleMessage itself list.replaceAllKotlin { when { it == null -> null it === firstOccurrence -> singleMessage key.isInstance(it) -> null // remove duplicates else -> it } } } } return ConstrainSingleData(list.filterNotNull(), hasConstrainSingle) } } /** * 要求 opt-in, 避免意外调用构造器. */ @RequiresOptIn internal annotation class MessageChainConstructor /** * 使用 [Collection] 作为委托的 [MessageChain] */ @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(MessageChain.Serializer::class) internal class LinearMessageChainImpl @MessageChainConstructor private constructor( /** * Must be guaranteed to be immutable */ @JvmField internal val delegate: List<SingleMessage>, override val hasConstrainSingle: Boolean ) : Message, MessageChain, List<SingleMessage> by delegate, AbstractMessageChain(), DirectSizeAccess, DirectToStringAccess { override val size: Int get() = delegate.size override fun iterator(): Iterator<SingleMessage> = delegate.iterator() private val toStringTemp: String by lazy { this.delegate.joinToString("") { it.toString() } } override fun toString(): String = toStringTemp private val contentToStringTemp: String by lazy { this.delegate.joinToString("") { it.contentToString() } } override fun contentToString(): String = contentToStringTemp override fun <D> acceptChildren(visitor: MessageVisitor<D, *>, data: D) { for (singleMessage in delegate) { singleMessage.accept(visitor, data) } } companion object { fun combineCreate(message: Message, tail: Message): MessageChain { return create( ConstrainSingleHelper.constrainSingleMessages( message.toMessageChain().asSequence() + tail.toMessageChain().asSequence() ) ) /* when { this is SingleMessage && tail is SingleMessage -> { if (this is ConstrainSingle && tail is ConstrainSingle) { if (this.key == tail.key) return SingleMessageChainImpl(tail) } return CombinedMessage(this, tail) } this is SingleMessage -> { // tail is not tail as MessageChain if (this is ConstrainSingle) { val key = this.key if (tail.any { (it as? ConstrainSingle)?.key == key }) { return tail } } return CombinedMessage(this, tail) } tail is SingleMessage -> { this as MessageChain if (tail is ConstrainSingle && this.hasDuplicationOfConstrain(tail.key)) { return MessageChainImplByCollection(constrainSingleMessagesImpl(this.asSequence() + tail)) } return CombinedMessage(this, tail) } else -> { // both chain this as MessageChain tail as MessageChain return MessageChainImplByCollection( constrainSingleMessagesImpl(this.asSequence() + tail) ) } }*/ } /** * @param delegate must be immutable */ @OptIn(MessageChainConstructor::class) fun create(delegate: List<SingleMessage>, hasConstrainSingle: Boolean): MessageChain { return if (delegate.isEmpty()) { emptyMessageChain() } else { LinearMessageChainImpl(delegate.asImmutable(), hasConstrainSingle) } } fun create(data: ConstrainSingleData): MessageChain { return create(data.value, data.hasConstrainSingle) } } } ////////////////////// // region Image impl ////////////////////// @get:JvmSynthetic internal val EMPTY_BYTE_ARRAY = ByteArray(0) @JvmSynthetic @Suppress("NOTHING_TO_INLINE") // no stack waste internal inline fun Char.hexDigitToByte(): Int { return when (this) { in '0'..'9' -> this - '0' in 'A'..'F' -> 10 + (this - 'A') in 'a'..'f' -> 10 + (this - 'a') else -> throw IllegalArgumentException("Illegal hex digit: $this") } } @JvmSynthetic internal fun String.skipToSecondHyphen(): Int { var count = 0 this.forEachIndexed { index, c -> if (c == '-' && ++count == 2) return index } error("Internal error: failed skipToSecondHyphen, cannot find two hyphens. Input=$this") } @JvmSynthetic internal fun String.imageIdToMd5(offset: Int): ByteArray { val result = ByteArray(16) var cur = 0 var hasCurrent = false var lastChar: Char = 0.toChar() for (index in offset..this.lastIndex) { val char = this[index] if (char == '-') continue if (hasCurrent) { result[cur++] = (lastChar.hexDigitToByte().shl(4) or char.hexDigitToByte()).toByte() if (cur == 16) return result hasCurrent = false } else { lastChar = char hasCurrent = true } } error("Internal error: failed imageIdToMd5, no enough chars. Input=$this, offset=$offset") } internal val ILLEGAL_IMAGE_ID_EXCEPTION_MESSAGE: String = "ImageId must match Regex `${IMAGE_RESOURCE_ID_REGEX_1.pattern}`, " + "`${IMAGE_RESOURCE_ID_REGEX_2.pattern}` or " + "`${IMAGE_ID_REGEX.pattern}`" // endregion ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/data/visitor/MessageVisitor.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.message.data.visitor import net.mamoe.mirai.message.data.* import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.NotStableForInheritance /** * 消息的访问器. * 优先考虑使用此 visitor API 而不是 [MessageChain] 的 [List] API, 例如 [MessageChain.iterator]. 使用 [MessageVisitor] 将会提升性能, 对于巨大的通过 `plus` 方式连接的消息链有重大区别. * * @suppress 这是内部 API, 请不要调用 * @since 2.12 */ @OptIn(MiraiExperimentalApi::class) @MiraiInternalApi @NotStableForInheritance public interface MessageVisitor<in D, out R> { public fun visitMessage(message: Message, data: D): R // region SingleMessage public fun visitSingleMessage(message: SingleMessage, data: D): R // region MessageContent public fun visitMessageContent(message: MessageContent, data: D): R public fun visitPlainText(message: PlainText, data: D): R public fun visitAt(message: At, data: D): R public fun visitAtAll(message: AtAll, data: D): R @Suppress("DEPRECATION_ERROR") public fun visitVoice(message: net.mamoe.mirai.message.data.Voice, data: D): R public fun visitAudio(message: Audio, data: D): R public fun visitShortVideo(message: ShortVideo, data: D): R // region HummerMessage public fun visitHummerMessage(message: HummerMessage, data: D): R public fun visitFlashImage(message: FlashImage, data: D): R public fun visitPokeMessage(message: PokeMessage, data: D): R public fun visitVipFace(message: VipFace, data: D): R public fun visitSuperFace(message: SuperFace, data: D): R // region MarketFace public fun visitMarketFace(message: MarketFace, data: D): R public fun visitDice(message: Dice, data: D): R public fun visitRockPaperScissors(message: RockPaperScissors, data: D): R // endregion // endregion public fun visitFace(message: Face, data: D): R public fun visitFileMessage(message: FileMessage, data: D): R public fun visitImage(message: Image, data: D): R public fun visitForwardMessage(message: ForwardMessage, data: D): R public fun visitMusicShare(message: MusicShare, data: D): R // region RichMessage public fun visitRichMessage(message: RichMessage, data: D): R public fun visitServiceMessage(message: ServiceMessage, data: D): R public fun visitSimpleServiceMessage(message: SimpleServiceMessage, data: D): R public fun visitLightApp(message: LightApp, data: D): R public fun visitAbstractServiceMessage(message: AbstractServiceMessage, data: D): R // endregion public fun visitUnsupportedMessage(message: UnsupportedMessage, data: D): R // endregion // region MessageMetadata public fun visitMessageMetadata(message: MessageMetadata, data: D): R public fun visitMessageOrigin(message: MessageOrigin, data: D): R public fun visitMessageSource(message: MessageSource, data: D): R public fun visitQuoteReply(message: QuoteReply, data: D): R public fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: D): R public fun visitShowImageFlag(message: ShowImageFlag, data: D): R // endregion // endregion // region MessageChain public fun visitMessageChain(messageChain: MessageChain, data: D): R public fun visitCombinedMessage(message: CombinedMessage, data: D): R // endregion } /** * @suppress 这是内部 API, 请不要调用 * @since 2.12 */ @OptIn(MiraiExperimentalApi::class) @MiraiInternalApi public abstract class AbstractMessageVisitor<in D, out R> : MessageVisitor<D, R> { public override fun visitSingleMessage(message: SingleMessage, data: D): R { return visitMessage(message, data) } public override fun visitMessageChain(messageChain: MessageChain, data: D): R { return visitMessage(messageChain, data) } public override fun visitCombinedMessage(message: CombinedMessage, data: D): R { return visitMessageChain(message, data) } public override fun visitMessageContent(message: MessageContent, data: D): R { return visitSingleMessage(message, data) } public override fun visitMessageMetadata(message: MessageMetadata, data: D): R { return visitSingleMessage(message, data) } public override fun visitMessageOrigin(message: MessageOrigin, data: D): R { return visitMessageMetadata(message, data) } public override fun visitMessageSource(message: MessageSource, data: D): R { return visitMessageMetadata(message, data) } public override fun visitQuoteReply(message: QuoteReply, data: D): R { return visitMessageMetadata(message, data) } public override fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: D): R { return visitMessageMetadata(message, data) } public override fun visitShowImageFlag(message: ShowImageFlag, data: D): R { return visitMessageMetadata(message, data) } public override fun visitPlainText(message: PlainText, data: D): R { return visitMessageContent(message, data) } public override fun visitAt(message: At, data: D): R { return visitMessageContent(message, data) } public override fun visitAtAll(message: AtAll, data: D): R { return visitMessageContent(message, data) } @Suppress("DEPRECATION_ERROR") public override fun visitVoice(message: net.mamoe.mirai.message.data.Voice, data: D): R { return visitMessageContent(message, data) } public override fun visitAudio(message: Audio, data: D): R { return visitMessageContent(message, data) } override fun visitShortVideo(message: ShortVideo, data: D): R { return visitMessageContent(message, data) } public override fun visitHummerMessage(message: HummerMessage, data: D): R { return visitMessageContent(message, data) } public override fun visitFlashImage(message: FlashImage, data: D): R { return visitHummerMessage(message, data) } public override fun visitPokeMessage(message: PokeMessage, data: D): R { return visitHummerMessage(message, data) } public override fun visitVipFace(message: VipFace, data: D): R { return visitHummerMessage(message, data) } override fun visitSuperFace(message: SuperFace, data: D): R { return visitHummerMessage(message, data) } public override fun visitMarketFace(message: MarketFace, data: D): R { return visitHummerMessage(message, data) } public override fun visitDice(message: Dice, data: D): R { return visitMarketFace(message, data) } public override fun visitRockPaperScissors(message: RockPaperScissors, data: D): R { return visitMarketFace(message, data) } public override fun visitFace(message: Face, data: D): R { return visitMessageContent(message, data) } public override fun visitFileMessage(message: FileMessage, data: D): R { return visitMessageContent(message, data) } public override fun visitImage(message: Image, data: D): R { return visitMessageContent(message, data) } public override fun visitForwardMessage(message: ForwardMessage, data: D): R { return visitMessageContent(message, data) } public override fun visitMusicShare(message: MusicShare, data: D): R { return visitMessageContent(message, data) } public override fun visitUnsupportedMessage(message: UnsupportedMessage, data: D): R { return visitMessageContent(message, data) } public override fun visitRichMessage(message: RichMessage, data: D): R { return visitMessageContent(message, data) } public override fun visitServiceMessage(message: ServiceMessage, data: D): R { return visitRichMessage(message, data) } public override fun visitSimpleServiceMessage(message: SimpleServiceMessage, data: D): R { return visitServiceMessage(message, data) } public override fun visitLightApp(message: LightApp, data: D): R { return visitRichMessage(message, data) } public override fun visitAbstractServiceMessage(message: AbstractServiceMessage, data: D): R { return visitServiceMessage(message, data) } } /** * @suppress 这是内部 API, 请不要调用 * @since 2.12 */ @MiraiInternalApi public abstract class RecursiveMessageVisitor<D> : MessageVisitorUnit() { protected open fun isFinished(): Boolean = false override fun visitMessage(message: Message, data: Unit) { if (isFinished()) return message.acceptChildren(this, data) } } /** * @suppress 这是内部 API, 请不要调用 * @since 2.12 */ @MiraiInternalApi public abstract class MessageVisitorUnit : AbstractMessageVisitor<Unit, Unit>() { override fun visitMessage(message: Message, data: Unit): Unit = Unit } /** * @suppress 这是内部 API, 请不要调用 * @since 2.12 */ @MiraiInternalApi public fun <R> Message.accept(visitor: MessageVisitor<Unit, R>): R = this.accept(visitor, Unit) /** * @suppress 这是内部 API, 请不要调用 * @since 2.12 */ @MiraiInternalApi public fun Message.acceptChildren(visitor: MessageVisitor<Unit, *>): Unit = this.acceptChildren(visitor, Unit) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/message/utils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MessageEventKt") @file:Suppress("unused") package net.mamoe.mirai.message import kotlinx.coroutines.* import net.mamoe.mirai.event.* import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.message.data.MessageChain import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic /** * 判断两个 [MessageEvent] 的语境, 即 [MessageEvent.sender] 和 [MessageEvent.subject] 是否相同 */ public fun MessageEvent.isContextIdenticalWith(another: MessageEvent): Boolean { return this.sender == another.sender && this.subject == another.subject } /** * 挂起当前协程, 等待下一条语境与 [this] 相同且通过 [筛选][filter] 的 [MessageEvent]. * 有关语境的定义可查看 [isContextIdenticalWith]. * * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param filter 过滤器. 过滤器函数返回 `true` 表示拦截并返回此 [MessageEvent]. 返回 `false` 表示忽略该事件并继续监听下一个事件. * @see syncFromEvent 实现原理 */ @JvmSynthetic public suspend inline fun <reified P : MessageEvent> P.nextMessage( timeoutMillis: Long = -1, priority: EventPriority = EventPriority.MONITOR, noinline filter: suspend P.(P) -> Boolean = { true } ): MessageChain = nextMessage(timeoutMillis, priority, false, filter) /** * 挂起当前协程, 等待下一条语境与 [this] 相同且通过 [筛选][filter] 的 [MessageEvent], 并且[拦截][Event.intercept]该事件. * 有关语境的定义可查看 [isContextIdenticalWith]. * 有关拦截的说明可查看 [Event.intercept]. * * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. `-1` 为不限制 * @param priority 事件优先级. 查看 [EventChannel.subscribe] 以获得更多帮助. * @param intercept 是否拦截, 传入 `true` 时表示拦截此事件不让接下来的监听器处理, 传入 `false` 时表示让接下来的监听器处理 * @param filter 过滤器. 过滤器函数返回 `true` 表示拦截并返回此 [MessageEvent]. 返回 `false` 表示忽略该事件并继续监听下一个事件. * * @see syncFromEvent 实现原理 * @see MessageEvent.intercept 拦截事件 * * @since 2.13 */ @JvmSynthetic public suspend inline fun <reified P : MessageEvent> P.nextMessage( timeoutMillis: Long = -1, priority: EventPriority = EventPriority.HIGH, intercept: Boolean = false, noinline filter: suspend P.(P) -> Boolean = { true } ): MessageChain { val mapper: suspend (P) -> P? = createMapper(filter) return (if (timeoutMillis == -1L) { GlobalEventChannel.syncFromEvent(priority, mapper) } else { withTimeout(timeoutMillis) { GlobalEventChannel.syncFromEvent(priority, mapper) } }).apply { if (intercept) intercept() }.message } /** * 挂起当前协程, 等待下一条 [MessageEvent.sender] 和 [MessageEvent.subject] 与 [this] 相同且通过 [筛选][filter] 的 [MessageEvent] * * 若 [filter] 抛出了一个异常, 本函数会立即抛出这个异常. * * @param timeoutMillis 超时. 单位为毫秒. * @param filter 过滤器. 过滤器函数返回 `true` 表示拦截并返回此 [MessageEvent]. 返回 `false` 表示忽略该事件并继续监听下一个事件. * @return 消息链. 超时时返回 `null` * * @see syncFromEvent */ @JvmSynthetic public suspend inline fun <reified P : MessageEvent> P.nextMessageOrNull( timeoutMillis: Long, priority: EventPriority = EventPriority.MONITOR, noinline filter: suspend P.(P) -> Boolean = { true } ): MessageChain? { require(timeoutMillis > 0) { "timeoutMillis must be > 0" } val mapper: suspend (P) -> P? = createMapper(filter) return (if (timeoutMillis == -1L) { GlobalEventChannel.syncFromEvent(priority, mapper) } else { withTimeoutOrNull(timeoutMillis) { GlobalEventChannel.syncFromEvent(priority, mapper) } })?.message } /** * [nextMessage] 的异步版本. * * 此方法总是会在 [bot] 的[协程作用域][CoroutineScope]之下[创建任务][CoroutineScope.async], * 不利于管理异常, 也不利于结构化并发, 建议使用自行创建协程并调用 [nextMessageOrNull]. * * @see nextMessage */ @JvmSynthetic public inline fun <reified P : MessageEvent> P.nextMessageAsync( timeoutMillis: Long = -1, coroutineContext: CoroutineContext = EmptyCoroutineContext, priority: EventPriority = EventPriority.MONITOR, noinline filter: suspend P.(P) -> Boolean = { true } ): Deferred<MessageChain> { return this.bot.async(coroutineContext) { nextMessage(timeoutMillis, priority, filter) } } /** * [nextMessageOrNull] 的异步版本. * * 此方法总是会在 [bot] 的[协程作用域][CoroutineScope]之下[创建任务][CoroutineScope.async], * 不利于管理异常, 也不利于结构化并发, 建议使用自行创建协程并调用 [nextMessageOrNull]. * * @see nextMessageOrNull */ @JvmSynthetic public inline fun <reified P : MessageEvent> P.nextMessageOrNullAsync( timeoutMillis: Long, coroutineContext: CoroutineContext = EmptyCoroutineContext, priority: EventPriority = EventPriority.MONITOR, noinline filter: suspend P.(P) -> Boolean = { true } ): Deferred<MessageChain?> { require(timeoutMillis > 0) { "timeoutMillis must be > 0" } return this.bot.async(coroutineContext) { nextMessageOrNull(timeoutMillis, priority, filter) } } /// internals /** * @since 2.10 */ @PublishedApi // inline, safe to remove in the future internal inline fun <reified P : MessageEvent> P.createMapper(crossinline filter: suspend P.(P) -> Boolean): suspend (P) -> P? = mapper@{ event -> if (!event.isContextIdenticalWith(this)) return@mapper null if (!filter(event, event)) return@mapper null event } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/network/ForceOfflineException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.network import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.DeprecatedSinceMirai import kotlin.jvm.JvmOverloads /** * 当 [Bot] 被迫下线时抛出, 作为 [Job.cancel] 的 `cause` */ @Deprecated("Not used anymore since 2.7", level = DeprecationLevel.HIDDEN) // deprecated since 2.7, error since 2.8 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10") public class ForceOfflineException @JvmOverloads constructor( public override val message: String? = null, public override val cause: Throwable? = null ) : CancellationException(message) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/network/LoginFailedException.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.network import net.mamoe.mirai.Bot import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.utils.LoginSolver import net.mamoe.mirai.utils.MiraiInternalApi /** * 在 [登录][Bot.login] 失败时抛出, 可正常地中断登录过程. */ public sealed class LoginFailedException( /** * 是否可因此登录失败而关闭 [Bot]. 一般是密码错误, 被冻结等异常时. */ public val killBot: Boolean = false, message: String? = null, cause: Throwable? = null ) : RuntimeException(message, cause) // 实现提示 (仅供网络层实现者参考): `LoginFailedException` 会被包装为 `NetworkException` (`LoginFailedExceptionAsNetworkException`), // 并在 `bot.login` 时 unwrap. /** * 密码输入错误 (有时候也会是其他错误, 如 `"当前上网环境异常,请更换网络环境或在常用设备上登录或稍后再试。"`) */ public class WrongPasswordException @MiraiInternalApi constructor( message: String? ) : LoginFailedException(true, message) /** * 二维码扫码账号与 BOT 账号不一致。 * * @since 2.15 */ public class InconsistentBotIdException @MiraiInternalApi constructor( public val expected: Long, public val actual: Long, message: String? = null ) : LoginFailedException( true, message ?: "trying to logging in a bot whose id is different from the one provided to BotFactory.newBot, expected=$expected, actual=$actual." ) /** * 无可用服务器 */ public class NoServerAvailableException @MiraiInternalApi constructor( public override val cause: Throwable? ) : LoginFailedException(false, "no server available") /** * 服务器要求稍后重试 */ public class RetryLaterException @MiraiInternalApi constructor( message: String?, cause: Throwable? = null, killBot: Boolean = false ) : LoginFailedException(killBot, message, cause) /** * 无标准输入或 Kotlin 不支持此输入. */ public class NoStandardInputForCaptchaException @MiraiInternalApi constructor( public override val cause: Throwable? = null ) : LoginFailedException(true, "no standard input for captcha") /** * 表示在登录过程中, [BotAuthorization] 抛出的异常. * @since 2.15 */ public class BotAuthorizationException @MiraiInternalApi constructor( public val authorization: BotAuthorization, cause: Throwable?, ) : LoginFailedException( killBot = true, "BotAuthorization(${authorization}) threw an exception during authorization process. See cause below.", cause ) /** * 当前 [LoginSolver] 不支持此验证方式 * * @since 2.15 */ public open class UnsupportedCaptchaMethodException : LoginFailedException { public constructor(killBot: Boolean) : super(killBot) public constructor(killBot: Boolean, message: String?) : super(killBot, message) public constructor(killBot: Boolean, message: String?, cause: Throwable?) : super(killBot, message, cause) public constructor(killBot: Boolean, cause: Throwable?) : super(killBot, cause = cause) } /** * 需要强制短信验证, 且当前 [LoginSolver] 不支持时抛出. * @since 2.13 */ public class UnsupportedSmsLoginException(message: String?) : UnsupportedCaptchaMethodException(true, message) /** * 无法完成滑块验证 */ public class UnsupportedSliderCaptchaException(message: String?) : UnsupportedCaptchaMethodException(true, message) /** * 需要二维码登录, 且当前 [LoginSolver] 不支持时抛出 * * @since 2.15 */ public class UnsupportedQRCodeCaptchaException(message: String?) : UnsupportedCaptchaMethodException(true, message) /** * 非 mirai 实现的异常 */ public abstract class CustomLoginFailedException : LoginFailedException { public constructor(killBot: Boolean) : super(killBot) public constructor(killBot: Boolean, message: String?) : super(killBot, message) public constructor(killBot: Boolean, message: String?, cause: Throwable?) : super(killBot, message, cause) public constructor(killBot: Boolean, cause: Throwable?) : super(killBot, cause = cause) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/spi/AudioToSilkService.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.spi import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.runAutoClose import net.mamoe.mirai.utils.useAutoClose import net.mamoe.mirai.utils.withAutoClose import kotlin.jvm.JvmStatic /** * 将源音频文件转换为 silk v3 with tencent 格式 * * @since 2.8.0 */ // stable since 2.15 public interface AudioToSilkService : BaseService { /** * implementation note: * * 如果返回值为转换后的资源文件: * * 如果 [ExternalResource.isAutoClose], 需要关闭 [source], * 返回的 [ExternalResource] 的 [ExternalResource.isAutoClose] 必须为 `true` * * 特别的, 如果该方法体抛出了一个错误, 如果 [ExternalResource.isAutoClose], 需要关闭 [source] * * @see [withAutoClose] * @see [runAutoClose] * @see [useAutoClose] */ public suspend fun convert(source: ExternalResource): ExternalResource public companion object { private val loader = SpiServiceLoader(AudioToSilkService::class) { object : AudioToSilkService { override suspend fun convert(source: ExternalResource): ExternalResource = source } } /** * 获取当前实例 */ @JvmStatic public val instance: AudioToSilkService get() = loader.service } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/spi/SPIServiceLoader.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.spi import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.lateinitMutableProperty import net.mamoe.mirai.utils.loadServices import kotlin.reflect.KClass /** * 基本 SPI 接口 * @since 2.8.0 */ // stable since 2.15 public interface BaseService { /** 使用优先级, 值越小越先使用 */ public val priority: Int get() = 0 } internal fun <T : BaseService> SpiServiceLoader( serviceType: KClass<T>, defaultImplementation: () -> T ): SpiServiceLoader<T> { return SpiServiceLoaderImpl(serviceType, defaultImplementation) } internal fun <T : BaseService> SpiServiceLoader( serviceType: KClass<T> ): SpiServiceLoader<T?> { return SpiServiceLoaderImpl(serviceType, null) } internal interface SpiServiceLoader<T : BaseService?> { val service: T val allServices: List<T & Any> } internal class SpiServiceLoaderImpl<T : BaseService?>( private val serviceType: KClass<T & Any>, defaultService: (() -> T)? ) : SpiServiceLoader<T> { private val defaultInstance: T? by lazy { defaultService?.invoke() } private val lock = SynchronizedObject() override val service: T get() = _service.bestService override val allServices: List<T & Any> get() = _service.allServices private class Loaded<T>( val bestService: T, val allServices: List<T & Any>, ) private var _service: Loaded<T> by lateinitMutableProperty { synchronized(lock) { reloadAndSelect() } } fun reload() { synchronized(lock) { _service = reloadAndSelect() } } private fun reloadAndSelect(): Loaded<T> { val allServices = loadServices(serviceType).toList() @Suppress("UNCHECKED_CAST") val bestService = (allServices.minByOrNull { it.priority } ?: defaultInstance) as T return Loaded(bestService, allServices) } companion object { val SPI_SERVICE_LOADER_LOGGER: MiraiLogger by lazy { MiraiLogger.Factory.create(SpiServiceLoader::class, "spi-service-loader") } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/AbstractBotConfiguration.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import net.mamoe.mirai.Bot import net.mamoe.mirai.utils.DeviceInfo.Companion.loadAsDeviceInfo import java.io.File import java.io.InputStream /** * [BotConfiguration] 的 JVM 平台特别配置 * @since 2.15 */ @NotStableForInheritance public abstract class AbstractBotConfiguration { // open for Java protected abstract var deviceInfo: ((Bot) -> DeviceInfo)? protected abstract var networkLoggerSupplier: ((Bot) -> MiraiLogger) protected abstract var botLoggerSupplier: ((Bot) -> MiraiLogger) /** * 工作目录. 默认为 "." */ public var workingDir: File = File(".") /////////////////////////////////////////////////////////////////////////// // Device /////////////////////////////////////////////////////////////////////////// /** * 使用文件存储设备信息. * * 此函数只在 JVM 和 Android 有效. 在其他平台将会抛出异常. * @param filepath 文件路径. 默认是相对于 [workingDir] 的文件 "device.json". * @see deviceInfo */ @JvmOverloads @BotConfiguration.ConfigurationDsl public fun fileBasedDeviceInfo(filepath: String = "device.json") { deviceInfo = { workingDir.resolve(filepath).loadAsDeviceInfo(BotConfiguration.json) } } /////////////////////////////////////////////////////////////////////////// // Logging /////////////////////////////////////////////////////////////////////////// /** * 重定向 [网络日志][networkLoggerSupplier] 到指定目录. 若目录不存在将会自动创建 ([File.mkdirs]) * 默认目录路径为 "$workingDir/logs/". * @see DirectoryLogger * @see redirectNetworkLogToDirectory */ @JvmOverloads @BotConfiguration.ConfigurationDsl public fun redirectNetworkLogToDirectory( dir: File = File("logs"), retain: Long = 1.weeksToMillis, identity: (bot: Bot) -> String = { "Net ${it.id}" } ) { require(!dir.isFile) { "dir must not be a file" } networkLoggerSupplier = { DirectoryLogger(identity(it), workingDir.resolve(dir), retain) } } /** * 重定向 [网络日志][networkLoggerSupplier] 到指定文件. 默认文件路径为 "$workingDir/mirai.log". * 日志将会逐行追加到此文件. 若文件不存在将会自动创建 ([File.createNewFile]) * @see SingleFileLogger * @see redirectNetworkLogToDirectory */ @JvmOverloads @BotConfiguration.ConfigurationDsl public fun redirectNetworkLogToFile( file: File = File("mirai.log"), identity: (bot: Bot) -> String = { "Net ${it.id}" } ) { require(!file.isDirectory) { "file must not be a dir" } networkLoggerSupplier = { SingleFileLogger(identity(it), workingDir.resolve(file)) } } /** * 重定向 [Bot 日志][botLoggerSupplier] 到指定文件. * 日志将会逐行追加到此文件. 若文件不存在将会自动创建 ([File.createNewFile]) * @see SingleFileLogger * @see redirectBotLogToDirectory */ @JvmOverloads @BotConfiguration.ConfigurationDsl public fun redirectBotLogToFile( file: File = File("mirai.log"), identity: (bot: Bot) -> String = { "Bot ${it.id}" } ) { require(!file.isDirectory) { "file must not be a dir" } botLoggerSupplier = { SingleFileLogger(identity(it), workingDir.resolve(file)) } } /** * 重定向 [Bot 日志][botLoggerSupplier] 到指定目录. 若目录不存在将会自动创建 ([File.mkdirs]) * @see DirectoryLogger * @see redirectBotLogToFile */ @JvmOverloads @BotConfiguration.ConfigurationDsl public fun redirectBotLogToDirectory( dir: File = File("logs"), retain: Long = 1.weeksToMillis, identity: (bot: Bot) -> String = { "Bot ${it.id}" } ) { require(!dir.isFile) { "dir must not be a file" } botLoggerSupplier = { DirectoryLogger(identity(it), workingDir.resolve(dir), retain) } } /////////////////////////////////////////////////////////////////////////// // Cache ////////////////////////////////////////////////////////////////////////// /** * 缓存数据目录, 相对于 [workingDir]. * * 缓存目录保存的内容均属于不稳定的 Mirai 内部数据, 请不要手动修改它们. 清空缓存不会影响功能. 只会导致一些操作如读取全部群列表要重新进行. * 默认启用的缓存可以加快登录过程. * * 注意: 这个目录只存储能在 [BotConfiguration] 配置的内容, 即包含: * - 联系人列表 * - 登录服务器列表 * - 资源服务秘钥 * * 其他内容如通过 [InputStream] 发送图片时的缓存使用 [FileCacheStrategy], 默认使用系统临时文件且会在关闭时删除文件. * * @since 2.4 */ public var cacheDir: File = File("cache") /////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////// internal fun applyMppCopy(new: BotConfiguration) { new.workingDir = workingDir new.cacheDir = cacheDir } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/AbstractExternalResource.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import io.ktor.utils.io.errors.* import io.ktor.utils.io.streams.* import kotlinx.atomicfu.atomic import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import net.mamoe.mirai.internal.utils.ExternalResourceHolder import net.mamoe.mirai.internal.utils.ExternalResourceLeakObserver import net.mamoe.mirai.internal.utils.detectFileTypeAndClose import net.mamoe.mirai.utils.AbstractExternalResource.ResourceCleanCallback import java.io.InputStream /** * 一个实现了基本方法的外部资源 * * ## 实现 * * [AbstractExternalResource] 实现了大部分必要的方法, * 只有 [ExternalResource.inputStream], [ExternalResource.size] 还未实现 * * 其中 [ExternalResource.inputStream] 要求每次读取的内容都是一致的 * * Example: * ``` * class MyCustomExternalResource: AbstractExternalResource() { * override fun inputStream0(): InputStream = FileInputStream("/test.txt") * override val size: Long get() = File("/test.txt").length() * } * ``` * * ## 资源释放 * * 如同 mirai 内置的 [ExternalResource] 实现一样, * [AbstractExternalResource] 也会被注册进入资源泄露监视器 * (即意味着 [AbstractExternalResource] 也要求手动关闭) * * 为了确保逻辑正确性, [AbstractExternalResource] 不允许覆盖其 [close] 方法, * 必须在构造 [AbstractExternalResource] 的时候给定一个 [ResourceCleanCallback] 以进行资源释放 * * 对于 [ResourceCleanCallback], 有以下要求 * * - 没有对 [AbstractExternalResource] 的访问 (即没有 [AbstractExternalResource] 的任何引用) * * Example: * ``` * class MyRes( * cleanup: ResourceCleanCallback, * val delegate: Closable, * ): AbstractExternalResource(cleanup) { * } * * // 错误, 该写法会导致 Resource 永远也不会被自动释放 * lateinit var myRes: MyRes * val cleanup = ResourceCleanCallback { * myRes.delegate.close() * } * myRes = MyRes(cleanup, fetchDelegate()) * * // 正确 * val delegate: Closable * val cleanup = ResourceCleanCallback { * delegate.close() * } * val myRes = MyRes(cleanup, delegate) * ``` * * @since 2.9 * * @see ExternalResource * @see AbstractExternalResource.setResourceCleanCallback * @see AbstractExternalResource.registerToLeakObserver */ @Suppress("MemberVisibilityCanBePrivate") public abstract class AbstractExternalResource @JvmOverloads public constructor( displayName: String? = null, cleanup: ResourceCleanCallback? = null, ) : ExternalResource { public constructor( cleanup: ResourceCleanCallback? = null, ) : this(null, cleanup) public fun interface ResourceCleanCallback { @Throws(IOException::class) public fun cleanup() } override val md5: ByteArray by lazy { inputStream().md5() } override val sha1: ByteArray by lazy { inputStream().sha1() } override val formatName: String by lazy { inputStream().detectFileTypeAndClose() ?: ExternalResource.DEFAULT_FORMAT_NAME } private val leakObserverRegistered = atomic(false) /** * 注册 [ExternalResource] 资源泄露监视器 * * 受限于类继承构造器调用顺序, [AbstractExternalResource] 无法做到在完成初始化后马上注册监视器 * * 该方法以允许 实现类 在完成初始化后直接注册资源监视器以避免意外的资源泄露 * * 在不调用本方法的前提下, 如果没有相关的资源访问操作, `this` 可能会被意外泄露 * * 正确示例: * ``` * // Kotlin * public class MyResource: AbstractExternalResource() { * init { * val res: SomeResource * // 一些资源初始化 * registerToLeakObserver() * setResourceCleanCallback(Releaser(res)) * } * * private class Releaser( * private val res: SomeResource, * ) : AbstractExternalResource.ResourceCleanCallback { * override fun cleanup() = res.close() * } * } * * // Java * public class MyResource extends AbstractExternalResource { * public MyResource() throws IOException { * SomeResource res; * // 一些资源初始化 * registerToLeakObserver(); * setResourceCleanCallback(new Releaser(res)); * } * * private static class Releaser implements ResourceCleanCallback { * private final SomeResource res; * Releaser(SomeResource res) { this.res = res; } * * public void cleanup() throws IOException { res.close(); } * } * } * ``` * * @see setResourceCleanCallback */ protected fun registerToLeakObserver() { // 用户自定义 AbstractExternalResource 也许会在 <init> 的时候失败 // 于是在第一次使用 ExternalResource 相关的函数的时候注册 LeakObserver if (leakObserverRegistered.compareAndSet(expect = false, update = true)) { ExternalResourceLeakObserver.register(this, holder) } } /** * 该方法用于告知 [AbstractExternalResource] 不需要注册资源泄露监视器。 * **仅在我知道我在干什么的前提下调用此方法** * * 不建议取消注册监视器, 这可能带来意外的错误 * * @see registerToLeakObserver */ protected fun dontRegisterLeakObserver() { leakObserverRegistered.value = true } final override fun inputStream(): InputStream { registerToLeakObserver() return inputStream0() } protected abstract fun inputStream0(): InputStream /** * 修改 `this` 的资源释放回调。 * **仅在我知道我在干什么的前提下调用此方法** * * ``` * class MyRes { * // region kotlin * * private inner class Releaser : ResourceCleanCallback * * private class NotInnerReleaser : ResourceCleanCallback * * init { * // 错误, 内部类, Releaser 存在对 MyRes 的引用 * setResourceCleanCallback(Releaser()) * // 错误, 匿名对象, 可能存在对 MyRes 的引用, 取决于编译器 * setResourceCleanCallback(object : ResourceCleanCallback {}) * // 正确, 无 inner 修饰, 等同于 java 的 private static class * setResourceCleanCallback(NotInnerReleaser(directResource)) * } * * // endregion kotlin * * // region java * * private class Releaser implements ResourceCleanCallback {} * private static class StaticReleaser implements ResourceCleanCallback {} * * MyRes() { * // 错误, 内部类, 存在对 MyRes 的引用 * setResourceCleanCallback(new Releaser()); * // 错误, 匿名对象, 可能存在对 MyRes 的引用, 取决于 javac * setResourceCleanCallback(new ResourceCleanCallback() {}); * // 正确 * setResourceCleanCallback(new StaticReleaser(directResource)); * } * * // endregion java * } * ``` * * @see registerToLeakObserver */ protected fun setResourceCleanCallback(cleanup: ResourceCleanCallback?) { holder.cleanup = cleanup } private class UsrCustomResHolder( @JvmField var cleanup: ResourceCleanCallback?, private val resourceName: String, ) : ExternalResourceHolder() { override val closed: Deferred<Unit> = CompletableDeferred() override fun closeImpl() { cleanup?.cleanup() } // display on logger of ExternalResourceLeakObserver override fun toString(): String = resourceName } private val holder = UsrCustomResHolder(cleanup, displayName ?: buildString { append("ExternalResourceHolder<") append(this@AbstractExternalResource.javaClass.name) append('@') append(System.identityHashCode(this@AbstractExternalResource)) append('>') }) final override val closed: Deferred<Unit> get() = holder.closed.also { registerToLeakObserver() } @Throws(IOException::class) final override fun close() { holder.close() } @OptIn(MiraiInternalApi::class) @MiraiExperimentalApi override fun input(): Input { return inputStream().asInput() } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/Annotations.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.annotation.AnnotationTarget.* /** * 标记为一个仅供 Mirai 内部使用的 API. * * 这些 API 可能会在任意时刻更改, 且不会发布任何预警. * 非常不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target( CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR, CLASS, FUNCTION, PROPERTY ) @MustBeDocumented public annotation class MiraiInternalApi( public val message: String = "" ) /** * 标记为一个仅供 Mirai 内部使用的 API. * * 这些 API 可能会在任意时刻更改, 且不会发布任何预警. * 非常不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.BINARY) @Target(FILE) public annotation class MiraiInternalFile /** * 标记这个类, 类型, 函数, 属性, 字段, 或构造器为实验性的 API. * * 这些 API 不具有稳定性, 且可能会在任意时刻更改. * 不建议在发行版本中使用这些 API. */ @Retention(AnnotationRetention.BINARY) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @Target(CLASS, TYPEALIAS, FUNCTION, PROPERTY, FIELD, CONSTRUCTOR) @MustBeDocumented public annotation class MiraiExperimentalApi( public val message: String = "" ) /** * 标记一个定义在使用上是稳定的 (如果没有特殊说明), 但只应该由 mirai 内部实现. * * 用户自行实现将可能造成对未来版本的不兼容, 因为新的抽象函数或属性会在未经警告的前提下添加. 自行实现还可能因 mirai 内部实现有部分硬编码成分而不兼容. * * @since 2.7 */ @Retention(AnnotationRetention.BINARY) @Target(CLASS, PROPERTY, FUNCTION) @MustBeDocumented public annotation class NotStableForInheritance( public val message: String = "This declaration is not stable for inheritance." ) /** * 标记一个正计划在 [version] 版本时删除 (对外隐藏) 的 API. */ @Target(CLASS, PROPERTY, FIELD, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER, TYPEALIAS) @Retention(AnnotationRetention.SOURCE) @MustBeDocumented internal annotation class PlannedRemoval(val version: String) /** * 该注解仅用于测试 EventHandler * * 标注了此注解的意为像处理 java 方法那样处理 kotlin 方法 */ @Retention(AnnotationRetention.RUNTIME) internal annotation class EventListenerLikeJava /** * 表明这个 API 是为了让 Java 使用者调用更方便. * * 一般有一定的性能损失, 且不能在 JVM/Android 以外平台使用. 不要在 Kotlin 调用它. */ @RequiresOptIn(level = RequiresOptIn.Level.ERROR) @Target(PROPERTY, FUNCTION, CLASS) public annotation class JavaFriendlyAPI // made public since 2.8.0-RC ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/BotConfiguration.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "DEPRECATION_ERROR", "EXPOSED_SUPER_CLASS", "MemberVisibilityCanBePrivate") @file:JvmMultifileClass @file:JvmName("Utils") package net.mamoe.mirai.utils import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob import kotlinx.serialization.json.Json import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.Mirai import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.event.events.BotOfflineEvent import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.coroutineContext import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds /** * [Bot] 配置. 用于 [BotFactory.newBot]. * * 部分平台相关配置位于 [AbstractBotConfiguration], 例如 `fileBasedDeviceInfo`. * * Kotlin 使用方法: * ``` * val bot = BotFactory.newBot(...) { * // 在这里配置 Bot * * bogLoggerSupplier = { bot -> ... } * fileBasedDeviceInfo() * inheritCoroutineContext() // 使用 `coroutineScope` 的 Job 作为父 Job * } * ``` * * Java 使用方法: * ```java * Bot bot = BotFactory.newBot(..., new BotConfiguration() {{ * setBogLoggerSupplier((Bot bot) -> { ... }) * fileBasedDeviceInfo() * ... * }}) * ``` */ @OptIn(MiraiInternalApi::class) public open class BotConfiguration : AbstractBotConfiguration() { // open for Java /////////////////////////////////////////////////////////////////////////// // 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.eventChannel.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 /** * 状态心跳包周期. 过长会导致掉线. * 该值会在登录时根据服务器下发的配置自动进行更新. * @since 2.6 * @see heartbeatStrategy */ public var statHeartbeatPeriodMillis: Long = 300.secondsToMillis /** * 心跳策略. * @since 2.6.3 */ public var heartbeatStrategy: HeartbeatStrategy = HeartbeatStrategy.STAT_HB /** * 心跳策略. * @since 2.6.3 */ public enum class HeartbeatStrategy { // IN ACTUAL DECLARATION DO NOT ADD EXTRA ELEMENTS. /** * 使用 2.6.0 增加的*状态心跳* (Stat Heartbeat). 通常推荐这个模式. * * 该模式大多数情况下更稳定. 但有些账号使用这个模式时会遇到一段时间后发送消息成功但客户端不可见的问题. */ STAT_HB, /** * 不发送状态心跳, 而是发送*切换在线状态* (可能会导致频繁的好友或客户端上线提示, 也可能产生短暂 (几秒) 发送消息不可见的问题). * * 建议在 [STAT_HB] 不可用时使用 [REGISTER]. */ REGISTER, /** * 不主动维护会话. 多数账号会每 16 分钟掉线然后重连. 则会有短暂的不可用时间. * * 仅当 [STAT_HB] 和 [REGISTER] 都造成无法接收等问题时使用. * 同时请在 [https://github.com/mamoe/mirai/issues/1209] 提交问题. */ NONE; } /** * 每次心跳时等待结果的时间. * 一旦心跳超时, 整个网络服务将会重启 (将消耗约 1s). 除正在进行的任务 (如图片上传) 会被中断外, 事件和插件均不受影响. */ public var heartbeatTimeoutMillis: Long = 5.secondsToMillis /** 心跳失败后的第一次重连前的等待时间. */ @Deprecated( "Useless since new network. Please just remove this.", level = DeprecationLevel.HIDDEN ) // deprecated since 2.7, error since 2.8 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10") public var firstReconnectDelayMillis: Long = 5.secondsToMillis /** 重连失败后, 继续尝试的每次等待时间 */ @Deprecated( "Useless since new network. Please just remove this.", level = DeprecationLevel.HIDDEN ) // deprecated since 2.7, error since 2.8 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.8", hiddenSince = "2.10") public var reconnectPeriodMillis: Long = 5.secondsToMillis /** 最多尝试多少次重连 */ public var reconnectionRetryTimes: Int = Int.MAX_VALUE /** * 在被挤下线时 ([BotOfflineEvent.Force]) 自动重连. 默认为 `false`. * * 其他情况掉线都默认会自动重连, 详见 [BotOfflineEvent.reconnect] * * @since 2.1 */ public var autoReconnectOnForceOffline: Boolean = false /** * 验证码处理器 * * - 在 Android 需要手动提供 [LoginSolver] * - 在 JVM, Mirai 会根据环境支持情况选择 Swing/CLI 实现 * * 详见 [LoginSolver.Default] * * @see LoginSolver */ public var loginSolver: LoginSolver? = LoginSolver.Default /** 使用协议类型 */ public var protocol: MiraiProtocol = MiraiProtocol.ANDROID_PHONE public enum class MiraiProtocol { /** * Android 手机. 所有功能都支持. */ ANDROID_PHONE, /** * Android 平板. */ ANDROID_PAD, /** * Android 手表. * * 注意: * - 不支持戳一戳事件解析 * - 由于该协议的省电特性, 当群被设置为不提醒的的时候, 服务器不会推送消息. */ ANDROID_WATCH, /** * iPad - 来自MiraiGo * * @since 2.8 */ IPAD, /** * MacOS - 来自MiraiGo * * @since 2.8 */ MACOS, ; /** * 当前协议是否支持[二维码登录][BotAuthorization.byQRCode] * * @since 2.15.0 */ public val isQRLoginSupported: Boolean get() = data.isQRLoginSupported /** * 当前协议是否支持[戳一戳][Bot.nudge] * * @since 2.16.0 */ public val isNudgeSupported: Boolean get() = data.isNudgeSupported private inline val data: InternalProtocolDataExchange.InternalProtocolData get() = InternalProtocolDataExchange.instance.of( this ) } /** * Highway 通道上传图片, 语音, 文件等资源时的协程数量. * * 每个协程的速度约为 200KB/s. 协程数量越多越快, 同时也更要求性能. * 默认为 CPU 核心数. * * @since 2.2 */ public var highwayUploadCoroutineCount: Int = availableProcessors() /** * 设置 [autoReconnectOnForceOffline] 为 `true`, 即在被挤下线时自动重连. * @since 2.1 */ @ConfigurationDsl public fun autoReconnectOnForceOffline() { autoReconnectOnForceOffline = true } /////////////////////////////////////////////////////////////////////////// // Device /////////////////////////////////////////////////////////////////////////// @JvmField internal var accountSecrets: Boolean = true /** * 禁止保存 `account.secrets`. * * `account.secrets` 保存账号的会话信息。 * 它可加速登录过程,也可能可以减少出现验证码的次数。如果遇到一段时间后无法接收消息通知等同步问题时可尝试禁用。 * * @since 2.11 */ public fun disableAccountSecretes() { accountSecrets = false } /** * 设备信息覆盖. 在没有手动指定时将会通过日志警告, 并使用随机设备信息. * @see fileBasedDeviceInfo 使用指定文件存储设备信息 * @see randomDeviceInfo 使用随机设备信息 */ public final override var deviceInfo: ((Bot) -> DeviceInfo)? = deviceInfoStub // allows user to set `null` manually. /** * 使用随机设备信息. * * @see deviceInfo */ @ConfigurationDsl public fun randomDeviceInfo() { deviceInfo = null } /** * 使用特定由 [DeviceInfo] 序列化产生的 JSON 的设备信息 * * @see deviceInfo */ @ConfigurationDsl public fun loadDeviceInfoJson(json: String) { deviceInfo = { DeviceInfoManager.deserialize(json, Companion.json) } } /////////////////////////////////////////////////////////////////////////// // Logging /////////////////////////////////////////////////////////////////////////// /** * 日志记录器 * * - 默认打印到标准输出, 通过 [MiraiLogger.Factory.create] * - 忽略所有日志: [noBotLog] * - 重定向到一个目录: `botLoggerSupplier = { DirectoryLogger("Bot ${it.id}") }` * - 重定向到一个文件: `botLoggerSupplier = { SingleFileLogger("Bot ${it.id}") }` * * @see MiraiLogger */ public final override var botLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.Factory.create(Bot::class, "Bot ${it.id}") } /** * 网络层日志构造器 * * - 默认打印到标准输出, 通过 [MiraiLogger.Factory.create] * - 忽略所有日志: [noNetworkLog] * - 重定向到一个目录: `networkLoggerSupplier = { DirectoryLogger("Net ${it.id}") }` * - 重定向到一个文件: `networkLoggerSupplier = { SingleFileLogger("Net ${it.id}") }` * * @see MiraiLogger */ public final override var networkLoggerSupplier: ((Bot) -> MiraiLogger) = { MiraiLogger.Factory.create(Bot::class, "Net ${it.id}") } /** * 不显示网络日志. 不推荐. * @see networkLoggerSupplier 更多日志处理方式 */ @ConfigurationDsl public fun noNetworkLog() { networkLoggerSupplier = { _ -> SilentLogger } } /** * 不显示 [Bot] 日志. 不推荐. * @see botLoggerSupplier 更多日志处理方式 */ @ConfigurationDsl public fun noBotLog() { botLoggerSupplier = { _ -> SilentLogger } } /** * 是否显示过于冗长的事件日志 * * 默认为 `false` * * @since 2.8 */ public var isShowingVerboseEventLog: Boolean = false /////////////////////////////////////////////////////////////////////////// // 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 秒. 在每次登录完成后有修改时都会立即保存一次. */ // was @ExperimentalTime before 2.9 public inline var saveInterval: Duration @JvmSynthetic inline get() = saveIntervalMillis.milliseconds @JvmSynthetic inline set(v) { saveIntervalMillis = v.inWholeMilliseconds } /** * 开启好友列表缓存. */ 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 } /** * 登录缓存. * * 开始后在密码登录成功时会保存秘钥等信息, 在下次启动时通过这些信息登录, 而不提交密码. * 可以减少验证码出现的频率. * * 秘钥信息会由密码加密保存. 如果秘钥过期, 则会进行普通密码登录. * * 默认 `true` (开启). * * @since 2.6 */ public var loginCacheEnabled: Boolean = true /////////////////////////////////////////////////////////////////////////// // Misc /////////////////////////////////////////////////////////////////////////// @Suppress("DuplicatedCode") public fun copy(): BotConfiguration { return BotConfiguration().also { new -> // To structural order new.parentCoroutineContext = parentCoroutineContext new.heartbeatPeriodMillis = heartbeatPeriodMillis new.heartbeatTimeoutMillis = heartbeatTimeoutMillis new.statHeartbeatPeriodMillis = statHeartbeatPeriodMillis new.heartbeatStrategy = heartbeatStrategy new.reconnectionRetryTimes = reconnectionRetryTimes new.autoReconnectOnForceOffline = autoReconnectOnForceOffline new.loginSolver = loginSolver new.protocol = protocol new.highwayUploadCoroutineCount = highwayUploadCoroutineCount new.accountSecrets = accountSecrets new.deviceInfo = deviceInfo new.botLoggerSupplier = botLoggerSupplier new.networkLoggerSupplier = networkLoggerSupplier new.contactListCache = contactListCache new.convertLineSeparator = convertLineSeparator new.isShowingVerboseEventLog = isShowingVerboseEventLog applyMppCopy(new) } } /** * 是否处理接受到的特殊换行符, 默认为 `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() /** * Json 序列化器, 使用 'kotlinx.serialization' */ internal val json: Json = kotlin.runCatching { Json { isLenient = true ignoreUnknownKeys = true prettyPrint = true } }.getOrElse { @Suppress("JSON_FORMAT_REDUNDANT_DEFAULT") // compatible for older versions (Json {}) } } } /** * 构建一个 [BotConfiguration]. * * @see BotConfiguration * @since 2.3 */ @JvmSynthetic public inline fun BotConfiguration(block: BotConfiguration.() -> Unit): BotConfiguration { return BotConfiguration().apply(block) } internal val deviceInfoStub: (Bot) -> DeviceInfo = { logger.warning("未指定设备信息, 已使用随机设备信息. 请查看 BotConfiguration.deviceInfo 以获取更多信息.") logger.warning("Device info isn't specified. Please refer to BotConfiguration.deviceInfo for more information") DeviceInfo.random() } private val logger by lazy { MiraiLogger.Factory.create(BotConfiguration::class) } /** @since 2.15.0 */ @MiraiInternalApi public interface InternalProtocolDataExchange { @MiraiInternalApi public interface InternalProtocolData { public val isQRLoginSupported: Boolean public val isNudgeSupported: Boolean public val mainVersion: String public val buildVersion: String public val sdkVersion: String } @MiraiInternalApi public fun of(protocol: BotConfiguration.MiraiProtocol): InternalProtocolData @MiraiInternalApi public companion object { internal val instance by lazy { Mirai // ensure service loaded loadService( InternalProtocolDataExchange::class, "net.mamoe.mirai.internal.utils.MiraiProtocolInternal\$Exchange" ) } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/DeviceInfo.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlinx.serialization.Transient import kotlinx.serialization.json.Json import kotlinx.serialization.protobuf.ProtoBuf import kotlinx.serialization.protobuf.ProtoNumber import java.io.File import kotlin.random.Random internal const val DeviceInfoConstructorDeprecationMessage = "DeviceInfo 构造器将会在未来删除. " + "这是因为构造器导致维护变得十分困难. " + "若要构造 DeviceInfo 实例, 请使用 DeviceInfoBuilder." + "若要序列化, 请使用 DeviceInfo.serializeToString 和 DeviceInfo.deserializeFromString." internal const val DeviceInfoConstructorReplaceWith = "DeviceInfoBuilder.create()" + ".display(display)" + ".product(product)" + ".device(device)" + ".board(board)" + ".brand(brand)" + ".model(model)" + ".bootloader(bootloader)" + ".fingerprint(fingerprint)" + ".bootId(bootId)" + ".procVersion(procVersion)" + ".baseBand(baseBand)" + ".version(version)" + ".simInfo(simInfo)" + ".osType(osType)" + ".macAddress(macAddress)" + ".wifiBSSID(wifiBSSID)" + ".wifiSSID(wifiSSID)" + ".imsiMd5(imsiMd5)" + ".imei(imei)" + ".apn(apn)" + ".androidId(androidId)" + ".build()" /** * 表示设备信息 * @see DeviceInfoBuilder */ @Serializable(DeviceInfoV1LegacySerializer::class) public class DeviceInfo @Deprecated( DeviceInfoConstructorDeprecationMessage, level = DeprecationLevel.WARNING, replaceWith = ReplaceWith( DeviceInfoConstructorReplaceWith, "net.mamoe.mirai.utils.DeviceInfoBuilder" ) ) @DeprecatedSinceMirai(warningSince = "2.15") // planned internal public constructor( public val display: ByteArray, public val product: ByteArray, public val device: ByteArray, public val board: ByteArray, public val brand: ByteArray, public val model: ByteArray, public val bootloader: ByteArray, public val fingerprint: ByteArray, public val bootId: ByteArray, public val procVersion: ByteArray, public val baseBand: ByteArray, public val version: Version, public val simInfo: ByteArray, public val osType: ByteArray, public val macAddress: ByteArray, public val wifiBSSID: ByteArray, public val wifiSSID: ByteArray, public val imsiMd5: ByteArray, public val imei: String, public val apn: ByteArray, public val androidId: ByteArray, ) { @Deprecated( DeviceInfoConstructorDeprecationMessage, replaceWith = ReplaceWith( "net.mamoe.mirai.utils.DeviceInfo(display, product, device, board, brand, model, " + "bootloader, fingerprint, bootId, procVersion, baseBand, version, simInfo, osType, " + "macAddress, wifiBSSID, wifiSSID, imsiMd5, imei, apn, androidId)" ), level = DeprecationLevel.WARNING ) @DeprecatedSinceMirai(warningSince = "2.15") @Suppress("DEPRECATION", "DEPRECATION_ERROR") public constructor( display: ByteArray, product: ByteArray, device: ByteArray, board: ByteArray, brand: ByteArray, model: ByteArray, bootloader: ByteArray, fingerprint: ByteArray, bootId: ByteArray, procVersion: ByteArray, baseBand: ByteArray, version: Version, simInfo: ByteArray, osType: ByteArray, macAddress: ByteArray, wifiBSSID: ByteArray, wifiSSID: ByteArray, imsiMd5: ByteArray, imei: String, apn: ByteArray ) : this( display, product, device, board, brand, model, bootloader, fingerprint, bootId, procVersion, baseBand, version, simInfo, osType, macAddress, wifiBSSID, wifiSSID, imsiMd5, imei, apn, androidId = 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) @Serializable public class Version( public val incremental: ByteArray = "5891938".toByteArray(), public val release: ByteArray = "10".toByteArray(), public val codename: ByteArray = "REL".toByteArray(), public val sdk: Int = 29 ) { /** * @since 2.9 */ override fun equals(other: Any?): Boolean { if (this === other) return true if (other !is Version) return false if (!incremental.contentEquals(other.incremental)) return false if (!release.contentEquals(other.release)) return false if (!codename.contentEquals(other.codename)) return false return sdk == other.sdk } /** * @since 2.9 */ override fun hashCode(): Int { var result = incremental.contentHashCode() result = 31 * result + release.contentHashCode() result = 31 * result + codename.contentHashCode() result = 31 * result + sdk return result } } public companion object { internal val logger = MiraiLogger.Factory.create(DeviceInfo::class, "DeviceInfo") /** * 加载一个设备信息. 若文件不存在或为空则随机并创建一个设备信息保存. */ @JvmOverloads @JvmStatic @JvmName("from") public fun File.loadAsDeviceInfo( json: Json = DeviceInfoManager.format ): DeviceInfo { if (!this.exists() || this.length() == 0L) { return random().also { this.writeText(DeviceInfoManager.serialize(it, json)) } } return DeviceInfoManager.deserialize(this.readText(), json) upg@{ upg -> if (!this.canWrite()) { logger.warning("Device info file $this is not writable, failed to upgrade legacy device info.") return@upg } try { this.writeText(DeviceInfoManager.serialize(upg, json)) } catch (ex: SecurityException) { logger.warning("Device info file $this is not writable, failed to upgrade legacy device info.", ex) } } } /** * 生成随机 [DeviceInfo] * * @see DeviceInfoBuilder * @since 2.0 */ @JvmStatic public fun random(): DeviceInfo = random(Random.Default) /** * 使用特定随机数生成器生成 [DeviceInfo] * * @see DeviceInfoBuilder * @since 2.9 */ @JvmStatic public fun random(random: Random): DeviceInfo { return DeviceInfoCommonImpl.randomDeviceInfo(random) } /** * 将此 [DeviceInfo] 序列化为字符串. 序列化的字符串可以在以后通过 [DeviceInfo.deserializeFromString] 反序列化为 [DeviceInfo]. * * 序列化的字符串有兼容性保证, 在旧版 mirai 序列化的字符串, 可以在新版 mirai 使用. 但新版 mirai 序列化的字符串不一定能在旧版使用. * * @since 2.15 */ @JvmStatic public fun serializeToString(deviceInfo: DeviceInfo): String = DeviceInfoManager.serialize(deviceInfo) /** * 将通过 [serializeToString] 序列化得到的字符串反序列化为 [DeviceInfo]. * 此函数兼容旧版 mirai 序列化的字符串. * @since 2.15 */ @JvmStatic public fun deserializeFromString(string: String): DeviceInfo = DeviceInfoManager.deserialize(string) } /** * @since 2.9 */ @Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean { return DeviceInfoCommonImpl.equalsImpl(this, other) } /** * @since 2.9 */ override fun hashCode(): Int { return DeviceInfoCommonImpl.hashCodeImpl(this) } @Suppress("ClassName") @Deprecated("For binary compatibility", level = DeprecationLevel.HIDDEN) public object `$serializer` : KSerializer<DeviceInfo> by DeviceInfoV1LegacySerializer } /** * 将此 [DeviceInfo] 序列化为字符串. 序列化的字符串可以在以后通过 [DeviceInfo.deserializeFromString] 反序列化为 [DeviceInfo]. * * 序列化的字符串有兼容性保证, 在旧版 mirai 序列化的字符串, 可以在新版 mirai 使用. 但新版 mirai 序列化的字符串不一定能在旧版使用. * * @since 2.15 */ @JvmSynthetic public fun DeviceInfo.serializeToString(): String = DeviceInfo.serializeToString(this) @Serializable private class DevInfo @OptIn(ExperimentalSerializationApi::class) constructor( @ProtoNumber(1) val bootloader: ByteArray, @ProtoNumber(2) val procVersion: ByteArray, @ProtoNumber(3) val codename: ByteArray, @ProtoNumber(4) val incremental: ByteArray, @ProtoNumber(5) val fingerprint: ByteArray, @ProtoNumber(6) val bootId: ByteArray, @ProtoNumber(7) val androidId: ByteArray, @ProtoNumber(8) val baseBand: ByteArray, @ProtoNumber(9) val innerVersion: ByteArray ) /** * 不要使用这个 API, 此 API 在未来可能会被删除 */ @OptIn(ExperimentalSerializationApi::class) public fun DeviceInfo.generateDeviceInfoData(): ByteArray { // ?? why is this public? return ProtoBuf.encodeToByteArray( DevInfo.serializer(), DevInfo( bootloader, procVersion, version.codename, version.incremental, fingerprint, bootId, androidId, baseBand, version.incremental ) ) } /** * Defaults "%4;7t>;28<fc.5*6".toByteArray() */ internal fun generateGuid(androidId: ByteArray, macAddress: ByteArray): ByteArray = (androidId + macAddress).md5() /* fun DeviceInfo.toOidb0x769DeviceInfo() : Oidb0x769.DeviceInfo = Oidb0x769.DeviceInfo( brand = brand.encodeToString(), model = model.encodeToString(), os = Oidb0x769.OS( version = version.release.encodeToString(), sdk = version.sdk.toString(), kernel = version.kernel ) ) */ /** * @see DeviceInfoManager */ internal object DeviceInfoCommonImpl { @Suppress("DEPRECATION") fun randomDeviceInfo(random: Random) = DeviceInfo( display = "MIRAI.${getRandomString(6, '0'..'9', random)}.001".toByteArray(), product = "mirai".toByteArray(), device = "mirai".toByteArray(), board = "mirai".toByteArray(), brand = "mamoe".toByteArray(), model = "mirai".toByteArray(), bootloader = "unknown".toByteArray(), fingerprint = "mamoe/mirai/mirai:10/MIRAI.200122.001/${ getRandomIntString(7, random) }:user/release-keys".toByteArray(), bootId = generateUUID(getRandomByteArray(16, random).md5()).toByteArray(), procVersion = "Linux version 3.0.31-${ getRandomString(8, random) } (android-build@xxx.xxx.xxx.xxx.com)".toByteArray(), baseBand = byteArrayOf(), version = DeviceInfo.Version(), simInfo = "T-Mobile".toByteArray(), osType = "android".toByteArray(), macAddress = "02:00:00:00:00:00".toByteArray(), wifiBSSID = "02:00:00:00:00:00".toByteArray(), wifiSSID = "<unknown ssid>".toByteArray(), imsiMd5 = getRandomByteArray(16, random).md5(), imei = "86${getRandomIntString(12, random)}".let { it + luhn(it) }, apn = "wifi".toByteArray(), androidId = getRandomByteArray(8, random).toUHexString("").lowercase().encodeToByteArray() ) /** * 计算 imei 校验位 */ private fun luhn(imei: String): Int { var odd = false val zero = '0' val sum = imei.sumOf { char -> odd = !odd if (odd) { char.code - zero.code } else { val s = (char.code - zero.code) * 2 s % 10 + s / 10 } } return (10 - sum % 10) % 10 } @OptIn(MiraiInternalApi::class) @Suppress("DuplicatedCode") fun equalsImpl(deviceInfo: DeviceInfo, other: Any?): Boolean = deviceInfo.run { if (deviceInfo === other) return true if (!isSameType(this, other)) return false // also remember to add equal compare to JvmDeviceInfoTest.`can read legacy v1` // when adding new field compare here. if (!display.contentEquals(other.display)) return false if (!product.contentEquals(other.product)) return false if (!device.contentEquals(other.device)) return false if (!board.contentEquals(other.board)) return false if (!brand.contentEquals(other.brand)) return false if (!model.contentEquals(other.model)) return false if (!bootloader.contentEquals(other.bootloader)) return false if (!fingerprint.contentEquals(other.fingerprint)) return false if (!bootId.contentEquals(other.bootId)) return false if (!procVersion.contentEquals(other.procVersion)) return false if (!baseBand.contentEquals(other.baseBand)) return false if (version != other.version) return false if (!simInfo.contentEquals(other.simInfo)) return false if (!osType.contentEquals(other.osType)) return false if (!macAddress.contentEquals(other.macAddress)) return false if (!wifiBSSID.contentEquals(other.wifiBSSID)) return false if (!wifiSSID.contentEquals(other.wifiSSID)) return false if (!imsiMd5.contentEquals(other.imsiMd5)) return false if (imei != other.imei) return false if (!apn.contentEquals(other.apn)) return false if (!guid.contentEquals(other.guid)) return false return androidId.contentEquals(other.androidId) } @OptIn(MiraiInternalApi::class) @Suppress("DuplicatedCode") fun hashCodeImpl(deviceInfo: DeviceInfo): Int = deviceInfo.run { var result = display.contentHashCode() result = 31 * result + product.contentHashCode() result = 31 * result + device.contentHashCode() result = 31 * result + board.contentHashCode() result = 31 * result + brand.contentHashCode() result = 31 * result + model.contentHashCode() result = 31 * result + bootloader.contentHashCode() result = 31 * result + fingerprint.contentHashCode() result = 31 * result + bootId.contentHashCode() result = 31 * result + procVersion.contentHashCode() result = 31 * result + baseBand.contentHashCode() result = 31 * result + version.hashCode() result = 31 * result + simInfo.contentHashCode() result = 31 * result + osType.contentHashCode() result = 31 * result + macAddress.contentHashCode() result = 31 * result + wifiBSSID.contentHashCode() result = 31 * result + wifiSSID.contentHashCode() result = 31 * result + imsiMd5.contentHashCode() result = 31 * result + imei.hashCode() result = 31 * result + apn.contentHashCode() result = 31 * result + guid.contentHashCode() result = 31 * result + androidId.contentHashCode() return result } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/DeviceInfoBuilder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import net.mamoe.mirai.utils.DeviceInfoBuilder.Companion.fromPrototype import net.mamoe.mirai.utils.DeviceInfoBuilder.Companion.fromRandom import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmStatic import kotlin.random.Random /** * [DeviceInfo] 的构建器. * * 通过 [fromPrototype] 或 [fromRandom] 可以构造一个构建器, 调用其各属性方法后使用 [build] 即可构建实例. * * @see build * * @since 2.15 */ public class DeviceInfoBuilder internal constructor( private val prototype: DeviceInfo = DeviceInfo.random() ) { private var display: ByteArray? = null public fun display(value: ByteArray): DeviceInfoBuilder = apply { this.display = value } public fun display(value: String): DeviceInfoBuilder = apply { this.display = value.encodeToByteArray() } private var product: ByteArray? = prototype.product public fun product(value: ByteArray): DeviceInfoBuilder = apply { this.product = value } public fun product(value: String): DeviceInfoBuilder = apply { this.product = value.encodeToByteArray() } private var device: ByteArray? = null public fun device(value: ByteArray): DeviceInfoBuilder = apply { this.device = value } public fun device(value: String): DeviceInfoBuilder = apply { this.device = value.encodeToByteArray() } private var board: ByteArray? = null public fun board(value: ByteArray): DeviceInfoBuilder = apply { this.board = value } public fun board(value: String): DeviceInfoBuilder = apply { this.board = value.encodeToByteArray() } private var brand: ByteArray? = null public fun brand(value: ByteArray): DeviceInfoBuilder = apply { this.brand = value } public fun brand(value: String): DeviceInfoBuilder = apply { this.brand = value.encodeToByteArray() } private var model: ByteArray? = null public fun model(value: ByteArray): DeviceInfoBuilder = apply { this.model = value } public fun model(value: String): DeviceInfoBuilder = apply { this.model = value.encodeToByteArray() } private var bootloader: ByteArray? = null public fun bootloader(value: ByteArray): DeviceInfoBuilder = apply { this.bootloader = value } public fun bootloader(value: String): DeviceInfoBuilder = apply { this.bootloader = value.encodeToByteArray() } private var fingerprint: ByteArray? = null public fun fingerprint(value: ByteArray): DeviceInfoBuilder = apply { this.fingerprint = value } public fun fingerprint(value: String): DeviceInfoBuilder = apply { this.fingerprint = value.encodeToByteArray() } private var bootId: ByteArray? = null public fun bootId(value: ByteArray): DeviceInfoBuilder = apply { this.bootId = value } public fun bootId(value: String): DeviceInfoBuilder = apply { this.bootId = value.encodeToByteArray() } private var procVersion: ByteArray? = null public fun procVersion(value: ByteArray): DeviceInfoBuilder = apply { this.procVersion = value } public fun procVersion(value: String): DeviceInfoBuilder = apply { this.procVersion = value.encodeToByteArray() } private var baseBand: ByteArray? = null public fun baseBand(value: ByteArray): DeviceInfoBuilder = apply { this.baseBand = value } public fun baseBand(value: String): DeviceInfoBuilder = apply { this.baseBand = value.encodeToByteArray() } private var version: DeviceInfo.Version? = null public fun version(value: DeviceInfo.Version): DeviceInfoBuilder = apply { this.version = value } private var simInfo: ByteArray? = null public fun simInfo(value: ByteArray): DeviceInfoBuilder = apply { this.simInfo = value } public fun simInfo(value: String): DeviceInfoBuilder = apply { this.simInfo = value.encodeToByteArray() } private var osType: ByteArray? = null public fun osType(value: ByteArray): DeviceInfoBuilder = apply { this.osType = value } public fun osType(value: String): DeviceInfoBuilder = apply { this.osType = value.encodeToByteArray() } private var macAddress: ByteArray? = null public fun macAddress(value: ByteArray): DeviceInfoBuilder = apply { this.macAddress = value } public fun macAddress(value: String): DeviceInfoBuilder = apply { this.macAddress = value.encodeToByteArray() } private var wifiBSSID: ByteArray? = null public fun wifiBSSID(value: ByteArray): DeviceInfoBuilder = apply { this.wifiBSSID = value } public fun wifiBSSID(value: String): DeviceInfoBuilder = apply { this.wifiBSSID = value.encodeToByteArray() } private var wifiSSID: ByteArray? = null public fun wifiSSID(value: ByteArray): DeviceInfoBuilder = apply { this.wifiSSID = value } public fun wifiSSID(value: String): DeviceInfoBuilder = apply { this.wifiSSID = value.encodeToByteArray() } private var imsiMd5: ByteArray? = null public fun imsiMd5(value: ByteArray): DeviceInfoBuilder = apply { this.imsiMd5 = value } public fun imsiMd5(value: String): DeviceInfoBuilder = apply { this.imsiMd5 = value.encodeToByteArray() } private var imei: String? = null public fun imei(value: String): DeviceInfoBuilder = apply { this.imei = value } private var apn: ByteArray? = null public fun apn(value: ByteArray): DeviceInfoBuilder = apply { this.apn = value } public fun apn(value: String): DeviceInfoBuilder = apply { this.apn = value.encodeToByteArray() } private var androidId: ByteArray? = null public fun androidId(value: ByteArray): DeviceInfoBuilder = apply { this.androidId = value } public fun androidId(value: String): DeviceInfoBuilder = apply { this.androidId = value.encodeToByteArray() } public fun build(): DeviceInfo { @Suppress("DEPRECATION") return DeviceInfo( display = display ?: prototype.display.copyOf(), product = product ?: prototype.product.copyOf(), device = device ?: prototype.device.copyOf(), board = board ?: prototype.board.copyOf(), brand = brand ?: prototype.brand.copyOf(), model = model ?: prototype.model.copyOf(), bootloader = bootloader ?: prototype.bootloader.copyOf(), fingerprint = fingerprint ?: prototype.fingerprint.copyOf(), bootId = bootId ?: prototype.bootId.copyOf(), procVersion = procVersion ?: prototype.procVersion.copyOf(), baseBand = baseBand ?: prototype.baseBand.copyOf(), version = version ?: prototype.version, simInfo = simInfo ?: prototype.simInfo.copyOf(), osType = osType ?: prototype.osType.copyOf(), macAddress = macAddress ?: prototype.macAddress.copyOf(), wifiBSSID = wifiBSSID ?: prototype.wifiBSSID.copyOf(), wifiSSID = wifiSSID ?: prototype.wifiSSID.copyOf(), imsiMd5 = imsiMd5 ?: prototype.imsiMd5.copyOf(), imei = imei ?: prototype.imei, apn = apn ?: prototype.apn.copyOf(), androidId = androidId ?: prototype.androidId.copyOf(), ) } public companion object { /** * 构造一个以随机属性填充的 [DeviceInfoBuilder]. */ @JvmStatic @JvmOverloads public fun fromRandom(random: Random = Random.Default): DeviceInfoBuilder = DeviceInfoBuilder(DeviceInfo.random(random)) /** * 构造一个复制 [prototype] 属性的 [DeviceInfoBuilder]. */ @JvmStatic public fun fromPrototype(prototype: DeviceInfo): DeviceInfoBuilder = DeviceInfoBuilder(prototype) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/DeviceInfoManager.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.serializer import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import net.mamoe.mirai.utils.DeviceInfoManager.Version.Companion.trans import kotlin.jvm.JvmInline import kotlin.jvm.JvmName internal object DeviceInfoManager { sealed interface Info { fun toDeviceInfo(): DeviceInfo } @Serializable(HexStringSerializer::class) @JvmInline value class HexString( val data: ByteArray ) object HexStringSerializer : KSerializer<HexString> by String.serializer().map( String.serializer().descriptor.copy("HexString"), deserialize = { HexString(it.hexToBytes()) }, serialize = { it.data.toUHexString("").lowercase() } ) // Note: property names must be kept intact during obfuscation process if applied. @Serializable class Wrapper<T : Info>( @Suppress("unused") val deviceInfoVersion: Int, // used by plain jsonObject val data: T ) internal object DeviceInfoVersionSerializer : KSerializer<DeviceInfo.Version> by SerialData.serializer().map( resultantDescriptor = SerialData.serializer().descriptor, deserialize = { DeviceInfo.Version(incremental, release, codename, sdk) }, serialize = { SerialData(incremental, release, codename, sdk) } ) { @SerialName("Version") @Serializable private class SerialData( val incremental: ByteArray = "5891938".toByteArray(), val release: ByteArray = "10".toByteArray(), val codename: ByteArray = "REL".toByteArray(), val sdk: Int = 29 ) } @Serializable class V1( val display: ByteArray, val product: ByteArray, val device: ByteArray, val board: ByteArray, val brand: ByteArray, val model: ByteArray, val bootloader: ByteArray, val fingerprint: ByteArray, val bootId: ByteArray, val procVersion: ByteArray, val baseBand: ByteArray, val version: @Serializable(DeviceInfoVersionSerializer::class) DeviceInfo.Version, val simInfo: ByteArray, val osType: ByteArray, val macAddress: ByteArray, val wifiBSSID: ByteArray, val wifiSSID: ByteArray, val imsiMd5: ByteArray, val imei: String, val apn: ByteArray ) : Info { override fun toDeviceInfo(): DeviceInfo { @Suppress("DEPRECATION", "DEPRECATION_ERROR") return DeviceInfo( display = display, product = product, device = device, board = board, brand = brand, model = model, bootloader = bootloader, fingerprint = fingerprint, bootId = bootId, procVersion = procVersion, baseBand = baseBand, version = version, simInfo = simInfo, osType = osType, macAddress = macAddress, wifiBSSID = wifiBSSID, wifiSSID = wifiSSID, imsiMd5 = imsiMd5, imei = imei, apn = apn, androidId = getRandomByteArray(8).toUHexString("").lowercase().encodeToByteArray() ) } } @Serializable class V2( val display: String, val product: String, val device: String, val board: String, val brand: String, val model: String, val bootloader: String, val fingerprint: String, val bootId: String, val procVersion: String, val baseBand: HexString, val version: Version, val simInfo: String, val osType: String, val macAddress: String, val wifiBSSID: String, val wifiSSID: String, val imsiMd5: HexString, val imei: String, val apn: String ) : Info { @Suppress("DEPRECATION", "DEPRECATION_ERROR") override fun toDeviceInfo(): DeviceInfo = DeviceInfo( this.display.toByteArray(), this.product.toByteArray(), this.device.toByteArray(), this.board.toByteArray(), this.brand.toByteArray(), this.model.toByteArray(), this.bootloader.toByteArray(), this.fingerprint.toByteArray(), this.bootId.toByteArray(), this.procVersion.toByteArray(), this.baseBand.data, this.version.trans(), this.simInfo.toByteArray(), this.osType.toByteArray(), this.macAddress.toByteArray(), this.wifiBSSID.toByteArray(), this.wifiSSID.toByteArray(), this.imsiMd5.data, this.imei, this.apn.toByteArray(), androidId = getRandomByteArray(8).toUHexString("").lowercase().encodeToByteArray() ) } @Serializable class V3( val display: String, val product: String, val device: String, val board: String, val brand: String, val model: String, val bootloader: String, val fingerprint: String, val bootId: String, val procVersion: String, val baseBand: HexString, val version: Version, val simInfo: String, val osType: String, val macAddress: String, val wifiBSSID: String, val wifiSSID: String, val imsiMd5: HexString, val imei: String, val apn: String, val androidId: String, ) : Info { @Suppress("DEPRECATION", "DEPRECATION_ERROR") override fun toDeviceInfo(): DeviceInfo = DeviceInfo( this.display.toByteArray(), this.product.toByteArray(), this.device.toByteArray(), this.board.toByteArray(), this.brand.toByteArray(), this.model.toByteArray(), this.bootloader.toByteArray(), this.fingerprint.toByteArray(), this.bootId.toByteArray(), this.procVersion.toByteArray(), this.baseBand.data, this.version.trans(), this.simInfo.toByteArray(), this.osType.toByteArray(), this.macAddress.toByteArray(), this.wifiBSSID.toByteArray(), this.wifiSSID.toByteArray(), this.imsiMd5.data, this.imei, this.apn.toByteArray(), this.androidId.toByteArray() ) } @Serializable class Version( val incremental: String, val release: String, val codename: String, val sdk: Int = 29 ) { companion object { fun DeviceInfo.Version.trans(): Version { return Version(incremental.decodeToString(), release.decodeToString(), codename.decodeToString(), sdk) } fun Version.trans(): DeviceInfo.Version { return DeviceInfo.Version(incremental.toByteArray(), release.toByteArray(), codename.toByteArray(), sdk) } } } fun DeviceInfo.toCurrentInfo(): V3 = V3( display.decodeToString(), product.decodeToString(), device.decodeToString(), board.decodeToString(), brand.decodeToString(), model.decodeToString(), bootloader.decodeToString(), fingerprint.decodeToString(), bootId.decodeToString(), procVersion.decodeToString(), HexString(baseBand), version.trans(), simInfo.decodeToString(), osType.decodeToString(), macAddress.decodeToString(), wifiBSSID.decodeToString(), wifiSSID.decodeToString(), HexString(imsiMd5), imei, apn.decodeToString(), androidId.decodeToString(), ) internal val format = Json { ignoreUnknownKeys = true isLenient = true } @Suppress("unused") @Deprecated("ABI compatibility for device generator", level = DeprecationLevel.HIDDEN) @JvmName("deserialize") fun deserializeDeprecated( string: String, format: Json = this.format, ): DeviceInfo = deserialize(string, format) @Throws(IllegalArgumentException::class, NumberFormatException::class) // in case malformed fun deserialize( string: String, format: Json = this.format, onUpgradeVersion: (DeviceInfo) -> Unit = { } ): DeviceInfo { val element = format.parseToJsonElement(string) val version = element.jsonObject["deviceInfoVersion"]?.jsonPrimitive?.content?.toInt() ?: 1 val deviceInfo = when (version) { /** * @since 2.0 */ 1 -> format.decodeFromJsonElement(V1.serializer(), element) /** * @since 2.9 */ 2 -> format.decodeFromJsonElement(Wrapper.serializer(V2.serializer()), element).data /** * @since 2.15 */ 3 -> format.decodeFromJsonElement(Wrapper.serializer(V3.serializer()), element).data else -> throw IllegalArgumentException("Unsupported deviceInfoVersion: $version") }.toDeviceInfo() if (version < 3) onUpgradeVersion(deviceInfo) return deviceInfo } fun serialize(info: DeviceInfo, format: Json = this.format): String { return format.encodeToString( Wrapper.serializer(V3.serializer()), Wrapper(3, info.toCurrentInfo()) ) } fun toJsonElement(info: DeviceInfo, format: Json = this.format): JsonElement { return format.encodeToJsonElement( Wrapper.serializer(V3.serializer()), Wrapper(3, info.toCurrentInfo()) ) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/DeviceInfoV1LegacySerializer.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable @Serializable internal class DeviceInfoV1Legacy( val product: ByteArray, val display: ByteArray, val device: ByteArray, val board: ByteArray, val brand: ByteArray, val model: ByteArray, val bootloader: ByteArray, val fingerprint: ByteArray, val bootId: ByteArray, val procVersion: ByteArray, val baseBand: ByteArray, val version: DeviceInfoV1LegacyVersion, val simInfo: ByteArray, val osType: ByteArray, val macAddress: ByteArray, val wifiBSSID: ByteArray, val wifiSSID: ByteArray, val imsiMd5: ByteArray, val imei: String, val apn: ByteArray, val androidId: ByteArray? = null ) @Serializable internal class DeviceInfoV1LegacyVersion( val incremental: ByteArray = "5891938".toByteArray(), val release: ByteArray = "10".toByteArray(), val codename: ByteArray = "REL".toByteArray(), val sdk: Int = 29 ) internal object DeviceInfoV1LegacySerializer : KSerializer<DeviceInfo> by DeviceInfoV1Legacy.serializer().map( DeviceInfoV1Legacy.serializer().descriptor.copy("DeviceInfo"), deserialize = { @Suppress("DEPRECATION") DeviceInfo( display, product, device, board, brand, model, bootloader, fingerprint, bootId, procVersion, baseBand, DeviceInfo.Version(version.incremental, version.release, version.codename, version.sdk), simInfo, osType, macAddress, wifiBSSID, wifiSSID, imsiMd5, imei, apn, androidId = display ) }, serialize = { DeviceInfoV1Legacy( display, product, device, board, brand, model, bootloader, fingerprint, bootId, procVersion, baseBand, DeviceInfoV1LegacyVersion(version.incremental, version.release, version.codename, version.sdk), simInfo, osType, macAddress, wifiBSSID, wifiSSID, imsiMd5, imei, apn ) } ) ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/ExternalResource.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE", "unused") package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Contact.Companion.sendImage import net.mamoe.mirai.contact.Contact.Companion.uploadImage import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.Group import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.sendTo import net.mamoe.mirai.message.data.toVoice import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage import java.io.File import java.io.IOException import java.io.InputStream import java.io.RandomAccessFile import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** * 一个*不可变的*外部资源. 仅包含资源内容, 大小, 文件类型, 校验值而不包含文件名, 文件位置等. 外部资源有可能是一个文件, 也有可能只存在于内存, 或者以任意其他方式实现. * * [ExternalResource] 在创建之后就应该保持其属性的不变, 即任何时候获取其属性都应该得到相同结果, 任何时候打开流都得到的一样的数据. * * # 创建 * - [File.toExternalResource] * - [RandomAccessFile.toExternalResource] * - [ByteArray.toExternalResource] * - [InputStream.toExternalResource] * * ## 在 Kotlin 获得和使用 [ExternalResource] 实例 * * ``` * file.toExternalResource().use { resource -> // 安全地使用资源 * contact.uploadImage(resource) // 用来上传图片 * contact.files.uploadNewFile("/foo/test.txt", resource) // 或者用来上传文件 * } * ``` * * 注意, 若使用 [InputStream], 必须手动关闭 [InputStream]. 一种使用情况示例: * * ``` * inputStream.use { input -> // 安全地使用 InputStream * input.toExternalResource().use { resource -> // 安全地使用资源 * contact.uploadImage(resource) // 用来上传图片 * contact.files.uploadNewFile("/foo/test.txt", resource) // 或者用来上传文件 * } * } * ``` * * ## 在 Java 获得和使用 [ExternalResource] 实例 * * ``` * try (ExternalResource resource = ExternalResource.create(file)) { // 使用文件 file * contact.uploadImage(resource); // 用来上传图片 * contact.files.uploadNewFile("/foo/test.txt", resource); // 或者用来上传文件 * } * ``` * * 注意, 若使用 [InputStream], 必须手动关闭 [InputStream]. 一种使用情况示例: * * ```java * try (InputStream stream = ...) { // 安全地使用 InputStream * try (ExternalResource resource = ExternalResource.create(stream)) { // 安全地使用资源 * contact.uploadImage(resource); // 用来上传图片 * contact.files.uploadNewFile("/foo/test.txt", resource); // 或者用来上传文件 * } * } * ``` * * # 释放 * * 当 [ExternalResource] 创建时就可能会打开一个文件 (如使用 [File.toExternalResource]). * 类似于 [InputStream], [ExternalResource] 需要被 [关闭][close]. * * ## 未释放资源的补救策略 * * 自 2.7 起, 每个 mirai 内置的 [ExternalResource] 实现都有引用跟踪, 当 [ExternalResource] 被 GC 后会执行被动释放. * 这依赖于 JVM 垃圾收集策略, 因此不可靠, 资源仍然需要手动 close. * * ## 使用单次自动释放 * * 若创建的资源仅需要*很快地*使用一次, 可使用 [toAutoCloseable] 获得在使用一次后就会自动关闭的资源. * * 示例: * ```java * contact.uploadImage(ExternalResource.create(file).toAutoCloseable()); // 创建并立即使用单次自动释放的资源 * ``` * * **注意**: 如果仅使用 [toAutoCloseable] 而不通过 [Contact.uploadImage] 等 mirai 内置方法使用资源, 资源仍然会处于打开状态且不会被自动关闭. * 最终资源会由上述*未释放资源的补救策略*关闭, 但这依赖于 JVM 垃圾收集策略而不可靠. * 因此建议在创建单次自动释放的资源后就尽快使用它, 否则仍然需要考虑在正确的时间及时关闭资源. * * # 实现 [ExternalResource] * * 可以自行实现 [ExternalResource]. 但通常上述创建方法已足够使用. * * 建议继承 [AbstractExternalResource], 这将支持上文提到的资源自动释放功能. * * 实现时需保持 [ExternalResource] 在构造后就不可变, 并且所有属性都总是返回一个固定值. * * @see ExternalResource.uploadAsImage 将资源作为图片上传, 得到 [Image] * @see ExternalResource.sendAsImageTo 将资源作为图片发送 * @see Contact.uploadImage 上传一个资源作为图片, 得到 [Image] * @see Contact.sendImage 发送一个资源作为图片 * * @see FileCacheStrategy */ public interface ExternalResource : java.io.Closeable { /** * 是否在 _使用一次_ 后自动 [close]. * * 该属性仅供调用方参考. 如 [Contact.uploadImage] 会在方法结束时关闭 [isAutoClose] 为 `true` 的 [ExternalResource], 无论上传图片是否成功. * * 所有 mirai 内置的上传图片, 上传语音等方法都支持该行为. * * @since 2.8 */ public val isAutoClose: Boolean get() = false /** * 文件内容 MD5. 16 bytes */ public val md5: ByteArray /** * 文件内容 SHA1. 16 bytes * @since 2.5 */ public val sha1: ByteArray get() = throw UnsupportedOperationException("ExternalResource.sha1 is not implemented by ${this::class.simpleName}") // 如果你要实现 [ExternalResource], 你也应该实现 [sha1]. // 这里默认抛出 [UnsupportedOperationException] 是为了 (姑且) 兼容 2.5 以前的版本的实现. /** * 文件格式,如 "png", "amr". 当无法自动识别格式时为 [DEFAULT_FORMAT_NAME]. * * 默认会从文件头识别, 支持的文件类型: * * 图片类型: png, jpg, gif, tif, bmp * * 语音类型: amr, silk * * 视频类类型: mp4, mkv * * @see net.mamoe.mirai.utils.getFileType * @see net.mamoe.mirai.utils.FILE_TYPES * @see DEFAULT_FORMAT_NAME */ public val formatName: String /** * 文件大小 bytes */ public val size: Long /** * 当 [close] 时会 [CompletableDeferred.complete] 的 [Deferred]. */ public val closed: Deferred<Unit> /** * 打开 [InputStream]. 在返回的 [InputStream] 被 [关闭][InputStream.close] 前无法再次打开流. * * 关闭此流不会关闭 [ExternalResource]. * @throws IllegalStateException 当上一个流未关闭又尝试打开新的流时抛出 */ public fun inputStream(): InputStream /** * 打开 [Input]. 在返回的 [Input] 被 [关闭][Input.close] 前无法再次打开流. * 注意: 此 API 不稳定, 请使用 [inputStream] 代替. * * 关闭此流不会关闭 [ExternalResource]. * @throws IllegalStateException 当上一个流未关闭又尝试打开新的流时抛出 * * @since 2.13 */ @MiraiInternalApi public fun input(): Input @MiraiInternalApi public fun calculateResourceId(): String { return generateImageId(md5, formatName.ifEmpty { DEFAULT_FORMAT_NAME }) } /** * 该 [ExternalResource] 的数据来源, 可能有以下的返回 * * - [File] 本地文件 * - [java.nio.file.Path] 某个具体文件路径 * - [java.nio.ByteBuffer] RAM * - [java.net.URI] uri * - [ByteArray] RAM * - Or more... * * implementation note: * * - 对于无法二次读取的数据来源 (如 [InputStream]), 返回 `null` * - 对于一个来自网络的资源, 请返回 [java.net.URI] (not URL, 或者其他库的 URI/URL 类型) * - 不要返回 [String], 没有约定 [String] 代表什么 * - 数据源外漏会严重影响 [inputStream] 等的执行的可以返回 `null` (如 [RandomAccessFile]) * * @since 2.8.0 */ public val origin: Any? get() = null /** * 创建一个在 _使用一次_ 后就会自动 [close] 的 [ExternalResource]. * * @since 2.8.0 */ public fun toAutoCloseable(): ExternalResource { return if (isAutoClose) this else { val delegate = this object : ExternalResource by delegate { override val isAutoClose: Boolean get() = true override fun toString(): String = "ExternalResourceWithAutoClose(delegate=$delegate)" override fun toAutoCloseable(): ExternalResource { return this } } } } public companion object { /** * 在无法识别文件格式时使用的默认格式名. "mirai". * * @see ExternalResource.formatName */ public const val DEFAULT_FORMAT_NAME: String = "mirai" /////////////////////////////////////////////////////////////////////////// // region toExternalResource /////////////////////////////////////////////////////////////////////////// /** * **打开文件**并创建 [ExternalResource]. * 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭. * * 将以只读模式打开这个文件 (因此文件会处于被占用状态), 直到 [ExternalResource.close]. * * @param formatName 查看 [ExternalResource.formatName] */ @JvmStatic @JvmOverloads @JvmName("create") public fun File.toExternalResource(formatName: String? = null): ExternalResource = // although RandomAccessFile constructor throws IOException, performance influence is minor so not propagating IOException RandomAccessFile(this, "r").toExternalResource(formatName).also { it.cast<ExternalResourceImplByFile>().origin = this@toExternalResource } /** * 创建 [ExternalResource]. * 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭, 届时将会关闭 [RandomAccessFile]. * * **注意**:若关闭 [RandomAccessFile], 也会间接关闭 [ExternalResource]. * * @see closeOriginalFileOnClose 若为 `true`, 在 [ExternalResource.close] 时将会同步关闭 [RandomAccessFile]. 否则不会. * * @param formatName 查看 [ExternalResource.formatName] */ @JvmStatic @JvmOverloads @JvmName("create") public fun RandomAccessFile.toExternalResource( formatName: String? = null, closeOriginalFileOnClose: Boolean = true, ): ExternalResource = ExternalResourceImplByFile(this, formatName, closeOriginalFileOnClose) /** * 创建 [ExternalResource]. 注意, 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭. * * @param formatName 查看 [ExternalResource.formatName] */ @JvmStatic @JvmOverloads @JvmName("create") public fun ByteArray.toExternalResource(formatName: String? = null): ExternalResource = ExternalResourceImplByByteArray(this, formatName) /** * 立即使用 [FileCacheStrategy] 缓存 [InputStream] 并创建 [ExternalResource]. * 返回的 [ExternalResource] 需要在使用完毕后调用 [ExternalResource.close] 关闭. * * **注意**:本函数不会关闭流. * * ### 在 Java 获得和使用 [ExternalResource] 实例 * * ``` * try(ExternalResource resource = ExternalResource.create(file)) { // 使用文件 file * contact.uploadImage(resource); // 用来上传图片 * contact.files.uploadNewFile("/foo/test.txt", file); // 或者用来上传文件 * } * ``` * * 注意, 若使用 [InputStream], 必须手动关闭 [InputStream]. 一种使用情况示例: * * ``` * try(InputStream stream = ...) { * try(ExternalResource resource = ExternalResource.create(stream)) { * contact.uploadImage(resource); // 用来上传图片 * contact.files.uploadNewFile("/foo/test.txt", file); // 或者用来上传文件 * } * } * ``` * * * @param formatName 查看 [ExternalResource.formatName] * @see ExternalResource */ @JvmStatic @JvmOverloads @JvmName("create") @Throws(IOException::class) // not in BIO context so propagate IOException public fun InputStream.toExternalResource(formatName: String? = null): ExternalResource = Mirai.FileCacheStrategy.newCache(this, formatName) // endregion /* note: 于 2.8.0-M1 添加 (#1392) 于 2.8.0-RC 移动至 `toExternalResource`(#1588) */ @JvmName("createAutoCloseable") @JvmStatic @Deprecated( level = DeprecationLevel.HIDDEN, message = "Moved to `toExternalResource()`", replaceWith = ReplaceWith("resource.toAutoCloseable()"), ) @DeprecatedSinceMirai(errorSince = "2.8", hiddenSince = "2.10") public fun createAutoCloseable(resource: ExternalResource): ExternalResource { return resource.toAutoCloseable() } /////////////////////////////////////////////////////////////////////////// // region sendAsImageTo /////////////////////////////////////////////////////////////////////////// /** * 将图片作为单独的消息发送给指定联系人. * * **注意**:本函数不会关闭 [ExternalResource]. * * @see Contact.uploadImage 上传图片 * @see Contact.sendMessage 最终调用, 发送消息. * * @throws OverFileSizeMaxException */ @JvmBlockingBridge @JvmStatic @JvmName("sendAsImage") public suspend fun <C : Contact> ExternalResource.sendAsImageTo(contact: C): MessageReceipt<C> = contact.uploadImage(this).sendTo(contact) /** * 读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人. * * 注意:本函数不会关闭流. * * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException */ @JvmStatic @JvmBlockingBridge @JvmName("sendAsImage") @JvmOverloads public suspend fun <C : Contact> InputStream.sendAsImageTo( contact: C, formatName: String? = null, ): MessageReceipt<C> = runBIO { // toExternalResource throws IOException however we're in BIO context so not propagating IOException to sendAsImageTo toExternalResource(formatName) }.withUse { sendAsImageTo(contact) } /** * 将文件作为图片发送到指定联系人. * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException */ @JvmStatic @JvmBlockingBridge @JvmName("sendAsImage") @JvmOverloads public suspend fun <C : Contact> File.sendAsImageTo(contact: C, formatName: String? = null): MessageReceipt<C> { require(this.exists() && this.canRead()) return toExternalResource(formatName).withUse { sendAsImageTo(contact) } } // endregion /////////////////////////////////////////////////////////////////////////// // region uploadAsImage /////////////////////////////////////////////////////////////////////////// /** * 上传图片并构造 [Image]. 这个函数可能需消耗一段时间. * * **注意**:本函数不会关闭 [ExternalResource]. * * @param contact 图片上传对象. 由于好友图片与群图片不通用, 上传时必须提供目标联系人. * * @see Contact.uploadImage 最终调用, 上传图片. */ @JvmStatic @JvmBlockingBridge public suspend fun ExternalResource.uploadAsImage(contact: Contact): Image = contact.uploadImage(this) /** * 读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image]. * * 注意:本函数不会关闭流. * * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException */ @JvmStatic @JvmBlockingBridge @JvmOverloads public suspend fun InputStream.uploadAsImage(contact: Contact, formatName: String? = null): Image = // toExternalResource throws IOException however we're in BIO context so not propagating IOException to sendAsImageTo runBIO { toExternalResource(formatName) }.withUse { uploadAsImage(contact) } // endregion /////////////////////////////////////////////////////////////////////////// // region uploadAsFile /////////////////////////////////////////////////////////////////////////// /** * 将文件作为图片上传后构造 [Image]. * * @param formatName 查看 [ExternalResource.formatName] * @throws OverFileSizeMaxException */ @JvmStatic @JvmBlockingBridge @JvmOverloads public suspend fun File.uploadAsImage(contact: Contact, formatName: String? = null): Image = toExternalResource(formatName).withUse { uploadAsImage(contact) } /** * 上传文件并获取文件消息. * * 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型. * * 需要调用方手动[关闭资源][ExternalResource.close]. * * ## 已弃用 * 查看 [RemoteFile.upload] 获取更多信息. * * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt' * @since 2.5 * @see RemoteFile.path * @see RemoteFile.upload */ @Suppress("DEPRECATION_ERROR") @JvmStatic @JvmBlockingBridge @JvmOverloads @Deprecated( "Use sendTo instead.", ReplaceWith( "this.sendTo(contact, path, callback)", "net.mamoe.mirai.utils.ExternalResource.Companion.sendTo" ), level = DeprecationLevel.HIDDEN ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public suspend fun File.uploadTo( contact: FileSupported, path: String, callback: RemoteFile.ProgressionCallback? = null, ): FileMessage = toExternalResource().use { contact.filesRoot.resolve(path).upload(it, callback) } /** * 上传文件并获取文件消息. * * 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型. * * 需要调用方手动[关闭资源][ExternalResource.close]. * * ## 已弃用 * 查看 [RemoteFile.upload] 获取更多信息. * * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt' * @since 2.5 * @see RemoteFile.path * @see RemoteFile.upload */ @Suppress("DEPRECATION_ERROR") @JvmStatic @JvmBlockingBridge @JvmName("uploadAsFile") @JvmOverloads @Deprecated( "Use sendAsFileTo instead.", ReplaceWith( "this.sendAsFileTo(contact, path, callback)", "net.mamoe.mirai.utils.ExternalResource.Companion.sendAsFileTo" ), level = DeprecationLevel.HIDDEN ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public suspend fun ExternalResource.uploadAsFile( contact: FileSupported, path: String, callback: RemoteFile.ProgressionCallback? = null, ): FileMessage { return contact.filesRoot.resolve(path).upload(this, callback) } // endregion /////////////////////////////////////////////////////////////////////////// // region sendAsFileTo /////////////////////////////////////////////////////////////////////////// /** * 上传文件并发送文件消息. * * 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型. * * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt' * @since 2.5 * @see RemoteFile.path * @see RemoteFile.uploadAndSend */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Deprecated. Please use AbsoluteFolder.uploadNewFile", ReplaceWith("contact.files.uploadNewFile(path, this, callback)"), level = DeprecationLevel.ERROR, ) // deprecated since 2.8.0-RC @JvmStatic @JvmBlockingBridge @JvmOverloads @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.14") public suspend fun <C : FileSupported> File.sendTo( contact: C, path: String, callback: RemoteFile.ProgressionCallback? = null, ): MessageReceipt<C> = toExternalResource().use { contact.filesRoot.resolve(path).upload(it, callback).sendTo(contact) } /** * 上传文件并发送件消息. 如果要上传的文件格式是图片或者语音, 也会将它们作为文件上传而不会调整消息类型. * * 需要调用方手动[关闭资源][ExternalResource.close]. * * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt' * @since 2.5 * @see RemoteFile.path * @see RemoteFile.uploadAndSend */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Deprecated. Please use AbsoluteFolder.uploadNewFile", ReplaceWith("contact.files.uploadNewFile(path, this, callback)"), level = DeprecationLevel.ERROR ) // deprecated since 2.8.0-RC @JvmStatic @JvmBlockingBridge @JvmName("sendAsFile") @JvmOverloads @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.14") public suspend fun <C : FileSupported> ExternalResource.sendAsFileTo( contact: C, path: String, callback: RemoteFile.ProgressionCallback? = null, ): MessageReceipt<C> { return contact.filesRoot.resolve(path).upload(this, callback).sendTo(contact) } // endregion /////////////////////////////////////////////////////////////////////////// // region uploadAsVoice /////////////////////////////////////////////////////////////////////////// @Suppress("DEPRECATION_ERROR") @JvmBlockingBridge @JvmStatic @Deprecated( "Use `contact.uploadAudio(resource)` instead", level = DeprecationLevel.HIDDEN ) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public suspend fun ExternalResource.uploadAsVoice(contact: Contact): net.mamoe.mirai.message.data.Voice { @Suppress("DEPRECATION_ERROR") if (contact is Group) return contact.uploadAudio(this).toVoice() else throw UnsupportedOperationException("Contact `$contact` is not supported uploading voice") } // endregion } } /** * 执行 [action], 如果 [ExternalResource.isAutoClose], 在执行完成后调用 [ExternalResource.close]. * * @since 2.8 */ @MiraiExperimentalApi // Continuing mark it as experimental until Kotlin's contextual receivers design is published. // We might be able to make `action` a type `context(ExternalResource) () -> R`. public inline fun <T : ExternalResource, R> T.withAutoClose(action: () -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } trySafely( block = { return action() }, finally = { if (isAutoClose) close() } ) } /** * 执行 [action], 如果 [ExternalResource.isAutoClose], 在执行完成后调用 [ExternalResource.close]. * * @since 2.8 */ public inline fun <T : ExternalResource, R> T.runAutoClose(action: T.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } @OptIn(MiraiExperimentalApi::class) return withAutoClose { action() } } /** * 执行 [action], 如果 [ExternalResource.isAutoClose], 在执行完成后调用 [ExternalResource.close]. * * @since 2.8 */ public inline fun <T : ExternalResource, R> T.useAutoClose(action: (resource: T) -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return runAutoClose(action) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/FileCacheStrategy.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "MemberVisibilityCanBePrivate") package net.mamoe.mirai.utils import io.ktor.utils.io.errors.* import kotlinx.coroutines.Dispatchers import net.mamoe.mirai.Bot import net.mamoe.mirai.IMirai import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsImage import net.mamoe.mirai.utils.FileCacheStrategy.MemoryCache import net.mamoe.mirai.utils.FileCacheStrategy.TempCache import java.io.File import java.io.InputStream /** * 资源缓存策略. * * 由于上传资源时服务器要求提前给出 MD5 和文件大小等数据, 一些资源如 [InputStream] 需要首先缓存才能使用. * * 资源的缓存都是将 [InputStream] 缓存未 [ExternalResource]. 根据 [FileCacheStrategy] 实现不同, 可以以临时文件存储, 也可以在数据库或是内存按需存储. * Mirai 内置的实现有 [内存存储][MemoryCache] 和 [临时文件存储][TempCache]. * 操作 [ExternalResource.toExternalResource] 时将会使用 [IMirai.FileCacheStrategy]. 可以覆盖, 示例: * ``` * // Kotlin * Mirai.FileCacheStrategy = FileCacheStrategy.TempCache() // 使用系统默认缓存路径, 也是默认的行为 * Mirai.FileCacheStrategy = FileCacheStrategy.TempCache(File("C:/cache")) // 使用自定义缓存路径 * * // Java * Mirai.getInstance().setFileCacheStrategy(new FileCacheStrategy.TempCache()); // 使用系统默认缓存路径, 也是默认的行为 * Mirai.getInstance().setFileCacheStrategy(new FileCacheStrategy.TempCache(new File("C:/cache"))); // 使用自定义的缓存路径 * ``` * * 此接口的实现和使用都是稳定的. 自行实现的 [FileCacheStrategy] 也可以被 Mirai 使用. * * 注意, 此接口目前仅缓存 [InputStream] 等一次性数据. 好友列表等数据由每个 [Bot] 的 [BotConfiguration.cacheDir] 缓存. * * ### 使用 [FileCacheStrategy] 的操作 * - [ExternalResource.toExternalResource] * - [ExternalResource.uploadAsImage] * - [ExternalResource.sendAsImageTo] * * @see ExternalResource */ public interface FileCacheStrategy { /** * 立即读取 [input] 所有内容并缓存为 [ExternalResource]. * * 注意: * - 此函数不会关闭输入 * - 此函数可能会阻塞线程读取 [input] 内容, 若在 Kotlin 协程使用请确保在允许阻塞的环境 ([Dispatchers.IO]). * * @param formatName 文件类型. 此参数通常只会影响官方客户端接收到的文件的文件后缀. 若为 `null` 则会自动根据文件头识别. 识别失败时将使用 "mirai" */ @Throws(IOException::class) public fun newCache(input: InputStream, formatName: String? = null): ExternalResource /** * 立即读取 [input] 所有内容并缓存为 [ExternalResource]. 自动根据文件头识别文件类型. 识别失败时将使用 "mirai". * * 注意: * - 此函数不会关闭输入 * - 此函数可能会阻塞线程读取 [input] 内容, 若在 Kotlin 协程使用请确保在允许阻塞的环境 ([Dispatchers.IO]). */ @Throws(IOException::class) public fun newCache(input: InputStream): ExternalResource = newCache(input, null) /** * 使用内存直接存储所有图片文件. 由 JVM 执行 GC. */ public object MemoryCache : FileCacheStrategy { @Throws(IOException::class) override fun newCache(input: InputStream, formatName: String?): ExternalResource { return input.readBytes().toExternalResource(formatName) } } /** * 使用系统临时文件夹缓存图片文件. 在图片使用完毕后或 JVM 正常结束时删除临时文件. */ public class TempCache @JvmOverloads public constructor( /** * 缓存图片存放位置. 为 `null` 时使用主机系统的临时文件夹: `File.createTempFile("tmp", null, directory)` */ public val directory: File? = null, ) : FileCacheStrategy { private fun createTempFile(): File { return File.createTempFile("tmp", null, directory) } @Throws(IOException::class) override fun newCache(input: InputStream, formatName: String?): ExternalResource { val file = createTempFile() return file.apply { deleteOnExit() outputStream().use { out -> input.copyTo(out) } }.toExternalResource(formatName).apply { closed.invokeOnCompletion { kotlin.runCatching { file.delete() } } } } } public companion object { /** * 当前平台下默认的缓存策略. 注意, 这可能不是 Mirai 全局默认使用的, Mirai 从 [IMirai.FileCacheStrategy] 获取. * * @see IMirai.FileCacheStrategy */ @MiraiExperimentalApi @JvmStatic public val PlatformDefault: FileCacheStrategy = TempCache(null) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/FileLogger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import java.io.File import java.text.SimpleDateFormat import java.util.* internal fun getCurrentDay() = Calendar.getInstance()[Calendar.DAY_OF_MONTH] internal fun getCurrentDate() = SimpleDateFormat("yyyy-MM-dd").format(Date()) private val STUB: (priority: SimpleLogger.LogPriority, message: String?, e: Throwable?) -> Unit = { _: SimpleLogger.LogPriority, _: String?, _: Throwable? -> error("stub") } /** * 将日志写入('append')到特定文件夹中的文件. 每日日志独立保存. * * @see PlatformLogger 查看格式信息 */ public class DirectoryLogger @JvmOverloads constructor( identity: String, private val directory: File = File(identity), /** * 保留日志文件多长时间. 毫秒数 */ private val retain: Long = 1.weeksToMillis ) : SimpleLogger("", STUB) { init { directory.mkdirs() } private fun checkOutdated() { val current = currentTimeMillis() directory.walk().filter(File::isFile).filter { current - it.lastModified() > retain }.forEach { it.delete() } } private var day = getCurrentDay() private var delegate: SingleFileLogger = SingleFileLogger(identity, File(directory, "${getCurrentDate()}.log")) get() { val currentDay = getCurrentDay() if (day != currentDay) { day = currentDay checkOutdated() field = SingleFileLogger(identity!!, File(directory, "${getCurrentDate()}.log")) } return field } override val logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit = { priority: LogPriority, message: String?, e: Throwable? -> delegate.call(priority, message, e) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/LoggerAdapters.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.utils import net.mamoe.mirai.internal.utils.JdkLoggerAdapter import net.mamoe.mirai.internal.utils.Log4jLoggerAdapter import net.mamoe.mirai.internal.utils.MARKER_MIRAI import net.mamoe.mirai.internal.utils.Slf4jLoggerAdapter import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Marker import org.apache.logging.log4j.MarkerManager /** * [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 到 [MiraiLogger] 的转换器. */ public object LoggerAdapters { /** * 使用 [LOG4J2][org.apache.logging.log4j.Logger] 接管全局 Mirai 日志系统. 请在调用 Mirai API 任何其他 API 前调用该方法. * * 注意, 若已经通过 service 方式提供 [MiraiLogger.Factory] 来接管日志系统, 则本方法无效. * * @since 2.7 */ @JvmStatic public fun useLog4j2() { MiraiLoggerFactoryImplementationBridge.wrapCurrent { object : MiraiLogger.Factory { override fun create(requester: Class<*>, identity: String?): MiraiLogger { val logger = LogManager.getLogger(requester) return Log4jLoggerAdapter( logger, MarkerManager.getMarker(identity ?: logger.name).addParents(MARKER_MIRAI) ) } } } } /** * 将 [java.util.logging.Logger] 转换作为 [MiraiLogger] 使用. */ @JvmStatic public fun java.util.logging.Logger.asMiraiLogger(): MiraiLogger { return JdkLoggerAdapter(this) } /** * 将 [org.apache.logging.log4j.Logger] 转换作为 [MiraiLogger] 使用. */ @JvmStatic public fun org.apache.logging.log4j.Logger.asMiraiLogger(): MiraiLogger { return Log4jLoggerAdapter(this, null) } /** * 将 [org.slf4j.Logger] 转换作为 [MiraiLogger] 使用. */ @JvmStatic public fun org.slf4j.Logger.asMiraiLogger(): MiraiLogger { return Slf4jLoggerAdapter(this, null) } /** * 将 [org.apache.logging.log4j.Logger] 转换作为 [MiraiLogger] 使用. * * @since 2.7 */ @JvmStatic public fun org.apache.logging.log4j.Logger.asMiraiLogger(marker: Marker): MiraiLogger { return Log4jLoggerAdapter(this, marker) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/LoginSolver.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmName("LoginSolver_common") package net.mamoe.mirai.utils import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.auth.BotAuthSession import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.auth.QRCodeLoginListener import net.mamoe.mirai.network.LoginFailedException import net.mamoe.mirai.network.RetryLaterException import net.mamoe.mirai.network.UnsupportedQRCodeCaptchaException import net.mamoe.mirai.network.UnsupportedSmsLoginException import net.mamoe.mirai.utils.LoginSolver.Companion.Default import kotlin.jvm.JvmField import kotlin.jvm.JvmName /** * 验证码, 设备锁解决器 * * @see Default * @see BotConfiguration.loginSolver */ public abstract class LoginSolver { /** * 处理图片验证码, 返回图片验证码内容. * * 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录. * * ## 异常类型 * * 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]). * 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录. * * 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误. * * @throws LoginFailedException */ public abstract suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? /** * 为 `true` 表示支持滑动验证码, 遇到滑动验证码时 mirai 会请求 [onSolveSliderCaptcha]. * 否则会跳过滑动验证码并告诉服务器此客户端不支持, 有可能导致登录失败 */ public open val isSliderCaptchaSupported: Boolean get() = PlatformLoginSolverImplementations.isSliderCaptchaSupported /** * 当使用二维码登录时会通过此方法创建二维码登录监听器 * * @see QRCodeLoginListener * @see BotAuthorization * @see BotAuthSession.authByQRCode * * @since 2.15 */ public open fun createQRCodeLoginListener(bot: Bot): QRCodeLoginListener { throw UnsupportedQRCodeCaptchaException("This login session requires QRCode login, but current LoginSolver($this) does not support it. Please override `LoginSolver.createQRCodeLoginListener`.") } /** * 处理滑动验证码. * * 返回 `null` 以表示无法处理验证码, 将会刷新验证码或重试登录. * * ## 异常类型 * * 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]). * 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录. * * 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误. * * @throws LoginFailedException * @return 验证码解决成功后获得的 ticket. */ public abstract suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? /** * 处理设备验证. 通常需要覆盖此函数. 此函数为 `open` 是为了兼容旧代码 (2.13 以前). * * 设备验证的类型可在 [DeviceVerificationRequests] 查看. * * ## 异常类型 * * 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]). * 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录. * * 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误. * * @return 验证结果, 可通过解决 [DeviceVerificationRequests] 获得. * @throws LoginFailedException * @since 2.13 */ public open suspend fun onSolveDeviceVerification( bot: Bot, requests: DeviceVerificationRequests, ): DeviceVerificationResult { requests.fallback?.let { fallback -> @Suppress("DEPRECATION") (onSolveUnsafeDeviceLoginVerify(bot, fallback.url)) return fallback.solved() } throw UnsupportedSmsLoginException("This login session requires SMS verification, but current LoginSolver($this) does not support it. Please override `LoginSolver.onSolveDeviceVerification`.") } /** * 处理不安全设备验证. 此函数已弃用, 请实现 [onSolveDeviceVerification]. * * 返回值保留给将来使用. 目前在处理完成后返回任意内容 (包含 `null`) 均视为处理成功. * * ## 异常类型 * * 抛出一个 [LoginFailedException] 以正常地终止登录, 并可建议系统进行重连或停止 bot (通过 [LoginFailedException.killBot]). * 例如抛出 [RetryLaterException] 可让 bot 重新进行一次登录. * * 抛出任意其他 [Throwable] 将视为验证码解决器的自身错误. * * @return 任意内容. 返回值保留以供未来更新. * @throws LoginFailedException */ @Deprecated( "Please use onSolveDeviceVerification instead", level = DeprecationLevel.WARNING, ) // softly @DeprecatedSinceMirai(warningSince = "2.13") // for hidden public open suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { // This function was abstract, open since 2.13.0 throw UnsupportedSmsLoginException("This login session requires device verification, but current LoginSolver($this) does not support it. Please override `LoginSolver.onSolveDeviceVerification`.") } public companion object { /** * 当前平台默认的 [LoginSolver]。 * * 在 Android 环境时, 此函数返回 `null`. * 在其他 JVM 环境时, 此函数返回一个默认实现, 它通常会是 [StandardCharImageLoginSolver][net.mamoe.mirai.utils.StandardCharImageLoginSolver], 但调用方不应该依赖该属性. */ @JvmField public val Default: LoginSolver? = PlatformLoginSolverImplementations.default @Suppress("unused") @Deprecated("Binary compatibility", level = DeprecationLevel.HIDDEN) public fun getDefault(): LoginSolver = Default ?: error("LoginSolver is not provided by default on your platform. Please specify by BotConfiguration.loginSolver") } } internal expect object PlatformLoginSolverImplementations { val isSliderCaptchaSupported: Boolean val default: LoginSolver? } /** * 属性 [sms] 为短信验证码验证方式, [fallback] 为其他验证方式. * 两个属性至少有一个不为 `null`, 在不为 `null` 时表示支持该验证方式. 可任意选用偏好的验证方式. * * 在使用时应该考虑未来有更新的情况. 未来服务器可能会增加一种新验证方式, 也有可能强制使用该验证方式, * 那么 [LoginSolver.onSolveDeviceVerification] 就应该抛出 [UnsupportedOperationException] 提示不支持该验证操作. * * @since 2.13 */ @NotStableForInheritance public interface DeviceVerificationRequests { /** * 短信验证码方式. 在不为 `null` 时表示支持该验证方式. */ public val sms: SmsRequest? /** * 其他验证方式. 在不为 `null` 时表示支持该验证方式. */ public val fallback: FallbackRequest? /** * 服务器要求使用短信验证码. 此时可能仍可以尝试 [fallback]. */ public val preferSms: Boolean /** * 服务器要求短信验证时提供的账号绑定的手机信息. 使用 [requestSms] 来请求发送验证码. * * @since 2.13 * @see LoginSolver.onSolveDeviceVerification */ @NotStableForInheritance public interface SmsRequest { /** * 手机号归属国家代码, 如中国为 86. * 在获取失败时会返回 `null`,但通常会获取到 */ public val countryCode: String? /** * 手机号码, 部分数字会被隐藏, 示例: `123*******1`. * 在获取失败时会返回 `null`, 但通常会获取到 */ public val phoneNumber: String? /** * 请求服务器发送短信到验证手机号 * * @throws RetryLaterException 当请求过于频繁, 服务器拒绝请求时抛出 */ @JvmBlockingBridge public suspend fun requestSms() /** * 通知此请求已被解决. 获取 [DeviceVerificationResult] 用于返回 [LoginSolver.onSolveDeviceVerification]. */ public fun solved(code: String): DeviceVerificationResult } /** * 其他验证方式. * * @since 2.13 * @see LoginSolver.onSolveDeviceVerification */ @NotStableForInheritance public interface FallbackRequest { /** * HTTP URL. 可能需要在 QQ 浏览器中打开并人工操作. */ public val url: String /** * 通知此请求已被解决. 获取 [DeviceVerificationResult] 用于返回 [LoginSolver.onSolveDeviceVerification]. */ public fun solved(): DeviceVerificationResult } } /** * 设备验证的验证结果. 请不要自行实现此接口, 而是通过解决 [DeviceVerificationRequests] 中的其中一种验证获得. * * @since 2.13 * @see LoginSolver.onSolveDeviceVerification */ @NotStableForInheritance public interface DeviceVerificationResult ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/MiraiLogger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("Utils") package net.mamoe.mirai.utils import me.him188.kotlin.dynamic.delegation.dynamicDelegation import java.util.* import kotlin.reflect.KClass /** * 给这个 logger 添加一个开关, 用于控制是否记录 log */ @JvmOverloads public fun MiraiLogger.withSwitch(default: Boolean = true): MiraiLoggerWithSwitch = MiraiLoggerWithSwitch(this, default) /** * 日志记录器. * * ## Mirai 日志系统 * * Mirai 内建简单的日志系统, 即 [MiraiLogger]. [MiraiLogger] 的实现有 [SimpleLogger], [PlatformLogger], [SilentLogger]. * * [MiraiLogger] 仅能处理简单的日志任务, 通常推荐使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 等日志库. * * ## 使用第三方日志库接管 Mirai 日志系统 * * 使用 [LoggerAdapters], 将第三方日志 `Logger` 转为 [MiraiLogger]. 然后通过 [MiraiLogger.Factory] 提供实现. * * ## 实现或使用 [MiraiLogger] * * 不建议实现或使用 [MiraiLogger]. 请优先考虑使用上述第三方框架. [MiraiLogger] 仅应用于兼容旧版本代码. * * @see SimpleLogger 简易 logger, 它将所有的日志记录操作都转移给 lambda `(String?, Throwable?) -> Unit` * @see PlatformLogger 各个平台下的默认日志记录实现. * @see SilentLogger 忽略任何日志记录操作的 logger 实例. * @see LoggerAdapters * * @see MiraiLoggerPlatformBase 平台通用基础实现. 若 Mirai 自带的日志系统无法满足需求, 请继承这个类并实现其抽象函数. */ public interface MiraiLogger { /** * 可以 service 实现的方式覆盖. * * @since 2.7 */ public interface Factory { /** * 创建 [MiraiLogger] 实例. * * @param requester 请求创建 [MiraiLogger] 的对象的 class * @param identity 对象标记 (备注) */ public fun create(requester: KClass<*>, identity: String? = null): MiraiLogger = this.create(requester.java, identity) /** * 创建 [MiraiLogger] 实例. * * @param requester 请求创建 [MiraiLogger] 的对象的 class * @param identity 对象标记 (备注) */ public fun create(requester: Class<*>, identity: String? = null): MiraiLogger /** * 创建 [MiraiLogger] 实例. * * @param requester 请求创建 [MiraiLogger] 的对象 */ public fun create(requester: KClass<*>): MiraiLogger = create(requester, null) /** * 创建 [MiraiLogger] 实例. * * @param requester 请求创建 [MiraiLogger] 的对象 */ public fun create(requester: Class<*>): MiraiLogger = create(requester, null) public companion object INSTANCE : Factory by dynamicDelegation({ MiraiLoggerFactoryImplementationBridge }) } public companion object { /** * 顶层日志, 仅供 Mirai 内部使用. */ @MiraiInternalApi @MiraiExperimentalApi @Deprecated("Deprecated.", level = DeprecationLevel.HIDDEN) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public val TopLevel: MiraiLogger by lazy { Factory.create(MiraiLogger::class, "Mirai") } /** * 已弃用, 请实现 service [net.mamoe.mirai.utils.MiraiLogger.Factory] 并以 [ServiceLoader] 支持的方式提供. */ @Deprecated( "Please set factory by providing an service of type net.mamoe.mirai.utils.MiraiLogger.Factory", level = DeprecationLevel.HIDDEN ) // deprecated since 2.7 @JvmStatic @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.13") public fun setDefaultLoggerCreator(@Suppress("UNUSED_PARAMETER") creator: (identity: String?) -> MiraiLogger) { // nop // DefaultFactoryOverrides.override { _, identity -> creator(identity) } } /** * 旧版本用于创建 [MiraiLogger]. 已弃用. 请使用 [MiraiLogger.Factory.INSTANCE.create]. */ @Deprecated( "Please use MiraiLogger.Factory.create", ReplaceWith( "MiraiLogger.Factory.create(YourClass::class, identity)", "net.mamoe.mirai.utils.MiraiLogger" ), level = DeprecationLevel.HIDDEN ) // deprecated since 2.7 @JvmStatic @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public fun create(identity: String?): MiraiLogger = Factory.create(MiraiLogger::class, identity) } /** * 日志的标记. 在 Mirai 中, identity 可为 * - "Bot" * - "BotNetworkHandler" * 等. * * 它只用于帮助调试或统计. 十分建议清晰定义 identity */ public val identity: String? /** * 获取 [MiraiLogger] 是否已开启 * * 除 [MiraiLoggerWithSwitch] 可控制开关外, 其他的所有 [MiraiLogger] 均一直开启. */ public val isEnabled: Boolean /** * 当 VERBOSE 级别的日志启用时返回 `true`. * * 若 [isEnabled] 为 `false`, 返回 `false`. * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值. * 其他情况下返回 [isEnabled] 的值. * * @since 2.7 */ public val isVerboseEnabled: Boolean get() = isEnabled /** * 当 DEBUG 级别的日志启用时返回 `true` * * 若 [isEnabled] 为 `false`, 返回 `false`. * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值. * 其他情况下返回 [isEnabled] 的值. * * @since 2.7 */ public val isDebugEnabled: Boolean get() = isEnabled /** * 当 INFO 级别的日志启用时返回 `true` * * 若 [isEnabled] 为 `false`, 返回 `false`. * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值. * 其他情况下返回 [isEnabled] 的值. * * @since 2.7 */ public val isInfoEnabled: Boolean get() = isEnabled /** * 当 WARNING 级别的日志启用时返回 `true` * * 若 [isEnabled] 为 `false`, 返回 `false`. * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值. * 其他情况下返回 [isEnabled] 的值. * * @since 2.7 */ public val isWarningEnabled: Boolean get() = isEnabled /** * 当 ERROR 级别的日志启用时返回 `true` * * 若 [isEnabled] 为 `false`, 返回 `false`. * 在使用 [SLF4J][org.slf4j.Logger], [LOG4J][org.apache.logging.log4j.Logger] 或 [JUL][java.util.logging.Logger] 时返回真实配置值. * 其他情况下返回 [isEnabled] 的值. * * @since 2.7 */ public val isErrorEnabled: Boolean get() = isEnabled @Suppress("UNUSED_PARAMETER") @Deprecated("follower 设计不佳, 请避免使用", level = DeprecationLevel.HIDDEN) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public var follower: MiraiLogger? get() = null set(value) {} /** * 记录一个 `verbose` 级别的日志. * 无关紧要的, 经常大量输出的日志应使用它. */ public fun verbose(message: String?) public fun verbose(e: Throwable?): Unit = verbose(null, e) public fun verbose(message: String?, e: Throwable?) /** * 记录一个 _调试_ 级别的日志. */ public fun debug(message: String?) public fun debug(e: Throwable?): Unit = debug(null, e) public fun debug(message: String?, e: Throwable?) /** * 记录一个 _信息_ 级别的日志. */ public fun info(message: String?) public fun info(e: Throwable?): Unit = info(null, e) public fun info(message: String?, e: Throwable?) /** * 记录一个 _警告_ 级别的日志. */ public fun warning(message: String?) public fun warning(e: Throwable?): Unit = warning(null, e) public fun warning(message: String?, e: Throwable?) /** * 记录一个 _错误_ 级别的日志. */ public fun error(message: String?) public fun error(e: Throwable?): Unit = error(null, e) public fun error(message: String?, e: Throwable?) /** 根据优先级调用对应函数 */ public fun call(priority: SimpleLogger.LogPriority, message: String? = null, e: Throwable? = null): Unit = @OptIn(MiraiExperimentalApi::class) priority.correspondingFunction(this, message, e) @Deprecated("plus 设计不佳, 请避免使用.", level = DeprecationLevel.HIDDEN) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10", hiddenSince = "2.11") public operator fun <T : MiraiLogger> plus(follower: T): T = follower } public inline fun MiraiLogger.verbose(message: () -> String) { if (isVerboseEnabled) verbose(message()) } public inline fun MiraiLogger.verbose(message: () -> String, e: Throwable?) { if (isVerboseEnabled) verbose(message(), e) } public inline fun MiraiLogger.debug(message: () -> String?) { if (isDebugEnabled) debug(message()) } public inline fun MiraiLogger.debug(message: () -> String?, e: Throwable?) { if (isDebugEnabled) debug(message(), e) } public inline fun MiraiLogger.info(message: () -> String?) { if (isInfoEnabled) info(message()) } public inline fun MiraiLogger.info(message: () -> String?, e: Throwable?) { if (isInfoEnabled) info(message(), e) } public inline fun MiraiLogger.warning(message: () -> String?) { if (isWarningEnabled) warning(message()) } public inline fun MiraiLogger.warning(message: () -> String?, e: Throwable?) { if (isWarningEnabled) warning(message(), e) } public inline fun MiraiLogger.error(message: () -> String?) { if (isErrorEnabled) error(message()) } public inline fun MiraiLogger.error(message: () -> String?, e: Throwable?) { if (isErrorEnabled) error(message(), e) } /** * 当前平台的默认的日志记录器. * - 在 _JVM 控制台_ 端的实现为 [println] * - 在 _Android_ 端的实现为 `android.util.Log` * * * 单条日志格式 (正则) 为: * ```regex * ^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$ * ``` * 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容. * * 示例: * ```log * 2020-05-21 19:51:09 V/Bot 123456789: Send: OidbSvc.0x88d_7 * ``` * * 日期时间格式为 `yyyy-MM-dd HH:mm:ss`, * * 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error * * @see MiraiLogger.Factory.create */ @MiraiInternalApi public expect open class PlatformLogger @JvmOverloads constructor( identity: String? = "Mirai", ) : MiraiLoggerPlatformBase /** * 不做任何事情的 logger, keep silent. */ @OptIn(MiraiInternalApi::class) @Suppress("unused") public object SilentLogger : PlatformLogger() { public override val identity: String? = null override val isEnabled: Boolean get() = false override val isVerboseEnabled: Boolean get() = false override val isDebugEnabled: Boolean get() = false override val isInfoEnabled: Boolean get() = false override val isWarningEnabled: Boolean get() = false override val isErrorEnabled: Boolean get() = false public override fun error0(message: String?): Unit = Unit public override fun debug0(message: String?): Unit = Unit public override fun warning0(message: String?): Unit = Unit public override fun verbose0(message: String?): Unit = Unit public override fun info0(message: String?): Unit = Unit public override fun verbose0(message: String?, e: Throwable?): Unit = Unit public override fun debug0(message: String?, e: Throwable?): Unit = Unit public override fun info0(message: String?, e: Throwable?): Unit = Unit public override fun warning0(message: String?, e: Throwable?): Unit = Unit public override fun error0(message: String?, e: Throwable?): Unit = Unit } /** * 简易日志记录, 所有类型日志都会被重定向 [logger] */ public open class SimpleLogger( public final override val identity: String?, protected open val logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit ) : MiraiLoggerPlatformBase() { public enum class LogPriority( @MiraiExperimentalApi public val nameAligned: String, public val simpleName: String, @MiraiExperimentalApi public val correspondingFunction: MiraiLogger.(message: String?, e: Throwable?) -> Unit ) { VERBOSE("VERBOSE", "V", MiraiLogger::verbose), DEBUG(" DEBUG ", "D", MiraiLogger::debug), INFO(" INFO ", "I", MiraiLogger::info), WARNING("WARNING", "W", MiraiLogger::warning), ERROR(" ERROR ", "E", MiraiLogger::error) } public companion object { public inline operator fun invoke(crossinline logger: (message: String?, e: Throwable?) -> Unit): SimpleLogger = SimpleLogger(null, logger) public inline operator fun invoke( identity: String?, crossinline logger: (message: String?, e: Throwable?) -> Unit ): SimpleLogger = SimpleLogger(identity) { _, message, e -> logger(message, e) } public operator fun invoke(logger: (priority: LogPriority, message: String?, e: Throwable?) -> Unit): SimpleLogger = SimpleLogger(null, logger) } public override fun verbose0(message: String?): Unit = logger(LogPriority.VERBOSE, message, null) public override fun verbose0(message: String?, e: Throwable?): Unit = logger(LogPriority.VERBOSE, message, e) public override fun debug0(message: String?): Unit = logger(LogPriority.DEBUG, message, null) public override fun debug0(message: String?, e: Throwable?): Unit = logger(LogPriority.DEBUG, message, e) public override fun info0(message: String?): Unit = logger(LogPriority.INFO, message, null) public override fun info0(message: String?, e: Throwable?): Unit = logger(LogPriority.INFO, message, e) public override fun warning0(message: String?): Unit = logger(LogPriority.WARNING, message, null) public override fun warning0(message: String?, e: Throwable?): Unit = logger(LogPriority.WARNING, message, e) public override fun error0(message: String?): Unit = logger(LogPriority.ERROR, message, null) public override fun error0(message: String?, e: Throwable?): Unit = logger(LogPriority.ERROR, message, e) } /** * 带有开关的 Logger. 仅能通过 [MiraiLogger.withSwitch] 构造 * * @see enable 开启 * @see disable 关闭 */ @Suppress("MemberVisibilityCanBePrivate") public class MiraiLoggerWithSwitch internal constructor(private val delegate: MiraiLogger, default: Boolean) : MiraiLoggerPlatformBase() { public override val identity: String? get() = delegate.identity /** * true 为开启. */ @PublishedApi internal var switch: Boolean = default public override val isEnabled: Boolean get() = switch public fun enable() { switch = true } public fun disable() { switch = false } public override fun verbose0(message: String?): Unit = delegate.verbose(message) public override fun verbose0(message: String?, e: Throwable?): Unit = delegate.verbose(message, e) public override fun debug0(message: String?): Unit = delegate.debug(message) public override fun debug0(message: String?, e: Throwable?): Unit = delegate.debug(message, e) public override fun info0(message: String?): Unit = delegate.info(message) public override fun info0(message: String?, e: Throwable?): Unit = delegate.info(message, e) public override fun warning0(message: String?): Unit = delegate.warning(message) public override fun warning0(message: String?, e: Throwable?): Unit = delegate.warning(message, e) public override fun error0(message: String?): Unit = delegate.error(message) public override fun error0(message: String?, e: Throwable?): Unit = delegate.error(message, e) } /** * 日志基类. * 若 Mirai 自带的日志系统无法满足需求, 请继承这个类或 [PlatformLogger] 并实现其抽象函数. * * 这个类不应该被用作变量的类型定义. 只应被作为继承对象. * 在定义 logger 变量时, 请一直使用 [MiraiLogger] 或者 [MiraiLoggerWithSwitch]. * * @see PlatformLogger * @see SimpleLogger */ @Suppress("DEPRECATION_ERROR") public abstract class MiraiLoggerPlatformBase : MiraiLogger { public override val isEnabled: Boolean get() = true public final override fun verbose(message: String?) { if (!isEnabled) return verbose0(message) } public final override fun verbose(message: String?, e: Throwable?) { if (!isEnabled) return verbose0(message, e) } public final override fun debug(message: String?) { if (!isEnabled) return debug0(message) } public final override fun debug(message: String?, e: Throwable?) { if (!isEnabled) return debug0(message, e) } public final override fun info(message: String?) { if (!isEnabled) return info0(message) } public final override fun info(message: String?, e: Throwable?) { if (!isEnabled) return info0(message, e) } public final override fun warning(message: String?) { if (!isEnabled) return warning0(message) } public final override fun warning(message: String?, e: Throwable?) { if (!isEnabled) return warning0(message, e) } public final override fun error(message: String?) { if (!isEnabled) return error0(message) } public final override fun error(message: String?, e: Throwable?) { if (!isEnabled) return error0(message, e) } protected open fun verbose0(message: String?): Unit = verbose0(message, null) protected abstract fun verbose0(message: String?, e: Throwable?) protected open fun debug0(message: String?): Unit = debug0(message, null) protected abstract fun debug0(message: String?, e: Throwable?) protected open fun info0(message: String?): Unit = info0(message, null) protected abstract fun info0(message: String?, e: Throwable?) protected open fun warning0(message: String?): Unit = warning0(message, null) protected abstract fun warning0(message: String?, e: Throwable?) protected open fun error0(message: String?): Unit = error0(message, null) protected abstract fun error0(message: String?, e: Throwable?) } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/MiraiLoggerFactoryImplementationBridge.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.atomicfu.atomic import kotlinx.atomicfu.loop import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.reflect.KClass /** * @since 2.13 */ internal object MiraiLoggerFactoryImplementationBridge : MiraiLogger.Factory { private var _instance by lateinitMutableProperty { createPlatformInstance() } internal val instance get() = _instance // It is required for MiraiConsole because default implementation // queries stdout on every message printing // It creates an infinite loop (StackOverflowError) internal var defaultLoggerFactory: (() -> MiraiLogger.Factory) = ::DefaultFactory fun createPlatformInstance() = loadService(MiraiLogger.Factory::class, defaultLoggerFactory) private val frozen = atomic(false) fun freeze(): Boolean { return frozen.compareAndSet(expect = false, update = true) } @TestOnly fun reinit() { defaultLoggerFactory = ::DefaultFactory frozen.loop { value -> _instance = createPlatformInstance() if (frozen.compareAndSet(value, false)) return } } fun setInstance(instance: MiraiLogger.Factory) { if (frozen.value) { error( "LoggerFactory instance had been frozen, so it's impossible to override it." + "If you are using Mirai Console and you want to override platform logging implementation, " + "please do so before initialization of MiraiConsole, that is, before `MiraiConsoleImplementation.start()`. " + "Plugins are not allowed to override logging implementation, and this is done in the very fundamental implementation of Mirai Console so there is no way to escape that." + "Normally it is only sensible for Mirai Console frontend implementor to do that." + "If you are just using mirai-core, this error should not happen. There should be no limitation in overriding logging implementation with mirai-core. " + "Check if you actually did use mirai-console somewhere, or please file an issue on https://github.com/mamoe/mirai/issues/new/choose" ) } this._instance = instance } inline fun wrapCurrent(mapper: (current: MiraiLogger.Factory) -> MiraiLogger.Factory) { contract { callsInPlace(mapper, InvocationKind.EXACTLY_ONCE) } setInstance(this.instance.let(mapper)) } override fun create(requester: KClass<*>, identity: String?): MiraiLogger { return instance.create(requester, identity) } override fun create(requester: Class<*>, identity: String?): MiraiLogger { return instance.create(requester, identity) } override fun create(requester: KClass<*>): MiraiLogger { return instance.create(requester) } override fun create(requester: Class<*>): MiraiLogger { return instance.create(requester) } } // used by Mirai Console private class DefaultFactory : MiraiLogger.Factory { @OptIn(MiraiInternalApi::class) override fun create(requester: Class<*>, identity: String?): MiraiLogger { return PlatformLogger(identity ?: requester.kotlin.simpleName ?: requester.simpleName) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/MiraiUtilsLogger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils internal fun MiraiLogger.asUtilsLogger(): UtilsLogger = MiraiUtilsLogger(this) internal class MiraiUtilsLogger( private val miraiLogger: MiraiLogger, ) : UtilsLogger { override val isVerboseEnabled: Boolean get() = miraiLogger.isVerboseEnabled override val isDebugEnabled: Boolean get() = miraiLogger.isDebugEnabled override val isInfoEnabled: Boolean get() = miraiLogger.isInfoEnabled override val isWarningEnabled: Boolean get() = miraiLogger.isWarningEnabled override val isErrorEnabled: Boolean get() = miraiLogger.isErrorEnabled override fun verbose(message: String?, e: Throwable?) { miraiLogger.verbose(message, e) } override fun debug(message: String?, e: Throwable?) { miraiLogger.debug(message, e) } override fun info(message: String?, e: Throwable?) { miraiLogger.info(message, e) } override fun warning(message: String?, e: Throwable?) { miraiLogger.warning(message, e) } override fun error(message: String?, e: Throwable?) { miraiLogger.error(message, e) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/OverFileSizeMaxException.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("Utils") package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /** * 图片文件过大 */ // 不要删除多平台结构, 这是 kotlin 的 bug public class OverFileSizeMaxException : IllegalStateException() ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/ProgressionCallback.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.channels.SendChannel import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.utils.ProgressionCallback.Companion.asProgressionCallback import kotlin.jvm.JvmStatic /** * 操作进度回调, 可供前端使用, 以提供进度显示. * * @param S subject, 操作对象, 如 [AbsoluteFile] * @param P progression, 用于提示进度. 如当下载文件时为已下载文件大小字节数 [Long]. * * @see asProgressionCallback * * @since 2.8 */ public interface ProgressionCallback<in S, in P> { /** * 当操作开始时调用 */ public fun onBegin(subject: S, resource: ExternalResource) {} /** * 每当有进度更新时调用. 此方法可能会同时被多个线程调用. */ public fun onProgression(subject: S, resource: ExternalResource, progression: P) {} /** * 当操作成功时调用. * * 在默认实现下只会由 [onFinished] 调用 */ public fun onSuccess(subject: S, resource: ExternalResource, progression: P) {} /** * 当操作以异常失败时调用. * * 在默认实现下只会由 [onFinished] 调用 */ public fun onFailure(subject: S, resource: ExternalResource, exception: Throwable) {} /** * 当操作完成时调用. */ public fun onFinished(subject: S, resource: ExternalResource, result: Result<P>) { result.fold( onSuccess = { onSuccess(subject, resource, it) }, onFailure = { onFailure(subject, resource, it) }, ) } public companion object { /** * 将一个 [SendChannel] 作为 [ProgressionCallback] 使用. * * ## 下载文件的使用示例 * * 每当有进度更新, 已下载的字节数都会被[发送][SendChannel.offer]到 [SendChannel] 中. * 进度的发送会通过 [offer][SendChannel.offer], 而不是通过 [send][SendChannel.send]. 意味着 [SendChannel] 通常要实现缓存. * * 若 [closeOnFinish] 为 `true`, 当下载完成 (无论是失败还是成功) 时会 [关闭][SendChannel.close] [SendChannel]. * * 使用示例: * ``` * val progress = Channel<Long>(Channel.BUFFERED) * * launch { * // 每 3 秒发送一次操作进度百分比 * progress.receiveAsFlow().sample(Duration.seconds(3)).collect { bytes -> * group.sendMessage("File upload: ${(bytes.toDouble() / resource.size * 100).toInt() / 100}%.") // 保留 2 位小数 * } * } * * group.files.uploadNewFile("/foo.txt", resource, callback = progress.asProgressionCallback(true)) * group.sendMessage("File uploaded successfully.") * ``` * * 直接使用 [ProgressionCallback] 也可以实现示例这样的功能, [asProgressionCallback] 是为了简化操作. */ @JvmStatic public fun <S, P> SendChannel<P>.asProgressionCallback(closeOnFinish: Boolean = true): ProgressionCallback<S, P> { return object : ProgressionCallback<S, P> { override fun onProgression(subject: S, resource: ExternalResource, progression: P) { trySend(progression) } override fun onFinished(subject: S, resource: ExternalResource, result: Result<P>) { if (closeOnFinish) this@asProgressionCallback.close(result.exceptionOrNull()) } } } } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/RemoteFile.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused", "DEPRECATION", "DEPRECATION_ERROR") @file:JvmBlockingBridge package net.mamoe.mirai.utils import kotlinx.coroutines.channels.SendChannel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.toList import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.message.data.sendTo import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.RemoteFile.Companion.uploadFile import net.mamoe.mirai.utils.RemoteFile.ProgressionCallback.Companion.asProgressionCallback import java.io.File /** * 表示一个远程文件或目录. * * # 注意: 此 API 已弃用, 请使用 [AbsoluteFile] 代替. * * [RemoteFile] 仅保存 [id], [name], [path], [parent], [contact] 这五个属性, 除获取这些属性外的所有的操作都是在*远程*完成的. * 意味着操作的结果会因文件或目录在服务器中的状态变化而变化. * * 与 [File] 类似, [RemoteFile] 是不可变的. [renameTo] 和 [copyTo] 会操作远程文件, 但不会修改当前 [RemoteFile.path] 等属性. * * ## 文件操作 * * 所有文件操作都在 [RemoteFile] 对象中完成. 可通过 [FileSupported.filesRoot] 获取到表示根目录路径的 [RemoteFile], 并通过 [resolve] 获取到其内文件. * * 示例: * ``` * val file1: RemoteFile = group.filesRoot.resolve("/foo.txt") // 获取表示群文件 "foo.txt" 的 RemoteFile 实例 * val file2: RemoteFile = group.filesRoot.resolve("/dir/foo.txt") // 获取表示群文件目录 "dir" 中的 "foo.txt" 的 RemoteFile 实例 * * * val downloadInfo = file1.getDownloadInfo() // 获取该文件的下载方式, 可以自行下载 * * * val message: FileMessage = file2.upload(resource) // 向路径 "/dir/foo.txt" 上传一个文件, 返回可以发送到群内的文件消息. * group.sendMessage(message) // 发送文件消息到群, 用户才会收到机器人上传文件的提醒. 可以多次发送. * * file2.uploadAndSend(resource) // 上传文件并发送文件消息. 是上面两行的简单版本. * * * // 要直接上传文件, 也可以简单地使用任一: * group.uploadFile("/foo.txt", resource) // Kotlin * resource.uploadAsFileTo(group, "/foo.txt") // Kotlin * FileSupported.uploadFile(group, "/foo.txt", resource"); // Java * ExternalResource.uploadAsFile(resource, group, "/foo.txt") // Java * ``` * * ## 目录操作 * [RemoteFile] 类似于 [java.io.File], 也可以表示一个目录. * ``` * val dir: RemoteFile = group.filesRoot.resolve("/foo") // 获取表示目录 "foo" 的 RemoteFile 实例 * * if (dir.exists()) { // 判断目录是否存在 * // ... * } * * dir.listFiles() // Kotlin 使用, 获取该目录中的文件列表. * dir.listFilesIterator() // Java 使用, 获取该目录中的文件列表. * ``` * * 注意, 服务器目前只支持一层目录. 即只能存在 "/foo.txt" 和 "/xxx/foo.txt", 而 "/xxx/xxx/foo.txt" 不受支持. * * ## 文件名和目录名可重复 * * 服务器允许相同名称的文件或目录存在, 这就导致 "/foo" 可能表示多个重名文件中的一个, 也可能表示一个目录. 依靠路径的判断因此不可靠. * * 这个特性带来的行为有: * - [`FileSupported.uploadFile`][uploadFile] 总是往一个路径上传文件, 如果有同名文件存在, 不会覆盖, 而是再创建一个同名文件. * - [delete] 可能会删除重名文件中的任何一个, 也可能会删除一个目录, 操作顺序取决于服务器. * * 为了解决这个问题, [RemoteFile] 可以拥有一个由服务器分配的固定的唯一识别号 [RemoteFile.id]. * * 通过 [listFiles] 获取到的 [RemoteFile] 都拥有非 `null` 的 [id]. * 服务器可以通过 [id] 准确定位重名文件中的某一个. * 对这样的文件进行 [upload] 时将会覆盖目标文件 (如果存在), 进行 [delete] 时也只会准确操作目标文件. * * 只要文件内容无变化, 文件的 [id] 就不会变更. 可以保存 [RemoteFile.id] 并在以后通过 [RemoteFile.resolveById] 准确获取一个目标文件. * * @suppress 使用 [RemoteFile] 是稳定的, 但不应该自行实现这个接口. * @see FileSupported * @since 2.5 */ @Deprecated( "Please use RemoteFiles and AbsoluteFileFolder form fileSupported.files", level = DeprecationLevel.ERROR ) // deprecated since 2.8.0-RC @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.14") @NotStableForInheritance public interface RemoteFile { /** * 文件名或目录名. */ public val name: String /** * 文件的 ID. 群文件允许重名, ID 非空时用来区分重名. */ public val id: String? /** * 标准的绝对路径, 起始字符为 '/'. 如 `/foo/bar.txt`. * * 根目录路径为 [ROOT_PATH] */ public val path: String /** * 获取父目录, 当 [RemoteFile] 表示根目录时返回 `null` */ public val parent: RemoteFile? /** * 此文件所属的群或好友 */ public val contact: FileSupported /** * 当 [RemoteFile] 表示一个文件时返回 `true`. */ public suspend fun isFile(): Boolean /** * 当 [RemoteFile] 表示一个目录时返回 `true`. */ public suspend fun isDirectory(): Boolean = !isFile() /** * 获取文件长度. 当 [RemoteFile] 表示一个目录时行为不确定. */ public suspend fun length(): Long @Deprecated("RemoteFile is deprecated. Use RemoteFiles instead.", level = DeprecationLevel.ERROR) @DeprecatedSinceMirai(errorSince = "2.14") // 在弃用 RemoteFile 的时候忘了弃用这个类, 所以它没有 warningSince public class FileInfo @MiraiInternalApi constructor( /** * 文件或目录名. */ public val name: String, /** * 唯一识别标识. */ public val id: String, /** * 标准绝对路径. */ public val path: String, /** * 文件长度 (大小) bytes, 目录的 [length] 为 0. */ public val length: Long, /** * 下载次数. 目录没有下载次数, 此属性总是 `0`. */ public val downloadTimes: Int, /** * 上传者 ID. 目录没有上传者, 此属性总是 `0`. */ public val uploaderId: Long, /** * 上传的时间. 目录没有上传时间, 此属性总是 `0`. */ public val uploadTime: Long, /** * 上次修改时间. 时间戳秒. */ public val lastModifyTime: Long, public val sha1: ByteArray, public val md5: ByteArray, ) { /** * 根据 [FileInfo.id] 或 [FileInfo.path] 获取到对应的 [RemoteFile]. */ public suspend fun resolveToFile(contact: FileSupported): RemoteFile = contact.filesRoot.resolveById(id) ?: contact.filesRoot.resolve(path) } /** * 获取这个文件或目录**此时**的详细信息. 当文件或目录不存在时返回 `null`. */ public suspend fun getInfo(): FileInfo? /** * 当文件或目录存在时返回 `true`. */ public suspend fun exists(): Boolean /** * @return [path] */ public override fun toString(): String /////////////////////////////////////////////////////////////////////////// // resolve /////////////////////////////////////////////////////////////////////////// /** * 获取该目录的子文件. 不会检查 [RemoteFile] 是否表示一个目录. * * @param relative 相对路径. 当初始字符为 '/' 时将作为绝对路径解析 * @see File.resolve stdlib 内的类似函数 */ public fun resolve(relative: String): RemoteFile /** * 获取该目录的子文件. 不会检查 [RemoteFile] 是否表示一个目录. 返回的 [RemoteFile.id] 将会与 `relative.id` 相同. * * @param relative 相对路径. 当 [RemoteFile.path] 初始字符为 '/' 时将作为绝对路径解析 * @see File.resolve stdlib 内的类似函数 */ public fun resolve(relative: RemoteFile): RemoteFile /** * 获取该目录下的 ID 为 [id] 的文件, 当 [deep] 为 `true` 时还会进入子目录继续寻找这样的文件. 在不存在时返回 `null`. * @see resolve */ @Suppress("_FUNCTION_WITH_DEFAULT_ARGUMENTS") // JVM ABI public suspend fun resolveById(id: String, deep: Boolean = true): RemoteFile? /** * 获取该目录或子目录下的 ID 为 [id] 的文件, 在不存在时返回 `null` * @see resolve */ public suspend fun resolveById(id: String): RemoteFile? = resolveById(id, deep = true) /** * 获取父目录的子文件. 如 `RemoteFile("/foo/bar").resolveSibling("gav")` 为 `RemoteFile("/foo/gav")`. * 不会检查 [RemoteFile] 是否表示一个目录. * * @param relative 当初始字符为 '/' 时将作为绝对路径解析 * @see File.resolveSibling stdlib 内的类似函数 */ public fun resolveSibling(relative: String): RemoteFile /** * 获取父目录的子文件. 如 `RemoteFile("/foo/bar").resolveSibling("gav")` 为 `RemoteFile("/foo/gav")`. * 不会检查 [RemoteFile] 是否表示一个目录. 返回的 [RemoteFile.id] 将会与 `relative.id` 相同. * * @param relative 当 [RemoteFile.path] 初始字符为 '/' 时将作为绝对路径解析 * @see File.resolveSibling stdlib 内的类似函数 */ public fun resolveSibling(relative: RemoteFile): RemoteFile /////////////////////////////////////////////////////////////////////////// // operations /////////////////////////////////////////////////////////////////////////// /** * 删除这个文件或目录. 若目录非空, 则会删除目录中的所有文件. 操作目录或非 Bot 自己上传的文件时需要管理员权限, 无管理员权限时返回 `false`. */ public suspend fun delete(): Boolean /** * 重命名这个文件或目录, 将会更改 [RemoteFile.name] 属性值. * 操作非 Bot 自己上传的文件时需要管理员权限. * * [renameTo] 只会操作远程文件, 而不会修改当前 [RemoteFile.path]. */ public suspend fun renameTo(name: String): Boolean /** * 将这个目录或文件移动到 [target] 位置. 操作目录或非 Bot 自己上传的文件时需要管理员权限, 无管理员权限时返回 `false`. * * [moveTo] 只会操作远程文件, 而不会修改当前 [RemoteFile.path]. * * **注意**: 与 [java.io.File] 类似, 这是将当前 [RemoteFile] 移动到作为 [target], 而不是移动成为 [target] 的子文件或目录. 例如: * ``` * val root = group.filesRoot * root.resolve("test.txt").moveTo(root) // 错误! 这是在将该文件的路径 "test.txt" 修改为 “/” , 而不是修改为 "/test.txt" * root.resolve("test.txt").moveTo(root.resolve("/")) // 错误! 与上一行相同. * root.resolve("/test.txt").moveTo(root.resolve("/test2.txt")) // 正确. 将该文件的路径 "/test.txt" 修改为 “/test2.txt”,相当于重命名文件 * ``` * * @param target 目标文件位置. */ public suspend fun moveTo(target: RemoteFile): Boolean /** * 将这个目录或文件移动到另一个位置. 操作目录或非 Bot 自己上传的文件时需要管理员权限, 无管理员权限时返回 `false`. * * [moveTo] 只会操作远程文件, 而不会修改当前 [RemoteFile.path]. * * **已弃用:** 当 [path] 是绝对路径时, 这个函数运行正常; * 当它是相对路径时, 将会尝试把当前文件移动到 [RemoteFile.path] 下的子路径 [path], 因此总是失败. * * 使用参数为 [RemoteFile] 的 [moveTo] 代替. * * @suppress 在 2.6 弃用. 请使用 [moveTo] */ @Deprecated( "Use moveTo(RemoteFile) instead.", replaceWith = ReplaceWith("this.moveTo(this.resolveSibling(path))"), level = DeprecationLevel.ERROR ) // deprecated since 2.7 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10") public suspend fun moveTo(path: String): Boolean { // Impl notes: // if `path` is absolute, this works as intended. // if not, `resolve(path)` will be a child path from this dir and fails always. return moveTo(resolve(path)) } /** * 创建目录. 目录已经存在或无管理员权限时返回 `false`. * * 创建后 [isDirectory] 也不一定会返回 `true`. * 当 [id] 未指定时, [RemoteFile] 总是表示一个路径而无法确定目标是文件还是目录, [isFile] 或 [isDirectory] 结果取决于服务器. */ public suspend fun mkdir(): Boolean /** * 获取该目录下所有文件, 返回的 [RemoteFile] 都拥有 [RemoteFile.id] 用于区分重名文件或目录. 当 [RemoteFile] 表示一个文件时返回 [emptyFlow]. * * 返回的 [Flow] 是*冷*的, 只会在被需要的时候向服务器查询. */ public suspend fun listFiles(): Flow<RemoteFile> /** * 获取该目录下所有文件, 返回的 [RemoteFile] 都拥有 [RemoteFile.id] 用于区分重名文件或目录. 当 [RemoteFile] 表示一个文件时返回空迭代器. * @param lazy 为 `true` 时惰性获取, 为 `false` 时立即获取全部文件列表. */ @JavaFriendlyAPI public suspend fun listFilesIterator(lazy: Boolean): Iterator<RemoteFile> /** * 获取该目录下所有文件, 返回的 [RemoteFile] 都拥有 [RemoteFile.id] 用于区分重名文件或目录. 当 [RemoteFile] 表示一个文件时返回 [emptyList]. */ public suspend fun listFilesCollection(): List<RemoteFile> = listFiles().toList() /** * 得到相应文件消息. 当 [RemoteFile] 表示一个目录或文件不存在时返回 `null`. */ public suspend fun toMessage(): FileMessage? /////////////////////////////////////////////////////////////////////////// // upload & download /////////////////////////////////////////////////////////////////////////// /** * 上传进度回调, 可供前端使用, 以提供进度显示. * @see asProgressionCallback */ @Deprecated( "Deprecated without replacement. Please use AbsoluteFolder.uploadNewFile", ReplaceWith("contact.files.uploadNewFile(path, this, callback)"), level = DeprecationLevel.ERROR ) // deprecated since 2.8.0-RC @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.14") public interface ProgressionCallback { /** * 当上传开始时调用 */ public fun onBegin(file: RemoteFile, resource: ExternalResource) {} /** * 每当有进度更新时调用. 此方法可能会同时被多个线程调用. * * 提示: 可通过 [ExternalResource.size] 获取文件总大小. */ public fun onProgression(file: RemoteFile, resource: ExternalResource, downloadedSize: Long) {} /** * 当上传成功时调用 */ public fun onSuccess(file: RemoteFile, resource: ExternalResource) {} /** * 当上传以异常失败时调用 */ public fun onFailure(file: RemoteFile, resource: ExternalResource, exception: Throwable) {} @Deprecated("RemoteFile is deprecated. Use RemoteFiles instead.", level = DeprecationLevel.ERROR) @DeprecatedSinceMirai(errorSince = "2.14") // 在弃用 RemoteFile 的时候忘了弃用这个类, 所以它没有 warningSince public companion object { /** * 将一个 [SendChannel] 作为 [ProgressionCallback] 使用. * * 每当有进度更新, 已下载的字节数都会被[发送][SendChannel.offer]到 [SendChannel] 中. * 进度的发送会通过 [offer][SendChannel.offer], 而不是通过 [send][SendChannel.send]. 意味着 [SendChannel] 通常要实现缓存. * * 若 [closeOnFinish] 为 `true`, 当下载完成 (无论是失败还是成功) 时会 [关闭][SendChannel.close] [SendChannel]. * * 使用示例: * ``` * val progress = Channel<Long>(Channel.BUFFERED) * * launch { * // 每 3 秒发送一次上传进度百分比 * progress.receiveAsFlow().sample(3.seconds).collect { bytes -> * group.sendMessage("File upload: ${(bytes.toDouble() / resource.size * 100).toInt() / 100}%.") // 保留 2 位小数 * } * } * * group.filesRoot.resolve("/foo.txt").upload(resource, progress.asProgressionCallback(true)) * group.sendMessage("File uploaded successfully.") * ``` * * 直接使用 [ProgressionCallback] 也可以实现示例这样的功能, [asProgressionCallback] 是为了简化操作. */ @JvmStatic public fun SendChannel<Long>.asProgressionCallback(closeOnFinish: Boolean = false): ProgressionCallback { return object : ProgressionCallback { override fun onProgression(file: RemoteFile, resource: ExternalResource, downloadedSize: Long) { trySend(downloadedSize) } override fun onSuccess(file: RemoteFile, resource: ExternalResource) { if (closeOnFinish) this@asProgressionCallback.close() } override fun onFailure(file: RemoteFile, resource: ExternalResource, exception: Throwable) { if (closeOnFinish) this@asProgressionCallback.close(exception) } } } } } /** * 上传文件到 [RemoteFile] 表示的路径, 上传过程中调用 [callback] 传递进度. * * 上传后不会发送文件消息, 即官方客户端只能在 "群文件" 中查看文件. * 可通过 [toMessage] 获取到文件消息并通过 [Group.sendMessage] 发送, 或使用 [uploadAndSend]. * * ## 已弃用 * * 使用 [sendFile] 代替. 本函数会上传文件但不会发送文件消息. * 不发送文件消息就导致其他操作都几乎不能完成, 而且经反馈, 用户通常会忘记后续的 [RemoteFile.toMessage] 操作. * 本函数造成了很大的不必要的迷惑, 故以既上传又发送消息的, 与官方客户端行为相同的 [sendFile] 代替. * * 相关问题: [#1250: 群文件在上传后 toRemoteFile 返回 null](https://github.com/mamoe/mirai/issues/1250) * * * **注意**: [resource] 仅表示资源数据, 而不带有文件名属性. * 与 [java.io.File] 类似, [upload] 是将 [resource] 上传成为 [this][RemoteFile], 而不是上传成为 [this][RemoteFile] 的子文件. 示例: * ``` * group.filesRoot.upload(resource) // 错误! 这是在把资源上传成为根目录. * group.filesRoot.resolve("/").upload(resource) // 错误! 与上一句相同, 这是在把资源上传成为根目录. * * val root = group.filesRoot * root.resolve("test.txt").upload(resource) // 正确. 把资源上传成为根目录下的 "test.txt". * root.resolve("/test.txt").upload(resource) // 正确. 与上一句相同, 把资源上传成为根目录下的 "test.txt". * ``` * * @param resource 需要上传的文件资源. 无论上传是否成功, 本函数都不会关闭 [resource]. * @param callback 进度回调 * @throws IllegalStateException 该文件上传失败或权限不足时抛出 */ @Deprecated( "Use uploadAndSend instead.", ReplaceWith("this.uploadAndSend(resource, callback)"), DeprecationLevel.ERROR ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10") // left ERROR intentionally @Suppress("_FUNCTION_WITH_DEFAULT_ARGUMENTS") public suspend fun upload( resource: ExternalResource, callback: ProgressionCallback? = null, ): FileMessage /** * 上传文件到 [RemoteFile.path] 表示的路径. * ## 已弃用 * 阅读 [upload] 获取更多信息 * @see upload */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Use uploadAndSend instead.", ReplaceWith("this.uploadAndSend(resource)"), DeprecationLevel.ERROR ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10") // left ERROR intentionally public suspend fun upload(resource: ExternalResource): FileMessage = upload(resource, null) /** * 上传文件. * ## 已弃用 * 阅读 [upload] 获取更多信息 * @see upload */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Use uploadAndSend instead.", ReplaceWith("this.uploadAndSend(file, callback)"), DeprecationLevel.ERROR ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10") // left ERROR intentionally public suspend fun upload( file: File, callback: ProgressionCallback? = null, ): FileMessage = file.toExternalResource().use { upload(it, callback) } /** * 上传文件. * ## 已弃用 * 阅读 [upload] 获取更多信息 * @see upload */ @Suppress("DEPRECATION_ERROR") @Deprecated( "Use sendFile instead.", ReplaceWith("this.uploadAndSend(file)"), DeprecationLevel.ERROR ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10") // left ERROR intentionally public suspend fun upload(file: File): FileMessage = file.toExternalResource().use { upload(it) } /** * 上传文件并发送文件消息. * * 若 [RemoteFile.id] 存在且旧文件存在, 将会覆盖旧文件. * 即使用 [resolve] 或 [resolveSibling] 获取到的 [RemoteFile] 的 [upload] 总是上传一个新文件, * 而使用 [resolveById] 或 [listFiles] 获取到的总是覆盖旧文件, 当旧文件已在远程删除时上传一个新文件. * * @param resource 需要上传的文件资源. 无论上传是否成功, 本函数都不会关闭 [resource]. * @see upload */ @MiraiExperimentalApi public suspend fun uploadAndSend(resource: ExternalResource): MessageReceipt<Contact> /** * 上传文件并发送文件消息. * @see uploadAndSend */ @MiraiExperimentalApi public suspend fun uploadAndSend(file: File): MessageReceipt<Contact> = file.toExternalResource().use { uploadAndSend(it) } /** * 获取文件下载链接, 当文件不存在或 [RemoteFile] 表示一个目录时返回 `null` */ public suspend fun getDownloadInfo(): DownloadInfo? @Deprecated("RemoteFile is deprecated. Use RemoteFiles instead.", level = DeprecationLevel.ERROR) @DeprecatedSinceMirai(errorSince = "2.14") // 在弃用 RemoteFile 的时候忘了弃用这个类, 所以它没有 warningSince public class DownloadInfo @MiraiInternalApi constructor( /** * @see RemoteFile.name */ public val filename: String, /** * @see RemoteFile.id */ public val id: String, /** * 标准绝对路径 * @see RemoteFile.path */ public val path: String, /** * HTTP or HTTPS URL */ public val url: String, public val sha1: ByteArray, public val md5: ByteArray, ) { override fun toString(): String { return "DownloadInfo(filename='$filename', path='$path', url='$url', sha1=${sha1.toUHexString("")}, " + "md5=${md5.toUHexString("")})" } } @Deprecated("RemoteFile is deprecated. Use RemoteFiles instead.", level = DeprecationLevel.ERROR) @DeprecatedSinceMirai(errorSince = "2.14") // 在弃用 RemoteFile 的时候忘了弃用这个类, 所以它没有 warningSince public companion object { /** * 根目录路径 * @see RemoteFile.path */ public const val ROOT_PATH: String = "/" /** * 上传文件并获取文件消息, 但不发送. * * ## 已弃用 * 在 [upload] 获取更多信息 * * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt' * @param resource 需要上传的文件资源. 无论上传是否成功, 本函数都不会关闭 [resource]. * @see RemoteFile.upload */ @JvmStatic @JvmOverloads @Deprecated( "Use sendFile instead.", ReplaceWith( "this.sendFile(path, resource, callback)", "net.mamoe.mirai.utils.RemoteFile.Companion.sendFile" ), level = DeprecationLevel.ERROR ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10") @Suppress("_FUNCTION_WITH_DEFAULT_ARGUMENTS") public suspend fun FileSupported.uploadFile( path: String, resource: ExternalResource, callback: ProgressionCallback? = null, ): FileMessage = @Suppress("DEPRECATION", "DEPRECATION_ERROR") this.filesRoot.resolve(path).upload(resource, callback) /** * 上传文件并获取文件消息, 但不发送. * ## 已弃用 * 阅读 [uploadFile] 获取更多信息. * * @param path 远程路径. 起始字符为 '/'. 如 '/foo/bar.txt' * @see RemoteFile.upload */ @JvmStatic @JvmOverloads @Deprecated( "Use sendFile instead.", ReplaceWith( "this.sendFile(path, file, callback)", "net.mamoe.mirai.utils.RemoteFile.Companion.sendFile" ), level = DeprecationLevel.ERROR ) // deprecated since 2.7-M1 @DeprecatedSinceMirai(warningSince = "2.7", errorSince = "2.10") // left ERROR intentionally public suspend fun FileSupported.uploadFile( path: String, file: File, callback: ProgressionCallback? = null, ): FileMessage = @Suppress("DEPRECATION", "DEPRECATION_ERROR") this.filesRoot.resolve(path).upload(file, callback) /** * 上传文件并发送文件消息到相关 [FileSupported]. * @param resource 需要上传的文件资源. 无论上传是否成功, 本函数都不会关闭 [resource]. * @see RemoteFile.uploadAndSend */ @JvmStatic @JvmOverloads @Deprecated( "Deprecated. Please use AbsoluteFolder.uploadNewFile or RemoteFiles.uploadNewFile", ReplaceWith("this.files.uploadNewFile(path, resource, callback)"), level = DeprecationLevel.HIDDEN ) // deprecated since 2.8.0-RC @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.12", hiddenSince = "2.13") @Suppress("_FUNCTION_WITH_DEFAULT_ARGUMENTS") public suspend fun <C : FileSupported> C.sendFile( path: String, resource: ExternalResource, callback: ProgressionCallback? = null, ): MessageReceipt<C> = @Suppress("DEPRECATION", "DEPRECATION_ERROR") this.filesRoot.resolve(path).upload(resource, callback).sendTo(this) /** * 上传文件并发送文件消息到相关 [FileSupported]. * @see RemoteFile.uploadAndSend */ @JvmStatic @JvmOverloads @Deprecated( "Deprecated. Please use AbsoluteFolder.uploadNewFile or RemoteFiles.uploadNewFile", ReplaceWith("file.toExternalResource().use { this.files.uploadNewFile(path, it, callback) }"), level = DeprecationLevel.HIDDEN ) // deprecated since 2.8.0-RC @DeprecatedSinceMirai(warningSince = "2.8", errorSince = "2.12", hiddenSince = "2.13") public suspend fun <C : FileSupported> C.sendFile( path: String, file: File, callback: ProgressionCallback? = null, ): MessageReceipt<C> = @Suppress("DEPRECATION", "DEPRECATION_ERROR") this.filesRoot.resolve(path).upload(file, callback).sendTo(this) } } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/SingleFileLogger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import java.io.File /** * 将日志写入('append')到特定文件. * * @see PlatformLogger 查看格式信息 */ public expect class SingleFileLogger : MiraiLogger { public constructor(identity: String) public constructor(identity: String, file: File = File("$identity-${getCurrentDate()}.log")) // Implementation notes v2.5.0: // default argument `file` to produce synthetic constructor with `DefaultConstructorMarker` for binary compatibility // dedicated constructor with single parameter `identity` for the same reason. } ================================================ FILE: mirai-core-api/src/commonMain/kotlin/utils/Streamable.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmBlockingBridge package net.mamoe.mirai.utils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.toList import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.announcement.Announcement import net.mamoe.mirai.contact.announcement.Announcements import net.mamoe.mirai.utils.JdkStreamSupport.toStream import java.util.stream.Stream import kotlin.coroutines.EmptyCoroutineContext /** * 表示一个可以创建数据流 [Flow] 和 [Stream] 的对象. * * 实现这个接口的对象可以看做为元素 [T] 的集合. * 例如 [Announcements] 可以看作是 [Announcement] 的集合, * 使用 [Announcements.asFlow] 可以获取到包含所有 [Announcement] 列表的 [Flow], * 使用 [Announcements.asStream] 可以获取到包含所有 [Announcement] 列表的 [Stream]. * * @since 2.13 */ public interface Streamable<T> { /** * 创建一个能获取 [T] 的 [Flow]. */ public fun asFlow(): Flow<T> /** * 创建一个能获取该群内所有 [T] 的 [Stream]. * * 实现细节: 为了适合 Java 调用, 实现类似为阻塞式的 [asFlow], 因此不建议在 Kotlin 使用. 在 Kotlin 请使用 [asFlow]. * * 注: 为了资源的正确释放, 使用 [Stream] 时需要使用 `try-with-resource`. 如 * * ```java * Streamable<String> tmp; * try (var stream = tmp.asStream()) { * System.out.println(stream.findFirst()); * } * ``` */ public fun asStream(): Stream<T> = asFlow().toStream( context = if (this is CoroutineScope) this.coroutineContext else EmptyCoroutineContext, ) /** * 获取所有 [T] 列表, 将全部 [T] 都加载后再返回. * * @return 此时刻的 [T] 只读列表. */ public suspend fun toList(): List<T> = asFlow().toList() } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/logging/AbstractLoggingTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.logging import net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge import org.junit.jupiter.api.AfterEach internal abstract class AbstractLoggingTest { @AfterEach fun cleanup() { MiraiLoggerFactoryImplementationBridge.run { setInstance(createPlatformInstance()) } } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/logging/Log4j2LoggingTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.logging import net.mamoe.mirai.Bot import net.mamoe.mirai.internal.utils.* import net.mamoe.mirai.utils.LoggerAdapters.asMiraiLogger import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge import org.apache.logging.log4j.LogManager import kotlin.test.* internal class Log4j2LoggingTest : AbstractLoggingTest() { @BeforeTest fun init() { MiraiLoggerFactoryImplementationBridge.wrapCurrent { object : MiraiLogger.Factory { override fun create(requester: Class<*>, identity: String?): MiraiLogger { return LogManager.getLogger(requester) .asMiraiLogger(Marker(identity ?: requester.simpleName, MARKER_MIRAI)) } } } } private fun MiraiLogger.cast(): Log4jLoggerAdapter = this as Log4jLoggerAdapter @Test fun `created is Log4jLoggerAdapter`() { val logger = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1") assertIs<Log4jLoggerAdapter>(logger) } @Test fun `identity is considered as marker`() { val logger = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1") assertEquals("test1", logger.cast().marker!!.name) } @Test fun `test subLogger Marker`() { val parent = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1") val parentMarker = parent.cast().marker!! val child = subLoggerImpl(parent, "sub") val childMarker = child.markerOrNull!! assertEquals("test1", parentMarker.name) assertEquals("sub", childMarker.name) assertSame(parentMarker, childMarker.parents.single()) assertSame("test1", childMarker.parents.single().name) } @Test fun `test subLogger Marker 2`() { val parent = MiraiLogger.Factory.create(Log4j2LoggingTest::class, "test1") val parentMarker = parent.cast().marker!! val child = parent.subLogger("sub").subLogger("sub2") val childMarker = child.markerOrNull!! assertEquals("test1", parentMarker.name) assertEquals("sub2", childMarker.name) assertSame("sub", childMarker.parents.single().name) assertSame(parentMarker, childMarker.parents.single().parents.single()) } @Test fun `logging output test`() { val logger = LogManager.getLogger(Bot::class.java) logger.info("Test") MiraiLogger.Factory.create(Bot::class).run { info("InfoFF") } } } internal fun MiraiLogger.subLogger(s: String): MiraiLogger { return subLoggerImpl(this, s) } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/logging/LoggingCompatibilityTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.logging import net.mamoe.mirai.utils.MiraiLogger import net.mamoe.mirai.utils.MiraiLoggerFactoryImplementationBridge import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class LoggingCompatibilityTest : AbstractLoggingTest() { @Suppress("DEPRECATION_ERROR") @Test fun `legacy overrides are still working if no services are found`() { val messages = StringBuilder() MiraiLoggerFactoryImplementationBridge.wrapCurrent { object : MiraiLogger.Factory { override fun create(requester: Class<*>, identity: String?): MiraiLogger { return net.mamoe.mirai.utils.SimpleLogger("my logger") { message: String?, _: Throwable? -> messages.append(message) } } } } val created = MiraiLogger.Factory.create(this::class) assertIs<MiraiLogger>(created) created.info("test") assertEquals("test", messages.toString().trim()) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/CombinedMessageTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.visitor.MessageVisitorUnit import net.mamoe.mirai.message.data.visitor.RecursiveMessageVisitor import net.mamoe.mirai.message.data.visitor.accept import net.mamoe.mirai.message.data.visitor.acceptChildren import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertIs @OptIn(MessageChainConstructor::class) internal class CombinedMessageTest { private fun linearMessageChainOf(vararg values: SingleMessage) = LinearMessageChainImpl.create(values.toList(), values.any { it.hasConstrainSingle }) @Test fun singlePlusSingleCreatesCombinedMessage() { kotlin.run { val chain = PlainText("fo").plus(PlainText("o")) assertIs<CombinedMessage>(chain) assertEquals(PlainText("fo"), chain.element) assertEquals(PlainText("o"), chain.tail) assertEquals(false, chain.hasConstrainSingle) } kotlin.run { val chain = PlainText("fo").plus(LightApp("c")) assertIs<LinearMessageChainImpl>(chain) assertEquals(LightApp("c"), chain.single()) assertEquals(true, chain.hasConstrainSingle) } } @Test fun singlePlusChainCreatesCombinedMessage() { val chain = PlainText("fo").plus(linearMessageChainOf(PlainText("o"))) assertIs<CombinedMessage>(chain) assertEquals(PlainText("fo"), chain.element) assertEquals(linearMessageChainOf(PlainText("o")), chain.tail) assertEquals(false, chain.hasConstrainSingle) } @Test fun chainPlusSingleCreatesCombinedMessage() { val chain = linearMessageChainOf(PlainText("o")).plus(PlainText("fo")) assertIs<CombinedMessage>(chain) assertEquals(linearMessageChainOf(PlainText("o")), chain.element) assertEquals(PlainText("fo"), chain.tail) assertEquals(false, chain.hasConstrainSingle) } private fun createTestInstance(): CombinedMessage { return CombinedMessage( buildMessageChain { +PlainText("bar") +emptyMessageChain() +buildMessageChain { +AtAll +PlainText("zoo") } }, buildMessageChain { +buildMessageChain { +At(2) } +At(1) }, false ) } private fun createComplexCombined() = CombinedMessage( PlainText("foo"), createTestInstance(), false ) private val complexCombined = createComplexCombined() /////////////////////////////////////////////////////////////////////////// // properties /////////////////////////////////////////////////////////////////////////// @Test fun sizeTest() { assertEquals(0, CombinedMessage(messageChainOf(), messageChainOf(), false).size) assertEquals( 0, CombinedMessage(messageChainOf(), CombinedMessage(messageChainOf(), messageChainOf(), false), false).size ) createTestInstance().run { assertEquals(5, size) assertFalse { slowList.isInitialized() } } } @Test fun slowListTest() { assertEquals(messageChainOf(), CombinedMessage(messageChainOf(), messageChainOf(), false).slowList.value) assertEquals( listOf( PlainText("foo"), PlainText("bar"), AtAll, PlainText("zoo"), At(2), At(1), ), complexCombined.slowList.value.toList() ) } @Test fun consistencyTest() { val first = createTestInstance() val second = createTestInstance() assertEquals(first, second) assertEquals(first.hashCode(), second.hashCode()) assertEquals(first.toString(), second.toString()) assertEquals(first.contentToString(), second.contentToString()) assertEquals(first.size, second.size) } /////////////////////////////////////////////////////////////////////////// // functions /////////////////////////////////////////////////////////////////////////// @Test fun subList() { assertEquals( createComplexCombined().slowList.value.subList(2, 4), complexCombined.subList(2, 4) ) assertFalse { complexCombined.slowList.isInitialized() } complexCombined.slowList.value // initialize assertEquals( createComplexCombined().slowList.value.subList(2, 4), complexCombined.subList(2, 4) ) } /////////////////////////////////////////////////////////////////////////// // visiting /////////////////////////////////////////////////////////////////////////// @Test fun acceptChildrenTest() { val list = buildList { complexCombined.acceptChildren(object : MessageVisitorUnit() { override fun visitMessage(message: Message, data: Unit) { add(message) super.visitMessage(message, data) } }) } assertEquals( listOf( PlainText("foo"), createTestInstance() ), list ) } @OptIn(MessageChainConstructor::class) @Test fun acceptChildrenRecursiveTest() { val list = buildList { complexCombined.accept(object : RecursiveMessageVisitor<Unit>() { override fun visitMessage(message: Message, data: Unit) { println("visitMessage: (${message::class.simpleName}) $message") super.visitMessage(message, data) } override fun visitSingleMessage(message: SingleMessage, data: Unit) { add(message) super.visitSingleMessage(message, data) } }) } assertEquals( listOf( PlainText("foo"), PlainText("bar"), AtAll, PlainText("zoo"), At(2), At(1), ), list ) } @Test fun hierarchicalIterator() { assertEquals( listOf( PlainText("foo"), PlainText("bar"), AtAll, PlainText("zoo"), At(2), At(1), ), complexCombined.iterator().asSequence().toList() ) assertFalse { complexCombined.slowList.isInitialized() } } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/ConstrainSingleHelperTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.utils.safeCast import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue internal class ConstrainSingleHelperTest { @Test fun linearPlains() { val list = listOf(PlainText("1"), PlainText("2")) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(list, value) assertFalse { hasConstrainSingle } } } @Test fun linearNonConstrains() { val list = listOf(PlainText("1"), At(2)) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(list, value) assertFalse { hasConstrainSingle } } } @Test fun hierarchicalNonConstrains() { val list = listOf(PlainText("1"), (PlainText("1") + At(2))) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(listOf(PlainText("1"), PlainText("1"), At(2)), value) assertFalse { hasConstrainSingle } } } @Test fun singleConstrain() { val list = listOf(Dice(2)) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(list, value) assertTrue { hasConstrainSingle } } } @Test fun linearDuplicatedConstrains() { val list = listOf(Dice(2), Dice(3)) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(1, value.size) assertEquals(Dice(3), value.first()) assertTrue { hasConstrainSingle } } } @Test fun linearMultipleDuplicatedConstrains() { val list = listOf(PlainText("a"), LightApp("aa"), Dice(2), Dice(3), LightApp("bb"), PlainText("c")) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(1, value.size) assertEquals(listOf(LightApp("bb")), value) assertTrue { hasConstrainSingle } } } @Test fun hierarchicalDuplicatedConstrains() { val list = listOf( MySingle(1), // before first MessageContent that can be replaced by the last ConstrainSingle PlainText("a"), LightApp("aa"), LinearMessageChainImpl.create( // without processing ConstrainSingle listOf( PlainText("ab"), LightApp("aab"), Dice(5), Dice(3), LightApp("bbb"), PlainText("cb") ), true ), Dice(3), LightApp("bb"), // last ConstrainSingle PlainText("c") ) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(2, value.size) assertEquals(listOf(MySingle(1), LightApp("bb")), value) assertTrue { hasConstrainSingle } } } @Test fun hierarchicalDuplicatedConstrains3() { val list = listOf( PlainText("a"), LightApp("aa"), LinearMessageChainImpl.create( // without processing ConstrainSingle listOf( PlainText("ab"), LightApp("aab"), Dice(5), Dice(3), LightApp("bbb"), PlainText("cb") ), true ), Dice(3), MySingle(1), // after first MessageContent that can be replaced by the last ConstrainSingle LightApp("bb"), // last ConstrainSingle PlainText("c") ) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(2, value.size) assertEquals(listOf(LightApp("bb"), MySingle(1)), value) assertTrue { hasConstrainSingle } } } @Test fun hierarchicalDuplicatedConstrains2() { val list = listOf( PlainText("a"), LightApp("aa"), Dice(3), LightApp("bb"), PlainText("c"), LinearMessageChainImpl.create( // without processing ConstrainSingle listOf( PlainText("a"), LightApp("aa"), Dice(2), MySingle(1), // after first MessageContent that can be replaced by the last ConstrainSingle Dice(3), LightApp("foo"), // last ConstrainSingle PlainText("c") ), true ), ) ConstrainSingleHelper.constrainSingleMessages(list.asSequence()).run { assertEquals(2, value.size) assertEquals(listOf(LightApp("foo"), MySingle(1)), value) assertTrue { hasConstrainSingle } } } } private class MySingle( val value: Int ) : MessageMetadata, ConstrainSingle { override val key: MessageKey<MySingle> get() = Key override fun toString(): String { return "MySingle@${this.hashCode()}" } override fun equals(other: Any?): Boolean { return other is MySingle && other.value == this.value } override fun hashCode(): Int { return value } private object Key : AbstractMessageKey<MySingle>({ it.safeCast() }) } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/ConstrainSingleTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.utils.safeCast import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertSame import kotlin.test.assertTrue internal class TestConstrainSingleMessage : ConstrainSingle, Any() { companion object Key : AbstractMessageKey<TestConstrainSingleMessage>({ it.safeCast() }) override fun toString(): String = "<TestConstrainSingleMessage#${super.hashCode()}>" override fun contentToString(): String = "" override val key: MessageKey<TestConstrainSingleMessage> get() = Key } internal class ConstrainSingleTest { @Test fun testSinglePlusChain() { val result = PlainText("te") + buildMessageChain { add(TestConstrainSingleMessage()) add("st") } assertEquals(3, result.size) assertEquals(result.contentToString(), "test") } @Test fun testSinglePlusChainConstrain() { val chain = buildMessageChain { add(TestConstrainSingleMessage()) add("st") } val result = TestConstrainSingleMessage() + chain assertEquals(chain, result) assertEquals(2, result.size) assertEquals(result.contentToString(), "st") assertTrue { result.first() is TestConstrainSingleMessage } } @Test fun testChainPlusSingle() { val new = TestConstrainSingleMessage() val result = buildMessageChain { add(" ") add(Face(Face.OK)) add(TestConstrainSingleMessage()) add( PlainText("ss") + " " ) } + buildMessageChain { add(PlainText("p ")) add(new) add(PlainText("test")) } assertEquals(7, result.size) assertEquals(" [OK]ss p test", result.contentToString()) result as LinearMessageChainImpl assertSame(new, result.delegate.toTypedArray()[2]) } @Test // net.mamoe.mirai/message/data/MessageChain.kt:441 fun testConstrainSingleInSequence() { val last = TestConstrainSingleMessage() val sequence: Sequence<SingleMessage> = sequenceOf( TestConstrainSingleMessage(), TestConstrainSingleMessage(), last ) val (result, has) = ConstrainSingleHelper.constrainSingleMessages(sequence) assertTrue { has } assertEquals(result.count(), 1) assertSame(result.single(), last) } @Test // net.mamoe.mirai/message/data/MessageChain.kt:441 fun testConstrainSingleOrderInSequence() { val last = TestConstrainSingleMessage() val sequence: Sequence<SingleMessage> = sequenceOf( TestConstrainSingleMessage(), // last should replace here PlainText("test"), TestConstrainSingleMessage(), last ) val (result, has) = ConstrainSingleHelper.constrainSingleMessages(sequence) assertTrue { has } assertEquals(result.count(), 2) assertSame(result.first(), last) } @Test fun testConversions() { val lastSingle = TestConstrainSingleMessage() val list: List<SingleMessage> = listOf( PlainText("test"), TestConstrainSingleMessage(), TestConstrainSingleMessage(), PlainText("foo"), TestConstrainSingleMessage(), lastSingle ) // Collection<SingleMessage>.asMessageChain() assertEquals("test${lastSingle}foo", list.toMessageChain().toString()) // Collection<Message>.asMessageChain() @Suppress("USELESS_CAST") assertEquals( "test${lastSingle}foo", list.map { it as Message }.toMessageChain().toString() ) // Iterable<SingleMessage>.asMessageChain() assertEquals("test${lastSingle}foo", list.asIterable().toMessageChain().toString()) // Iterable<Message>.asMessageChain() @Suppress("USELESS_CAST") assertEquals( "test${lastSingle}foo", list.map { it as Message }.asIterable().toMessageChain().toString() ) // Sequence<SingleMessage>.asMessageChain() assertEquals("test${lastSingle}foo", list.asSequence().toMessageChain().toString()) // Sequence<Message>.asMessageChain() @Suppress("USELESS_CAST") assertEquals( "test${lastSingle}foo", list.map { it as Message }.asSequence().toMessageChain().toString() ) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/ImageTest.kt ================================================ /* * 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_API_USAGE") package net.mamoe.mirai.message.data import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith internal fun String.autoHexToBytes(): ByteArray = this.replace("\n", "").replace(" ", "").asSequence().chunked(2).map { (it[0].toString() + it[1]).toUByte(16).toByte() }.toList().toByteArray() internal class ImageTest { @Test fun testHexDigitToByte() { assertEquals(0xf, 'f'.hexDigitToByte()) assertEquals(0x1, '1'.hexDigitToByte()) assertEquals(0x0, '0'.hexDigitToByte()) assertFailsWith<IllegalArgumentException> { 'g'.hexDigitToByte() } } @Test fun testCalculateImageMd5ByImageId() { assertEquals( "01E9451B-70ED-EAE3-B37C-101F1EEBF5B5".filterNot { it == '-' }.autoHexToBytes().contentToString(), Image.calculateImageMd5ByImageId("{01E9451B-70ED-EAE3-B37C-101F1EEBF5B5}.mirai").contentToString() ) assertEquals( "f8f1ab55-bf8e-4236-b55e-955848d7069f".filterNot { it == '-' }.autoHexToBytes().contentToString(), Image.calculateImageMd5ByImageId("/f8f1ab55-bf8e-4236-b55e-955848d7069f").contentToString() ) assertEquals( "BFB7027B9354B8F899A062061D74E206".filterNot { it == '-' }.autoHexToBytes().contentToString(), Image.calculateImageMd5ByImageId("/000000000-3814297509-BFB7027B9354B8F899A062061D74E206").contentToString() ) } // `/f8f1ab55-bf8e-4236-b55e-955848d7069f` 或 `/000000000-3814297509-BFB7027B9354B8F899A062061D74E206` } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/LinearMessageChainImplTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.message.data.visitor.MessageVisitorUnit import net.mamoe.mirai.message.data.visitor.acceptChildren import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs internal class LinearMessageChainImplTest { private val complexLinearChain = buildMessageChain { +PlainText("foo") +buildMessageChain { +PlainText("bar") +emptyMessageChain() +buildMessageChain { +AtAll +PlainText("zoo") } } +buildMessageChain { +buildMessageChain { +At(2) } +At(1) } } @Test fun buildMessageChainCreatesLinearMessageChainImpl() { assertIs<LinearMessageChainImpl>(complexLinearChain) } @Test fun hierarchicalIterator() { assertEquals( listOf( PlainText("foo"), PlainText("bar"), AtAll, PlainText("zoo"), At(2), At(1), ), complexLinearChain.iterator().asSequence().toList() ) } @Test fun sizeTest() { assertEquals(0, messageChainOf().size) assertEquals(6, complexLinearChain.size) assertEquals(6, complexLinearChain.count()) } @Test fun acceptChildrenTest() { val list = buildList { complexLinearChain.acceptChildren(object : MessageVisitorUnit() { override fun visitMessage(message: Message, data: Unit) { add(message) super.visitMessage(message, data) } }) } assertEquals( listOf( PlainText("foo"), PlainText("bar"), AtAll, PlainText("zoo"), At(2), At(1), ), list ) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/MessageChainBuilderTest.kt ================================================ /* * Copyright 2019-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.message.data import kotlin.test.Test import kotlin.test.assertEquals internal class MessageChainBuilderTest { @Test fun testConcat() { val chain = buildMessageChain { +"test" +" " +PlainText("foo") +" " +(PlainText("bar") + " goo ") buildMessageChain { +"1" +"2" +"3" }.joinTo(this) } assertEquals("test foo bar goo 123", chain.toString()) } @Test fun testConstrain() { val lastSingle = TestConstrainSingleMessage() val chain = buildMessageChain { +"test" +TestConstrainSingleMessage() +TestConstrainSingleMessage() +PlainText("foo") +TestConstrainSingleMessage() +lastSingle } assertEquals(chain.size, 3) assertEquals("test${lastSingle}foo", chain.toString()) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/MessageChainImmutableTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") package net.mamoe.mirai.message.data import kotlin.test.Test import kotlin.test.assertFails internal open class MessageChainImmutableTest { @Test fun `LinearMessageChainImpl is immutable`() { runCheck( messageChainOf( AtAll, PlainText("Hello!"), At(114514), ) as java.util.List<SingleMessage> ) } @Test fun `CombinedMessage is immutable`() { runCheck( (AtAll + PlainText("Hello!")) as java.util.List<SingleMessage>, ) } private fun runCheck(chain: java.util.List<SingleMessage>) { assertFails { chain.set(0, AtAll) } assertFails { chain.remove(0) } assertFails { chain.clear() } assertFails { chain.add(PlainText("Hey Hey!")) } assertFails { chain.iterator().remove() } assertFails { chain.iterator().also { it.next() }.remove() } assertFails { chain.listIterator().remove() } assertFails { chain.listIterator().also { it.next() }.remove() } } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/MessageChainImplTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import kotlin.test.Test import kotlin.test.assertIs internal class MessageChainImplTest { @OptIn(MessageChainConstructor::class) @Test fun allInternalImplementationsOfMessageChainAreMessageChainImpl() { assertIs<AbstractMessageChain>(CombinedMessage(AtAll, AtAll, false)) assertIs<AbstractMessageChain>(emptyMessageChain()) val linear = LinearMessageChainImpl.create(listOf(AtAll), true) assertIs<LinearMessageChainImpl>(linear) assertIs<AbstractMessageChain>(linear) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/MessageKeyTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.utils.safeCast import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertSame private open class TestStandaloneConstrainSingleMessage : ConstrainSingle, MessageContent { companion object Key : AbstractMessageKey<TestStandaloneConstrainSingleMessage>({ it.safeCast() }) override fun toString(): String = "<TestStandaloneConstrainSingleMessage#${super.hashCode()}>" override fun contentToString(): String = "" override val key: MessageKey<TestStandaloneConstrainSingleMessage> get() = Key } private class TestPolymorphicConstrainSingleMessage : ConstrainSingle, TestStandaloneConstrainSingleMessage(), MessageContent { companion object Key : AbstractPolymorphicMessageKey<TestStandaloneConstrainSingleMessage, TestPolymorphicConstrainSingleMessage>( TestStandaloneConstrainSingleMessage, { it.safeCast() } ) override fun toString(): String = "<TestPolymorphicConstrainSingleMessage#${super.hashCode()}>" override fun contentToString(): String = "" override val key: MessageKey<TestPolymorphicConstrainSingleMessage> get() = Key } private class TestPolymorphicConstrainSingleMessageOverridingMessageContent : ConstrainSingle, MessageContent, TestStandaloneConstrainSingleMessage() { companion object Key : AbstractPolymorphicMessageKey<MessageContent, TestPolymorphicConstrainSingleMessageOverridingMessageContent>( MessageContent, { it.safeCast() } ) override fun toString(): String = "<TestPolymorphicConstrainSingleMessageOverridingMessageContent#${super.hashCode()}>" override fun contentToString(): String = "" override val key: MessageKey<TestPolymorphicConstrainSingleMessageOverridingMessageContent> get() = Key } internal class MessageKeyTest { @Test fun `test polymorphism get`() { val constrainSingle: TestStandaloneConstrainSingleMessage val chain = buildMessageChain { +TestStandaloneConstrainSingleMessage() +PlainText("test") +TestStandaloneConstrainSingleMessage() +PlainText("test") +TestStandaloneConstrainSingleMessage().also { constrainSingle = it } } assertEquals(constrainSingle, chain[MessageContent]) assertEquals(constrainSingle, chain[TestStandaloneConstrainSingleMessage]) } @Test fun `test polymorphism override base`() { val constrainSingle: TestPolymorphicConstrainSingleMessage val chain = buildMessageChain { +TestStandaloneConstrainSingleMessage() +PlainText("test") +TestStandaloneConstrainSingleMessage() +PlainText("test") +TestPolymorphicConstrainSingleMessage().also { constrainSingle = it } } assertEquals(constrainSingle, chain[MessageContent]) assertEquals(constrainSingle, chain[TestPolymorphicConstrainSingleMessage]) } @Test fun `test polymorphism override message content`() { val constrainSingle: TestPolymorphicConstrainSingleMessageOverridingMessageContent val chain = buildMessageChain { +TestStandaloneConstrainSingleMessage() +PlainText("test") +TestStandaloneConstrainSingleMessage() +PlainText("test") +TestPolymorphicConstrainSingleMessageOverridingMessageContent().also { constrainSingle = it } } assertEquals(constrainSingle, chain[MessageContent]) assertEquals<Any?>(constrainSingle, chain[TestStandaloneConstrainSingleMessage]) assertEquals(1, chain.size) } @Test fun `can get from MessageChain`() { val cons = TestStandaloneConstrainSingleMessage() assertSame(cons, messageChainOf(cons)[TestStandaloneConstrainSingleMessage]) } @Test fun `can get from MessageChain2`() { val cons = TestStandaloneConstrainSingleMessage() assertSame(cons, messageChainOf(PlainText("test"), AtAll, cons)[TestStandaloneConstrainSingleMessage]) } @Test fun `get can be null`() { assertEquals(null, messageChainOf()[TestStandaloneConstrainSingleMessage]) } @Test fun `get can be null2`() { assertEquals(null, messageChainOf(PlainText("test"), AtAll)[TestStandaloneConstrainSingleMessage]) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/MessageUtilsTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.runTest import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue internal class MessageUtilsTest { @Test fun testIsContentEmpty() { assertTrue { emptyMessageChain().isContentEmpty() } assertTrue { buildMessageChain { }.isContentEmpty() } assertTrue { PlainText("").isContentEmpty() } assertTrue { (PlainText("") + PlainText("")).isContentEmpty() } assertTrue { buildMessageChain { append(PlainText("")); append(PlainText("")) }.isContentEmpty() } } @Test fun `flow toMessageChain`() = runTest { assertEquals(messageChainOf(PlainText("1")), flowOf(PlainText("1")).toMessageChain()) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/MessageVisitorTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.message.data import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.message.data.visitor.AbstractMessageVisitor import net.mamoe.mirai.message.data.visitor.accept import net.mamoe.mirai.utils.MiraiExperimentalApi import kotlin.test.Test import kotlin.test.assertContentEquals internal class MessageVisitorTest { object GetCalledMethodNames : AbstractMessageVisitor<Unit, Array<String>>() { override fun visitMessage(message: Message, data: Unit): Array<String> { return arrayOf("visitMessage") } override fun visitSingleMessage(message: SingleMessage, data: Unit): Array<String> { return arrayOf("visitSingleMessage") + super.visitSingleMessage(message, data) } override fun visitMessageChain(messageChain: MessageChain, data: Unit): Array<String> { return arrayOf("visitMessageChain") + super.visitMessageChain(messageChain, data) } override fun visitCombinedMessage(message: CombinedMessage, data: Unit): Array<String> { return arrayOf("visitCombinedMessage") + super.visitCombinedMessage(message, data) } override fun visitMessageContent(message: MessageContent, data: Unit): Array<String> { return arrayOf("visitMessageContent") + super.visitMessageContent(message, data) } override fun visitMessageMetadata(message: MessageMetadata, data: Unit): Array<String> { return arrayOf("visitMessageMetadata") + super.visitMessageMetadata(message, data) } override fun visitMessageOrigin(message: MessageOrigin, data: Unit): Array<String> { return arrayOf("visitMessageOrigin") + super.visitMessageOrigin(message, data) } override fun visitMessageSource(message: MessageSource, data: Unit): Array<String> { return arrayOf("visitMessageSource") + super.visitMessageSource(message, data) } override fun visitQuoteReply(message: QuoteReply, data: Unit): Array<String> { return arrayOf("visitQuoteReply") + super.visitQuoteReply(message, data) } override fun visitCustomMessageMetadata(message: CustomMessageMetadata, data: Unit): Array<String> { return arrayOf("visitCustomMessageMetadata") + super.visitCustomMessageMetadata(message, data) } override fun visitShowImageFlag(message: ShowImageFlag, data: Unit): Array<String> { return arrayOf("visitShowImageFlag") + super.visitShowImageFlag(message, data) } override fun visitPlainText(message: PlainText, data: Unit): Array<String> { return arrayOf("visitPlainText") + super.visitPlainText(message, data) } override fun visitAt(message: At, data: Unit): Array<String> { return arrayOf("visitAt") + super.visitAt(message, data) } override fun visitAtAll(message: AtAll, data: Unit): Array<String> { return arrayOf("visitAtAll") + super.visitAtAll(message, data) } @Suppress("DEPRECATION_ERROR") override fun visitVoice(message: Voice, data: Unit): Array<String> { return arrayOf("visitVoice") + super.visitVoice(message, data) } override fun visitAudio(message: Audio, data: Unit): Array<String> { return arrayOf("visitAudio") + super.visitAudio(message, data) } override fun visitHummerMessage(message: HummerMessage, data: Unit): Array<String> { return arrayOf("visitHummerMessage") + super.visitHummerMessage(message, data) } override fun visitFlashImage(message: FlashImage, data: Unit): Array<String> { return arrayOf("visitFlashImage") + super.visitFlashImage(message, data) } override fun visitPokeMessage(message: PokeMessage, data: Unit): Array<String> { return arrayOf("visitPokeMessage") + super.visitPokeMessage(message, data) } override fun visitVipFace(message: VipFace, data: Unit): Array<String> { return arrayOf("visitVipFace") + super.visitVipFace(message, data) } override fun visitMarketFace(message: MarketFace, data: Unit): Array<String> { return arrayOf("visitMarketFace") + super.visitMarketFace(message, data) } override fun visitDice(message: Dice, data: Unit): Array<String> { return arrayOf("visitDice") + super.visitDice(message, data) } override fun visitRockPaperScissors(message: RockPaperScissors, data: Unit): Array<String> { return arrayOf("visitRockPaperScissors") + super.visitRockPaperScissors(message, data) } override fun visitFace(message: Face, data: Unit): Array<String> { return arrayOf("visitFace") + super.visitFace(message, data) } override fun visitSuperFace(message: SuperFace, data: Unit): Array<String> { return arrayOf("visitSuperFace") + super.visitSuperFace(message, data) } override fun visitFileMessage(message: FileMessage, data: Unit): Array<String> { return arrayOf("visitFileMessage") + super.visitFileMessage(message, data) } override fun visitImage(message: Image, data: Unit): Array<String> { return arrayOf("visitImage") + super.visitImage(message, data) } override fun visitForwardMessage(message: ForwardMessage, data: Unit): Array<String> { return arrayOf("visitForwardMessage") + super.visitForwardMessage(message, data) } override fun visitMusicShare(message: MusicShare, data: Unit): Array<String> { return arrayOf("visitMusicShare") + super.visitMusicShare(message, data) } override fun visitUnsupportedMessage(message: UnsupportedMessage, data: Unit): Array<String> { return arrayOf("visitUnsupportedMessage") + super.visitUnsupportedMessage(message, data) } override fun visitRichMessage(message: RichMessage, data: Unit): Array<String> { return arrayOf("visitRichMessage") + super.visitRichMessage(message, data) } override fun visitServiceMessage(message: ServiceMessage, data: Unit): Array<String> { return arrayOf("visitServiceMessage") + super.visitServiceMessage(message, data) } override fun visitSimpleServiceMessage(message: SimpleServiceMessage, data: Unit): Array<String> { return arrayOf("visitSimpleServiceMessage") + super.visitSimpleServiceMessage(message, data) } override fun visitLightApp(message: LightApp, data: Unit): Array<String> { return arrayOf("visitLightApp") + super.visitLightApp(message, data) } override fun visitAbstractServiceMessage(message: AbstractServiceMessage, data: Unit): Array<String> { return arrayOf("visitAbstractServiceMessage") + super.visitAbstractServiceMessage(message, data) } } @OptIn(MessageChainConstructor::class) @Test fun visitMessageChain() { assertContentEquals( arrayOf( "visitMessageChain", "visitMessage", ), messageChainOf(PlainText("1")).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitMessageChain", "visitMessage", ), emptyMessageChain().accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitCombinedMessage", "visitMessageChain", "visitMessage", ), CombinedMessage(AtAll, AtAll, false).accept(GetCalledMethodNames) ) } @Test fun visitSingleMessageContent() { assertContentEquals( arrayOf( "visitMessage", ), object : Message { @Suppress("RedundantOverride") // false positive override fun toString(): String = super.toString() override fun contentToString(): String = super.toString() }.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitSingleMessage", "visitMessage", ), object : SingleMessage { @Suppress("RedundantOverride") // false positive override fun toString(): String = super.toString() override fun contentToString(): String = super.toString() }.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitMessageContent", "visitSingleMessage", "visitMessage", ), object : MessageContent { @Suppress("RedundantOverride") // false positive override fun toString(): String = super.toString() override fun contentToString(): String = super.toString() }.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitPlainText", "visitMessageContent", "visitSingleMessage", "visitMessage", ), PlainText("1").accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitAt", "visitMessageContent", "visitSingleMessage", "visitMessage", ), At(1).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitAtAll", "visitMessageContent", "visitSingleMessage", "visitMessage", ), AtAll.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitVoice", "visitMessageContent", "visitSingleMessage", "visitMessage", ), @Suppress("DEPRECATION_ERROR") Voice("", byteArrayOf(), 1, 1, "").accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitAudio", "visitMessageContent", "visitSingleMessage", "visitMessage", ), object : OfflineAudio { override val filename: String get() = "" override val fileMd5: ByteArray get() = byteArrayOf() override val fileSize: Long get() = 1 override val codec: AudioCodec get() = AudioCodec.AMR override val extraData: ByteArray? get() = null override fun toString(): String = "test" }.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitFlashImage", "visitHummerMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), FlashImage(createImage()).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitSuperFace", "visitHummerMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), SuperFace.from(Face(Face.DA_CALL)).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitPokeMessage", "visitHummerMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), PokeMessage.BiXin.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitVipFace", "visitHummerMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), VipFace(VipFace.AiXin, 1).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitMarketFace", "visitHummerMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), object : MarketFace { override val name: String get() = "f" override val id: Int get() = 1 override fun toString(): String = "ok" }.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitDice", "visitMarketFace", "visitHummerMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), Dice(1).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitRockPaperScissors", "visitMarketFace", "visitHummerMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), RockPaperScissors.PAPER.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitFace", "visitMessageContent", "visitSingleMessage", "visitMessage", ), Face(Face.AI_NI).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitFileMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), object : FileMessage { override val id: String get() = "" override val internalId: Int get() = 1 override val name: String get() = "" override val size: Long get() = 1 override suspend fun toAbsoluteFile(contact: FileSupported): Nothing = throw UnsupportedOperationException() override fun toString(): String = "" }.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitImage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), createImage().accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitForwardMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), ForwardMessage(listOf(), "", "", "", "", listOf()).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitMusicShare", "visitMessageContent", "visitSingleMessage", "visitMessage", ), MusicShare( kind = MusicKind.NeteaseCloudMusic, title = "ファッション", summary = "rinahamu/Yunomi", brief = "", jumpUrl = "https://music.163.com/song/1338728297/?userid=324076307", pictureUrl = "https://p2.music.126.net/y19E5SadGUmSR8SZxkrNtw==/109951163785855539.jpg", musicUrl = "https://music.163.com/song/media/outer/url?id=1338728297&userid=324076307" ).accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitUnsupportedMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), object : UnsupportedMessage { override val struct: ByteArray get() = byteArrayOf() override fun toString(): String = "test" }.accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitSimpleServiceMessage", "visitServiceMessage", "visitRichMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), SimpleServiceMessage(1, "str").accept(GetCalledMethodNames) ) assertContentEquals( arrayOf( "visitLightApp", "visitRichMessage", "visitMessageContent", "visitSingleMessage", "visitMessage", ), LightApp("str").accept(GetCalledMethodNames) ) } } private fun createImage(): Image { return object : Image { override val imageId: String get() = "{88914B32-B758-74ED-B00D-CAA6D2A5D7F6}.jpg" override val width: Int get() = 1 override val height: Int get() = 1 override val size: Long get() = 1 override val imageType: ImageType get() = ImageType.APNG override fun toString(): String = "test" override fun contentToString(): String = "test" @MiraiExperimentalApi override fun appendMiraiCodeTo(builder: StringBuilder) { } } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/message.data/TestMessageChainDelegate.kt ================================================ /* * Copyright 2019-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.message.data import kotlin.test.Test import kotlin.test.assertEquals internal class TestMessageChainDelegate { private val message = messageChainOf(AtAll, PlainText("test")) @Test fun testGetValue() { @Suppress("UNUSED_VARIABLE") val atAll: AtAll by message val plain: PlainText by message assertEquals(plain.content, "test") } @Test fun testOrNull() { @Suppress("UNUSED_VARIABLE") val atAll: AtAll? by message.orNull() val plain: PlainText? by message.orNull() assertEquals(plain!!.content, "test") } @Test fun testOrElse() { val message = messageChainOf() @Suppress("UNUSED_VARIABLE") val plain: PlainText? by message.orElse { PlainText("test") } assertEquals(plain!!.content, "test") } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/package.kt ================================================ /* * Copyright 2019-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 ================================================ FILE: mirai-core-api/src/commonTest/kotlin/test/TestDSL.kt ================================================ /* * Copyright 2019-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("NOTHING_TO_INLINE") package net.mamoe.mirai.test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue @DslMarker internal annotation class TestDSL @TestDSL fun Boolean.shouldBeTrue() = assertTrue { this } @TestDSL fun Boolean.shouldBeFalse() = assertFalse { this } @TestDSL infix fun <E> E.shouldBeEqualTo(another: E) = assertEquals(another, this) @TestDSL infix fun <E> E.shouldNotBeEqualTo(another: E) = assertNotEquals(another, this) ================================================ FILE: mirai-core-api/src/commonTest/kotlin/utils/DeviceInfoTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.json.Json import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertTrue class CommonDeviceInfoTest { @Test fun `DeviceInfo_random with custom Random is stable`() { val time = currentTimeMillis() assertEquals(DeviceInfo.random(Random(time)), DeviceInfo.random(Random(time))) } class HexStringTest { @Test fun `can serialize as String`() { val hexString = DeviceInfoManager.HexString(byteArrayOf(1, 2)) val string = Json.encodeToString(DeviceInfoManager.HexString.serializer(), hexString) assertEquals("\"0102\"", string) } @Test fun `can deserialize from String`() { val hex = Json.decodeFromString(DeviceInfoManager.HexString.serializer(), "\"0102\"") assertContentEquals(byteArrayOf(1, 2), hex.data) } } @Test fun `can serialize and deserialize v3`() { val device = DeviceInfo.random() assertEquals(device, DeviceInfoManager.deserialize(DeviceInfoManager.serialize(device))) } @Test fun `current version pretty print preview`() { val device = DeviceInfo.random() val text = DeviceInfoManager.serialize(device, Json { prettyPrint = true }) println(text) /* { "deviceInfoVersion": 2, "data": { "display": "MIRAI.868912.001", "product": "mirai", "device": "mirai", "board": "mirai", "brand": "mamoe", "model": "mirai", "bootloader": "unknown", "fingerprint": "mamoe/mirai/mirai:10/MIRAI.200122.001/6174518:user/release-keys", "bootId": "500E9D6F-1A76-4ED0-20F3-66A5B20C7049", "procVersion": "Linux version 3.0.31-r35YRB94 (android-build@xxx.xxx.xxx.xxx.com)", "baseBand": "", "version": { "incremental": "5891938", "release": "10", "codename": "REL" }, "simInfo": "T-Mobile", "osType": "android", "macAddress": "02:00:00:00:00:00", "wifiBSSID": "02:00:00:00:00:00", "wifiSSID": "<unknown ssid>", "imsiMd5": "d1ead821747a3ad3f8f3784fafa3b954", "imei": "155970036849035", "apn": "wifi" } } */ val element = DeviceInfoManager.toJsonElement(device) assertEquals(3, element.jsonObject["deviceInfoVersion"]!!.jsonPrimitive.content.toInt()) val imsiMd5 = element.jsonObject["data"]!!.jsonObject["imsiMd5"]!!.jsonPrimitive.content assertEquals( device.imsiMd5.toUHexString("").lowercase(), imsiMd5 ) assertTrue { imsiMd5 matches Regex("""[a-z0-9]+""") } } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/utils/JvmDeviceInfoTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.json.Json import net.mamoe.mirai.utils.DeviceInfo.Companion.loadAsDeviceInfo import net.mamoe.mirai.utils.DeviceInfoManager.Version.Companion.trans import org.junit.jupiter.api.io.TempDir import java.io.File import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue class JvmDeviceInfoTest { @TempDir lateinit var dir: File @Test fun `can write and read`() { val device = DeviceInfo.random() val file = dir.resolve("device.json") file.writeText(DeviceInfoManager.serialize(device)) assertEquals(device, file.loadAsDeviceInfo()) } @Test fun `can write read legacy v1`() { val device = DeviceInfo.random() val file = dir.resolve("device.json") val encoded = Json.encodeToString( DeviceInfoManager.V1.serializer(), DeviceInfoManager.V1( display = device.display, product = device.product, device = device.device, board = device.board, brand = device.brand, model = device.model, bootloader = device.bootloader, fingerprint = device.fingerprint, bootId = device.bootId, procVersion = device.procVersion, baseBand = device.baseBand, version = device.version, simInfo = device.simInfo, osType = device.osType, macAddress = device.macAddress, wifiBSSID = device.wifiBSSID, wifiSSID = device.wifiSSID, imsiMd5 = device.imsiMd5, imei = device.imei, apn = device.apn, ) ) file.writeText(encoded) val fileDeviceInfo = file.loadAsDeviceInfo() assertTrue { isSameType(device, fileDeviceInfo) } assertTrue { device.display.contentEquals(fileDeviceInfo.display) } assertTrue { device.product.contentEquals(fileDeviceInfo.product) } assertTrue { device.device.contentEquals(fileDeviceInfo.device) } assertTrue { device.board.contentEquals(fileDeviceInfo.board) } assertTrue { device.brand.contentEquals(fileDeviceInfo.brand) } assertTrue { device.model.contentEquals(fileDeviceInfo.model) } assertTrue { device.bootloader.contentEquals(fileDeviceInfo.bootloader) } assertTrue { device.fingerprint.contentEquals(fileDeviceInfo.fingerprint) } assertTrue { device.bootId.contentEquals(fileDeviceInfo.bootId) } assertTrue { device.procVersion.contentEquals(fileDeviceInfo.procVersion) } assertTrue { device.baseBand.contentEquals(fileDeviceInfo.baseBand) } assertEquals(device.version, fileDeviceInfo.version) assertTrue { device.simInfo.contentEquals(fileDeviceInfo.simInfo) } assertTrue { device.osType.contentEquals(fileDeviceInfo.osType) } assertTrue { device.macAddress.contentEquals(fileDeviceInfo.macAddress) } assertTrue { device.wifiBSSID.contentEquals(fileDeviceInfo.wifiBSSID) } assertTrue { device.wifiSSID.contentEquals(fileDeviceInfo.wifiSSID) } assertTrue { device.imsiMd5.contentEquals(fileDeviceInfo.imsiMd5) } assertEquals(device.imei, fileDeviceInfo.imei) assertTrue { device.apn.contentEquals(fileDeviceInfo.apn) } assertTrue { device.androidId.size == fileDeviceInfo.androidId.size } } @Test fun `can write and read legacy v2`() { val device = DeviceInfo.random() val file = dir.resolve("device.json") val encoded = Json.encodeToString( DeviceInfoManager.Wrapper.serializer(DeviceInfoManager.V2.serializer()), DeviceInfoManager.Wrapper( 2, DeviceInfoManager.V2( display = device.display.decodeToString(), product = device.product.decodeToString(), device = device.device.decodeToString(), board = device.board.decodeToString(), brand = device.brand.decodeToString(), model = device.model.decodeToString(), bootloader = device.bootloader.decodeToString(), fingerprint = device.fingerprint.decodeToString(), bootId = device.bootId.decodeToString(), procVersion = device.procVersion.decodeToString(), baseBand = DeviceInfoManager.HexString(device.baseBand), version = device.version.trans(), simInfo = device.simInfo.decodeToString(), osType = device.osType.decodeToString(), macAddress = device.macAddress.decodeToString(), wifiBSSID = device.wifiBSSID.decodeToString(), wifiSSID = device.wifiSSID.decodeToString(), imsiMd5 = DeviceInfoManager.HexString(device.imsiMd5), imei = device.imei, apn = device.apn.decodeToString(), ) ) ) file.writeText(encoded) val fileDeviceInfo = file.loadAsDeviceInfo() assertTrue { isSameType(device, fileDeviceInfo) } assertTrue { device.display.contentEquals(fileDeviceInfo.display) } assertTrue { device.product.contentEquals(fileDeviceInfo.product) } assertTrue { device.device.contentEquals(fileDeviceInfo.device) } assertTrue { device.board.contentEquals(fileDeviceInfo.board) } assertTrue { device.brand.contentEquals(fileDeviceInfo.brand) } assertTrue { device.model.contentEquals(fileDeviceInfo.model) } assertTrue { device.bootloader.contentEquals(fileDeviceInfo.bootloader) } assertTrue { device.fingerprint.contentEquals(fileDeviceInfo.fingerprint) } assertTrue { device.bootId.contentEquals(fileDeviceInfo.bootId) } assertTrue { device.procVersion.contentEquals(fileDeviceInfo.procVersion) } assertTrue { device.baseBand.contentEquals(fileDeviceInfo.baseBand) } assertEquals(device.version, fileDeviceInfo.version) assertTrue { device.simInfo.contentEquals(fileDeviceInfo.simInfo) } assertTrue { device.osType.contentEquals(fileDeviceInfo.osType) } assertTrue { device.macAddress.contentEquals(fileDeviceInfo.macAddress) } assertTrue { device.wifiBSSID.contentEquals(fileDeviceInfo.wifiBSSID) } assertTrue { device.wifiSSID.contentEquals(fileDeviceInfo.wifiSSID) } assertTrue { device.imsiMd5.contentEquals(fileDeviceInfo.imsiMd5) } assertEquals(device.imei, fileDeviceInfo.imei) assertTrue { device.apn.contentEquals(fileDeviceInfo.apn) } assertTrue { device.androidId.size == fileDeviceInfo.androidId.size } } @Test fun `can write and read v3`() { val device = DeviceInfo.random() val file = dir.resolve("device.json") val encoded = Json.encodeToString( DeviceInfoManager.Wrapper.serializer(DeviceInfoManager.V3.serializer()), DeviceInfoManager.Wrapper( 3, DeviceInfoManager.V3( display = device.display.decodeToString(), product = device.product.decodeToString(), device = device.device.decodeToString(), board = device.board.decodeToString(), brand = device.brand.decodeToString(), model = device.model.decodeToString(), bootloader = device.bootloader.decodeToString(), fingerprint = device.fingerprint.decodeToString(), bootId = device.bootId.decodeToString(), procVersion = device.procVersion.decodeToString(), baseBand = DeviceInfoManager.HexString(device.baseBand), version = device.version.trans(), simInfo = device.simInfo.decodeToString(), osType = device.osType.decodeToString(), macAddress = device.macAddress.decodeToString(), wifiBSSID = device.wifiBSSID.decodeToString(), wifiSSID = device.wifiSSID.decodeToString(), imsiMd5 = DeviceInfoManager.HexString(device.imsiMd5), imei = device.imei, apn = device.apn.decodeToString(), androidId = device.androidId.decodeToString() ) ) ) file.writeText(encoded) val fileDeviceInfo = file.loadAsDeviceInfo() assertEquals(device, fileDeviceInfo) } } ================================================ FILE: mirai-core-api/src/commonTest/kotlin/utils/TimeTest.kt ================================================ /* * 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.utils import kotlin.test.Test import kotlin.test.assertTrue import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime import kotlin.time.toDuration internal class TimeTest { // since 2.7 @OptIn(ExperimentalTime::class) @Test fun testTimeHumanReadable2() { val time0 = (1.toDuration(DurationUnit.DAYS) + 20.toDuration(DurationUnit.HOURS) + 15.toDuration(DurationUnit.MINUTES) + 2057.toDuration(DurationUnit.MILLISECONDS)).inWholeMilliseconds println(time0.millisToHumanReadableString()) assertTrue { time0.millisToHumanReadableString() == "1d 20h 15min 2.057s" } val time1 = (1.toDuration(DurationUnit.DAYS) + 59.toDuration(DurationUnit.MINUTES)).inWholeMilliseconds println(time1.millisToHumanReadableString()) assertTrue { time1.millisToHumanReadableString() == "1d 59min 0.0s" } } } ================================================ FILE: mirai-core-api/src/commonTest/resources/log4j.properties ================================================ # # 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/dev/LICENSE # log4j.rootLogger=INFO,stdout log4j.logger.mirai=INFO log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t%m%n ================================================ FILE: mirai-core-api/src/jvmMain/kotlin/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai ================================================ FILE: mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.TxCaptchaHelper.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.* import java.io.IOException import java.net.HttpURLConnection import java.net.URL internal abstract class TxCaptchaHelper { private val newClient: Boolean = true private lateinit var queue: Job private var latestDisplay = "Sending request..." abstract fun onComplete(ticket: String) abstract fun updateDisplay(msg: String) fun start(scope: CoroutineScope, url: String) { val url0 = url.replace("ssl.captcha.qq.com", "txhelper.glitch.me") val queue = scope.launch { updateDisplay(latestDisplay) while (isActive) { try { val response: String = runInterruptible(Dispatchers.IO) { httpGet(url0) } if (response.startsWith("请在")) { if (response != latestDisplay) { latestDisplay = response updateDisplay(response) } } else { onComplete(response) return@launch } } catch (e: Throwable) { updateDisplay(e.toString().also { latestDisplay = it }) } delay(1000) } } if (newClient) { queue.invokeOnCompletion { } } this.queue = queue } fun dispose() { queue.cancel() } @Throws(IOException::class) private fun httpGet(url: String): String { val connection = URL(url).openConnection() as? HttpURLConnection ?: throw UnsupportedOperationException("Could not create HttpURLConnection") connection.requestMethod = "GET" connection.connectTimeout = 5000 connection.readTimeout = 5000 connection.instanceFollowRedirects = true connection.doInput = true val result = connection.inputStream.use { it.readBytes() }.decodeToString() connection.disconnect() return result } } ================================================ FILE: mirai-core-api/src/jvmMain/kotlin/utils/LoginSolver.jvm.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runInterruptible import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext import net.mamoe.mirai.Bot import net.mamoe.mirai.auth.QRCodeLoginListener import net.mamoe.mirai.network.NoStandardInputForCaptchaException import net.mamoe.mirai.utils.StandardCharImageLoginSolver.Companion.createBlocking import java.awt.Image import java.awt.image.BufferedImage import java.io.File import javax.imageio.ImageIO import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine internal actual object PlatformLoginSolverImplementations { actual val isSliderCaptchaSupported: Boolean get() = default!!.isSliderCaptchaSupported actual val default: LoginSolver? by lazy { StandardCharImageLoginSolver() } } /** * CLI 环境 [LoginSolver]. 将验证码图片转为字符画并通过 `output` 输出, [input] 获取用户输入. * * 使用字符图片展示验证码, 使用 [input] 获取输入, 使用 [loggerSupplier] 输出 * * @see createBlocking */ public class StandardCharImageLoginSolver @JvmOverloads constructor( input: suspend () -> String = { readlnOrNull() ?: @OptIn(MiraiInternalApi::class) throw NoStandardInputForCaptchaException() }, /** * 为 `null` 时使用 [Bot.logger] */ private val loggerSupplier: (bot: Bot) -> MiraiLogger = { it.logger } ) : LoginSolver() { public constructor( input: suspend () -> String = { readlnOrNull() ?: @OptIn(MiraiInternalApi::class) throw NoStandardInputForCaptchaException() }, overrideLogger: MiraiLogger? ) : this(input, { overrideLogger ?: it.logger }) private val input: suspend () -> String = suspend { withContext(Dispatchers.IO) { input() } } override val isSliderCaptchaSupported: Boolean get() = true override fun createQRCodeLoginListener(bot: Bot): QRCodeLoginListener { return object : QRCodeLoginListener { private var tmpFile: File? = null override val qrCodeMargin: Int get() = 1 override val qrCodeSize: Int get() = 1 override fun onFetchQRCode(bot: Bot, data: ByteArray) { val logger = loggerSupplier(bot) logger.info { "[QRCodeLogin] 已获取登录二维码,请在手机 QQ 使用账号 ${bot.id} 扫码" } logger.info { "[QRCodeLogin] Fetched login qrcode, please scan via qq android with account ${bot.id}." } try { val tempFile: File if (tmpFile == null) { tempFile = File.createTempFile( "mirai-qrcode-${bot.id}-${currentTimeSeconds()}", ".png" ).apply { deleteOnExit() } tempFile.createNewFile() tmpFile = tempFile } else { tempFile = tmpFile!! } tempFile.writeBytes(data) logger.info { "[QRCodeLogin] 将会显示二维码图片,若看不清图片,请查看文件 ${tempFile.toPath().toUri()}" } logger.info { "[QRCodeLogin] Displaying qrcode image. If not clear, view file ${tempFile.toPath().toUri()}." } } catch (e: Exception) { logger.warning("[QRCodeLogin] 无法写出二维码图片. 请尽量关闭终端个性化样式后扫描二维码字符图片", e) logger.warning( "[QRCodeLogin] Failed to export qrcode image. Please try to scan the char-image after disabling custom terminal style.", e ) } data.inputStream().use { stream -> try { val isCacheEnabled = ImageIO.getUseCache() try { ImageIO.setUseCache(false) val img = ImageIO.read(stream) if (img == null) { logger.warning { "[QRCodeLogin] 无法创建字符图片. 请查看文件" } logger.warning { "[QRCodeLogin] Failed to create char-image. Please see the file." } } else { logger.info { "[QRCodeLogin] \n" + img.renderQRCode() } } } finally { ImageIO.setUseCache(isCacheEnabled) } } catch (throwable: Throwable) { logger.warning("[QRCodeLogin] 创建字符图片时出错. 请查看文件.", throwable) logger.warning("[QRCodeLogin] Failed to create char-image. Please see the file.", throwable) } } } override fun onStateChanged(bot: Bot, state: QRCodeLoginListener.State) { val logger = loggerSupplier(bot) logger.info { buildString { append("[QRCodeLogin] ") when (state) { QRCodeLoginListener.State.WAITING_FOR_SCAN -> append("等待扫描二维码中") QRCodeLoginListener.State.WAITING_FOR_CONFIRM -> append("扫描完成,请在手机 QQ 确认登录") QRCodeLoginListener.State.CANCELLED -> append("已取消登录,将会重新获取二维码") QRCodeLoginListener.State.TIMEOUT -> append("扫描超时,将会重新获取二维码") QRCodeLoginListener.State.CONFIRMED -> append("已确认登录") else -> append("default state") } } } logger.info { buildString { append("[QRCodeLogin] ") when (state) { QRCodeLoginListener.State.WAITING_FOR_SCAN -> append("Waiting for scanning qrcode.") QRCodeLoginListener.State.WAITING_FOR_CONFIRM -> append("Scan complete. Please confirm login.") QRCodeLoginListener.State.CANCELLED -> append("Login cancelled, we will try to fetch qrcode again.") QRCodeLoginListener.State.TIMEOUT -> append("Timeout scanning, we will try to fetch qrcode again.") QRCodeLoginListener.State.CONFIRMED -> append("Login confirmed.") else -> append("default state") } } } if (state == QRCodeLoginListener.State.CONFIRMED) { kotlin.runCatching { tmpFile?.delete() }.onFailure { logger.warning(it) } } } } } override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String? = loginSolverLock.withLock { val logger = loggerSupplier(bot) runInterruptible(Dispatchers.IO) { val tempFile: File = File.createTempFile("tmp", ".png").apply { deleteOnExit() } tempFile.createNewFile() logger.info { "[PicCaptcha] 需要图片验证码登录, 验证码为 4 字母" } logger.info { "[PicCaptcha] Picture captcha required. Captcha consists of 4 letters." } try { tempFile.writeBytes(data) logger.info { "[PicCaptcha] 将会显示字符图片. 若看不清字符图片, 请查看文件 file://${tempFile.absolutePath}" } logger.info { "[PicCaptcha] Displaying char-image. If not clear, view file file://${tempFile.absolutePath}." } } catch (e: Exception) { logger.warning("[PicCaptcha] 无法写出验证码文件, 请尝试查看以上字符图片", e) logger.warning("[PicCaptcha] Failed to export captcha image. Please see the char-image.", e) } tempFile.inputStream().use { stream -> try { val img = ImageIO.read(stream) if (img == null) { logger.warning { "[PicCaptcha] 无法创建字符图片. 请查看文件" } logger.warning { "[PicCaptcha] Failed to create char-image. Please see the file." } } else { logger.info { "[PicCaptcha] \n" + img.createCharImg() } } } catch (throwable: Throwable) { logger.warning("[PicCaptcha] 创建字符图片时出错. 请查看文件.", throwable) logger.warning("[PicCaptcha] Failed to create char-image. Please see the file.", throwable) } } } logger.info { "[PicCaptcha] 请输入 4 位字母验证码. 若要更换验证码, 请直接回车" } logger.info { "[PicCaptcha] Please type 4-letter captcha. Press Enter directly to refresh." } return input().takeUnless { it.isEmpty() || it.length != 4 }.also { logger.info { "[PicCaptcha] 正在提交 $it..." } logger.info { "[PicCaptcha] Submitting $it..." } } } override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String = loginSolverLock.withLock { val logger = loggerSupplier(bot) logger.info { "[SliderCaptcha] 需要滑动验证码, 请按照以下链接的步骤完成滑动验证码, 然后输入获取到的 ticket" } logger.info { "[SliderCaptcha] Slider captcha required. Please solve the captcha with following link. Type ticket here after completion." } logger.info { "[SliderCaptcha] @see https://github.com/project-mirai/mirai-login-solver-selenium" } logger.info { "[SliderCaptcha] @see https://docs.mirai.mamoe.net/mirai-login-solver-selenium/" } logger.info { "[SliderCaptcha] 或者输入 helper 来使用 TxCaptchaHelper 完成滑动验证码" } logger.info { "[SliderCaptcha] Or type helper to resolve slider captcha with TxCaptchaHelper.apk" } logger.warning { "[SliderCaptcha] TxCaptchaHelper 的在线服务疑似被屏蔽,可能无法使用。TxCaptchaHelper 现已无法满足登录QQ机器人,请在以下链接下载全新的验证器" } logger.warning { "[SliderCaptcha] The service of TxCaptchaHelper might be blocked. We recommend you to download the new login solver plugin in below link." } logger.warning { "[SliderCaptcha] @see https://github.com/KasukuSakura/mirai-login-solver-sakura" } logger.info { "[SliderCaptcha] Captcha link: $url" } suspend fun runTxCaptchaHelper(): String { logger.info { "[SliderCaptcha] @see https://github.com/mzdluo123/TxCaptchaHelper" } return coroutineScope { suspendCoroutine { coroutine -> val helper = object : TxCaptchaHelper() { override fun onComplete(ticket: String) { coroutine.resume(ticket) } override fun updateDisplay(msg: String) { logger.info(msg) } } helper.start(this, url) } } } return input().also { if (it == "TxCaptchaHelper" || it == "`TxCaptchaHelper`" || it == "helper" || it == "`helper`") { return runTxCaptchaHelper() } logger.info { "[SliderCaptcha] 正在提交中..." } logger.info { "[SliderCaptcha] Submitting..." } } } @Suppress("DuplicatedCode") override suspend fun onSolveDeviceVerification( bot: Bot, requests: DeviceVerificationRequests ): DeviceVerificationResult { val logger = loggerSupplier(bot) requests.sms?.let { req -> solveSms(logger, req)?.let { return it } } requests.fallback?.let { fallback -> solveFallback(logger, fallback.url) return fallback.solved() } error("User rejected SMS login while fallback login method not available.") } private suspend fun solveSms( logger: MiraiLogger, request: DeviceVerificationRequests.SmsRequest ): DeviceVerificationResult? = loginSolverLock.withLock { val countryCode = request.countryCode val phoneNumber = request.phoneNumber if (countryCode != null && phoneNumber != null) { logger.info("一条短信验证码将发送到你的手机 (+$countryCode) $phoneNumber. 运营商可能会收取正常短信费用, 是否继续? 输入 yes 继续, 输入其他终止并尝试其他验证方式.") logger.info( "A verification code will be send to your phone (+$countryCode) $phoneNumber, which may be charged normally, do you wish to continue? Type yes to continue, type others to cancel and try other methods." ) } else { logger.info("一条短信验证码将发送到你的手机 (无法获取到手机号码). 运营商可能会收取正常短信费用, 是否继续? 输入 yes 继续, 输入其他终止并尝试其他验证方式.") logger.info( "A verification code will be send to your phone (failed to get phone number), " + "which may be charged normally by your carrier, " + "do you wish to continue? Type yes to continue, type others to cancel and try other methods." ) } val answer = input().trim() return if (answer.equals("yes", ignoreCase = true)) { logger.info("Attempting SMS verification.") request.requestSms() logger.info("Please enter code: ") val code = input().trim() logger.info("Continuing with code '$code'.") request.solved(code) } else { logger.info("Cancelled.") null } } @Deprecated( "Please use onSolveDeviceVerification instead", replaceWith = ReplaceWith("onSolveDeviceVerification(bot, url, null)"), level = DeprecationLevel.WARNING ) override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String = solveFallback(loggerSupplier(bot), url) private suspend fun solveFallback( logger: MiraiLogger, url: String ): String { return loginSolverLock.withLock { logger.info { "[UnsafeLogin] 当前登录环境不安全,服务器要求账户认证。请在 QQ 浏览器打开 $url 并完成验证后输入任意字符。" } logger.info { "[UnsafeLogin] Account verification required by the server. Please open $url in QQ browser and complete challenge, then type anything here to submit." } input().also { logger.info { "[UnsafeLogin] 正在提交中..." } logger.info { "[UnsafeLogin] Submitting..." } } } } public companion object { /** * 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver] * * @param input 将在协程 IO 池执行, 可以有阻塞调用 */ @JvmStatic public fun createBlocking(input: () -> String, output: MiraiLogger?): StandardCharImageLoginSolver { return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } }, output) } /** * 创建 Java 阻塞版 [input] 的 [StandardCharImageLoginSolver] * * @param input 将在协程 IO 池执行, 可以有阻塞调用 */ @JvmStatic public fun createBlocking(input: () -> String): StandardCharImageLoginSolver { return StandardCharImageLoginSolver({ withContext(Dispatchers.IO) { input() } }) } } } private val loginSolverLock = Mutex() /** * @author NaturalHG */ private fun BufferedImage.createCharImg(outputWidth: Int = 100, ignoreRate: Double = 0.95): String { val newHeight = (this.height * (outputWidth.toDouble() / this.width)).toInt() val tmp = this.getScaledInstance(outputWidth, newHeight, Image.SCALE_SMOOTH) val image = BufferedImage(outputWidth, newHeight, BufferedImage.TYPE_INT_ARGB) val g2d = image.createGraphics() g2d.drawImage(tmp, 0, 0, null) fun gray(rgb: Int): Int { val r = rgb and 0xff0000 shr 16 val g = rgb and 0x00ff00 shr 8 val b = rgb and 0x0000ff return (r * 30 + g * 59 + b * 11 + 50) / 100 } fun grayCompare(g1: Int, g2: Int): Boolean = kotlin.math.min(g1, g2).toDouble() / kotlin.math.max(g1, g2) >= ignoreRate val background = gray(image.getRGB(0, 0)) return buildString(capacity = height) { val lines = mutableListOf<StringBuilder>() var minXPos = outputWidth var maxXPos = 0 for (y in 0 until image.height) { val builderLine = StringBuilder() for (x in 0 until image.width) { val gray = gray(image.getRGB(x, y)) if (grayCompare(gray, background)) { builderLine.append(" ") } else { builderLine.append("#") if (x < minXPos) { minXPos = x } if (x > maxXPos) { maxXPos = x } } } if (builderLine.toString().isBlank()) { continue } lines.add(builderLine) } for (line in lines) { append(line.substring(minXPos, maxXPos)).append("\n") } } } @Suppress("LocalVariableName", "SpellCheckingInspection") private fun BufferedImage.renderQRCode( blackPlaceholder: String = " ", whitePlaceholder: String = " ", doColorSwitch: Boolean = true, ): String { var lastStatus: Boolean? = null fun isBlackBlock(rgb: Int): Boolean { val r = rgb and 0xff0000 shr 16 val g = rgb and 0x00ff00 shr 8 val b = rgb and 0x0000ff return r < 10 && g < 10 && b < 10 } val sb = StringBuilder() sb.append("\n") val BLACK = "\u001b[30;40m" val WHITE = "\u001b[97;107m" val RESET = "\u001b[0m" for (y in 0 until height) { for (x in 0 until width) { val rgbcolor = getRGB(x, y) val crtStatus = isBlackBlock(rgbcolor) if (doColorSwitch && crtStatus != lastStatus) { lastStatus = crtStatus sb.append( if (crtStatus) BLACK else WHITE ) } sb.append( if (crtStatus) blackPlaceholder else whitePlaceholder ) } if (doColorSwitch) { sb.append(RESET) } sb.append("\n") lastStatus = null } if (doColorSwitch) { sb.append(RESET) } return sb.toString() } ================================================ FILE: mirai-core-api/src/jvmMain/kotlin/utils/PlatformLogger.jvm.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("MemberVisibilityCanBePrivate") package net.mamoe.mirai.utils import java.text.DateFormat import java.text.SimpleDateFormat import java.time.LocalDateTime import java.time.format.DateTimeFormatter import java.util.* /** * JVM 控制台日志实现 * * * 单条日志格式 (正则) 为: * ```regex * ^([\w-]*\s[\w:]*)\s(\w)\/(.*?):\s(.+)$ * ``` * 其中 group 分别为: 日期与时间, 严重程度, [identity], 消息内容. * * 示例: * ```log * 2020-05-21 19:51:09 V/Bot 123456789: Send: OidbSvc.0x88d_7 * ``` * * 日期时间格式为 `yyyy-MM-dd HH:mm:ss`, * * 严重程度为 V, I, W, E. 分别对应 verbose, info, warning, error * * @param isColored 是否添加 ANSI 颜色 * * @see MiraiLogger.create * @see SingleFileLogger 使用单一文件记录日志 * @see DirectoryLogger 在一个目录中按日期存放文件记录日志, 自动清理过期日志 */ @MiraiInternalApi public actual open class PlatformLogger constructor( // same as StdoutLogger but doesn't matter public override val identity: String? = "Mirai", /** * 日志输出. 不会自动添加换行 */ public open val output: (String) -> Unit, public val isColored: Boolean = true ) : MiraiLoggerPlatformBase() { // PlatformLogger("") resolves to this one. public actual constructor(identity: String?) : this(identity, ::println) public constructor(identity: String?, output: (String) -> Unit = ::println) : this(identity, output, true) /** * 输出一条日志. [message] 末尾可能不带换行符. */ protected open fun printLog(message: String?, priority: SimpleLogger.LogPriority) { @OptIn(MiraiExperimentalApi::class) if (isColored) output("${priority.color}$currentTimeFormatted ${priority.simpleName}/$identity: $message${Color.RESET}") else output("$currentTimeFormatted ${priority.simpleName}/$identity: $message") } /** * 获取指定 [SimpleLogger.LogPriority] 的颜色 */ @MiraiExperimentalApi protected open val SimpleLogger.LogPriority.color: Color get() = when (this) { SimpleLogger.LogPriority.VERBOSE -> Color.RESET SimpleLogger.LogPriority.INFO -> Color.LIGHT_GREEN SimpleLogger.LogPriority.WARNING -> Color.LIGHT_YELLOW SimpleLogger.LogPriority.ERROR -> Color.LIGHT_RED SimpleLogger.LogPriority.DEBUG -> Color.LIGHT_CYAN } public override fun verbose0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.VERBOSE) public override fun verbose0(message: String?, e: Throwable?) { if (e != null) verbose((message ?: e.toString()) + "\n${e.stackTraceToString()}") else verbose(message.toString()) } public override fun info0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.INFO) public override fun info0(message: String?, e: Throwable?) { if (e != null) info((message ?: e.toString()) + "\n${e.stackTraceToString()}") else info(message.toString()) } public override fun warning0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.WARNING) public override fun warning0(message: String?, e: Throwable?) { if (e != null) warning((message ?: e.toString()) + "\n${e.stackTraceToString()}") else warning(message.toString()) } public override fun error0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.ERROR) public override fun error0(message: String?, e: Throwable?) { if (e != null) error((message ?: e.toString()) + "\n${e.stackTraceToString()}") else error(message.toString()) } public override fun debug0(message: String?): Unit = printLog(message, SimpleLogger.LogPriority.DEBUG) public override fun debug0(message: String?, e: Throwable?) { if (e != null) debug((message ?: e.toString()) + "\n${e.stackTraceToString()}") else debug(message.toString()) } @Deprecated("Use formatter instead.", level = DeprecationLevel.HIDDEN) protected open val timeFormat: DateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.SIMPLIFIED_CHINESE) @Suppress("NewApi") protected open val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") private val currentTimeFormatted @Suppress("NewApi") // we are in JVM module get() = formatter.format(LocalDateTime.now()) @MiraiExperimentalApi("This is subject to change.") protected enum class Color(private val format: String) { RESET("\u001b[0m"), WHITE("\u001b[30m"), RED("\u001b[31m"), EMERALD_GREEN("\u001b[32m"), GOLD("\u001b[33m"), BLUE("\u001b[34m"), PURPLE("\u001b[35m"), GREEN("\u001b[36m"), GRAY("\u001b[90m"), LIGHT_RED("\u001b[91m"), LIGHT_GREEN("\u001b[92m"), LIGHT_YELLOW("\u001b[93m"), LIGHT_BLUE("\u001b[94m"), LIGHT_PURPLE("\u001b[95m"), LIGHT_CYAN("\u001b[96m") ; override fun toString(): String = format } } ================================================ FILE: mirai-core-api/src/jvmMain/kotlin/utils/SingleFileLogger.jvm.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("unused") package net.mamoe.mirai.utils import java.io.File /** * 将日志写入('append')到特定文件. * * @see PlatformLogger 查看格式信息 */ @OptIn(MiraiInternalApi::class) public actual class SingleFileLogger actual constructor( identity: String, file: File ) : MiraiLogger, PlatformLogger(identity, { file.appendText(it + "\n") }) { // Implementation notes v2.5.0: // Extending `PlatformLogger` for binary compatibility for JVM target only. // See actual declaration in androidMain for a better impl (implements `MiraiLogger` only) public actual constructor(identity: String) : this(identity, File("$identity-${getCurrentDate()}.log")) init { file.createNewFile() require(file.isFile) { "Log file must be a file: $file" } require(file.canWrite()) { "Log file must be write: $file" } } } ================================================ FILE: mirai-core-api/src/jvmTest/kotlin/message/data/MessageChainImmutableTestJdk8.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") package net.mamoe.mirai.message.data import kotlin.test.Test import kotlin.test.assertFails internal class MessageChainImmutableTestJdk8 : MessageChainImmutableTest() { @Test fun `access with JDK8 lambda`() { val chain = messageChainOf(AtAll, PlainText("Hello!"), At(114514)) as java.util.List<SingleMessage> assertFails { chain.removeIf { true } } assertFails { chain.replaceAll { AtAll } } assertFails { chain.sort { o1, o2 -> o1.javaClass.name.compareTo(o2.javaClass.name) } } assertFails { chain.clear() } } } ================================================ FILE: mirai-core-api/src/jvmTest/kotlin/package.kt ================================================ /* * Copyright 2019-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 ================================================ FILE: mirai-core-mock/README.md ================================================ # mirai-core-mock mirai 模拟环境测试框架 > 模拟环境目前仅支持 JVM -------------- # src 架构 - `contact` - 与 `mirai-core-api` 架构一致 - `database` - 数据库, 用于存储一些临时的零碎数据 - `resserver` - 资源服务 - `userprofile` - 与 `UserProfile` 相关的一些服务 - `utils` - 工具类 # test 架构 - `<toplevel>` 与 mirai-core-api 关系不大或者一些独立的组件的测试 - `.mock` 模拟的各个部分的测试, 每个测试都继承 `MockBotTestBase` ================================================ FILE: mirai-core-mock/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ plugins { kotlin("jvm") kotlin("plugin.serialization") `maven-publish` id("me.him188.kotlin-jvm-blocking-bridge") } version = Versions.project description = "Mirai core mock testing framework" kotlin { explicitApiWarning() optInForAllSourceSets("net.mamoe.mirai.LowLevelApi") optInForAllSourceSets("net.mamoe.mirai.utils.MiraiInternalApi") optInForAllSourceSets("net.mamoe.mirai.utils.MiraiExperimentalApi") } dependencies { api(project(":mirai-core-api")) implementation(project(":mirai-core-utils")) implementation(project(":mirai-core")) implementation(`ktor-server-core`) implementation(`ktor-server-netty`) implementation(`java-in-memory-file-system`) implementation(`kotlin-jvm-blocking-bridge`) } tasks.register("buildRuntimeClasspath") { // this task is used for mirai-mock-framework (external) dependsOn("assemble") doLast { val out = temporaryDir.also { it.mkdirs() }.resolve("classpath.txt") out.bufferedWriter().use { writer -> configurations["runtimeClasspath"].files.forEach { f -> writer.write(f.absolutePath) writer.write("\n") } } } } configurePublishing("mirai-core-mock") tasks.named("shadowJar") { enabled = false } ================================================ FILE: mirai-core-mock/src/MockActions.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.contact.MockFriend import net.mamoe.mirai.mock.contact.MockNormalMember import net.mamoe.mirai.mock.contact.MockStranger import net.mamoe.mirai.mock.contact.MockUserOrBot import net.mamoe.mirai.mock.database.removeMessageInfo import net.mamoe.mirai.mock.utils.NudgeDsl import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.mock.utils.nudged0 import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.cast @JvmBlockingBridge public object MockActions { /** * 修改 [MockUserOrBot.nick] 并广播相关事件 (如 [FriendNickChangedEvent]) */ @OptIn(MiraiInternalApi::class) @JvmStatic public suspend fun fireNickChanged(target: MockUserOrBot, value: String) { when (target) { is MockFriend -> { val ov = target.nick target.mockApi.nick = value FriendNickChangedEvent(target, ov, target.nick).broadcast() } is MockStranger -> { target.mockApi.nick = value // TODO: StrangerNickChangedEvent } is MockNormalMember -> { val friend0 = target.bot.getFriend(target.id) if (friend0 != null) { return fireNickChanged(friend0, value) } target.mockApi.nick = value } is MockBot -> { target.nick = value } } } /** * 修改 [MockNormalMember.nameCard] 并广播 [MemberCardChangeEvent] */ @OptIn(MiraiInternalApi::class) @JvmStatic public suspend fun fireNameCardChanged(member: MockNormalMember, value: String) { val ov = member.nameCard member.mockApi.nameCard = value MemberCardChangeEvent(ov, value, member).broadcast() } /** * 修改 [MockNormalMember.specialTitle] 并广播 [MemberSpecialTitleChangeEvent] */ @OptIn(MiraiInternalApi::class) @JvmStatic public suspend fun fireSpecialTitleChanged(member: MockNormalMember, value: String) { val ov = member.specialTitle member.mockApi.specialTitle = value MemberSpecialTitleChangeEvent( ov, value, member, operator = member.group.owner.takeIf { it.id != member.bot.id }, ).broadcast() } /** * 修改一名成员的权限并广播 [MemberPermissionChangeEvent] */ @OptIn(MiraiInternalApi::class) @JvmStatic public suspend fun firePermissionChanged(member: MockNormalMember, perm: MemberPermission) { if (perm == MemberPermission.OWNER || member == member.group.owner) { error("Use group.changeOwner to modify group owner") } val ov = member.permission member.mockApi.permission = perm if (member.id == member.bot.id) { BotGroupPermissionChangeEvent(member.group, ov, perm) } else { MemberPermissionChangeEvent(member, ov, perm) }.broadcast() } /** * 令 [operator] 撤回一条消息 * * @param operator 当 [operator] 为 null 时代表是发送者自己撤回 */ @JvmStatic public suspend fun fireMessageRecalled(chain: MessageChain, operator: User? = null) { return fireMessageRecalled(chain.source, operator) } /** * 令 [operator] 撤回一条消息 * * @param operator 当 [operator] 为 null 时代表是发送者自己撤回 */ @OptIn(MiraiInternalApi::class) @JvmStatic public suspend fun fireMessageRecalled(source: MessageSource, operator: User? = null) { fun notSupported(): Nothing = error("Unsupported message source kind: ${source.kind}: ${source.javaClass}") val bot: MockBot = when { source is OnlineMessageSource -> source.bot.mock() operator != null -> operator.bot.mock() else -> source.botOrNull?.mock() ?: error("Cannot find bot from source or operator") } val sourceKind = source.kind fun target(): ContactOrBot = when { source is OnlineMessageSource -> source.target source.targetId == bot.id -> bot sourceKind == MessageSourceKind.FRIEND -> bot.getFriendOrFail(source.targetId) sourceKind == MessageSourceKind.STRANGER -> bot.getStrangerOrFail(source.targetId) sourceKind == MessageSourceKind.TEMP -> error("Cannot detect message target from TEMP source kind") sourceKind == MessageSourceKind.GROUP -> bot.getGroupOrFail(source.targetId) else -> notSupported() } fun sender(): ContactOrBot = when { source is OnlineMessageSource -> source.sender source.fromId == bot.id -> bot sourceKind == MessageSourceKind.FRIEND -> bot.getFriendOrFail(source.fromId) sourceKind == MessageSourceKind.STRANGER -> bot.getStrangerOrFail(source.fromId) sourceKind == MessageSourceKind.TEMP -> error("Cannot detect message sender from TEMP source kind") sourceKind == MessageSourceKind.GROUP -> throw AssertionError("Message from group") else -> notSupported() } fun subject(): Contact = when { source is OnlineMessageSource -> source.subject source.fromId == bot.id -> target() as Contact sourceKind == MessageSourceKind.GROUP -> target() as Contact else -> sender() as Contact } when (sourceKind) { MessageSourceKind.GROUP -> { val sender = sender() val group = subject() as Group val operator0 = when { operator === bot -> null operator === group.botAsMember -> null operator == null -> sender.cast() operator is Member -> operator else -> error("Provided operator $operator(${operator.javaClass}) not a member") } bot.msgDatabase.removeMessageInfo(source) MessageRecallEvent.GroupRecall( bot, sender.id, source.ids, source.internalIds, source.time, operator0, group, when (sender) { is Bot -> group.botAsMember else -> sender.cast() } ).broadcast() } MessageSourceKind.FRIEND -> { val subject = subject() as Friend bot.msgDatabase.removeMessageInfo(source) if (source.fromId == bot.id) { return // no event } MessageRecallEvent.FriendRecall(bot, source.ids, source.internalIds, source.time, subject.id, subject) .broadcast() } MessageSourceKind.TEMP -> { bot.mock().msgDatabase.removeMessageInfo(source) // TODO: event not available } MessageSourceKind.STRANGER -> { bot.mock().msgDatabase.removeMessageInfo(source) // TODO: event not available } else -> notSupported() } } /** * 令 [operator] 撤回一条消息 * * @param operator 当 [operator] 为 null 时代表是发送者自己撤回 */ @JvmStatic public suspend fun mockFireRecalled(receipt: MessageReceipt<*>, operator: User? = null) { return fireMessageRecalled(receipt.source, operator) } /** * 令 [actor] 戳一下 [actee] * * @param actor 发起戳一戳的人 * @param actee 被戳的人 */ @JvmStatic public suspend fun fireNudge(actor: MockUserOrBot, actee: MockUserOrBot, dsl: NudgeDsl) { actor.nudged0(actee, dsl) } } ================================================ FILE: mirai-core-mock/src/MockBot.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.mock import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.ContactList import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotAvatarChangedEvent import net.mamoe.mirai.event.events.BotOfflineEvent import net.mamoe.mirai.event.events.NewFriendRequestEvent import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.OnlineAudio import net.mamoe.mirai.mock.contact.* import net.mamoe.mirai.mock.database.MessageDatabase import net.mamoe.mirai.mock.resserver.TmpResourceServer import net.mamoe.mirai.mock.userprofile.UserProfileService import net.mamoe.mirai.mock.utils.AvatarGenerator import net.mamoe.mirai.mock.utils.NameGenerator import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.cast import kotlin.random.Random /** * 一个虚拟的机器人对象. 继承于 [Bot] * * @see MockBotFactory 构造 [MockBot] 的工厂, [MockBot] 的唯一构造方式 */ @Suppress("unused") @JvmBlockingBridge public interface MockBot : Bot, MockContactOrBot, MockUserOrBot { override val bot: MockBot get() = this /** * bot 昵称, 访问此字段时与 [nick] 一致 * 修改此字段时不会广播事件 */ @MockBotDSL public var nickNoEvent: String /** * bot 昵称 * * 修改此字段时会广播事件 */ override var nick: String /** * Bot 头像, 可自定义, 修改时会广播 [BotAvatarChangedEvent] */ @set:MockBotDSL override var avatarUrl: String /// Contact API override override fun getFriend(id: Long): MockFriend? = super.getFriend(id)?.cast() override fun getFriendOrFail(id: Long): MockFriend = super.getFriendOrFail(id).cast() override fun getGroup(id: Long): MockGroup? = super.getGroup(id)?.cast() override fun getGroupOrFail(id: Long): MockGroup = super.getGroupOrFail(id).cast() override fun getStranger(id: Long): MockStranger? = super.getStranger(id)?.cast() override fun getStrangerOrFail(id: Long): MockStranger = super.getStrangerOrFail(id).cast() override val groups: ContactList<MockGroup> override val friends: ContactList<MockFriend> override val strangers: ContactList<MockStranger> override val otherClients: ContactList<MockOtherClient> override val asFriend: MockFriend override val asStranger: MockStranger /// All mock api will not broadcast event public val nameGenerator: NameGenerator public val tmpResourceServer: TmpResourceServer public val msgDatabase: MessageDatabase public val userProfileService: UserProfileService /** @since 2.14.0 */ public val avatarGenerator: AvatarGenerator /// Mock Contact API @MockBotDSL public fun addGroup(id: Long, name: String): MockGroup @MockBotDSL public fun addGroup(id: Long, uin: Long, name: String): MockGroup @MockBotDSL public fun addFriend(id: Long, name: String): MockFriend @MockBotDSL public fun addStranger(id: Long, name: String): MockStranger /** * 将 [resource] 上传到 [临时资源服务器][tmpResourceServer], * 并返回一个 [OnlineAudio] 对象, 可用于测试语音接收 * * @see MockUser.says */ @MockBotDSL public suspend fun uploadOnlineAudio(resource: ExternalResource): OnlineAudio /** * 将 [resource] 上传到 [临时资源服务器][tmpResourceServer] * 并返回一个 [Image] 对象, 可用于测试图片接收 * * @see MockUser.says */ @MockBotDSL public suspend fun uploadMockImage(resource: ExternalResource): Image /** * 广播 [Bot] 掉线事件 */ @MockBotDSL public suspend fun broadcastOfflineEvent() { BotOfflineEvent.Dropped(this, java.net.SocketException("socket closed")).broadcast() } /** * 广播 [Bot] 头像更新事件 */ @MockBotDSL public suspend fun broadcastAvatarChangeEvent() { BotAvatarChangedEvent(this).broadcast() } /** * 广播新好友添加事件 * * @see NewFriendRequestEvent */ @MockBotDSL public suspend fun broadcastNewFriendRequestEvent( requester: Long, requesterNick: String, fromGroup: Long, message: String ): NewFriendRequestEvent { return NewFriendRequestEvent( this, eventId = Random.nextLong(), fromId = requester, fromGroupId = fromGroup, message = message, fromNick = requesterNick ).broadcast() } } ================================================ FILE: mirai-core-mock/src/MockBotDSL.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock @DslMarker public annotation class MockBotDSL ================================================ FILE: mirai-core-mock/src/MockBotFactory.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock import net.mamoe.mirai.BotFactory import net.mamoe.mirai.Mirai import net.mamoe.mirai.mock.database.MessageDatabase import net.mamoe.mirai.mock.internal.MockBotFactoryImpl import net.mamoe.mirai.mock.internal.MockMiraiImpl import net.mamoe.mirai.mock.resserver.TmpResourceServer import net.mamoe.mirai.mock.userprofile.UserProfileService import net.mamoe.mirai.mock.utils.AvatarGenerator import net.mamoe.mirai.mock.utils.NameGenerator import net.mamoe.mirai.utils.BotConfiguration public interface MockBotFactory : BotFactory { public interface BotBuilder { public fun id(value: Long): BotBuilder public fun nick(value: String): BotBuilder public fun configuration(value: BotConfiguration): BotBuilder public fun nameGenerator(value: NameGenerator): BotBuilder public fun tmpResourceServer(server: TmpResourceServer): BotBuilder public fun msgDatabase(db: MessageDatabase): BotBuilder public fun userProfileService(service: UserProfileService): BotBuilder public fun avatarGenerator(avatarGenerator: AvatarGenerator): BotBuilder public fun create(): MockBot public fun createNoInstanceRegister(): MockBot } public fun newMockBotBuilder(): BotBuilder @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") public companion object : MockBotFactory by MockBotFactoryImpl() { init { Mirai net.mamoe.mirai._MiraiInstance.set(MockMiraiImpl()) } @JvmStatic public fun initialize() { // noop } } } public inline fun MockBotFactory.BotBuilder.configuration( block: BotConfiguration.() -> Unit ): MockBotFactory.BotBuilder = configuration(BotConfiguration(block)) ================================================ FILE: mirai-core-mock/src/contact/MockAnonymousMember.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import net.mamoe.mirai.contact.AnonymousMember public interface MockAnonymousMember : AnonymousMember, MockMember ================================================ FILE: mirai-core-mock/src/contact/MockContact.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.mock.MockBotDSL @JvmBlockingBridge public interface MockContact : Contact, MockContactOrBot { public interface MockApi { public var avatarUrl: String } /** * 获取直接修改字段内容的 API, 通过该 API 修改的值都不会触发广播 */ @MockBotDSL public val mockApi: MockApi /** * 修改 [avatarUrl] 的地址, 同时会广播相关事件 (如果有) */ @MockBotDSL public fun changeAvatarUrl(newAvatar: String) } ================================================ FILE: mirai-core-mock/src/contact/MockContactOrBot.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.mock.MockBot public interface MockContactOrBot : ContactOrBot { override val bot: MockBot } ================================================ FILE: mirai-core-mock/src/contact/MockFriend.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotInvitedJoinGroupRequestEvent import net.mamoe.mirai.event.events.FriendAddEvent import net.mamoe.mirai.event.events.FriendInputStatusChangedEvent import net.mamoe.mirai.mock.MockBotDSL import kotlin.random.Random @JvmBlockingBridge public interface MockFriend : Friend, MockContact, MockUser, MockMsgSyncSupport { public interface MockApi : MockContact.MockApi { public val contact: MockFriend public var nick: String public var remark: String public var friendGroupId: Int public override var avatarUrl: String } /** * 获取直接修改字段内容的 API, 通过该 API 修改的值都不会触发广播 */ @MockBotDSL public override val mockApi: MockApi /** * 修改 nick 同时广播相关事件 */ override var nick: String /** * 修改 remark 同时广播相关事件 */ override var remark: String /** * 广播好友添加事件 */ @MockBotDSL public suspend fun broadcastFriendAddEvent(): FriendAddEvent { return FriendAddEvent(this).broadcast() } /** * 广播好友邀请 [bot] 加入一个群聊的事件 */ @MockBotDSL public suspend fun broadcastInviteBotJoinGroupRequestEvent( groupId: Long, groupName: String, ): BotInvitedJoinGroupRequestEvent { return BotInvitedJoinGroupRequestEvent( bot, Random.nextLong(), id, groupId, groupName, nick ).broadcast() } /** * 广播好友主动删除 [bot] 好友的事件 * * 即使该函数体实现为 [delete], 也请使用该方法广播 **bot 被好友删除**, * 以确保不会受到未来的事件架构变更带来的影响 */ @MockBotDSL public suspend fun broadcastFriendDelete() { delete() } /** * 广播好友输入状态改变事件 */ @MockBotDSL public suspend fun broadcastFriendInputStateChange(inputting: Boolean) { FriendInputStatusChangedEvent(this, inputting).broadcast() } } ================================================ FILE: mirai-core-mock/src/contact/MockGroup.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.ContactList import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.MemberJoinRequestEvent import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.MockBotDSL import net.mamoe.mirai.mock.contact.active.MockGroupActive import net.mamoe.mirai.mock.contact.announcement.MockAnnouncements import net.mamoe.mirai.mock.userprofile.MockMemberInfoBuilder import net.mamoe.mirai.utils.cast import kotlin.random.Random @JvmBlockingBridge public interface MockGroup : Group, MockContact, MockMsgSyncSupport { /** @see net.mamoe.mirai.IMirai.getUin */ public val uin: Long override val bot: MockBot override val members: ContactList<MockNormalMember> override val owner: MockNormalMember override val botAsMember: MockNormalMember override val avatarUrl: String override val announcements: MockAnnouncements override val active: MockGroupActive public interface MockApi : MockContact.MockApi { override var avatarUrl: String } override val mockApi: MockApi /** * 群荣耀, 可直接修改此属性, 修改此属性不会广播相关事件 * * @see changeHonorMember */ @MockBotDSL @Deprecated( "use active.changeHonorMember", ReplaceWith(".active.changeHonorMember(member, honorType)"), level = DeprecationLevel.ERROR ) public val honorMembers: MutableMap<GroupHonorType, MockNormalMember> /** * 更改拥有群荣耀的群成员. * * 会自动广播 [MemberHonorChangeEvent.Achieve] 和 [MemberHonorChangeEvent.Lose] 等相关事件. * * 此外如果 [honorType] 是 [GroupHonorType.TALKATIVE], * 会额外广播 [net.mamoe.mirai.event.events.GroupTalkativeChangeEvent]. * * 如果不需要广播事件, 可直接使用 [MockGroupActive.mockSetHonorHistory] */ public fun changeHonorMember(member: MockNormalMember, honorType: GroupHonorType) { active.changeHonorMember(member, honorType) } /** * 获取群控制面板 * * 注, 通过本属性获取的控制面板为原始数据存储面板, 修改并不会广播相关事件, 如果需要广播事件, * 请使用 [MockGroupControlPane.withActor] */ @MockBotDSL public val controlPane: MockGroupControlPane /** 添加一位成员, 该操作不会广播任何事件 * @see MockMemberInfoBuilder */ @MockBotDSL public fun appendMember(mockMember: MemberInfo): MockGroup // chain call /** * 添加一位成员, 该操作不会广播任何事件 * @see MockMemberInfoBuilder */ @MockBotDSL public fun addMember(mockMember: MemberInfo): MockNormalMember /** 添加一位成员, 该操作不会广播任何事件 */ @MockBotDSL public fun appendMember(uin: Long, nick: String): MockGroup = appendMember(MockMemberInfoBuilder.create { uin(uin).nick(nick) }) /** 添加一位成员, 该操作不会广播任何事件 * @see MockMemberInfoBuilder */ @MockBotDSL public fun addMember(uin: Long, nick: String): MockNormalMember = addMember(MockMemberInfoBuilder.create { uin(uin).nick(nick) }) /** * 修改群主, 该操作会广播群转让的相关事件 */ @MockBotDSL public suspend fun changeOwner(member: NormalMember) /** * 修改群主, 该操作不会广播任何事件 */ @MockBotDSL public fun changeOwnerNoEventBroadcast(member: NormalMember) /** * 创建新的匿名群成员. * * @param id 该匿名群成员的 id, 可自定义, 建议使用 ASCII 纯文本 */ @MockBotDSL public fun newAnonymous(nick: String, id: String): MockAnonymousMember override fun get(id: Long): MockNormalMember? override fun getOrFail(id: Long): MockNormalMember = super.getOrFail(id).cast() /** * 主动广播有新成员申请加入的事件 */ @MockBotDSL public suspend fun broadcastNewMemberJoinRequestEvent( requester: Long, requesterName: String, message: String, invitor: Long = 0L, ): MemberJoinRequestEvent { return MemberJoinRequestEvent( bot, Random.nextLong(), message, requester, this.id, this.name, requesterName, invitor.takeIf { it != 0L }, ).broadcast() } } ================================================ FILE: mirai-core-mock/src/contact/MockGroupControlPane.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact /** * 群设置面板, 如果是由 [withActor] 得到的面板在操作的同时会进行事件广播 * * 与 [MockGroup.settings] 不同的是, 该控制面板不会进行权限校检 */ public interface MockGroupControlPane { public val group: MockGroup /** * 如果为 [MockGroup.controlPane] 获得的原始控制面板, 此属性为 [MockGroup.botAsMember] * * @see withActor */ public val currentActor: MockNormalMember public var isAllowMemberInvite: Boolean public var isMuteAll: Boolean public var isAllowMemberFileUploading: Boolean public var isAnonymousChatAllowed: Boolean public var isAllowConfessTalk: Boolean public var groupName: String /** * 通过 [withActor] 得到的 [MockGroupControlPane] 在修改属性的同时会广播相关事件 */ public fun withActor(actor: MockNormalMember): MockGroupControlPane } ================================================ FILE: mirai-core-mock/src/contact/MockMember.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.mock.MockBotDSL import net.mamoe.mirai.mock.contact.active.MockMemberActive @JvmBlockingBridge public interface MockMember : Member, MockContact, MockUser { public interface MockApi : MockContact.MockApi { public val member: MockMember public var nick: String public var remark: String public var permission: MemberPermission } override val group: MockGroup /** * 获取直接修改字段内容的 API, 通过该 API 修改的值都不会触发广播 */ @MockBotDSL public override val mockApi: MockApi override val active: MockMemberActive } ================================================ FILE: mirai-core-mock/src/contact/MockMsgSyncSupport.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import net.mamoe.mirai.contact.OtherClient import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.mock.MockBotDSL public interface MockMsgSyncSupport : MockContact { /** * 广播消息同步事件 */ @MockBotDSL public suspend fun broadcastMsgSyncEvent(client: OtherClient, message: MessageChain, time: Int) } ================================================ FILE: mirai-core-mock/src/contact/MockNormalMember.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import kotlinx.coroutines.cancel import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotLeaveEvent import net.mamoe.mirai.event.events.MemberJoinEvent import net.mamoe.mirai.event.events.MemberLeaveEvent import net.mamoe.mirai.mock.MockBotDSL import net.mamoe.mirai.mock.utils.broadcastBlocking import java.util.concurrent.CancellationException @JvmBlockingBridge public interface MockNormalMember : NormalMember, MockMember { public interface MockApi : MockMember.MockApi { override val member: MockNormalMember public var lastSpeakTimestamp: Int public var joinTimestamp: Int public var nameCard: String public var specialTitle: String /** * 单位 秒 */ public var muteTimeEndTimestamp: Long } /** * 获取直接修改字段内容的 API, 通过该 API 修改的值都不会触发广播 */ @MockBotDSL override val mockApi: MockApi /** * 广播该成员加入了群 */ @MockBotDSL public suspend fun broadcastMemberJoinEvent() { broadcastMemberJoinEvent(null) } /** * 广播该成员加入了群 * * @param invitor 邀请者, 当邀请者不为 `null` 时广播 [MemberJoinEvent.Invite] */ @MockBotDSL public suspend fun broadcastMemberJoinEvent(invitor: NormalMember?) { if (invitor == null) { MemberJoinEvent.Active(this) } else { MemberJoinEvent.Invite(this, invitor) }.broadcast() } /** * 广播该群员主动离开了群, 此方法同时会在 [group] 中移除此成员 */ @MockBotDSL public suspend fun broadcastMemberLeave() { if (group.members.delegate.remove(this)) { MemberLeaveEvent.Quit(this).broadcast() cancel(CancellationException("Member $id left")) } } /** * 广播该群员将 [bot] 踢出了群聊, 并同时在 [bot] 的群聊列表里删除该群 */ @MockBotDSL public suspend fun broadcastKickBot() { if (bot.groups.delegate.remove(group)) { BotLeaveEvent.Kick(this).broadcast() cancel(CancellationException("Bot was kicked")) } } /** * 广播 该群成员被 [actor] 踢出, 此方法同时会在 [group] 中移除此成员 */ @MockBotDSL public suspend fun broadcastKickedBy(actor: MockNormalMember) { if (group.members.delegate.remove(this)) { MemberLeaveEvent.Kick(this, actor).broadcastBlocking() cancel(CancellationException("Member $id kicked")) } } /** * 广播该群员 禁言了 [target], 此方法没有权限校检 * * @param durationSeconds 0 为取消禁言 * @param target 被禁言群成员 */ @MockBotDSL public suspend fun broadcastMute(target: MockNormalMember, durationSeconds: Int) } ================================================ FILE: mirai-core-mock/src/contact/MockOtherClient.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import net.mamoe.mirai.contact.OtherClient public interface MockOtherClient : OtherClient, MockContact ================================================ FILE: mirai-core-mock/src/contact/MockStranger.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.StrangerAddEvent import net.mamoe.mirai.event.events.StrangerRelationChangeEvent import net.mamoe.mirai.mock.MockBotDSL @JvmBlockingBridge public interface MockStranger : Stranger, MockContact, MockUser { public interface MockApi : MockContact.MockApi { public val contact: MockStranger public var nick: String public var remark: String } /** * 广播陌生人加入 */ @MockBotDSL public suspend fun broadcastStrangerAddEvent(): StrangerAddEvent { return StrangerAddEvent(this).broadcast() } /** * 添加为好友 */ @MockBotDSL public suspend fun addAsFriend() { this.bot.addFriend(this.id, this.nick) bot.strangers.delegate.remove(this) StrangerRelationChangeEvent.Friended(this, bot.getFriend(this.id)!!).broadcast() } /** * 获取直接修改字段内容的 API, 通过该 API 修改的值都不会触发广播 */ @MockBotDSL public override val mockApi: MockApi /** * 广播陌生人主动解除与 [bot] 的关系的事件 * * @suppress * 即使该函数体实现为 [delete], 也请使用该方法广播 **bot 被陌生人删除**, * 以确保不会受到未来的事件架构变更带来的影响 */ @MockBotDSL public suspend fun broadcastStrangerDeleteEvent() { delete() } } ================================================ FILE: mirai-core-mock/src/contact/MockUser.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.mock.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.events.GroupMessageEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockActions import net.mamoe.mirai.mock.MockActions.mockFireRecalled import net.mamoe.mirai.mock.MockBotDSL import net.mamoe.mirai.mock.utils.broadcastMockEvents import net.mamoe.mirai.utils.JavaFriendlyAPI import java.util.function.Consumer import java.util.function.Supplier import kotlin.internal.LowPriorityInOverloadResolution @JvmBlockingBridge public interface MockUser : MockContact, MockUserOrBot, User { /** * 令 [MockUserOrBot] 撤回一条消息 * * @see [mockFireRecalled] */ @MockBotDSL public suspend fun recallMessage(message: MessageChain) { broadcastMockEvents { message.recalledBy(this@MockUser) } } /** * 令 [MockUserOrBot] 撤回一条消息 * * @see [mockFireRecalled] */ @MockBotDSL public suspend fun recallMessage(message: MessageSource) { broadcastMockEvents { message.recalledBy(this@MockUser) } } /** * 令 [MockUserOrBot] 撤回一条消息 * * @see [mockFireRecalled] */ @MockBotDSL public suspend fun recallMessage(message: MessageReceipt<*>) { mockFireRecalled(message, this) } /** * 令 [MockContact] 发出一条信息, 并广播相关的消息事件 (如 [GroupMessageEvent]) * * @return 返回 [MockContact] 发出的消息 (包含 [MessageSource]), * 可用于测试消息发出后马上撤回 `says().recall()` * * @see [MockActions.mockFireRecalled] * @see [MockUser.recallMessage] */ @MockBotDSL public suspend fun says(message: MessageChain): MessageChain @MockBotDSL public suspend fun says(message: Message): MessageChain { return says(message.toMessageChain()) } @MockBotDSL public suspend fun says(message: String): MessageChain { return says(PlainText(message)) } @JavaFriendlyAPI @LowPriorityInOverloadResolution public suspend fun says(message: Consumer<MessageChainBuilder>): MessageChain { return says(buildMessageChain { message.accept(this) }) } @JavaFriendlyAPI @LowPriorityInOverloadResolution public suspend fun says(message: Supplier<Message>): MessageChain { return says(message.get()) } public suspend fun says(message: suspend MessageChainBuilder.() -> Unit): MessageChain { return says(buildMessageChain { message(this) }) } } ================================================ FILE: mirai-core-mock/src/contact/MockUserOrBot.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.mock.contact import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.UserOrBot @JvmBlockingBridge public interface MockUserOrBot : MockContactOrBot, UserOrBot ================================================ FILE: mirai-core-mock/src/contact/active/MockGroupActive.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact.active import net.mamoe.mirai.contact.active.* import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.events.MemberHonorChangeEvent import net.mamoe.mirai.mock.MockBotDSL import net.mamoe.mirai.mock.contact.MockNormalMember public interface MockGroupActive : GroupActive { /** * 设置该群内所有群活跃度记录 * @see asFlow * @see asStream */ public fun mockSetActiveRecords(records: Collection<ActiveRecord>) /** * 设置活跃度图表数据 * @see queryChart */ public fun mockSetChart(chart: ActiveChart) /** * 设置群荣耀历史数据 * @see queryHonorHistory */ @Suppress("INAPPLICABLE_JVM_NAME") @JvmName("mockSetHonorHistory") public fun mockSetHonorHistory(type: GroupHonorType, activeHonorList: ActiveHonorList?) /** * 设置活跃度排行榜 * @see queryActiveRank */ public fun mockSetRankRecords(list: List<ActiveRankRecord>) /** * 更改拥有群荣耀的群成员. * * 会自动广播 [MemberHonorChangeEvent.Achieve] 和 [MemberHonorChangeEvent.Lose] 等相关事件. * * 此外如果 [honorType] 是 [GroupHonorType.TALKATIVE], * 会额外广播 [net.mamoe.mirai.event.events.GroupTalkativeChangeEvent]. * * 如果不需要广播事件, 可直接使用 [mockSetHonorHistory] */ @MockBotDSL public fun changeHonorMember(member: MockNormalMember, honorType: GroupHonorType) } ================================================ FILE: mirai-core-mock/src/contact/active/MockMemberActive.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact.active import net.mamoe.mirai.contact.active.MemberActive import net.mamoe.mirai.contact.active.MemberMedalInfo import net.mamoe.mirai.data.GroupHonorType public interface MockMemberActive : MemberActive { /** * @see rank */ public fun mockSetRank(value: Int) /** * @see point */ public fun mockSetPoint(value: Int) /** * @see honors */ public fun mockSetHonors(value: Set<GroupHonorType>) /** * @see temperature */ public fun mockSetTemperature(value: Int) /** * @see queryMedal */ public fun mockSetMedal(info: MemberMedalInfo) } ================================================ FILE: mirai-core-mock/src/contact/announcement/MockAnnouncements.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact.announcement import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.announcement.* import net.mamoe.mirai.mock.MockBotDSL import net.mamoe.mirai.utils.MiraiInternalApi public interface MockAnnouncements : Announcements { /** * 直接以 [actor] 的身份推送一则公告 * * @param events 当为 `true` 时会广播相关事件 * @param announcement 见 [OfflineAnnouncement], [OfflineAnnouncement.create] */ @MockBotDSL public fun mockPublish( announcement: Announcement, actor: NormalMember, events: Boolean ): OnlineAnnouncement @MockBotDSL public fun mockPublish( announcement: Announcement, actor: NormalMember, ): OnlineAnnouncement = mockPublish(announcement, actor, false) } public class MockOnlineAnnouncement @MiraiInternalApi public constructor( override val content: String, override val parameters: AnnouncementParameters, override val senderId: Long, override val fid: String = "", override val allConfirmed: Boolean, override val confirmedMembersCount: Int, override val publicationTime: Long ) : OnlineAnnouncement { override lateinit var group: Group override val sender: NormalMember? get() = group[senderId] } internal fun MockOnlineAnnouncement.copy( content: String = this.content, parameters: AnnouncementParameters = this.parameters, senderId: Long = this.senderId, fid: String = this.fid, allConfirmed: Boolean = this.allConfirmed, confirmedMembersCount: Int = this.confirmedMembersCount, publicationTime: Long = this.publicationTime, ): MockOnlineAnnouncement = MockOnlineAnnouncement( content = content, parameters = parameters, senderId = senderId, fid = fid, allConfirmed = allConfirmed, confirmedMembersCount = confirmedMembersCount, publicationTime = publicationTime, ) ================================================ FILE: mirai-core-mock/src/contact/essence/MockEssences.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock.contact.essence import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.essence.Essences import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.mock.MockBotDSL public interface MockEssences : Essences { /** * 直接以 [actor] 的身份设置一条精华消息 */ @MockBotDSL public fun mockSetEssences(source: MessageSource, actor: NormalMember) } ================================================ FILE: mirai-core-mock/src/database/MessageDatabase.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.database import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.roaming.RoamingMessageFilter import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.internal.db.MsgDatabaseImpl import net.mamoe.mirai.utils.concatAsLong /** * 一个消息数据库 * * 该数据库用于存储发送者, 发送目标, 发送类型 等数据, * 用于支持 撤回/消息获取 等相关的功能的实现 * * 一般在测试结束后销毁整个数据库 */ public interface MessageDatabase { /** * implementation note: 该方法可能同时被多个线程同时调用 * * @param time 单位秒 */ public fun newMessageInfo( sender: Long, subject: Long, kind: MessageSourceKind, time: Long, message: MessageChain, ): MessageInfo public fun queryMessageInfo(msgId: Long): MessageInfo? public fun queryMessageInfosBy( subject: Long, kind: MessageSourceKind, contact: Contact, timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter ): Sequence<MessageInfo> /** * implementation note: 该方法可能同时被多个线程同时调用 */ public fun removeMessageInfo(msgId: Long) /** * 断开与数据库的连接, 在 [MockBot.close] 时会自动调用 */ public fun disconnect() /** * 建立与数据库的连接, 在 [MockBot] 构造后马上调用, * 抛出任何错误都会中断 [MockBot] 的初始化 */ public fun connect() public companion object { @JvmStatic public fun newDefaultDatabase(): MessageDatabase { return MsgDatabaseImpl() } } } public data class MessageInfo( public val mixinedMsgId: Long, public val sender: Long, public val subject: Long, public val kind: MessageSourceKind, public val time: Long, // seconds public val message: MessageChain, ) { public fun buildSource(bot: MockBot): MessageSource { return bot.buildMessageSource(kind = kind) { val info = this@MessageInfo sender(info.sender) time(info.time.toInt()) if (kind == MessageSourceKind.GROUP) { target(subject) } else { if (info.sender == info.subject) { target(bot.id) } else { target(info.subject) } } ids = intArrayOf(info.id) internalIds = intArrayOf(info.internal) messages(info.message as Iterable<Message>) } } // ids public val id: Int get() = (mixinedMsgId shr 32).toInt() // internalIds public val internal: Int get() = mixinedMsgId.toInt() } public fun mockMsgDatabaseId(id: Int, internalId: Int): Long { return id.concatAsLong(internalId) } public fun MessageDatabase.removeMessageInfo(id: Int, internalId: Int) { removeMessageInfo(mockMsgDatabaseId(id, internalId)) } public fun MessageDatabase.queryMessageInfo(ids: IntArray, internalIds: IntArray): MessageInfo? { return queryMessageInfo(mockMsgDatabaseId(ids[0], internalIds[0])) } public fun MessageDatabase.removeMessageInfo(source: MessageSource) { removeMessageInfo(source.ids[0], source.internalIds[0]) } ================================================ FILE: mirai-core-mock/src/internal/MockBotFactoryImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal import net.mamoe.mirai.Bot import net.mamoe.mirai.auth.BotAuthorization import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.MockBotFactory import net.mamoe.mirai.mock.database.MessageDatabase import net.mamoe.mirai.mock.resserver.TmpResourceServer import net.mamoe.mirai.mock.userprofile.UserProfileService import net.mamoe.mirai.mock.utils.AvatarGenerator import net.mamoe.mirai.mock.utils.NameGenerator import net.mamoe.mirai.mock.utils.randomImageContent import net.mamoe.mirai.utils.BotConfiguration import net.mamoe.mirai.utils.lateinitMutableProperty import kotlin.math.absoluteValue import kotlin.random.Random internal class MockBotFactoryImpl : MockBotFactory { override fun newMockBotBuilder(): MockBotFactory.BotBuilder { return object : MockBotFactory.BotBuilder { var id: Long = Random.nextLong().absoluteValue var nick_: String by lateinitMutableProperty { "Mock Bot $id" } var configuration_: BotConfiguration by lateinitMutableProperty { BotConfiguration { } } var nameGenerator: NameGenerator = NameGenerator.getDefault() var tmpResourceServer_: TmpResourceServer by lateinitMutableProperty { TmpResourceServer.newInMemoryTmpResourceServer() } var msgDb: MessageDatabase by lateinitMutableProperty { MessageDatabase.newDefaultDatabase() } var userProfileService: UserProfileService by lateinitMutableProperty { UserProfileService.getInstance() } var avatarGenerator: AvatarGenerator by lateinitMutableProperty { object : AvatarGenerator { override fun generateRandomAvatar(): ByteArray = Image.randomImageContent() } } override fun id(value: Long): MockBotFactory.BotBuilder = apply { this.id = value } override fun nick(value: String): MockBotFactory.BotBuilder = apply { this.nick_ = value } override fun configuration(value: BotConfiguration): MockBotFactory.BotBuilder = apply { this.configuration_ = value } override fun nameGenerator(value: NameGenerator): MockBotFactory.BotBuilder = apply { this.nameGenerator = value } override fun tmpResourceServer(server: TmpResourceServer): MockBotFactory.BotBuilder = apply { tmpResourceServer_ = server } override fun msgDatabase(db: MessageDatabase): MockBotFactory.BotBuilder = apply { msgDb = db } override fun userProfileService(service: UserProfileService): MockBotFactory.BotBuilder = apply { userProfileService = service } override fun avatarGenerator(avatarGenerator: AvatarGenerator): MockBotFactory.BotBuilder = apply { this.avatarGenerator = avatarGenerator } override fun createNoInstanceRegister(): MockBot { return MockBotImpl( configuration_, id, nick_, nameGenerator, tmpResourceServer_, msgDb, userProfileService, avatarGenerator, ) } @Suppress("INVISIBLE_MEMBER") override fun create(): MockBot { return createNoInstanceRegister().also { Bot._instances[id] = it } } } } override fun newBot(qq: Long, password: String, configuration: BotConfiguration): Bot { return newMockBotBuilder() .id(qq) .configuration(configuration) .create() } override fun newBot(qq: Long, passwordMd5: ByteArray, configuration: BotConfiguration): Bot { return newMockBotBuilder() .id(qq) .configuration(configuration) .create() } override fun newBot(qq: Long, authorization: BotAuthorization, configuration: BotConfiguration): Bot { return newMockBotBuilder() .id(qq) .configuration(configuration) .create() } } ================================================ FILE: mirai-core-mock/src/internal/MockBotImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") package net.mamoe.mirai.mock.internal import kotlinx.coroutines.cancel import kotlinx.coroutines.isActive import net.mamoe.mirai.Bot import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.AvatarSpec import net.mamoe.mirai.contact.ContactList import net.mamoe.mirai.contact.ContactOrBot import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.friendgroup.FriendGroups import net.mamoe.mirai.event.EventChannel import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.BotEvent import net.mamoe.mirai.event.events.BotOnlineEvent import net.mamoe.mirai.event.events.BotReloginEvent import net.mamoe.mirai.internal.network.component.ComponentStorage import net.mamoe.mirai.internal.network.component.ConcurrentComponentStorage import net.mamoe.mirai.internal.network.components.EventDispatcher import net.mamoe.mirai.message.data.OnlineAudio import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockFriend import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.contact.MockOtherClient import net.mamoe.mirai.mock.contact.MockStranger import net.mamoe.mirai.mock.database.MessageDatabase import net.mamoe.mirai.mock.internal.components.MockEventDispatcherImpl import net.mamoe.mirai.mock.internal.contact.* import net.mamoe.mirai.mock.internal.contact.friendfroup.MockFriendGroups import net.mamoe.mirai.mock.internal.contactbase.ContactDatabase import net.mamoe.mirai.mock.internal.serverfs.TmpResourceServerImpl import net.mamoe.mirai.mock.resserver.TmpResourceServer import net.mamoe.mirai.mock.userprofile.UserProfileService import net.mamoe.mirai.mock.utils.AvatarGenerator import net.mamoe.mirai.mock.utils.NameGenerator import net.mamoe.mirai.mock.utils.simpleMemberInfo import net.mamoe.mirai.utils.* import java.util.concurrent.CancellationException import java.util.concurrent.atomic.AtomicBoolean import kotlin.contracts.contract import kotlin.coroutines.CoroutineContext import net.mamoe.mirai.internal.utils.subLoggerImpl as subLog internal class MockBotImpl( override val configuration: BotConfiguration, override val id: Long, nick: String, override val nameGenerator: NameGenerator, override val tmpResourceServer: TmpResourceServer, override val msgDatabase: MessageDatabase, override val userProfileService: UserProfileService, override val avatarGenerator: AvatarGenerator, ) : MockBot, Bot, ContactOrBot { @JvmField internal val contactDatabase = ContactDatabase(this) private val botccinfo = contactDatabase.acquireCI(id, nick) private val loginBefore = AtomicBoolean(false) override var nickNoEvent: String by botccinfo::nick override var nick: String get() = botccinfo.nick set(value) { botccinfo.changeNick(value) } override var avatarUrl: String get() = asFriend.avatarUrl set(value) { asFriend.changeAvatarUrl(value) } override fun avatarUrl(spec: AvatarSpec): String { return avatarUrl } override val logger: MiraiLogger by lazy { configuration.botLoggerSupplier(this) } init { if (tmpResourceServer is TmpResourceServerImpl) { // Not using logger.subLogger caused by kotlin compile error tmpResourceServer.logger = subLog(this.logger, "TmpFsServer").takeUnless { it == this.logger } ?: kotlin.run { MiraiLogger.Factory.create(TmpResourceServerImpl::class.java, "TFS $id") } } tmpResourceServer.startupServer() msgDatabase.connect() } val components: ComponentStorage by lazy { ConcurrentComponentStorage { set(EventDispatcher, MockEventDispatcherImpl(coroutineContext, logger)) } } @TestOnly internal suspend fun joinEventBroadcast() { components[EventDispatcher].joinBroadcast() } override suspend fun login() { BotOnlineEvent(this).broadcast() if (!loginBefore.compareAndSet(false, true)) { BotReloginEvent(this, null).broadcast() } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") override fun close(cause: Throwable?) { tmpResourceServer.close() Bot._instances.remove(id, this) cancel(when (cause) { null -> CancellationException("Bot cancelled") else -> CancellationException(cause.message).also { it.initCause(cause) } }) } override val groups: ContactList<MockGroup> = ContactList() override val friends: ContactList<MockFriend> = ContactList() override val strangers: ContactList<MockStranger> = ContactList() override val otherClients: ContactList<MockOtherClient> = ContactList() override val friendGroups: FriendGroups = MockFriendGroups(this) @Suppress("DEPRECATION") override fun addGroup(id: Long, name: String): MockGroup = addGroup(id, Mirai.calculateGroupUinByGroupCode(id), name) override fun addGroup(id: Long, uin: Long, name: String): MockGroup { val group = MockGroupImpl(coroutineContext, this, id, uin, name) groups.delegate.add(group) group.appendMember(simpleMemberInfo(this.id, this.nick, permission = MemberPermission.OWNER)) return group } override fun addFriend(id: Long, name: String): MockFriend { val friend = MockFriendImpl(coroutineContext, this, id, name, "") friends.delegate.add(friend) return friend } override fun addStranger(id: Long, name: String): MockStranger { val stranger = MockStrangerImpl(coroutineContext, this, id, "", name) strangers.delegate.add(stranger) return stranger } override val isOnline: Boolean get() = isActive override val eventChannel: EventChannel<BotEvent> = GlobalEventChannel.filterIsInstance<BotEvent>().filter { it.bot === this@MockBotImpl } override val asFriend: MockFriend by lazy { MockFriendImpl(coroutineContext, this, id, nick, "").also { basm -> @Suppress("QUALIFIED_SUPERTYPE_EXTENDED_BY_OTHER_SUPERTYPE", "RemoveExplicitSuperQualifier") basm.initAvatarUrl(super<ContactOrBot>.avatarUrl(spec = AvatarSpec.LARGEST)) } } override val asStranger: MockStranger by lazy { MockStrangerImpl(coroutineContext, this, id, "", nick) } override val coroutineContext: CoroutineContext by lazy { configuration.parentCoroutineContext.childScopeContext() } override suspend fun uploadOnlineAudio(resource: ExternalResource): OnlineAudio { return resource.mockImplUploadAudioAsOnline(this) } override suspend fun uploadMockImage(resource: ExternalResource): MockImage { val md5 = resource.md5 val format = resource.formatName // todo width, height ? return MockImage( imageId = generateImageId(md5, format), urlPath = bot.tmpResourceServer.uploadResourceAsImage(resource).toString(), size = resource.size ) } override fun toString(): String { return "MockBot($id)" } } internal fun MockBot.impl(): MockBotImpl { contract { returns() implies (this@impl is MockBotImpl) } return cast() } ================================================ FILE: mirai-core-mock/src/internal/MockMiraiImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") package net.mamoe.mirai.mock.internal import net.mamoe.mirai.Bot import net.mamoe.mirai.BotFactory import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.FriendInfo import net.mamoe.mirai.data.StrangerInfo import net.mamoe.mirai.data.UserProfile import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.MiraiImpl import net.mamoe.mirai.internal.network.components.EventDispatcher import net.mamoe.mirai.internal.utils.MiraiProtocolInternal import net.mamoe.mirai.message.action.Nudge import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockActions import net.mamoe.mirai.mock.MockBotFactory import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.database.queryMessageInfo import net.mamoe.mirai.mock.database.removeMessageInfo import net.mamoe.mirai.mock.internal.contact.* import net.mamoe.mirai.mock.internal.msgsrc.registerMockMsgSerializers import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.mock.utils.simpleMemberInfo import net.mamoe.mirai.utils.currentTimeSeconds internal class MockMiraiImpl : MiraiImpl() { companion object { init { registerMockMsgSerializers() registerMockServices() } } override suspend fun solveBotInvitedJoinGroupRequestEvent( bot: Bot, eventId: Long, invitorId: Long, groupId: Long, accept: Boolean ) { bot.mock() if (accept) { val group = bot.addGroup(groupId, bot.nameGenerator.nextGroupName()) group.appendMember( simpleMemberInfo( uin = 111111111, permission = MemberPermission.OWNER, name = "MockMember - Owner", nameCard = "Custom NameCard", ) ).appendMember( simpleMemberInfo( uin = 222222222, permission = MemberPermission.ADMINISTRATOR, name = "MockMember - Administrator", nameCard = "root", ) ) group.appendMember( simpleMemberInfo( uin = bot.id, permission = MemberPermission.MEMBER, name = bot.nick, ) ) if (invitorId != 0L) { val invitor = group[invitorId] ?: kotlin.run { group.addMember( simpleMemberInfo( uin = invitorId, permission = MemberPermission.ADMINISTRATOR, name = bot.getFriend(invitorId)?.nick ?: "A random invitor", nameCard = "invitor", ) ) } BotJoinGroupEvent.Invite(invitor) } else { BotJoinGroupEvent.Active(group) }.broadcast() } } override suspend fun solveMemberJoinRequestEvent( bot: Bot, eventId: Long, fromId: Long, fromNick: String, groupId: Long, accept: Boolean?, blackList: Boolean, message: String ) { if (accept == null || !accept) return // ignore val member = bot.getGroupOrFail(groupId).mock().addMember( simpleMemberInfo( uin = fromId, name = fromNick, permission = MemberPermission.MEMBER ) ) MemberJoinEvent.Active(member).broadcast() } override suspend fun solveNewFriendRequestEvent( bot: Bot, eventId: Long, fromId: Long, fromNick: String, accept: Boolean, blackList: Boolean ) { if (!accept) return // No event broadcast in mirai-core bot.mock().addFriend(fromId, fromNick) } override fun getUin(contactOrBot: ContactOrBot): Long { if (contactOrBot is MockGroup) return contactOrBot.uin return super.getUin(contactOrBot) } override suspend fun muteAnonymousMember( bot: Bot, anonymousId: String, anonymousNick: String, groupId: Long, seconds: Int ) { // noop } override suspend fun recallFriendMessageRaw( bot: Bot, targetId: Long, messageIds: IntArray, messageInternalIds: IntArray, time: Int ): Boolean { val info = bot.mock().msgDatabase.queryMessageInfo(messageIds, messageInternalIds) ?: return false if (info.kind != MessageSourceKind.FRIEND) return false if (info.sender != bot.id) return false if (currentTimeSeconds() - info.time > 120) return false bot.msgDatabase.removeMessageInfo(info.mixinedMsgId) // MessageRecallEvent.FriendRecall() // TODO: Unknown Logic return true } override suspend fun recallGroupMessageRaw( bot: Bot, groupCode: Long, messageIds: IntArray, messageInternalIds: IntArray ): Boolean { val info = bot.mock().msgDatabase.queryMessageInfo(messageIds, messageInternalIds) ?: return false if (info.kind != MessageSourceKind.GROUP) return false val group = bot.getGroup(info.subject) ?: return false val canDelete = when (group.botPermission) { MemberPermission.OWNER -> true MemberPermission.ADMINISTRATOR -> kotlin.run w@{ if (info.sender == bot.id) return@w true val member = group.getMember(info.sender) ?: return@w true member.permission == MemberPermission.MEMBER } else -> kotlin.run w@{ if (info.sender != bot.id) return@w false currentTimeSeconds() - info.time <= 120 } } if (!canDelete) return false bot.msgDatabase.removeMessageInfo(info.mixinedMsgId) MessageRecallEvent.GroupRecall( bot, info.sender, messageIds, messageInternalIds, info.time.toInt(), null, group, group[info.sender] ?: return true ).broadcast() return true } override suspend fun recallGroupTempMessageRaw( bot: Bot, groupUin: Long, targetId: Long, messageIds: IntArray, messageInternalIds: IntArray, time: Int ): Boolean = false // TODO: No recall event override suspend fun recallMessage(bot: Bot, source: MessageSource) { fun doFailed() { error("Failed to recall message #${source.ids.contentToString()}: $AQQ_RECALL_FAILED_MESSAGE") } if (source is OnlineMessageSource) { when (source) { is OnlineMessageSource.Incoming.FromFriend, is OnlineMessageSource.Outgoing.ToFriend, -> { val resp = recallFriendMessageRaw( bot, source.subject.id, source.ids, source.internalIds, source.time ) if (!resp) doFailed() } is OnlineMessageSource.Incoming.FromGroup, is OnlineMessageSource.Outgoing.ToGroup, -> { val resp = recallGroupMessageRaw( bot, source.subject.id, source.ids, source.internalIds ) if (!resp) doFailed() } is OnlineMessageSource.Incoming.FromStranger -> doFailed() is OnlineMessageSource.Incoming.FromTemp -> doFailed() is OnlineMessageSource.Outgoing.ToStranger -> { bot.mock().msgDatabase.removeMessageInfo(source) // TODO: No Event } is OnlineMessageSource.Outgoing.ToTemp -> { bot.mock().msgDatabase.removeMessageInfo(source) // TODO: No Event } else -> doFailed() } } else { source as OfflineMessageSource when (source.kind) { MessageSourceKind.GROUP -> { val resp = recallGroupMessageRaw( bot, source.targetId, source.ids, source.internalIds ) if (!resp) doFailed() } MessageSourceKind.FRIEND -> { val resp = recallFriendMessageRaw( bot, source.targetId, source.ids, source.internalIds, source.time ) if (!resp) doFailed() } MessageSourceKind.TEMP, MessageSourceKind.STRANGER -> { if (source.fromId != bot.id) { doFailed() } MockActions.fireMessageRecalled(source, bot.asFriend) } else -> doFailed() } } } override suspend fun sendNudge(bot: Bot, nudge: Nudge, receiver: Contact): Boolean { if (!bot.configuration.protocol.isNudgeSupported) { throw UnsupportedOperationException("nudge is supported only with protocol ${ MiraiProtocolInternal.protocols.filter { it.value.supportsNudge }.map { it.key } }") } NudgeEvent( from = bot, target = nudge.target, subject = receiver, action = "戳了戳", suffix = "" ).broadcast() return true } override suspend fun queryProfile(bot: Bot, targetId: Long): UserProfile { return bot.mock().userProfileService.doQueryUserProfile(targetId) } override val BotFactory: BotFactory get() = MockBotFactory /*override suspend fun getGroupVoiceDownloadUrl(bot: Bot, md5: ByteArray, groupId: Long, dstUin: Long): String { return super.getGroupVoiceDownloadUrl(bot, md5, groupId, dstUin) }*/ @Suppress("RETURN_TYPE_MISMATCH_ON_OVERRIDE") override fun newFriend(bot: Bot, friendInfo: FriendInfo): Friend { bot.mock() return MockFriendImpl( bot.coroutineContext, bot, friendInfo.uin, friendInfo.nick, friendInfo.remark, ) } @Suppress("RETURN_TYPE_MISMATCH_ON_OVERRIDE") override fun newStranger(bot: Bot, strangerInfo: StrangerInfo): Stranger { bot.mock() return MockStrangerImpl( bot.coroutineContext, bot, strangerInfo.uin, strangerInfo.remark, strangerInfo.nick, ) } override fun createImage(imageId: String): Image { if (imageId matches Image.IMAGE_ID_REGEX) { return MockImage(imageId, "images/" + imageId.substring(1..36)) } //imageId.substring(1..36) return super.createImage(imageId) } override suspend fun broadcastEvent(event: Event) { if (event is BotEvent) { val bot = event.bot if (bot is MockBotImpl) { bot.components[EventDispatcher].broadcast(event) return } } super.broadcastEvent(event) } override suspend fun refreshKeys(bot: Bot) { } } ================================================ FILE: mirai-core-mock/src/internal/components/MockEventDispatcherImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") package net.mamoe.mirai.mock.internal.components import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.job import kotlinx.coroutines.launch import net.mamoe.mirai.event.Event import net.mamoe.mirai.internal.network.components.EventDispatcherImpl import net.mamoe.mirai.utils.MiraiLogger import kotlin.coroutines.CoroutineContext /* Copied from: mirai-core/src/commonTest/kotlin/network/framework/components/EventDispatcherImpl.kt */ internal open class MockEventDispatcherImpl( lifecycleContext: CoroutineContext, logger: MiraiLogger, ) : EventDispatcherImpl(lifecycleContext, logger) { override suspend fun broadcast(event: Event) { if (isActive) { // This requires the scope to be active, while the original one doesn't. // so that [joinBroadcast] works. launch( start = CoroutineStart.UNDISPATCHED ) { super.broadcast(event) }.join() } else { // Scope closed, typically when broadcasting `BotOfflineEvent` by StateObserver from `bot.close` super.broadcast(event) } } override suspend fun joinBroadcast() { for (child in coroutineContext.job.children) { child.join() } } } ================================================ FILE: mirai-core-mock/src/internal/contact/AbstractMockContact.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") package net.mamoe.mirai.mock.internal.contact import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.event.events.MessagePreSendEvent import net.mamoe.mirai.internal.contact.broadcastMessagePreSendEvent import net.mamoe.mirai.internal.contact.replaceMagicCodes import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockContact import net.mamoe.mirai.utils.* import kotlin.coroutines.CoroutineContext internal abstract class AbstractMockContact( parentCoroutineContext: CoroutineContext, override val bot: MockBot, override val id: Long ) : MockContact { override val coroutineContext: CoroutineContext = parentCoroutineContext.childScopeContext() /** * @return isCancelled */ protected abstract fun newMessagePreSend(message: Message): MessagePreSendEvent protected abstract suspend fun postMessagePreSend(message: MessageChain, receipt: MessageReceipt<*>) protected abstract fun newMessageSource(message: MessageChain): OnlineMessageSource.Outgoing override suspend fun sendMessage(message: Message): MessageReceipt<Contact> { val msg = broadcastMessagePreSendEvent(message, false) { _, _ -> newMessagePreSend(message) } val source = newMessageSource(msg) val response = source.withMessage(msg) bot.logger.verbose("$this <- $msg".replaceMagicCodes()) @Suppress("DEPRECATION_ERROR") return MessageReceipt(source, this).also { postMessagePreSend(response, it) } } override suspend fun uploadImage(resource: ExternalResource): Image { return bot.uploadMockImage(resource) } override suspend fun uploadShortVideo( thumbnail: ExternalResource, video: ExternalResource, fileName: String? ): ShortVideo { TODO("mock upload short video") } override fun toString(): String { return "$id" } } internal suspend inline fun <T : ExternalResource, R> T.inResource(action: () -> R): R { return useAutoClose { runBIO { inputStream().dropContent(close = true) } action() } } ================================================ FILE: mirai-core-mock/src/internal/contact/MockAnnouncementsImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.contact.announcement.Announcement import net.mamoe.mirai.contact.announcement.AnnouncementImage import net.mamoe.mirai.contact.announcement.OnlineAnnouncement import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.mock.contact.announcement.MockAnnouncements import net.mamoe.mirai.mock.contact.announcement.MockOnlineAnnouncement import net.mamoe.mirai.mock.contact.announcement.copy import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.currentTimeSeconds import net.mamoe.mirai.utils.withAutoClose import java.util.* import java.util.concurrent.ConcurrentHashMap import java.util.stream.Stream internal class MockAnnouncementsImpl( val group: Group, ) : MockAnnouncements { val announcements = ConcurrentHashMap<String, OnlineAnnouncement>() override fun asFlow(): Flow<OnlineAnnouncement> = announcements.values.asFlow() override fun asStream(): Stream<OnlineAnnouncement> = announcements.values.toList().stream() override suspend fun delete(fid: String): Boolean = announcements.remove(fid) != null override suspend fun get(fid: String): OnlineAnnouncement? = announcements[fid] @Suppress("MemberVisibilityCanBePrivate") internal fun putDirect(announcement: MockOnlineAnnouncement) { val ann = if (announcement.fid.isEmpty()) { announcement.copy(fid = UUID.randomUUID().toString()) } else announcement if (ann.parameters.sendToNewMember) { announcements.entries.removeIf { (_, v) -> v.parameters.sendToNewMember } } announcements[ann.fid] = ann ann.group = group } override fun mockPublish(announcement: Announcement, actor: NormalMember, events: Boolean): OnlineAnnouncement { if (announcement.parameters.sendToNewMember) { announcements.elements().toList().firstOrNull { oa -> oa.parameters.sendToNewMember } } val ann = MockOnlineAnnouncement( content = announcement.content, parameters = announcement.parameters, senderId = actor.id, fid = UUID.randomUUID().toString(), allConfirmed = false, confirmedMembersCount = 0, publicationTime = currentTimeSeconds() ) putDirect(ann) if (!events) return ann // TODO: mirai-core no other events about announcements return ann } override suspend fun publish(announcement: Announcement): OnlineAnnouncement { if (!group.botPermission.isOperator()) { throw PermissionDeniedException("Failed to publish a new announcement because bot don't have admin permission to perform it.") } return mockPublish(announcement, this.group.botAsMember, true) } override suspend fun uploadImage(resource: ExternalResource): AnnouncementImage = resource.withAutoClose { AnnouncementImage.create(group.bot.mock().uploadMockImage(resource).imageId, 500, 500) } override suspend fun members(fid: String, confirmed: Boolean): List<NormalMember> { if (!group.botPermission.isOperator()) { throw PermissionDeniedException("Only administrator have permission see announcement confirmed detail") } // TODO: 设置用户可读状态,而不返回全部 return group.members.toList() } override suspend fun remind(fid: String) { if (!group.botPermission.isOperator()) { throw PermissionDeniedException("Only administrator have permission send announcement remind") } } } ================================================ FILE: mirai-core-mock/src/internal/contact/MockAnonymousMemberImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact import kotlinx.coroutines.runBlocking import net.mamoe.mirai.contact.AvatarSpec import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.nameCardOrNick import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.GroupMessageEvent import net.mamoe.mirai.event.events.MessagePreSendEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockAnonymousMember import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.contact.MockMember import net.mamoe.mirai.mock.contact.active.MockMemberActive import net.mamoe.mirai.mock.internal.contact.active.MockMemberActiveImpl import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcFromGroup import net.mamoe.mirai.mock.internal.msgsrc.newMsgSrc import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.lateinitMutableProperty import kotlin.coroutines.CoroutineContext internal class MockAnonymousMemberImpl( parentCoroutineContext: CoroutineContext, bot: MockBot, id: Long, override val anonymousId: String, override val group: MockGroup, nameCard: String ) : AbstractMockContact(parentCoroutineContext, bot, id), MockAnonymousMember { override fun newMessagePreSend(message: Message): MessagePreSendEvent { throw AssertionError() } override fun avatarUrl(spec: AvatarSpec): String { return avatarUrl } override suspend fun postMessagePreSend(message: MessageChain, receipt: MessageReceipt<*>) { throw AssertionError() } override fun newMessageSource(message: MessageChain): OnlineMessageSource.Outgoing { throw AssertionError() } @Suppress("DEPRECATION_ERROR") override suspend fun sendMessage(message: Message): Nothing = super<MockAnonymousMember>.sendMessage(message) override suspend fun uploadImage(resource: ExternalResource): Image = super<AbstractMockContact>.uploadImage(resource) @Suppress("UNUSED_PARAMETER") override var permission: MemberPermission get() = MemberPermission.MEMBER set(value) { error("Modifying permission of AnonymousMember") } override val specialTitle: String get() = "匿名" override val active: MockMemberActive by lazy { MockMemberActiveImpl() } override suspend fun mute(durationSeconds: Int) { } override var remark: String get() = "" set(_) {} override var nick: String get() = nameCard set(_) {} override val nameCard: String get() = mockApi.nick override val mockApi: MockMember.MockApi = object : MockMember.MockApi { override val member: MockMember get() = this@MockAnonymousMemberImpl override var nick: String = nameCard override var remark: String get() = "" set(_) {} override var permission: MemberPermission get() = MemberPermission.MEMBER set(_) {} override var avatarUrl: String by lateinitMutableProperty { runBlocking { MockImage.random(bot).getUrl(bot) } } } // TODO override val avatarUrl: String by mockApi::avatarUrl override fun changeAvatarUrl(newAvatar: String) { mockApi.avatarUrl = newAvatar } override suspend fun says(message: MessageChain): MessageChain { val src = newMsgSrc(true, message) { ids, internalIds, time -> OnlineMsgSrcFromGroup(ids, internalIds, time, message, bot, this) } val msg = src.withMessage(message) GroupMessageEvent(nameCardOrNick, permission, this, msg, src.time).broadcast() return msg } override fun toString(): String { return "AnonymousMember($nameCard, $anonymousId)" } } ================================================ FILE: mirai-core-mock/src/internal/contact/MockFriendImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") package net.mamoe.mirai.mock.internal.contact import kotlinx.coroutines.cancel import net.mamoe.mirai.contact.AvatarSpec import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.OtherClient import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OfflineAudio import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockFriend import net.mamoe.mirai.mock.internal.contact.friendfroup.MockFriendGroups import net.mamoe.mirai.mock.internal.contact.roaming.MockRoamingMessages import net.mamoe.mirai.mock.internal.impl import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcFromFriend import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcToFriend import net.mamoe.mirai.mock.internal.msgsrc.newMsgSrc import net.mamoe.mirai.mock.utils.broadcastBlocking import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.cast import java.util.concurrent.CancellationException import kotlin.coroutines.CoroutineContext internal class MockFriendImpl( parentCoroutineContext: CoroutineContext, bot: MockBot, id: Long, nick: String, remark: String ) : AbstractMockContact( parentCoroutineContext, bot, id ), MockFriend { private val ccinfo = bot.impl().contactDatabase.acquireCI(id, nick) override val mockApi: MockFriend.MockApi = object : MockFriend.MockApi { override val contact: MockFriend get() = this@MockFriendImpl override var remark: String = remark override var nick: String by ccinfo::nick override var avatarUrl: String by ccinfo::avatarUrl override var friendGroupId: Int = 0 } override val friendGroup: FriendGroup get() = bot.friendGroups.cast<MockFriendGroups>().findOrDefault(mockApi.friendGroupId) override val avatarUrl: String get() = ccinfo.avatarUrl internal fun initAvatarUrl(v: String) { ccinfo.avatarUrl = v } override fun changeAvatarUrl(newAvatar: String) { ccinfo.changeAvatarUrl(newAvatar) } override fun avatarUrl(spec: AvatarSpec): String { return avatarUrl } override var nick: String get() = ccinfo.nick set(value) { ccinfo.changeNick(value) } override var remark: String get() = mockApi.remark set(value) { val ov = mockApi.remark if (ov == value) return mockApi.remark = value FriendRemarkChangeEvent(this, ov, value).broadcastBlocking() } override fun newMessagePreSend(message: Message): MessagePreSendEvent { return FriendMessagePreSendEvent(this, message) } override suspend fun postMessagePreSend(message: MessageChain, receipt: MessageReceipt<*>) { FriendMessagePostSendEvent(this, message, null, receipt.cast()).broadcast() } override fun newMessageSource(message: MessageChain): OnlineMessageSource.Outgoing { return newMsgSrc(false, message) { ids, internalIds, time -> OnlineMsgSrcToFriend(ids, internalIds, time, message, bot, bot, this) } } override suspend fun sendMessage(message: Message): MessageReceipt<Friend> { return super<AbstractMockContact>.sendMessage(message).cast() } override suspend fun delete() { if (bot.friends.delegate.remove(this)) { FriendDeleteEvent(this).broadcast() cancel(CancellationException("Friend deleted")) } } override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = resource.mockUploadAudio(bot) override val roamingMessages: RoamingMessages = MockRoamingMessages(this) override suspend fun says(message: MessageChain): MessageChain { val src = newMsgSrc(true, message) { ids, internalIds, time -> OnlineMsgSrcFromFriend(ids, internalIds, time, message, bot, this, bot) } val msg = src.withMessage(message) FriendMessageEvent(this, msg, src.time).broadcast() return msg } override suspend fun broadcastMsgSyncEvent(client: OtherClient, message: MessageChain, time: Int) { val src = newMsgSrc(true, message, time.toLong()) { ids, internalIds, time0 -> OnlineMsgSrcToFriend(ids, internalIds, time0, message, bot, bot, this) } val msg = src.withMessage(message) FriendMessageSyncEvent(client, this, msg, time).broadcast() } override fun toString(): String { return "Friend($id)" } } ================================================ FILE: mirai-core-mock/src/internal/contact/MockGroupImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") package net.mamoe.mirai.mock.internal.contact import kotlinx.coroutines.cancel import kotlinx.coroutines.runBlocking import net.mamoe.mirai.contact.* import net.mamoe.mirai.contact.announcement.OfflineAnnouncement import net.mamoe.mirai.contact.announcement.buildAnnouncementParameters import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.data.MemberInfo import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockAnonymousMember import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.contact.MockGroupControlPane import net.mamoe.mirai.mock.contact.MockNormalMember import net.mamoe.mirai.mock.contact.active.MockGroupActive import net.mamoe.mirai.mock.contact.essence.MockEssences import net.mamoe.mirai.mock.internal.contact.active.MockGroupActiveImpl import net.mamoe.mirai.mock.internal.contact.essence.MockEssencesImpl import net.mamoe.mirai.mock.internal.contact.roaming.MockRoamingMessages import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcToGroup import net.mamoe.mirai.mock.internal.msgsrc.newMsgSrc import net.mamoe.mirai.mock.utils.broadcastBlocking import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.utils.* import java.util.concurrent.CancellationException import kotlin.coroutines.CoroutineContext internal class MockGroupImpl( parentCoroutineContext: CoroutineContext, bot: MockBot, id: Long, override val uin: Long, name: String, ) : AbstractMockContact( parentCoroutineContext, bot, id ), MockGroup { @Deprecated( "use active.changeHonorMember", replaceWith = ReplaceWith(".active.changeHonorMember(member, honorType)"), level = DeprecationLevel.ERROR ) override val honorMembers: MutableMap<GroupHonorType, MockNormalMember> = ConcurrentHashMap() private val txFileSystem by lazy { bot.mock().tmpResourceServer.mockServerFileDisk.newFsSystem() } override fun avatarUrl(spec: AvatarSpec): String { return avatarUrl } override val active: MockGroupActive by lazy { MockGroupActiveImpl(this) } override fun appendMember(mockMember: MemberInfo): MockGroup { addMember(mockMember) return this } override fun addMember(mockMember: MemberInfo): MockNormalMember { val nMember = MockNormalMemberImpl( this.coroutineContext, bot, mockMember.uin, this, mockMember.permission, mockMember.remark, mockMember.nick, mockMember.muteTimestamp, mockMember.joinTimestamp, mockMember.lastSpeakTimestamp, mockMember.specialTitle, mockMember.nameCard ) if (nMember.id == bot.id) { botAsMember = nMember } else { members.delegate.removeAll { it.uin == nMember.id } members.delegate.add(nMember) } if (nMember.permission == MemberPermission.OWNER) { if (::owner.isInitialized) { owner.mock().mockApi.permission = MemberPermission.MEMBER } owner = nMember } return nMember } override suspend fun changeOwner(member: NormalMember) { if (member === owner) return val oldOwner = owner val oldPerm = member.permission oldOwner.mock().mockApi.permission = MemberPermission.MEMBER member.mock().mockApi.permission = MemberPermission.OWNER owner = member if (member === botAsMember) { BotGroupPermissionChangeEvent(this, oldPerm, MemberPermission.OWNER) } else { MemberPermissionChangeEvent(member, oldPerm, MemberPermission.OWNER) }.broadcast() if (oldOwner === botAsMember) { BotGroupPermissionChangeEvent(this, MemberPermission.OWNER, MemberPermission.MEMBER) } else { MemberPermissionChangeEvent(oldOwner, MemberPermission.OWNER, MemberPermission.MEMBER) }.broadcast() } override fun changeOwnerNoEventBroadcast(member: NormalMember) { val oldOwner = owner member.mock().mockApi.permission = MemberPermission.OWNER oldOwner.mockApi.permission = MemberPermission.MEMBER owner = member } override fun newAnonymous(nick: String, id: String): MockAnonymousMember { return MockAnonymousMemberImpl( coroutineContext, bot, 80000000, id, this, nick ) } private val rawGroupControlPane = object : MockGroupControlPane { override val group: MockGroup get() = this@MockGroupImpl override val currentActor: MockNormalMember get() = group.botAsMember override var isAllowMemberInvite: Boolean = false override var isMuteAll: Boolean = false override var isAllowMemberFileUploading: Boolean = false override var isAnonymousChatAllowed: Boolean = false override var isAllowConfessTalk: Boolean = false override var groupName: String = name override fun withActor(actor: MockNormalMember): MockGroupControlPane { return GroupControlPaneImpl(actor) } } internal inner class GroupControlPaneImpl( override val currentActor: MockNormalMember ) : MockGroupControlPane { override val group: MockGroup get() = this@MockGroupImpl private val actorNullIfBot: MockNormalMember? get() = currentActor.takeIf { it.id != bot.id } override var groupName: String get() = rawGroupControlPane.groupName set(value) { val ov = rawGroupControlPane.groupName if (ov == value) return rawGroupControlPane.groupName = value GroupNameChangeEvent(ov, value, group, actorNullIfBot).broadcastBlocking() } override var isMuteAll: Boolean get() = rawGroupControlPane.isMuteAll set(value) { val ov = rawGroupControlPane.isMuteAll if (ov == value) return rawGroupControlPane.isMuteAll = value GroupMuteAllEvent(ov, value, group, actorNullIfBot).broadcastBlocking() } override var isAllowMemberFileUploading: Boolean get() = rawGroupControlPane.isAllowMemberFileUploading set(value) { // TODO: core-api no event rawGroupControlPane.isAllowMemberFileUploading = value } override var isAllowMemberInvite: Boolean get() = rawGroupControlPane.isAllowMemberInvite set(value) { val ov = rawGroupControlPane.isAllowMemberInvite if (ov == value) return rawGroupControlPane.isAllowMemberInvite = value GroupAllowMemberInviteEvent(ov, value, group, actorNullIfBot).broadcastBlocking() } override var isAnonymousChatAllowed: Boolean get() = rawGroupControlPane.isAnonymousChatAllowed set(value) { val ov = rawGroupControlPane.isAnonymousChatAllowed if (ov == value) return rawGroupControlPane.isAnonymousChatAllowed = value GroupAllowAnonymousChatEvent(ov, value, group, actorNullIfBot).broadcastBlocking() } override var isAllowConfessTalk: Boolean get() = rawGroupControlPane.isAllowConfessTalk set(value) { val ov = rawGroupControlPane.isAllowConfessTalk if (ov == value) return rawGroupControlPane.isAllowConfessTalk = value GroupAllowConfessTalkEvent(ov, value, group, currentActor.id == bot.id).broadcastBlocking() } override fun withActor(actor: MockNormalMember): MockGroupControlPane { return GroupControlPaneImpl(actor) } } override val controlPane: MockGroupControlPane get() = rawGroupControlPane override var name: String get() = controlPane.groupName set(value) { checkBotPermission(MemberPermission.ADMINISTRATOR) controlPane.withActor(botAsMember).groupName = value } override val mockApi: MockGroup.MockApi = object : MockGroup.MockApi { override var avatarUrl: String by lateinitMutableProperty { runBlocking { MockImage.randomForGroup(bot, id).getUrl(bot) } } } override fun changeAvatarUrl(newAvatar: String) { mockApi.avatarUrl = newAvatar } override val avatarUrl: String by mockApi::avatarUrl override lateinit var owner: MockNormalMember override lateinit var botAsMember: MockNormalMember override val members: ContactList<MockNormalMember> = ContactList() override fun get(id: Long): MockNormalMember? { if (id == bot.id) return botAsMember return members[id] } override fun contains(id: Long): Boolean = members.any { it.id == id } override suspend fun quit(): Boolean { return if (bot.groups.delegate.remove(this)) { BotLeaveEvent.Active(this).broadcast() cancel(CancellationException("Bot quited group $id")) true } else { false } } override val announcements = MockAnnouncementsImpl(this) @Suppress("OverridingDeprecatedMember", "OVERRIDE_DEPRECATION") override val settings: GroupSettings = object : GroupSettings { override var entranceAnnouncement: String get() = announcements.announcements.values.asSequence() .filter { it.parameters.sendToNewMember } .firstOrNull()?.content ?: "" set(value) { checkBotPermission(MemberPermission.ADMINISTRATOR) announcements.mockPublish(OfflineAnnouncement.create(value, buildAnnouncementParameters { sendToNewMember = true }), this@MockGroupImpl.botAsMember) } override var isMuteAll: Boolean get() = rawGroupControlPane.isMuteAll set(value) { checkBotPermission(MemberPermission.ADMINISTRATOR) rawGroupControlPane.withActor(botAsMember).isMuteAll = value } override var isAllowMemberInvite: Boolean get() = rawGroupControlPane.isAllowMemberInvite set(value) { checkBotPermission(MemberPermission.ADMINISTRATOR) rawGroupControlPane.withActor(botAsMember).isAllowMemberInvite = value } @MiraiExperimentalApi override val isAutoApproveEnabled: Boolean get() = false // TODO override var isAnonymousChatEnabled: Boolean get() = rawGroupControlPane.isAnonymousChatAllowed set(value) { checkBotPermission(MemberPermission.ADMINISTRATOR) rawGroupControlPane.withActor(botAsMember).isAnonymousChatAllowed = value } } override fun newMessagePreSend(message: Message): MessagePreSendEvent = GroupMessagePreSendEvent(this, message) override suspend fun postMessagePreSend(message: MessageChain, receipt: MessageReceipt<*>) { GroupMessagePostSendEvent(this, message, null, receipt = receipt.cast()) .broadcast() } override fun newMessageSource(message: MessageChain): OnlineMessageSource.Outgoing { return newMsgSrc(false, message) { ids, internalIds, time -> OnlineMsgSrcToGroup(ids, internalIds, time, message, bot, bot, this) } } override suspend fun broadcastMsgSyncEvent(client: OtherClient, message: MessageChain, time: Int) { val src = newMsgSrc(true, message, time.toLong()) { ids, internalIds, time0 -> OnlineMsgSrcToGroup(ids, internalIds, time0, message, bot, bot, this) } val msg = src.withMessage(message) GroupMessageSyncEvent(client, this, msg, botAsMember, bot.nick, time).broadcast() } override suspend fun sendMessage(message: Message): MessageReceipt<Group> { return super<AbstractMockContact>.sendMessage(message).cast() } @Suppress("OverridingDeprecatedMember", "DEPRECATION_ERROR", "OVERRIDE_DEPRECATION") override suspend fun uploadVoice(resource: ExternalResource): net.mamoe.mirai.message.data.Voice = resource.mockUploadVoice(bot) override suspend fun setEssenceMessage(source: MessageSource): Boolean { checkBotPermission(MemberPermission.ADMINISTRATOR) essences.mockSetEssences(source, this.botAsMember) return true } override val essences: MockEssences = MockEssencesImpl(this) @Deprecated("Please use files instead.", replaceWith = ReplaceWith("files.root")) @Suppress("OverridingDeprecatedMember", "DEPRECATION", "DEPRECATION_ERROR") override val filesRoot: RemoteFile by lazy { net.mamoe.mirai.mock.internal.remotefile.remotefile.RootRemoteFile(txFileSystem, this) } override val files: RemoteFiles by lazy { net.mamoe.mirai.mock.internal.remotefile.absolutefile.MockRemoteFiles(this, txFileSystem) } override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = resource.mockUploadAudio(bot) override fun toString(): String { return "Group($id)" } override val roamingMessages: RoamingMessages = MockRoamingMessages(this) } ================================================ FILE: mirai-core-mock/src/internal/contact/MockNormalMemberImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact import kotlinx.coroutines.cancel import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockFriend import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.contact.MockNormalMember import net.mamoe.mirai.mock.contact.active.MockMemberActive import net.mamoe.mirai.mock.internal.contact.active.MockMemberActiveImpl import net.mamoe.mirai.mock.internal.impl import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcFromGroup import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcToTemp import net.mamoe.mirai.mock.internal.msgsrc.newMsgSrc import net.mamoe.mirai.mock.utils.broadcastBlocking import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.currentTimeSeconds import kotlin.coroutines.CoroutineContext import kotlin.coroutines.cancellation.CancellationException import kotlin.math.max internal class MockNormalMemberImpl( parentCoroutineContext: CoroutineContext, bot: MockBot, id: Long, override val group: MockGroup, permission: MemberPermission, remark: String, nick: String, muteTimeRemaining: Int, joinTimestamp: Int, lastSpeakTimestamp: Int, specialTitle: String, nameCard: String, ) : AbstractMockContact( parentCoroutineContext, bot, id ), MockNormalMember { private val ccinfo = bot.impl().contactDatabase.let { if (nick.isEmpty()) it.acquireCI(id) else it.acquireCI(id, nick) } override val avatarUrl: String get() = ccinfo.avatarUrl override fun avatarUrl(spec: AvatarSpec): String { return avatarUrl } override fun changeAvatarUrl(newAvatar: String) { ccinfo.changeAvatarUrl(newAvatar) } private inline fun <T> crossFriendAccess( ifExists: (MockFriend) -> T, ifNotExists: () -> T, ): T { val f = bot.getFriend(id) ?: return ifNotExists() return ifExists(f) } override val mockApi: MockNormalMember.MockApi = object : MockNormalMember.MockApi { override val member: MockNormalMember get() = this@MockNormalMemberImpl override var lastSpeakTimestamp: Int = lastSpeakTimestamp override var joinTimestamp: Int = joinTimestamp override var muteTimeEndTimestamp: Long = currentTimeSeconds() + muteTimeRemaining override var nick: String by ccinfo::nick override var remark: String = remark get() = crossFriendAccess(ifExists = { it.remark }, ifNotExists = { field }) set(value) { crossFriendAccess(ifExists = { it.mockApi.remark = value }, ifNotExists = { field = value }) } override var permission: MemberPermission = permission override var nameCard: String = nameCard override var specialTitle: String = specialTitle override var avatarUrl: String by ccinfo::avatarUrl } override val permission: MemberPermission get() = mockApi.permission override val active: MockMemberActive by lazy { MockMemberActiveImpl() } override val joinTimestamp: Int get() = mockApi.joinTimestamp override val lastSpeakTimestamp: Int get() = mockApi.lastSpeakTimestamp override val muteTimeRemaining: Int get() = max((mockApi.muteTimeEndTimestamp - currentTimeSeconds()).toInt(), 0) override val remark: String get() = mockApi.remark override var nameCard: String get() = mockApi.nameCard set(value) { if (!group.botPermission.isOperator()) { throw PermissionDeniedException("Bot don't have permission to change the namecard of $this") } MemberCardChangeEvent(mockApi.nameCard, value, this).broadcastBlocking() mockApi.nameCard = value } override var specialTitle: String get() = mockApi.specialTitle set(value) { if (group.botPermission != MemberPermission.OWNER) { throw PermissionDeniedException("Bot is not the owner of $group so bot cannot change the specialTitle of $this") } MemberSpecialTitleChangeEvent(mockApi.specialTitle, value, this, group.botAsMember).broadcastBlocking() mockApi.specialTitle = value } override val nick: String get() = mockApi.nick override suspend fun unmute() { requireBotPermissionHigherThanThis("unmute") mockApi.muteTimeEndTimestamp = 0 MemberUnmuteEvent(this, null) } override suspend fun kick(message: String, block: Boolean) { kick(message) } override suspend fun kick(message: String) { requireBotPermissionHigherThanThis("kick") if (group.members.delegate.remove(this)) { MemberLeaveEvent.Kick(this, group.botAsMember).broadcastBlocking() cancel(CancellationException("Member kicked: $message")) } } override suspend fun modifyAdmin(operation: Boolean) { if (group.botPermission != MemberPermission.OWNER) { throw PermissionDeniedException("Bot is not the owner of group ${group.id}, can't modify the permission of $id($permission") } if (operation && permission > MemberPermission.MEMBER) return if (permission == MemberPermission.OWNER) { throw IllegalArgumentException("Not allowed modify permission of owner ($id, $permission)") } val newPerm = if (operation) MemberPermission.ADMINISTRATOR else MemberPermission.MEMBER if (newPerm != permission) { val oldPerm = permission mockApi.permission = newPerm MemberPermissionChangeEvent(this, oldPerm, newPerm).broadcast() } } override suspend fun sendMessage(message: Message): MessageReceipt<NormalMember> { return super<AbstractMockContact>.sendMessage(message).cast() } override suspend fun mute(durationSeconds: Int) { requireBotPermissionHigherThanThis("mute") require(durationSeconds > 0) { "$durationSeconds < 0" } mockApi.muteTimeEndTimestamp = currentTimeSeconds() + durationSeconds MemberMuteEvent(this, durationSeconds, null) } override suspend fun broadcastMute(target: MockNormalMember, durationSeconds: Int) { target.mockApi.muteTimeEndTimestamp = currentTimeSeconds() + durationSeconds if (target.id == bot.id) { if (durationSeconds == 0) { BotUnmuteEvent(this) } else { BotMuteEvent(durationSeconds, this) } } else { if (durationSeconds == 0) { MemberUnmuteEvent(target, this) } else { MemberMuteEvent(target, durationSeconds, this) } }.broadcast() } override suspend fun says(message: MessageChain): MessageChain { val src = newMsgSrc(true, message) { ids, internalIds, time -> mockApi.lastSpeakTimestamp = time OnlineMsgSrcFromGroup(ids, internalIds, time, message, bot, this) } val msg = src.withMessage(message) GroupMessageEvent(nameCardOrNick, permission, this, msg, src.time).broadcast() return msg } override fun newMessagePreSend(message: Message): MessagePreSendEvent { return GroupTempMessagePreSendEvent(this, message) } override suspend fun postMessagePreSend(message: MessageChain, receipt: MessageReceipt<*>) { GroupTempMessagePostSendEvent(this, message, null, receipt.cast()).broadcast() } override fun newMessageSource(message: MessageChain): OnlineMessageSource.Outgoing { return newMsgSrc(false, message) { ids, internalIds, time -> OnlineMsgSrcToTemp(ids, internalIds, time, message, bot, bot, this) } } override fun toString(): String { return "NormalMember($id)" } } ================================================ FILE: mirai-core-mock/src/internal/contact/MockStrangerImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact import kotlinx.coroutines.cancel import net.mamoe.mirai.contact.AvatarSpec import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.Message import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.OnlineMessageSource import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockStranger import net.mamoe.mirai.mock.internal.impl import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcFromStranger import net.mamoe.mirai.mock.internal.msgsrc.OnlineMsgSrcToStranger import net.mamoe.mirai.mock.internal.msgsrc.newMsgSrc import net.mamoe.mirai.utils.cast import java.util.concurrent.CancellationException import kotlin.coroutines.CoroutineContext internal class MockStrangerImpl( parentCoroutineContext: CoroutineContext, bot: MockBot, id: Long, remark: String, nick: String ) : AbstractMockContact(parentCoroutineContext, bot, id), MockStranger { private val ccinfo = bot.impl().contactDatabase.acquireCI(id, nick) override val mockApi: MockStranger.MockApi = object : MockStranger.MockApi { override val contact: MockStranger get() = this@MockStrangerImpl override var nick: String by ccinfo::nick override var remark: String = remark override var avatarUrl: String by ccinfo::avatarUrl } override val avatarUrl: String get() = ccinfo.avatarUrl override fun avatarUrl(spec: AvatarSpec): String { return avatarUrl } override fun changeAvatarUrl(newAvatar: String) { ccinfo.changeAvatarUrl(newAvatar) } override val nick: String get() = mockApi.nick override val remark: String get() = mockApi.remark override fun newMessagePreSend(message: Message): MessagePreSendEvent { return StrangerMessagePreSendEvent(this, message) } override suspend fun postMessagePreSend(message: MessageChain, receipt: MessageReceipt<*>) { StrangerMessagePostSendEvent(this, message, null, receipt.cast()).broadcast() } override fun newMessageSource(message: MessageChain): OnlineMessageSource.Outgoing { return newMsgSrc(false, message) { ids, internalIds, time -> OnlineMsgSrcToStranger(ids, internalIds, time, message, bot, bot, this) } } override suspend fun sendMessage(message: Message): MessageReceipt<Stranger> { return super<AbstractMockContact>.sendMessage(message).cast() } override suspend fun delete() { if (bot.strangers.delegate.remove(this)) { StrangerRelationChangeEvent.Deleted(this).broadcast() cancel(CancellationException("Stranger deleted")) } } override suspend fun says(message: MessageChain): MessageChain { val src = newMsgSrc(true, message) { ids, internalIds, time -> OnlineMsgSrcFromStranger(ids, internalIds, time, message, bot, this, bot) } val msg = src.withMessage(message) StrangerMessageEvent(this, msg, src.time).broadcast() return msg } } ================================================ FILE: mirai-core-mock/src/internal/contact/active/MockGroupActive.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact.active import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.active.* import net.mamoe.mirai.contact.checkBotPermission import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.events.GroupTalkativeChangeEvent import net.mamoe.mirai.event.events.MemberHonorChangeEvent import net.mamoe.mirai.mock.contact.MockNormalMember import net.mamoe.mirai.mock.contact.active.MockGroupActive import net.mamoe.mirai.mock.internal.contact.MockGroupImpl import net.mamoe.mirai.mock.utils.broadcastBlocking import net.mamoe.mirai.utils.ConcurrentHashMap import net.mamoe.mirai.utils.JavaFriendlyAPI import net.mamoe.mirai.utils.asImmutable import java.util.stream.Stream import kotlin.collections.set internal class MockGroupActiveImpl( private val group: MockGroupImpl ) : MockGroupActive { @Volatile override var isHonorVisible: Boolean = false override suspend fun setHonorVisible(newValue: Boolean) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) isHonorVisible = newValue } @Volatile override var isTitleVisible: Boolean = false override suspend fun setTitleVisible(newValue: Boolean) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) isTitleVisible = newValue } @Volatile override var isTemperatureVisible: Boolean = false override suspend fun setTemperatureVisible(newValue: Boolean) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) isTemperatureVisible = newValue } @Volatile override var rankTitles: Map<Int, String> = ConcurrentHashMap() override suspend fun setRankTitles(newValue: Map<Int, String>) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) rankTitles = newValue } @Volatile override var temperatureTitles: Map<Int, String> = ConcurrentHashMap() override suspend fun setTemperatureTitles(newValue: Map<Int, String>) { group.checkBotPermission(MemberPermission.ADMINISTRATOR) temperatureTitles = newValue } override suspend fun refresh() { } @Volatile private var records: Collection<ActiveRecord> = listOf() override fun asFlow(): Flow<ActiveRecord> = records.asFlow() @JavaFriendlyAPI override fun asStream(): Stream<ActiveRecord> = records.stream() @Volatile private var activeChart: ActiveChart = ActiveChart(mapOf(), mapOf(), mapOf(), mapOf(), mapOf()) override suspend fun queryChart(): ActiveChart = activeChart private var honorHistories: MutableMap<GroupHonorType, ActiveHonorList> = ConcurrentHashMap() @Suppress("INVISIBLE_MEMBER") // for ActiveHonorInfo override fun changeHonorMember(member: MockNormalMember, honorType: GroupHonorType) { val old = honorHistories[honorType] val info = ActiveHonorInfo(member.nameCard, member.id, member.avatarUrl, member, 0, 0, 0) if (old == null) { // if not history record found, add a new one with current honor member honorHistories[honorType] = ActiveHonorList(honorType, info, emptyList()) } else if (old.current?.memberId != info.memberId) { honorHistories[honorType] = ActiveHonorList(honorType, info, old.current?.let { old.records.plus(it) } ?: old.records) if (old.current != null) { if (honorType == GroupHonorType.TALKATIVE) { GroupTalkativeChangeEvent( this.group, member, old.current!!.member!! ).broadcastBlocking() } MemberHonorChangeEvent.Lose(old.current!!.member!!, honorType).broadcastBlocking() } } MemberHonorChangeEvent.Achieve(member, honorType).broadcastBlocking() } override suspend fun queryHonorHistory(type: GroupHonorType): ActiveHonorList { return honorHistories.getOrElse(type) { ActiveHonorList(type, null, listOf()) } } @Volatile private var ranks: List<ActiveRankRecord> = listOf() override suspend fun queryActiveRank(): List<ActiveRankRecord> = ranks.asImmutable() /////////////////////////////////////////////////////////////////////////// // mock API /////////////////////////////////////////////////////////////////////////// override fun mockSetActiveRecords(records: Collection<ActiveRecord>) { this.records = records } override fun mockSetChart(chart: ActiveChart) { activeChart = chart } override fun mockSetHonorHistory(type: GroupHonorType, activeHonorList: ActiveHonorList?) { if (activeHonorList != null) { honorHistories[type] = activeHonorList } else { honorHistories.remove(type) } } override fun mockSetRankRecords(list: List<ActiveRankRecord>) { this.ranks = list } } ================================================ FILE: mirai-core-mock/src/internal/contact/active/MockMemberActiveImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact.active import net.mamoe.mirai.contact.active.MemberMedalInfo import net.mamoe.mirai.contact.active.MemberMedalType import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.mock.contact.active.MockMemberActive internal class MockMemberActiveImpl : MockMemberActive { override fun mockSetRank(value: Int) { rank = value } override fun mockSetPoint(value: Int) { point = value } override fun mockSetHonors(value: Set<GroupHonorType>) { honors = value } override fun mockSetTemperature(value: Int) { temperature = value } override fun mockSetMedal(info: MemberMedalInfo) { medal = info } override var rank: Int = 0 override var point: Int = 0 override var honors: Set<GroupHonorType> = setOf() override var temperature: Int = 0 @Volatile var medal: MemberMedalInfo = MemberMedalInfo("", "", MemberMedalType.ACTIVE, setOf()) override suspend fun queryMedal(): MemberMedalInfo = medal } ================================================ FILE: mirai-core-mock/src/internal/contact/essence/MockEssences.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact.essence import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.essence.EssenceMessageRecord import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.mock.contact.essence.MockEssences import net.mamoe.mirai.mock.internal.contact.MockGroupImpl import net.mamoe.mirai.utils.ConcurrentHashMap import net.mamoe.mirai.utils.currentTimeSeconds internal class MockEssencesImpl( private val group: MockGroupImpl ) : MockEssences { private val cache: MutableMap<MessageSource, EssenceMessageRecord> = ConcurrentHashMap() override fun mockSetEssences(source: MessageSource, actor: NormalMember) { val record = EssenceMessageRecord( group = group, sender = group[source.fromId], senderId = source.fromId, senderNick = group[source.fromId]?.nick.orEmpty(), senderTime = source.time, operator = actor, operatorId = actor.id, operatorNick = actor.nick, operatorTime = currentTimeSeconds().toInt(), loadMessageSource = { source } ) cache[source] = record } override suspend fun getPage(start: Int, limit: Int): List<EssenceMessageRecord> { return cache.values.toList().subList(start, start + limit) } override suspend fun share(source: MessageSource): String { return "https://qun.qq.com/essence/share?_wv=3&_wwv=128&_wvx=2&sharekey=..." } override suspend fun remove(source: MessageSource) { cache.remove(source) } override fun asFlow(): Flow<EssenceMessageRecord> { return cache.values.asFlow() } } ================================================ FILE: mirai-core-mock/src/internal/contact/friendfroup/MockFriendGroup.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact.friendfroup import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockFriend import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.utils.cast internal class MockFriendGroup( private val bot: MockBot, override val id: Int, override var name: String, ) : FriendGroup { override val friends: Collection<Friend> = object : AbstractCollection<Friend>() { private val seq = sequence<Friend> { bot.friends.forEach { mf -> if (mf.mockApi.friendGroupId == id) { yield(mf) } } } override fun isEmpty(): Boolean { return bot.friends.none { it.mockApi.friendGroupId == id } } override fun contains(element: Friend): Boolean { if (element !is MockFriend) return false if (element.bot !== bot) return false return element.mockApi.friendGroupId == id } override val size: Int get() = bot.friends.count { it.mockApi.friendGroupId == id } override fun iterator(): Iterator<Friend> { return seq.iterator() } } override suspend fun renameTo(newName: String): Boolean { name = newName return true } override suspend fun moveIn(friend: Friend): Boolean { val api = friend.mock().mockApi if (api.friendGroupId == id) return false api.friendGroupId = id return true } override suspend fun delete(): Boolean { if (id == 0) return false if (bot.friendGroups.cast<MockFriendGroups>().groups.remove(this)) { friends.forEach { it.mock().mockApi.friendGroupId = 0 } return true } return false } override val count: Int get() = friends.size } ================================================ FILE: mirai-core-mock/src/internal/contact/friendfroup/MockFriendGroups.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact.friendfroup import net.mamoe.mirai.contact.friendgroup.FriendGroup import net.mamoe.mirai.contact.friendgroup.FriendGroups import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.utils.ConcurrentLinkedDeque import net.mamoe.mirai.utils.asImmutable import kotlin.math.absoluteValue import kotlin.random.Random internal class MockFriendGroups( private val bot: MockBot, ) : FriendGroups { internal val groups = ConcurrentLinkedDeque<MockFriendGroup>() private val defaultX = MockFriendGroup(bot, 0, "默认分组") override val default: FriendGroup get() = defaultX init { groups.addLast(defaultX) } override suspend fun create(name: String): FriendGroup { var newId: Int do { newId = Random.nextInt().absoluteValue } while (groups.any { it.id == newId }) val newG = MockFriendGroup(bot, newId, name) groups.addLast(newG) return newG } override fun get(id: Int): FriendGroup? { if (id == 0) return defaultX return groups.find { it.id == id } } override fun asCollection(): Collection<FriendGroup> { return groups.asImmutable() } fun findOrDefault(friendGroupId: Int): FriendGroup { return get(friendGroupId) ?: defaultX } } ================================================ FILE: mirai-core-mock/src/internal/contact/roaming/MockRoamingMessages.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contact.roaming import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import net.mamoe.mirai.contact.Friend import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Stranger import net.mamoe.mirai.contact.roaming.RoamingMessageFilter import net.mamoe.mirai.contact.roaming.RoamingMessages import net.mamoe.mirai.contact.roaming.RoamingSupported import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.mock.internal.MockBotImpl import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.utils.JavaFriendlyAPI import net.mamoe.mirai.utils.cast import java.util.stream.Stream import kotlin.streams.asStream internal class MockRoamingMessages( internal val contact: RoamingSupported, ) : RoamingMessages { override suspend fun getMessagesIn( timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter? ): Flow<MessageChain> { return getMsg(timeStart, timeEnd, filter).asFlow() } private fun getMsg( timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter? ): Sequence<MessageChain> { val msgDb = contact.bot.cast<MockBotImpl>().msgDatabase return msgDb.queryMessageInfosBy( contact.id, when (contact) { is Friend -> MessageSourceKind.FRIEND is Group -> MessageSourceKind.GROUP is Stranger -> MessageSourceKind.STRANGER else -> error(contact.javaClass.toString()) }, contact, timeStart, timeEnd, filter ?: RoamingMessageFilter.ANY ).map { it.buildSource(contact.bot.mock()) + it.message } } @JavaFriendlyAPI override suspend fun getMessagesStream( timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter? ): Stream<MessageChain> { return getMsg(timeStart, timeEnd, filter).asStream() } } ================================================ FILE: mirai-core-mock/src/internal/contact/util.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") package net.mamoe.mirai.mock.internal.contact import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.Member import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.internal.contact.uin import net.mamoe.mirai.internal.message.data.OnlineAudioImpl import net.mamoe.mirai.internal.message.image.DeferredOriginUrlAware import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.mock.utils.plusHttpSubpath import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.Services import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.toUHexString internal fun Member.requireBotPermissionHigherThanThis(msg: String) { if (this.permission < this.group.botPermission) return throw PermissionDeniedException("bot current permission ${group.botPermission} can't modify $id($permission), $msg") } internal fun MessageSource.withMessage(msg: Message): MessageChain = buildMessageChain { add(this@withMessage) if (msg is MessageChain) { msg.forEach { sub -> if (sub !is MessageSource) { add(sub) } } } else if (msg !is MessageSource) { add(msg) } } @Suppress("UNUSED_PARAMETER") internal suspend fun ExternalResource.mockUploadAudio(bot: MockBot): OfflineAudio { val md5 = md5 // calculate before using resource return inResource { OfflineAudio( filename = md5.toUHexString() + ".amr", fileMd5 = md5, fileSize = size, codec = AudioCodec.SILK, extraData = null, ) } } internal suspend fun ExternalResource.mockUploadVoice(bot: MockBot) = kotlin.run { val md5 = this.md5 val size = this.size @Suppress("DEPRECATION_ERROR") net.mamoe.mirai.message.data.Voice( fileName = md5.toUHexString() + ".amr", md5 = md5, fileSize = size, _url = bot.tmpResourceServer.uploadResourceAndGetUrl(this) ) } internal const val AQQ_RECALL_FAILED_MESSAGE: String = "No message meets the requirements" internal val Group.mockUin: Long get() = when (this) { is MockGroup -> this.uin else -> this.uin } internal suspend fun ExternalResource.mockImplUploadAudioAsOnline(bot: MockBot): OnlineAudio { val md5 = this.md5 val size = this.size return OnlineAudioImpl( filename = md5.toUHexString() + ".amr", fileMd5 = md5, fileSize = size, codec = AudioCodec.SILK, url = bot.tmpResourceServer.uploadResourceAndGetUrl(this), length = size, originalPtt = null, ) } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(MockImage.Serializer::class) internal class MockImage( override val imageId: String, private val urlPath: String, override val width: Int = 0, override val height: Int = 0, override val size: Long = 0, override val imageType: ImageType = ImageType.UNKNOWN, ) : DeferredOriginUrlAware, Image { companion object { // create a mockImage with random content internal suspend fun random(bot: MockBot): MockImage { val text = bot.avatarGenerator.generateRandomAvatar() return bot.uploadMockImage(text.toExternalResource().toAutoCloseable()).cast() } internal suspend fun randomForPerson(bot: MockBot, id: Long): MockImage { val text = bot.avatarGenerator.generateAvatarForPerson(id) return bot.uploadMockImage(text.toExternalResource().toAutoCloseable()).cast() } internal suspend fun randomForGroup(bot: MockBot, id: Long): MockImage { val text = bot.avatarGenerator.generateAvatarForGroup(id) return bot.uploadMockImage(text.toExternalResource().toAutoCloseable()).cast() } } object Serializer : Image.FallbackSerializer("MockImage") private val _stringValue: String? by lazy(LazyThreadSafetyMode.NONE) { "[mirai:image:$imageId]" } override fun getUrl(bot: Bot): String { if (urlPath.startsWith("http")) return urlPath return bot.mock().tmpResourceServer.storageRoot.toString().plusHttpSubpath(urlPath) } override fun toString(): String = _stringValue!! override fun contentToString(): String = if (isEmoji) { "[动画表情]" } else { "[图片]" } override fun appendMiraiCodeTo(builder: StringBuilder) { builder.append("[mirai:image:").append(imageId).append("]") } override fun hashCode(): Int = imageId.hashCode() override fun equals(other: Any?): Boolean { if (other === this) return true if (other !is Image) return false return this.imageId == other.imageId } } internal object MockInternalImageProtocolImpl : InternalImageProtocol { override fun createImage( imageId: String, size: Long, type: ImageType, width: Int, height: Int, isEmoji: Boolean ): Image = MockImage(imageId, "images/" + imageId.substring(1..36), width, height, size, type) override suspend fun isUploaded( bot: Bot, md5: ByteArray, size: Long, context: Contact?, type: ImageType, width: Int, height: Int ): Boolean = bot.cast<MockBot>().tmpResourceServer.isImageUploaded(md5, size) } internal fun registerMockServices() { Services.registerAsOverride( Services.qualifiedNameOrFail(InternalImageProtocol::class), "net.mamoe.mirai.mock.internal.contact.MockInternalImageProtocolImpl" ) { MockInternalImageProtocolImpl } } ================================================ FILE: mirai-core-mock/src/internal/contactbase/ContactDatabase.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contactbase import net.mamoe.mirai.mock.internal.MockBotImpl import net.mamoe.mirai.utils.ConcurrentHashMap internal class ContactDatabase( private val bot: MockBotImpl, ) { val contacts = ConcurrentHashMap<Long, ContactInfo>() fun acquireCI(id: Long): ContactInfo { return contacts.computeIfAbsent(id) { ContactInfo(bot, id, bot.nameGenerator.nextFriendName()) } } fun acquireCI(id: Long, name: String): ContactInfo { return contacts.computeIfAbsent(id) { ContactInfo(bot, id, name) }.also { rsp -> if (rsp.nick != name) rsp.changeNick(name) } } } ================================================ FILE: mirai-core-mock/src/internal/contactbase/ContactInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.contactbase import kotlinx.coroutines.runBlocking import net.mamoe.mirai.event.events.BotAvatarChangedEvent import net.mamoe.mirai.event.events.BotNickChangedEvent import net.mamoe.mirai.event.events.FriendAvatarChangedEvent import net.mamoe.mirai.event.events.FriendNickChangedEvent import net.mamoe.mirai.mock.internal.MockBotImpl import net.mamoe.mirai.mock.internal.contact.MockImage import net.mamoe.mirai.mock.utils.broadcastBlocking import net.mamoe.mirai.utils.lateinitMutableProperty internal class ContactInfo( private val declaredBot: MockBotImpl, @JvmField val id: Long, @JvmField var nick: String, ) { var avatarUrl: String by lateinitMutableProperty { runBlocking { MockImage.randomForPerson(declaredBot, id).getUrl(declaredBot) } } fun changeAvatarUrl(newAvatar: String) { avatarUrl = newAvatar if (declaredBot.id == id) { BotAvatarChangedEvent(declaredBot).broadcastBlocking() return } declaredBot.getFriend(id)?.let { FriendAvatarChangedEvent(it).broadcastBlocking() } } fun changeNick(newNick: String) { if (id == declaredBot.id) { val o = nick nick = newNick BotNickChangedEvent(declaredBot, o, newNick).broadcastBlocking() return } val friend = declaredBot.getFriend(id) if (friend == null) { nick = newNick return } val o = nick nick = newNick FriendNickChangedEvent(friend, o, newNick).broadcastBlocking() } } ================================================ FILE: mirai-core-mock/src/internal/db/MsgDatabaseImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.db import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.roaming.RoamingMessage import net.mamoe.mirai.contact.roaming.RoamingMessageFilter import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.mock.database.MessageDatabase import net.mamoe.mirai.mock.database.MessageInfo import net.mamoe.mirai.mock.database.mockMsgDatabaseId import java.util.concurrent.ConcurrentLinkedDeque import java.util.concurrent.atomic.AtomicInteger import kotlin.random.Random internal class MsgDatabaseImpl : MessageDatabase { override fun disconnect() {} override fun connect() {} val db = ConcurrentLinkedDeque<MessageInfo>() val idCounter1 = AtomicInteger(Random.nextInt()) val idCounter2 = AtomicInteger(Random.nextInt()) override fun newMessageInfo( sender: Long, subject: Long, kind: MessageSourceKind, time: Long, message: MessageChain, ): MessageInfo { val dbid = mockMsgDatabaseId(idCounter1.getAndIncrement(), idCounter2.getAndDecrement()) val info = MessageInfo( mixinedMsgId = dbid, sender = sender, subject = subject, kind = kind, time = time, message = message, ) db.add(info) return info } override fun queryMessageInfo(msgId: Long): MessageInfo? { return db.firstOrNull { it.mixinedMsgId == msgId } } override fun removeMessageInfo(msgId: Long) { db.removeIf { it.mixinedMsgId == msgId } } override fun queryMessageInfosBy( subject: Long, kind: MessageSourceKind, contact: Contact, timeStart: Long, timeEnd: Long, filter: RoamingMessageFilter ): Sequence<MessageInfo> { if (timeEnd < timeStart) return emptySequence() return sequence<MessageInfo> { val rm = object : RoamingMessage { override val contact: Contact get() = contact override var sender: Long = -1 override var target: Long = -1 override var time: Long = -1 override val ids: IntArray = intArrayOf(-1) override val internalIds: IntArray = intArrayOf(-1) } for (msgInfo in db) { if (msgInfo.kind != kind) continue if (msgInfo.time < timeStart) continue if (msgInfo.time > timeEnd) continue if (msgInfo.subject != subject) continue rm.sender = msgInfo.sender if (kind != MessageSourceKind.GROUP) { if (msgInfo.sender == contact.id) { rm.target = contact.bot.id } else { rm.target = msgInfo.subject } } else { rm.target = msgInfo.subject } rm.time = msgInfo.time rm.ids[0] = msgInfo.id rm.internalIds[0] = msgInfo.internal if (filter.invoke(rm)) { yield(msgInfo) } } } } } ================================================ FILE: mirai-core-mock/src/internal/msgsrc/OnlineMsgSrc.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") @file:OptIn(MiraiInternalApi::class) package net.mamoe.mirai.mock.internal.msgsrc import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl import net.mamoe.mirai.internal.message.protocol.MessageProtocolFacadeImpl import net.mamoe.mirai.internal.message.protocol.StubMessageProtocol import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.internal.contact.AbstractMockContact import net.mamoe.mirai.mock.internal.contact.MockImage import net.mamoe.mirai.utils.MiraiInternalApi import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.currentTimeSeconds internal fun registerMockMsgSerializers() { val serializers = mutableListOf<MessageSerializer<*>>() MessageSerializer.superclassesScope(Image::class, MessageContent::class, SingleMessage::class) { serializers.add( MessageSerializer( MockImage::class, MockImage.serializer() ) ) } MessageSerializer.superclassesScope(MessageSource::class, MessageMetadata::class, SingleMessage::class) { serializers.add( MessageSerializer( OnlineMsgSrcToGroup::class, OnlineMsgSrcToGroup.serializer() ) ) serializers.add( MessageSerializer( OnlineMsgSrcToFriend::class, OnlineMsgSrcToFriend.serializer() ) ) serializers.add( MessageSerializer( OnlineMsgSrcToStranger::class, OnlineMsgSrcToStranger.serializer() ) ) serializers.add( MessageSerializer( OnlineMsgSrcToTemp::class, OnlineMsgSrcToTemp.serializer() ) ) serializers.add( MessageSerializer( OnlineMsgSrcFromGroup::class, OnlineMsgSrcFromGroup.serializer() ) ) serializers.add( MessageSerializer( OnlineMsgSrcFromFriend::class, OnlineMsgSrcFromFriend.serializer() ) ) serializers.add( MessageSerializer( OnlineMsgSrcFromStranger::class, OnlineMsgSrcFromStranger.serializer() ) ) serializers.add( MessageSerializer( OnlineMsgSrcFromTemp::class, OnlineMsgSrcFromTemp.serializer() ) ) } val module = MessageProtocolFacadeImpl(listOf(StubMessageProtocol), "").also { it.serializers.addAll(serializers) }.createSerializersModule() MessageSerializers.registerSerializers(module) } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcToGroup.Serializer::class) internal class OnlineMsgSrcToGroup( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Bot, override val target: Group ) : OnlineMessageSource.Outgoing.ToGroup() { override val isOriginalMessageInitialized: Boolean get() = true object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("Mock_OnlineMessageSourceToGroup") } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcToFriend.Serializer::class) internal class OnlineMsgSrcToFriend( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Bot, override val target: Friend ) : OnlineMessageSource.Outgoing.ToFriend() { override val isOriginalMessageInitialized: Boolean get() = true object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("Mock_OnlineMessageSourceToFriend") } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcToStranger.Serializer::class) internal class OnlineMsgSrcToStranger( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Bot, override val target: Stranger ) : OnlineMessageSource.Outgoing.ToStranger() { override val isOriginalMessageInitialized: Boolean get() = true object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("Mock_OnlineMessageSourceToStranger") } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcToTemp.Serializer::class) internal class OnlineMsgSrcToTemp( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Bot, override val target: Member ) : OnlineMessageSource.Outgoing.ToTemp() { override val isOriginalMessageInitialized: Boolean get() = true object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("Mock_OnlineMessageSourceToTemp") } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcFromFriend.Serializer::class) internal class OnlineMsgSrcFromFriend( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Friend, override val target: ContactOrBot, ) : OnlineMessageSource.Incoming.FromFriend() { override val isOriginalMessageInitialized: Boolean get() = true override val subject: Friend get() { if (target is Bot) return sender return target.cast() } object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("Mock_OnlineMessageSourceFromFriend") } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcFromStranger.Serializer::class) internal class OnlineMsgSrcFromStranger( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Stranger, override val target: ContactOrBot, ) : OnlineMessageSource.Incoming.FromStranger() { override val isOriginalMessageInitialized: Boolean get() = true override val subject: Stranger get() { if (target is Bot) return sender return target.cast() } object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl( "Mock_OnlineMessageSourceFromStranger" ) } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcFromTemp.Serializer::class) internal class OnlineMsgSrcFromTemp( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Member, override val target: ContactOrBot, ) : OnlineMessageSource.Incoming.FromTemp() { override val isOriginalMessageInitialized: Boolean get() = true override val subject: Member get() { if (target is Bot) return sender return target.cast() } object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("Mock_OnlineMessageSourceFromTemp") } @Suppress("SERIALIZER_TYPE_INCOMPATIBLE") @Serializable(OnlineMsgSrcFromGroup.Serializer::class) internal class OnlineMsgSrcFromGroup( override val ids: IntArray, override val internalIds: IntArray, override val time: Int, override val originalMessage: MessageChain, override val bot: Bot, override val sender: Member ) : OnlineMessageSource.Incoming.FromGroup() { override val isOriginalMessageInitialized: Boolean get() = true object Serializer : KSerializer<MessageSource> by MessageSourceSerializerImpl("Mock_OnlineMessageSourceFromGroup") } internal typealias MsgSrcConstructor<R> = ( ids: IntArray, internalIds: IntArray, time: Int, ) -> R internal inline fun <R> AbstractMockContact.newMsgSrc( isSaying: Boolean, messageChain: MessageChain, time: Long = currentTimeSeconds(), constructor: MsgSrcConstructor<R>, ): R { val db = bot.msgDatabase val info = if (isSaying) { db.newMessageInfo( sender = id, subject = when (this) { is Member -> group.id is Stranger, is Friend, -> this.id else -> error("Invalid contact: $this") }, kind = when (this) { is Member -> MessageSourceKind.GROUP is Stranger -> MessageSourceKind.STRANGER is Friend -> MessageSourceKind.FRIEND else -> error("Invalid contact: $this") }, message = messageChain, time = time, ) } else { db.newMessageInfo( sender = bot.id, subject = this.id, kind = when (this) { is NormalMember -> MessageSourceKind.TEMP is Stranger -> MessageSourceKind.STRANGER is Friend -> MessageSourceKind.FRIEND is Group -> MessageSourceKind.GROUP else -> error("Invalid contact: $this") }, message = messageChain, time = time, ) } return constructor( intArrayOf(info.id), intArrayOf(info.internal), info.time.toInt(), ) } ================================================ FILE: mirai-core-mock/src/internal/remotefile/absolutefile/MockAbsoluteFile.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("invisible_member", "INVISIBLE_REFERENCE") package net.mamoe.mirai.mock.internal.remotefile.absolutefile import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.firstOrNull import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.internal.message.data.FileMessageImpl import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.mock.internal.remotefile.remotefile.MockRemoteFile import net.mamoe.mirai.mock.resserver.MockServerRemoteFile import net.mamoe.mirai.mock.utils.mock internal class MockAbsoluteFile( override val sha1: ByteArray, override val md5: ByteArray, private val files: MockRemoteFiles, override var parent: AbsoluteFolder?, override val id: String, override var name: String, override var absolutePath: String, override val contact: FileSupported = files.contact, override var expiryTime: Long = 0L, override val size: Long = 0, override val isFile: Boolean = true, override val isFolder: Boolean = false, override val uploadTime: Long = 0, override var lastModifiedTime: Long = 0, override val uploaderId: Long = 0 ) : AbsoluteFile { @Volatile private var _exists = true override suspend fun moveTo(folder: AbsoluteFolder): Boolean { if (!exists()) return false files.fileSystem.resolveById(id)!!.moveTo(files.fileSystem.resolveById(folder.id)!!) this.parent = folder refresh() return true } override suspend fun getUrl(): String = files.contact.bot.mock().tmpResourceServer.resolveHttpUrlByPath( files.fileSystem.resolveById(id)!!.resolveNativePath() ).toString() override fun toMessage(): FileMessage { //todo busId return FileMessageImpl(id, 0, name, size) } override suspend fun refreshed(): AbsoluteFile? = parent!!.files().filter { it.id == id }.firstOrNull() private fun canModify(resolved: MockServerRemoteFile): Boolean { return MockRemoteFile.canModify(resolved, contact) } override suspend fun exists(): Boolean = _exists override suspend fun renameTo(newName: String): Boolean { if (!exists()) return false val resolved = files.fileSystem.resolveById(id) ?: return false if (!canModify(resolved)) return false if (resolved.rename(newName)) { refresh() return true } return false } override suspend fun delete(): Boolean { if (!exists()) return false val resolved = files.fileSystem.resolveById(id) ?: return false if (!canModify(resolved)) return false if (resolved.delete()) { _exists = false return true } return false } override suspend fun refresh(): Boolean { val new = refreshed() if (new == null) { _exists = false return false } _exists = true this.parent = new.parent this.expiryTime = new.expiryTime this.name = new.name this.lastModifiedTime = new.lastModifiedTime this.absolutePath = new.absolutePath return true } override fun toString(): String = "MockAbsoluteFile(id=$id,absolutePath=$absolutePath,name=$name)" override fun equals(other: Any?): Boolean = other != null && other is AbsoluteFile && other.id == id override fun hashCode(): Int { // from absoluteFileImpl var result = super.hashCode() result = 31 * result + expiryTime.hashCode() result = 31 * result + size.hashCode() result = 31 * result + sha1.contentHashCode() result = 31 * result + md5.contentHashCode() return result } } ================================================ FILE: mirai-core-mock/src/internal/remotefile/absolutefile/MockAbsoluteFolder.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file: Suppress("invisible_member", "invisible_reference") package net.mamoe.mirai.mock.internal.remotefile.absolutefile import kotlinx.coroutines.flow.* import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.contact.file.AbsoluteFile import net.mamoe.mirai.contact.file.AbsoluteFileFolder import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.internal.utils.FileSystem import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.internal.remotefile.remotefile.MockRemoteFile import net.mamoe.mirai.mock.resserver.MockServerRemoteFile import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.JavaFriendlyAPI import net.mamoe.mirai.utils.ProgressionCallback import net.mamoe.mirai.utils.safeCast import java.util.stream.Stream import kotlin.streams.asStream private fun MockServerRemoteFile.toMockAbsFolder(files: MockRemoteFiles): AbsoluteFolder { if (this == files.fileSystem.root) return files.root val parent = this.parent.toMockAbsFolder(files) return MockAbsoluteFolder( files, parent, this.id, this.name, parent.absolutePath.removeSuffix("/") + "/" + this.name, contentsCount = this.listFiles()?.count() ?: 0 ) } private fun MockServerRemoteFile.toMockAbsFile( files: MockRemoteFiles, md5: ByteArray = byteArrayOf(), sha1: ByteArray = byteArrayOf() ): AbsoluteFile { val parent = this.parent.toMockAbsFolder(files) return if (md5.isEmpty() || sha1.isEmpty()) { asExternalResource().use { res -> MockAbsoluteFile( if (sha1.isEmpty()) res.sha1 else sha1, if (md5.isEmpty()) res.md5 else md5, files, parent, this.id, this.name, parent.absolutePath.removeSuffix("/") + "/" + this.name ) } } else { MockAbsoluteFile( sha1, md5, files, parent, this.id, this.name, parent.absolutePath.removeSuffix("/") + "/" + this.name ) } } internal open class MockAbsoluteFolder( internal val files: MockRemoteFiles, override val parent: AbsoluteFolder? = null, override val id: String = "/", override var name: String = "/", override var absolutePath: String = "/", override val contact: FileSupported = files.contact, override val isFile: Boolean = false, override val isFolder: Boolean = true, override val uploadTime: Long = 0L, override var lastModifiedTime: Long = 0L, override val uploaderId: Long = 0L, override var contentsCount: Int = 0 ) : AbsoluteFolder { private var _exists = true override suspend fun refreshed(): AbsoluteFolder? = parent!!.resolveFolderById(id) private fun currentTxRF() = files.fileSystem.resolveById(id)!! override suspend fun folders(): Flow<AbsoluteFolder> = currentTxRF().listFiles()?.filter { it.isDirectory }?.map { it.toMockAbsFolder(files) }?.asFlow() ?: emptyFlow() @JavaFriendlyAPI override suspend fun foldersStream(): Stream<AbsoluteFolder> = currentTxRF().listFiles()?.filter { it.isDirectory }?.map { it.toMockAbsFolder(files) }?.asStream() ?: Stream.empty() override suspend fun files(): Flow<AbsoluteFile> = currentTxRF().listFiles()?.filter { it.isFile }?.map { it.toMockAbsFile(files) }?.asFlow() ?: emptyFlow() @JavaFriendlyAPI override suspend fun filesStream(): Stream<AbsoluteFile> = currentTxRF().listFiles()?.filter { it.isFile }?.map { it.toMockAbsFile(files) }?.asStream() ?: Stream.empty() override suspend fun children(): Flow<AbsoluteFileFolder> = files.fileSystem.resolveById(id)!!.listFiles()?.map { if (it.isFile) it.toMockAbsFile(files) else it.toMockAbsFolder(files) }?.asFlow() ?: emptyFlow() @JavaFriendlyAPI override suspend fun childrenStream(): Stream<AbsoluteFileFolder> = files.fileSystem.resolveById(id)!!.listFiles()?.map { if (it.isFile) it.toMockAbsFile(files) else it.toMockAbsFolder(files) }?.asStream() ?: Stream.empty() override suspend fun createFolder(name: String): AbsoluteFolder { if (name.isBlank()) throw IllegalArgumentException("folder name cannot be blank.") contact.safeCast<MockGroup>()?.let check@{ group -> if (group.botPermission.isOperator()) return@check throw IllegalStateException("Requires admin permission to create folder `$name`") } FileSystem.checkLegitimacy(name) currentTxRF().mksubdir(name, 0L) return resolveFolder(name)!! } override suspend fun resolveFolder(name: String): AbsoluteFolder? { FileSystem.checkLegitimacy(name) if (name.isBlank()) throw IllegalArgumentException("folder path cannot be blank") val n = name.removePrefix("/").removeSuffix("/") val a = absolutePath.removeSuffix("/") val f = files.fileSystem.findByPath("$a/$n").firstOrNull() ?: return null return f.toMockAbsFolder(files) } override suspend fun resolveFolderById(id: String): AbsoluteFolder? { if (name.isBlank()) throw IllegalArgumentException("folder id cannot be blank.") if (!FileSystem.isLegal(id)) return null if (id == files.root.id) return files.root if (this.id != files.root.id) return null // tx服务器只支持一层文件夹 val f = files.fileSystem.resolveById(id) ?: return null if (!f.exists || !f.isDirectory) return null return f.toMockAbsFolder(files) } override suspend fun resolveFileById(id: String, deep: Boolean): AbsoluteFile? { if (id == "/" || id.isEmpty()) throw IllegalArgumentException("Illegal file id: $id") files().firstOrNull { it.id == id }?.let { return it } if (!deep) return null return folders().map { it.resolveFileById(id, deep) }.firstOrNull { it != null } } override suspend fun resolveFiles(path: String): Flow<AbsoluteFile> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") if (!FileSystem.isLegal(path)) return emptyFlow() if (path[0] == '/') return files.root.resolveFiles(path.removePrefix("/")) return files.fileSystem.findByPath(absolutePath.removeSuffix("/") + "/" + path.removePrefix("/")) .filter { it.isFile } .map { it.toMockAbsFile(files) }.asFlow() } @JavaFriendlyAPI override suspend fun resolveFilesStream(path: String): Stream<AbsoluteFile> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") if (!FileSystem.isLegal(path)) return Stream.empty() if (path[0] == '/') return files.root.resolveFilesStream(path.removePrefix("/")) if (path.contains("/")) return resolveFolder(path.substringBefore("/"))?.resolveFilesStream( path.substringAfter( "/" ) ) ?: Stream.empty() return files.fileSystem.findByPath(absolutePath).map { it.toMockAbsFile(files) }.asStream() } override suspend fun resolveAll(path: String): Flow<AbsoluteFileFolder> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") FileSystem.checkLegitimacy(path) val p = if (path.startsWith("/")) path else "${absolutePath.removeSuffix("/")}/$path" return files.fileSystem.findByPath(p).map { if (it.isDirectory) it.toMockAbsFolder(files) else it.toMockAbsFile(files) }.asFlow() } @JavaFriendlyAPI override suspend fun resolveAllStream(path: String): Stream<AbsoluteFileFolder> { if (path.isBlank()) throw IllegalArgumentException("path cannot be blank.") FileSystem.checkLegitimacy(path) val p = if (path.startsWith("/")) path else "${absolutePath.removeSuffix("/")}/$path" return files.fileSystem.findByPath(p).map { if (it.isDirectory) it.toMockAbsFolder(files) else it.toMockAbsFile(files) }.asStream() } override suspend fun uploadNewFile( filepath: String, content: ExternalResource, callback: ProgressionCallback<AbsoluteFile, Long>? ): AbsoluteFile { contact.safeCast<MockGroup>()?.let check@{ group -> if (group.controlPane.isAllowMemberFileUploading) return@check if (group.botPermission.isOperator()) return@check throw PermissionDeniedException("Group $group not allowed members to uploading new files.") } FileSystem.checkLegitimacy(filepath) val folderName = filepath.removePrefix("/").substringBeforeLast("/") val folder = if (folderName == "") files.root else if (filepath.removePrefix("/").contains("/")) resolveFolder(folderName) ?: createFolder(folderName) else this val f = files.fileSystem.resolveById(folder.id)!! .uploadFile(filepath.substringAfterLast("/"), content, 0L) return f.toMockAbsFile(files, content.md5, content.sha1) } override suspend fun exists(): Boolean = _exists private fun canModify(resolved: MockServerRemoteFile): Boolean { return MockRemoteFile.canModify(resolved, contact) } override suspend fun renameTo(newName: String): Boolean { val resolved = files.fileSystem.resolveById(id) ?: return false if (!canModify(resolved)) return false if (resolved.rename(newName)) { refresh() return true } return false } override suspend fun delete(): Boolean { if (!_exists) return false val resolved = files.fileSystem.resolveById(id) ?: return false if (!canModify(resolved)) return false if (resolved.delete()) { _exists = false return true } return false } override suspend fun refresh(): Boolean { val new = refreshed() ?: let { _exists = false return false } this.name = new.name this.lastModifiedTime = new.lastModifiedTime this.contentsCount = new.contentsCount this.absolutePath = new.absolutePath return false } override fun toString(): String = "MockAbsoluteFolder(id=$id,absolutePath=$absolutePath,name=$name" override fun equals(other: Any?): Boolean = other != null && other is AbsoluteFolder && other.id == id override fun hashCode(): Int { // from AbsoluteFolderImpl var result = super.hashCode() result = 31 * result + contentsCount.hashCode() return result } } ================================================ FILE: mirai-core-mock/src/internal/remotefile/absolutefile/MockRemoteFiles.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("ClassName") package net.mamoe.mirai.mock.internal.remotefile.absolutefile import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.file.AbsoluteFolder import net.mamoe.mirai.contact.file.RemoteFiles import net.mamoe.mirai.mock.resserver.MockServerFileSystem internal class MockRemoteFiles( override val contact: FileSupported, val fileSystem: MockServerFileSystem, ) : RemoteFiles { override val root: AbsoluteFolder = MRF_AbsoluteFolderRoot(this) } internal class MRF_AbsoluteFolderRoot(files: MockRemoteFiles) : MockAbsoluteFolder(files) { override var contentsCount: Int get() = 0 set(_) {} override suspend fun refreshed(): AbsoluteFolder = MRF_AbsoluteFolderRoot(files) override val parent: AbsoluteFolder? get() = null override val id: String get() = "/" override var name: String get() = "/" set(_) {} override var absolutePath: String get() = "/" set(_) {} override val isFile: Boolean get() = false override val isFolder: Boolean get() = true override val uploadTime: Long get() = 0 override var lastModifiedTime: Long get() = 0 set(_) {} override val uploaderId: Long get() = 0 override suspend fun exists(): Boolean = true override suspend fun renameTo(newName: String): Boolean = false override suspend fun delete(): Boolean = false override suspend fun refresh(): Boolean = true } ================================================ FILE: mirai-core-mock/src/internal/remotefile/remotefile/MockRemoteFile.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("DEPRECATION", "DEPRECATION_ERROR", "INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.mock.internal.remotefile.remotefile import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import net.mamoe.mirai.contact.Contact import net.mamoe.mirai.contact.FileSupported import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.contact.isOperator import net.mamoe.mirai.internal.message.data.FileMessageImpl import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.mock.contact.MockGroup import net.mamoe.mirai.mock.resserver.MockServerFileSystem import net.mamoe.mirai.mock.resserver.MockServerRemoteFile import net.mamoe.mirai.mock.utils.mock import net.mamoe.mirai.utils.* import kotlin.io.path.inputStream internal class RootRemoteFile( val fileSystem: MockServerFileSystem, override val contact: FileSupported, ) : RemoteFile { override val name: String get() = "" override val id: String get() = "/" override val path: String get() = "/" override val parent: RemoteFile get() = this override suspend fun isFile(): Boolean = false override suspend fun length(): Long = 0 override suspend fun getInfo(): RemoteFile.FileInfo = fileSystem.root.fileInfo.let { inf -> RemoteFile.FileInfo( name = "/", path = "/", id = "/", length = 0, downloadTimes = 0, uploaderId = inf.creator, uploadTime = inf.createTime, lastModifyTime = inf.lastUpdateTime, sha1 = byteArrayOf(), md5 = byteArrayOf(), ) } override suspend fun exists(): Boolean = true override fun toString(): String = "MockRemoteFile[ROOT, contact=$contact]" override fun resolve(relative: String): RemoteFile { if (relative.isEmpty()) return this val fixedPath = when { relative[0] == '/' -> relative else -> "/$relative" }.let { ist -> var end = ist.length while (end > 1 && ist[end - 1] == '/') { end-- } ist.substring(0, end) } if (fixedPath == "/" || fixedPath == ".") return this val fixedName = fixedPath.substringAfterLast('/') return MockRemoteFile( root = this, parent = resolve(fixedPath.substring(0, fixedPath.lastIndexOf('/'))), path = fixedPath, fileId = null, name = fixedName, ) } override fun resolve(relative: RemoteFile): RemoteFile = resolve(relative.path) override suspend fun resolveById(id: String, deep: Boolean): RemoteFile? { if (id == "/") return this val resolved = fileSystem.resolveById(id) ?: return null return convert(resolved) } internal fun convert(src: MockServerRemoteFile): RemoteFile { if (src == fileSystem.root) return this return MockRemoteFile( name = src.name, root = this, path = src.path, fileId = src.id, parent = convert(src.parent) ) } override fun resolveSibling(relative: String): RemoteFile = resolve(relative) override fun resolveSibling(relative: RemoteFile): RemoteFile = resolveSibling(relative.path) override suspend fun delete(): Boolean = false override suspend fun renameTo(name: String): Boolean = false override suspend fun moveTo(target: RemoteFile): Boolean = false override suspend fun mkdir(): Boolean = true private fun listFilesSeq(): Sequence<RemoteFile> { return fileSystem.root.listFiles()!!.map { convert(it) } } override suspend fun listFiles(): Flow<RemoteFile> = listFilesSeq().asFlow() @JavaFriendlyAPI override suspend fun listFilesIterator(lazy: Boolean): Iterator<RemoteFile> { return listFilesSeq().iterator() } override suspend fun toMessage(): FileMessage? = null @Deprecated( "Use uploadAndSend instead.", replaceWith = ReplaceWith("this.uploadAndSend(resource, callback)"), level = DeprecationLevel.ERROR ) override suspend fun upload(resource: ExternalResource, callback: RemoteFile.ProgressionCallback?): FileMessage { error("Uploading as root directory") } @MiraiExperimentalApi override suspend fun uploadAndSend(resource: ExternalResource): MessageReceipt<Contact> { error("Uploading as root directory") } override suspend fun getDownloadInfo(): RemoteFile.DownloadInfo? = null fun resolveTx(f: RemoteFile?): MockServerRemoteFile? { if (f === this) return fileSystem.root return f.cast<MockRemoteFile>().resolveFile() } } @Suppress("DuplicatedCode") internal class MockRemoteFile( val root: RootRemoteFile, override val parent: RemoteFile, override val path: String, val fileId: String?, override val name: String, ) : RemoteFile { override val id: String? get() = fileId override val contact: FileSupported get() = root.contact private val fileSystem get() = root.fileSystem internal fun resolveFile(): MockServerRemoteFile? { fileId?.let { fid -> fileSystem.resolveById(fid)?.let { return it } } return fileSystem.findByPath(path).firstOrNull() } private fun convert(src: MockServerRemoteFile): RemoteFile = root.convert(src) override suspend fun isFile(): Boolean { return resolveFile()?.isFile ?: false } override suspend fun length(): Long { val file = resolveFile() ?: return 0 return file.size } override suspend fun getInfo(): RemoteFile.FileInfo? { val resolved = resolveFile() ?: return null val fileInf = resolved.fileInfo return RemoteFile.FileInfo( name = resolved.name, id = resolved.id, path = resolved.path, length = resolved.size, downloadTimes = if (resolved.isFile) 1 else 0, uploaderId = fileInf.creator, uploadTime = fileInf.createTime, lastModifyTime = fileInf.lastUpdateTime, sha1 = if (resolved.isDirectory) { byteArrayOf() } else { resolved.resolveNativePath().inputStream().use { it.sha1() } }, md5 = if (resolved.isDirectory) { byteArrayOf() } else { resolved.resolveNativePath().inputStream().use { it.md5() } }, ) } override suspend fun exists(): Boolean = resolveFile() != null override fun toString(): String { val resolved = resolveFile() return "MockFile[c=$contact, resolved=$resolved]" } override fun resolve(relative: String): RemoteFile { if (relative == "/" || relative == "" || relative[0] == '/') { return root.resolve(relative) } return root.resolve("$path/$relative") } override fun resolve(relative: RemoteFile): RemoteFile = resolve(relative.path) override suspend fun resolveById(id: String, deep: Boolean): RemoteFile? { val resolved = fileSystem.resolveById(id) ?: return null if (deep) return convert(resolved) val thiz = resolveFile() if (resolved.parent == thiz) return convert(resolved) return null } override fun resolveSibling(relative: String): RemoteFile { return parent.resolve(relative) } override fun resolveSibling(relative: RemoteFile): RemoteFile { return parent.resolve(relative) } override suspend fun delete(): Boolean { val resolved = resolveFile() ?: return false if (!canModify(resolved, contact)) return false return resolved.delete() } override suspend fun renameTo(name: String): Boolean { val resolved = resolveFile() ?: return false if (!canModify(resolved, contact)) return false return resolved.rename(name) } override suspend fun moveTo(target: RemoteFile): Boolean { val resolved = resolveFile() ?: return false if (!canModify(resolved, contact)) return false val targetF = root.resolveTx(target.parent) ?: return false resolved.moveTo(targetF) resolved.rename(target.name) return true } override suspend fun mkdir(): Boolean { contact.safeCast<MockGroup>()?.let check@{ group -> if (group.botPermission.isOperator()) return@check return false } if (resolveFile() != null) return false val dirx = root.resolveTx(parent) ?: return false return kotlin.runCatching { dirx.mksubdir(name, contact.bot.id) }.isSuccess } private fun listFilesSeq(): Sequence<RemoteFile> { val resolved = resolveFile()?.listFiles() ?: return emptySequence() return resolved.map { convert(it) } } override suspend fun listFiles(): Flow<RemoteFile> { return listFilesSeq().asFlow() } @JavaFriendlyAPI override suspend fun listFilesIterator(lazy: Boolean): Iterator<RemoteFile> { return listFilesSeq().iterator() } override suspend fun toMessage(): FileMessage? { val resolved = resolveFile() ?: return null return FileMessageImpl( name = resolved.name, id = resolved.id, size = resolved.size, busId = 1544241 ) } @Suppress("DEPRECATION", "DEPRECATION_ERROR", "OVERRIDE_DEPRECATION", "OverridingDeprecatedMember") override suspend fun upload(resource: ExternalResource, callback: RemoteFile.ProgressionCallback?): FileMessage { callback?.onBegin(this, resource) try { contact.safeCast<MockGroup>()?.let check@{ group -> if (group.botPermission.isOperator()) return@check if (group.controlPane.isAllowMemberFileUploading) return@check throw PermissionDeniedException("Group $group disabled member file uploading...") } val parent = root.resolveTx(this.parent) ?: throw IllegalStateException("Parent ${this.parent} not found.") val rsSize = resource.size val rsp = parent.uploadFile(this.name, resource, contact.bot.id) callback?.onProgression(this, resource, rsSize) callback?.onSuccess(this, resource) return FileMessageImpl( name = rsp.name, id = rsp.id, size = rsp.size, busId = 1544241 ) } catch (errx: Throwable) { callback?.onFailure(this, resource, errx) throw errx } } @MiraiExperimentalApi @Suppress("DEPRECATION_ERROR", "OverridingDeprecatedMember") override suspend fun uploadAndSend(resource: ExternalResource): MessageReceipt<Contact> { return contact.sendMessage(upload(resource)) } override suspend fun getDownloadInfo(): RemoteFile.DownloadInfo? { val resolved = resolveFile() ?: return null if (!resolved.isFile) return null val ntp = resolved.resolveNativePath() return RemoteFile.DownloadInfo( filename = resolved.name, id = resolved.id, path = resolved.path, sha1 = ntp.inputStream().use { it.sha1() }, md5 = ntp.inputStream().use { it.md5() }, url = contact.bot.mock().tmpResourceServer.resolveHttpUrlByPath(ntp).toString() ) } companion object { internal fun canModify(resolved: MockServerRemoteFile, contact: FileSupported): Boolean { contact.safeCast<MockGroup>()?.let check@{ group -> if (group.botPermission.isOperator()) return true if (resolved.isDirectory) return false val finf = resolved.fileInfo if (finf.creator == group.bot.id) return true return false } return true } } } ================================================ FILE: mirai-core-mock/src/internal/serverfs/MockServerFileDiskImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.mock.internal.serverfs import net.mamoe.mirai.mock.resserver.MockServerFileDisk import net.mamoe.mirai.mock.resserver.MockServerFileSystem import net.mamoe.mirai.mock.resserver.MockServerRemoteFile import net.mamoe.mirai.mock.resserver.TxRemoteFileInfo import net.mamoe.mirai.utils.* import java.io.InputStream import java.nio.file.Files import java.nio.file.Path import java.nio.file.attribute.FileTime import java.util.* import java.util.concurrent.ConcurrentLinkedDeque import kotlin.io.path.* import net.mamoe.mirai.internal.utils.FileSystem as MiraiFileSystem private fun allocateNewPath(base: Path): Path { while (true) { val p = base.resolve(UUID.randomUUID().toString()) if (!p.exists()) return p } } private fun checkFileName(name: String) { MiraiFileSystem.checkLegitimacy(name) if (name.contains('/')) error("$name contains '/'") if (name.isEmpty()) error("Empty name") } internal class MockServerFileDiskImpl( internal val storage: Path ) : MockServerFileDisk { internal val fs: MutableCollection<MockServerFileSystem> = ConcurrentLinkedDeque() override val availableSystems: Sequence<MockServerFileSystem> = Sequence { fs.iterator() } override fun newFsSystem(): MockServerFileSystem = MockServerFileSystemImpl(this) } internal class MockServerFileSystemImpl( override val disk: MockServerFileDiskImpl, ) : MockServerFileSystem { internal val storage: Path = allocateNewPath(disk.storage) internal fun resolvePath(id: String): Path = when { id.isEmpty() || id == "/" -> storage.resolve("root") id[0] == '/' -> storage.resolve(id.substring(1)) else -> error("file not exists: $id") } internal fun fileDetails(id: String): Path? = when { id.isEmpty() || id == "/" -> storage.resolve("details/root") id[0] == '/' -> { storage .resolve("details") .resolve(id.substring(1)) } else -> null } internal fun resolveName(id: String): String = when { id.isEmpty() || id == "/" -> "" id[0] == '/' -> { val nameMapping = fileDetails(id)?.resolve("name") if (nameMapping == null) null else if (nameMapping.isFile) { nameMapping.readText() } else null } else -> null } ?: id.substringAfterLast('/') fun resolveParent(id: String): MockServerFileImpl { val details = fileDetails(id) ?: return root val parent = details.resolve("parent") if (parent.isFile) { return resolveById(parent.readText()) ?: root } return root } init { storage.mkdirs() storage.resolve("details/root").mkdirs() storage.resolve("root").mkdirs() overrideDetails(fileDetails("/")!!, name = "", creator = 0, createTime = 0) disk.fs.add(this) } override val root = MockServerFileImpl(this, "/") override fun resolveById(id: String): MockServerFileImpl? { if (id == "/" || id.isEmpty()) return root if (id[0] != '/') return null if (MiraiFileSystem.isLegal(id) && id.count { it == '/' } == 1) { return MockServerFileImpl(this, id).takeIf { it.toPath.exists() } } return null } override fun findByPath(path: String): Sequence<MockServerRemoteFile> { return root.findByPath( MiraiFileSystem.normalize(path) .removePrefix("/") .split('/') .toMutableList() ) } fun findDirByName(base: MockServerFileImpl, name: String): MockServerFileImpl? { return (base.listFiles() ?: return null) .filter { it.isDirectory } .filter { it.name == name } .firstOrNull()?.cast() } fun uploadFile( name: String, content: ExternalResource, uploader: Long, id: String, toPath: Path ): MockServerFileImpl { val path = allocateNewPath(storage) val fid = '/' + path.name path.outputStream().buffered().use { output -> content.inputStream().use { resource -> resource.copyTo(output) } } toPath.resolve(path.name).createFile() val details = fileDetails(fid)!! details.mkdirs() overrideDetails(details, id, name, uploader, currentTimeMillis()) return MockServerFileImpl(this, fid) } fun overrideDetails( details: Path, parent: String? = null, name: String? = null, creator: Long = -1L, createTime: Long = -1L, ) { if (parent != null) { details.resolve("parent").writeText(parent) } if (name != null) { details.resolve("name").writeText(name) } if (creator != -1L) { details.resolve("creator").writeBytes(creator.toByteArray()) } if (createTime != -1L) { details.resolve("createTime").writeBytes(createTime.toByteArray()) } } fun mkdir(id: String, name: String, creator: Long, toPath: Path): MockServerFileImpl { if (id != "/") error("Creating 2nd directories, MockServerFileSystem current not support") // Find existing subdir Files.newDirectoryStream(toPath).use { ptdirstream -> val exists = ptdirstream.firstOrNull { subfile -> if (storage.resolve(subfile).isFile) return@firstOrNull false val nameFile = storage.resolve("details").resolve(subfile.fileName).resolve("name") return@firstOrNull nameFile.readText() == name } if (exists != null) { return MockServerFileImpl(this, "/" + exists.fileName) } } val path = allocateNewPath(storage) val fid = '/' + path.name path.mkdir() toPath.resolve(path.name).createFile() val details = fileDetails(fid)!! details.mkdirs() overrideDetails(details, id, name, creator, currentTimeMillis()) return MockServerFileImpl(this, fid) } fun resolveAbsPath(id: String): String { if (id == "/") return "/" val details = fileDetails(id) ?: return "<not exists>" val fileNamePath = details.resolve("name") val fileName = fileNamePath.takeIf { it.isFile }?.readText() ?: "<not exists>" val parentPath = details.resolve("parent") if (!parentPath.isFile) { return fileName } val pid = parentPath.readText() val pabs = resolveAbsPath(pid) if (pabs.endsWith("/")) return "$pabs$fileName" return "$pabs/$fileName" } } internal class MockServerFileImpl( override val system: MockServerFileSystemImpl, override val id: String, ) : MockServerRemoteFile { internal val toPath: Path get() = system.resolvePath(id) override val exists: Boolean get() = toPath.exists() override val isFile: Boolean get() = toPath.isFile override val isDirectory: Boolean get() = toPath.isDirectory() override val name: String get() = system.resolveName(id) override val path: String get() = system.resolveAbsPath(id) override val parent: MockServerFileImpl get() = system.resolveParent(id) override val size: Long get() { val pt = toPath if (pt.isFile) return pt.fileSize() return 0 } override fun listFiles(): Sequence<MockServerRemoteFile>? { val pt = toPath if (!pt.isDirectory()) { return null } return pt.listDirectoryEntries().asSequence().filter { it.exists() }.map { MockServerFileImpl(system, '/' + it.name) } } override fun delete(): Boolean { if (!toPath.deleteIfExists()) return false val details = system.fileDetails(id) ?: return false system.resolvePath(details.resolve("parent").readText()) .resolve(id.substring(1)) .deleteIfExists() details.deleteRecursivelyMirai() return true } override fun rename(name: String): Boolean { checkFileName(name) if (id.isEmpty() || id == "/") return false val details = system.fileDetails(id) ?: return false details.resolve("name").writeText(name) return true } override fun moveTo(path: MockServerRemoteFile) { path.cast<MockServerFileImpl>() if (path.system !== this.system) error("Cross file system moving") if (!path.isDirectory) error("Remote file $path not exists") if (id == "/") error("Moving root") // TODO: 移动到自己的子目录 val details = system.fileDetails(id) ?: error("Moving ghost file: $id") val currentParent = parent currentParent.toPath.resolve(id.substring(1)).deleteIfExists() details.resolve("parent").writeText(path.id) path.toPath.resolve(id.substring(1)).createFile() } override fun resolveNativePath(): Path { val pt = toPath if (!pt.isFile) error("file not exists: $this <$pt>") return pt } override fun asExternalResource(): ExternalResource { val pt = toPath if (!pt.isFile) error("file not exists: $pt") return object : AbstractExternalResource() { override fun inputStream0(): InputStream { return toPath.inputStream() } override val size: Long get() = toPath.fileSize() } } override fun uploadFile(name: String, content: ExternalResource, uploader: Long): MockServerFileImpl { content.withAutoClose { checkFileName(name) val storage = toPath if (storage.isFile) error("Uploading file to a file") if (!storage.isDirectory()) error("$this not exists") return system.uploadFile(name, content, uploader, id, toPath) } } override fun mksubdir(name: String, creator: Long): MockServerRemoteFile { checkFileName(name) return system.mkdir(id, name, creator, toPath) } override var fileInfo: TxRemoteFileInfo get() { val details = system.fileDetails(id) ?: error("File not exists") if (!details.isDirectory()) { error("File not exists") } // parent, name, creator, createTime return TxRemoteFileInfo( creator = details.resolve("creator").readBytes().toLong(), createTime = details.resolve("createTime").readBytes().toLong(), lastUpdateTime = toPath.getLastModifiedTime().toMillis(), ) } set(value) { val details = system.fileDetails(id) ?: error("File not exists") if (!details.isDirectory()) { error("File not exists") } details.resolve("creator").writeBytes(value.creator.toByteArray()) details.resolve("createTime").writeBytes(value.createTime.toByteArray()) toPath.setLastModifiedTime(FileTime.fromMillis(value.lastUpdateTime)) } override fun toString(): String = "$path := $id" override fun equals(other: Any?): Boolean { if (other !is MockServerFileImpl) return false if (other.system !== system) return false return other.id == this.id } override fun hashCode(): Int { return id.hashCode() + system.hashCode() } fun findByPath(path: MutableList<String>): Sequence<MockServerRemoteFile> { if (path.isEmpty()) error("Empty path") val nxt = path.removeAt(0) if (nxt.isEmpty()) error("Empty subpath") if (path.isEmpty()) return listFiles()?.filter { it.name == nxt } ?: emptySequence() return system.findDirByName(this, nxt)?.findByPath(path) ?: emptySequence() } } ================================================ FILE: mirai-core-mock/src/internal/serverfs/TmpResourceServerImpl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.internal.serverfs import io.ktor.server.application.* import io.ktor.server.engine.* import io.ktor.server.netty.* import io.ktor.server.plugins.* import io.ktor.server.response.* import net.mamoe.mirai.mock.resserver.MockServerFileDisk import net.mamoe.mirai.mock.resserver.TmpResourceServer import net.mamoe.mirai.utils.* import java.net.ServerSocket import java.net.URI import java.net.URLDecoder import java.net.URLEncoder import java.nio.file.Files import java.nio.file.Path import kotlin.io.path.* internal class TmpResourceServerImpl( override val storageRoot: Path, private val serverPort: Int, private val closeSystemOnShutdown: Boolean, ) : TmpResourceServer { var logger by lateinitMutableProperty { MiraiLogger.Factory.create(TmpResourceServerImpl::class.java, "TmpFsServer-${hashCode()}") } lateinit var server: NettyApplicationEngine private var _serverUri: URI by lateinitMutableProperty { URI.create("http://localhost:$serverPort") } override val serverUri: URI get() = _serverUri override val mockServerFileDisk: MockServerFileDisk by lazy { MockServerFileDiskImpl(storageRoot.resolve("tx-fs-disk")) } private var _isActive: Boolean = false override val isActive: Boolean get() = _isActive private val storage: Path = storageRoot.resolve("storage").mkdirsIfMissing() private val images: Path = storageRoot.resolve("images").mkdirsIfMissing() override suspend fun uploadResource(resource: ExternalResource): String { fun ByteArray.hex() = toUHexString(separator = "") resource.useAutoClose { val resourceId = "${resource.size}-${resource.sha1.hex()}-${resource.md5.hex()}" val locPath = storage.resolve(resourceId) if (locPath.isFile) return resourceId runBIO { locPath.outputStream().use { output -> resource.inputStream().use { it.copyTo(output) } } } return resourceId } } override fun isImageUploaded(md5: ByteArray, size: Long): Boolean { val img = images.resolve(generateUUID(md5)) if (img.exists()) { return Files.size(img) == size } return false } override suspend fun uploadResourceAsImage(resource: ExternalResource): URI { val imgId = generateUUID(resource.md5) val resId = uploadResource(resource) val imgPath = images.resolve(imgId) val storagePath = storage.resolve(resId).toAbsolutePath() if (imgPath.exists()) { return resolveImageUrl(imgId) } kotlin.runCatching { imgPath.createLinkPointingTo(storagePath) }.recoverCatchingSuppressed { imgPath.createSymbolicLinkPointingTo(storagePath) }.getOrThrow() return resolveImageUrl(imgId) } override fun resolveHttpUrl(resourceId: String): URI { return serverUri.resolve("storage/$resourceId") } override fun resolveImageUrl(imgId: String): URI { return serverUri.resolve("images/$imgId") } override suspend fun invalidateResource(resourceId: String) { storage.resolve(resourceId).deleteIfExists() } override fun resolveHttpUrlByPath(path: Path): URI { if (path.fileSystem !== storageRoot.fileSystem) throw UnsupportedOperationException("Cross file system linking is not supported now") val pt = path.toAbsolutePath().toString().replace('\\', '/') return serverUri.resolve( "abs/" + URLEncoder.encode(pt, "UTF-8") ) } override fun startupServer() { val port = if (serverPort == 0) { ServerSocket(0).use { it.localPort } } else serverPort _serverUri = URI.create("http://127.0.0.1:$port/") logger.info { "Tmp Fs Server started: $serverUri" } val server = embeddedServer(Netty, environment = applicationEngineEnvironment { connector { this.host = "127.0.0.1" this.port = port } module { @Suppress("BlockingMethodInNonBlockingContext") intercept(ApplicationCallPipeline.Call) { val req = URI.create(call.request.origin.uri).path.removePrefix("/") val targetPath = if (req.startsWith("abs/")) { storageRoot.fileSystem.getPath(URLDecoder.decode(req.substring(4), "UTF-8")) } else { storageRoot.resolve(req) } if (targetPath.exists()) { call.respondOutputStream { net.mamoe.mirai.utils.runBIO { targetPath.inputStream().buffered().use { it.copyTo(this@respondOutputStream) } } } return@intercept } if (req.startsWith("images/")) { call.respondRedirect( "http://gchat.qpic.cn/gchatpic_new/1145141919/0-0-${ req.substring(7) }/0?term=2", false ) return@intercept } } } }) this.server = server server.start(wait = false) } override fun close() { if (this::server.isInitialized) { server.stop(0, 0) } if (closeSystemOnShutdown) { storageRoot.fileSystem.close() } } } private fun Path.mkdirsIfMissing(): Path { if (!exists()) createDirectories() return this } ================================================ FILE: mirai-core-mock/src/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock ================================================ FILE: mirai-core-mock/src/resserver/MockServerFileDisk.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.resserver import net.mamoe.mirai.mock.internal.serverfs.MockServerFileDiskImpl import java.nio.file.Path public interface MockServerFileDisk { public val availableSystems: Sequence<MockServerFileSystem> public fun newFsSystem(): MockServerFileSystem public companion object { @JvmStatic public fun newFileDisk(storage: Path): MockServerFileDisk { return MockServerFileDiskImpl(storage) } } } ================================================ FILE: mirai-core-mock/src/resserver/MockServerFileSystem.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.resserver public interface MockServerFileSystem { public val disk: MockServerFileDisk public val root: MockServerRemoteFile public fun resolveById(id: String): MockServerRemoteFile? public fun findByPath(path: String): Sequence<MockServerRemoteFile> } ================================================ FILE: mirai-core-mock/src/resserver/MockServerRemoteFile.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.resserver import net.mamoe.mirai.utils.ExternalResource import java.nio.file.Path public interface MockServerRemoteFile { public val system: MockServerFileSystem public val isFile: Boolean public val isDirectory: Boolean public val name: String public val path: String public val id: String public val exists: Boolean public val parent: MockServerRemoteFile public val size: Long public fun listFiles(): Sequence<MockServerRemoteFile>? public fun delete(): Boolean public fun rename(name: String): Boolean /** * 移动文件 * @param path 目标目录 */ public fun moveTo(path: MockServerRemoteFile) public fun asExternalResource(): ExternalResource public fun resolveNativePath(): Path public fun uploadFile(name: String, content: ExternalResource, uploader: Long): MockServerRemoteFile public fun mksubdir(name: String, creator: Long): MockServerRemoteFile public var fileInfo: TxRemoteFileInfo } public data class TxRemoteFileInfo( @JvmField var creator: Long, @JvmField var createTime: Long, @JvmField var lastUpdateTime: Long, ) ================================================ FILE: mirai-core-mock/src/resserver/TmpResourceServer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.resserver import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.internal.serverfs.TmpResourceServerImpl import net.mamoe.mirai.utils.ExternalResource import java.io.Closeable import java.net.URI import java.nio.file.Path /** * 临时资源中转服务器 * * 此服务器用于中转测试中涉及到的各种临时数据, 如 图片、语音、群文件 等 * * 如果 [TmpResourceServer] 被用于 [MockBot], 在 [MockBot] 关闭时也会同步关闭 [TmpResourceServer] * */ @JvmBlockingBridge public interface TmpResourceServer : Closeable { public val serverUri: URI public val storageRoot: Path public val mockServerFileDisk: MockServerFileDisk public val isActive: Boolean /** * 上传一个资源 * * @return 资源 ID, 可通过 [resolveHttpUrl] 获得 http 链接 */ public suspend fun uploadResource(resource: ExternalResource): String /** * 上传图片 * * @return 图片的 http 链接 */ public suspend fun uploadResourceAsImage(resource: ExternalResource): URI /** * 通过图片 md5 和 size 判断图片是否已经上传 */ public fun isImageUploaded(md5: ByteArray, size: Long): Boolean public suspend fun uploadResourceAndGetUrl(resource: ExternalResource): String { return resolveHttpUrl(uploadResource(resource)).toString() } public fun resolveHttpUrl(resourceId: String): URI public fun resolveImageUrl(imgId: String): URI /** * 立即释放目标资源, 此后再次访问该资源 ([resourceId]) 时会得到 404 Not Found */ public suspend fun invalidateResource(resourceId: String) /** * 获取一个对应 [path] 的 http 链接 */ public fun resolveHttpUrlByPath(path: Path): URI /** * 启动 Http Server. * * 如果 [TmpResourceServer] 被用于 [MockBot], [MockBot] 会自动启动服务器, 请不要自行启动 */ public fun startupServer() public companion object { @JvmStatic public fun of( path: Path, port: Int = 0, closeFileSystemWhenClose: Boolean = false, ): TmpResourceServer { return TmpResourceServerImpl(path, port, closeFileSystemWhenClose) } @JvmStatic public fun newInMemoryTmpResourceServer(port: Int = 0): TmpResourceServer { val fs = Jimfs.newFileSystem( Configuration.unix() .toBuilder() .setWorkingDirectory("/") .build() ) return of(fs.getPath("/"), port, true) } } } ================================================ FILE: mirai-core-mock/src/userprofile/UserProfileService.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.userprofile import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.IMirai import net.mamoe.mirai.data.UserProfile import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.userprofile.MockUserProfileBuilder.Companion.invoke import net.mamoe.mirai.utils.runBIO import java.util.concurrent.ConcurrentHashMap import kotlin.contracts.InvocationKind import kotlin.contracts.contract /** * 用户资料服务, 用于 [IMirai.queryProfile] 查询用户资料 * * implementation note: Java 请实现 [JUserProfileService] * * @see MockBot.userProfileService * @see MockUserProfileBuilder */ @JvmBlockingBridge public interface UserProfileService { public suspend fun doQueryUserProfile(id: Long): UserProfile /** * 将 [id] 的用户资料指定为 [profile] * * implementation note: * * 框架内部并不会使用此接口, 该接口是设计于测试单元动态注册 [UserProfile], * 如无调用此接口的需求可以实现为 `throw new UnsupportedOperationException()` */ public suspend fun putUserProfile(id: Long, profile: UserProfile) public companion object { @JvmStatic public fun getInstance(): UserProfileService { return UserProfileServiceImpl() } } } /** * 用于资料服务, 用于 [IMirai.queryProfile] 查询用户资料 * * 该接口是为了方便 Java 实现 [UserProfileService], * kotlin 请实现 [UserProfileService] */ @Suppress("ILLEGAL_JVM_NAME", "INAPPLICABLE_JVM_NAME") public interface JUserProfileService : UserProfileService { override suspend fun doQueryUserProfile(id: Long): UserProfile { return runBIO { doQueryUserProfileJ(id) ?: buildUserProfile { } } } override suspend fun putUserProfile(id: Long, profile: UserProfile) { runBIO { putUserProfileJ(id, profile) } } // override UserProfileService @JvmBlockingBridge @JvmName("doQueryUserProfile") public fun doQueryUserProfileJ(id: Long): UserProfile? @JvmName("putUserProfile") public fun putUserProfileJ(id: Long, profile: UserProfile) } /** * [UserProfile] 的构造器 * * @see [invoke] * @see [buildUserProfile] */ public interface MockUserProfileBuilder { public fun build(): UserProfile public fun nickname(value: String): MockUserProfileBuilder public fun email(value: String): MockUserProfileBuilder public fun age(value: Int): MockUserProfileBuilder public fun qLevel(value: Int): MockUserProfileBuilder public fun sex(value: UserProfile.Sex): MockUserProfileBuilder public fun sign(value: String): MockUserProfileBuilder public fun friendGroupId(value: Int): MockUserProfileBuilder public companion object { @JvmStatic @JvmName("newBuilder") public operator fun invoke(): MockUserProfileBuilder = MockUPBuilderImpl() } } /** * 构造一个 [UserProfile] * * @see MockUserProfileBuilder */ public inline fun buildUserProfile(block: MockUserProfileBuilder.() -> Unit): UserProfile { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return MockUserProfileBuilder().apply(block).build() } internal class MockUPBuilderImpl : MockUserProfileBuilder, UserProfile { override var nickname: String = "" override var email: String = "" override var age: Int = -1 override var qLevel: Int = -1 override var sex: UserProfile.Sex = UserProfile.Sex.UNKNOWN override var sign: String = "" override var friendGroupId: Int = 0 // unmodifiable override fun build(): UserProfile { return object : UserProfile by this {} } override fun nickname(value: String): MockUserProfileBuilder = apply { nickname = value } override fun email(value: String): MockUserProfileBuilder = apply { email = value } override fun age(value: Int): MockUserProfileBuilder = apply { age = value } override fun qLevel(value: Int): MockUserProfileBuilder = apply { qLevel = value } override fun sex(value: UserProfile.Sex): MockUserProfileBuilder = apply { sex = value } override fun sign(value: String): MockUserProfileBuilder = apply { sign = value } override fun friendGroupId(value: Int): MockUserProfileBuilder = apply { friendGroupId = value } } internal class UserProfileServiceImpl : UserProfileService { val db = ConcurrentHashMap<Long, UserProfile>() val def = buildUserProfile { } override suspend fun doQueryUserProfile(id: Long): UserProfile { return db[id] ?: def } override suspend fun putUserProfile(id: Long, profile: UserProfile) { db[id] = profile } } ================================================ FILE: mirai-core-mock/src/userprofile/contactinfos.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.userprofile import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.data.* import net.mamoe.mirai.utils.currentTimeSeconds public interface MockUserInfoBuilder { public fun uin(value: Long): MockUserInfoBuilder public fun nick(value: String): MockUserInfoBuilder public fun remark(value: String): MockUserInfoBuilder public fun build(): UserInfo public companion object { @JvmStatic @JvmName("builder") public operator fun invoke(): MockUserInfoBuilder = ThreeInOneInfoBuilder() @JvmSynthetic public inline fun create(action: MockUserInfoBuilder.() -> Unit): UserInfo = invoke().apply(action).build() } } public interface MockFriendInfoBuilder : MockUserInfoBuilder { public override fun build(): FriendInfo override fun uin(value: Long): MockFriendInfoBuilder override fun nick(value: String): MockFriendInfoBuilder override fun remark(value: String): MockFriendInfoBuilder public fun friendGroupId(value: Int): MockFriendInfoBuilder public companion object { @JvmStatic @JvmName("builder") public operator fun invoke(): MockFriendInfoBuilder = ThreeInOneInfoBuilder() @JvmSynthetic public inline fun create(action: MockFriendInfoBuilder.() -> Unit): FriendInfo = invoke().apply(action).build() } } public interface MockMemberInfoBuilder : MockUserInfoBuilder { override fun build(): MemberInfo public fun nameCard(value: String): MockMemberInfoBuilder public fun specialTitle(value: String): MockMemberInfoBuilder public fun anonymousId(value: String?): MockMemberInfoBuilder public fun joinTimestamp(value: Int): MockMemberInfoBuilder public fun lastSpeakTimestamp(value: Int): MockMemberInfoBuilder public fun isOfficialBot(value: Boolean): MockMemberInfoBuilder public fun rank(value: Int): MockMemberInfoBuilder public fun temperature(value: Int): MockMemberInfoBuilder public fun honors(value: Set<GroupHonorType>): MockMemberInfoBuilder public fun point(value: Int): MockMemberInfoBuilder public fun permission(value: MemberPermission): MockMemberInfoBuilder override fun uin(value: Long): MockMemberInfoBuilder override fun nick(value: String): MockMemberInfoBuilder override fun remark(value: String): MockMemberInfoBuilder public companion object { @JvmStatic @JvmName("builder") public operator fun invoke(): MockMemberInfoBuilder = ThreeInOneInfoBuilder() @JvmSynthetic public inline fun create(action: MockMemberInfoBuilder.() -> Unit): MemberInfo = invoke().apply(action).build() } } public interface MockStrangerInfoBuilder : MockUserInfoBuilder { public fun fromGroup(value: Long): MockUserInfoBuilder override fun uin(value: Long): MockUserInfoBuilder override fun nick(value: String): MockUserInfoBuilder override fun remark(value: String): MockUserInfoBuilder override fun build(): StrangerInfo public companion object { @JvmStatic @JvmName("builder") public operator fun invoke(): MockStrangerInfoBuilder = ThreeInOneInfoBuilder() @JvmSynthetic public inline fun create(action: MockStrangerInfoBuilder.() -> Unit): StrangerInfo = invoke().apply(action).build() } } private class ThreeInOneInfoBuilder : MockUserInfoBuilder, MockFriendInfoBuilder, MockMemberInfoBuilder, MockStrangerInfoBuilder, UserInfo, FriendInfo, MemberInfo, StrangerInfo { override var nameCard: String = "" override var permission: MemberPermission = MemberPermission.MEMBER override var specialTitle: String = "" override var muteTimestamp: Int = 0 override var joinTimestamp: Int = currentTimeSeconds().toInt() override var lastSpeakTimestamp: Int = 0 override var isOfficialBot: Boolean = false override var rank: Int = 0 override var point: Int = 0 override var honors: Set<GroupHonorType> = setOf() override var temperature: Int = 0 override var fromGroup: Long = 0L override var remark: String = "" override var uin: Long = 0 override var nick: String = "" override var anonymousId: String? = null override var friendGroupId: Int = 0 override fun build(): ThreeInOneInfoBuilder = this override fun nameCard(value: String): ThreeInOneInfoBuilder = apply { this.nameCard = value } override fun specialTitle(value: String): ThreeInOneInfoBuilder = apply { this.specialTitle = value } override fun anonymousId(value: String?): ThreeInOneInfoBuilder = apply { this.anonymousId = value } override fun joinTimestamp(value: Int): ThreeInOneInfoBuilder = apply { this.joinTimestamp = value } override fun lastSpeakTimestamp(value: Int): ThreeInOneInfoBuilder = apply { this.lastSpeakTimestamp = value } override fun isOfficialBot(value: Boolean): ThreeInOneInfoBuilder = apply { this.isOfficialBot = value } override fun fromGroup(value: Long): ThreeInOneInfoBuilder = apply { this.fromGroup = value } override fun uin(value: Long): ThreeInOneInfoBuilder = apply { this.uin = value } override fun nick(value: String): ThreeInOneInfoBuilder = apply { this.nick = value } override fun remark(value: String): ThreeInOneInfoBuilder = apply { this.remark = value } override fun rank(value: Int): MockMemberInfoBuilder = apply { this.rank = value } override fun point(value: Int): MockMemberInfoBuilder = apply { this.point = value } override fun temperature(value: Int): MockMemberInfoBuilder = apply { this.temperature = value } override fun honors(value: Set<GroupHonorType>): MockMemberInfoBuilder = apply { this.honors = value } override fun permission(value: MemberPermission): ThreeInOneInfoBuilder = apply { this.permission = value } override fun friendGroupId(value: Int): ThreeInOneInfoBuilder = apply { this.friendGroupId = value } } ================================================ FILE: mirai-core-mock/src/utils/AvatarGenerator.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.utils /** * 头像生成器 * * @since 2.14.0 */ public interface AvatarGenerator { public fun generateAvatarForPerson(id: Long): ByteArray = generateRandomAvatar() public fun generateAvatarForGroup(id: Long): ByteArray = generateRandomAvatar() public fun generateRandomAvatar(): ByteArray } ================================================ FILE: mirai-core-mock/src/utils/MemberInfo.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.utils import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.data.MemberInfo public fun simpleMemberInfo( uin: Long, name: String, nick: String = name, nameCard: String = "", remark: String = "", permission: MemberPermission, specialTitle: String = "", ): MemberInfo { return object : MemberInfo { override val nameCard: String get() = nameCard override val permission: MemberPermission get() = permission override val specialTitle: String get() = specialTitle override val muteTimestamp: Int get() = 0 override val joinTimestamp: Int get() = 0 override val lastSpeakTimestamp: Int get() = 0 override val isOfficialBot: Boolean get() = false override val rank: Int get() = 0 override val point: Int get() = 0 override val honors: Set<GroupHonorType> get() = setOf() override val temperature: Int get() = 0 override val uin: Long get() = uin override val nick: String get() = nick override val remark: String get() = remark } } ================================================ FILE: mirai-core-mock/src/utils/MockActionsScope.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.utils import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.User import net.mamoe.mirai.event.events.MemberPermissionChangeEvent import net.mamoe.mirai.event.events.MemberSpecialTitleChangeEvent import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.MockActions import net.mamoe.mirai.mock.contact.MockNormalMember import net.mamoe.mirai.mock.contact.MockUser import net.mamoe.mirai.mock.contact.MockUserOrBot /** * 广播一些模拟事件 */ public inline fun broadcastMockEvents(action: MockActionsScope.() -> Unit) { return MockActionsScopeInstance.action() } @PublishedApi internal val MockActionsScopeInstance: MockActionsScope = object : MockActionsScope {} public interface MockActionsScope { // use context receivers in the future /** * 修改 [MockUserOrBot.nick] 并广播相关事件 (如 [FriendNickChangedEvent]) */ @MockActionsDsl public suspend infix fun MockUserOrBot.nickChangesTo(value: String) { return MockActions.fireNickChanged(this, value) } /** * 修改 [MockNormalMember.nameCard] 并广播 [MemberCardChangeEvent] */ @MockActionsDsl public suspend infix fun MockNormalMember.nameCardChangesTo(value: String) { return MockActions.fireNameCardChanged(this, value) } /** * 修改 [MockNormalMember.specialTitle] 并广播 [MemberSpecialTitleChangeEvent] */ @MockActionsDsl public suspend infix fun MockNormalMember.specialTitleChangesTo(value: String) { return MockActions.fireSpecialTitleChanged(this, value) } /** * 修改一名成员的权限并广播 [MemberPermissionChangeEvent] */ @MockActionsDsl public suspend infix fun MockNormalMember.permissionChangesTo(perm: MemberPermission) { return MockActions.firePermissionChanged(this, perm) } /** * 广播 [this] 被 [actor] 戳了的事件([NudgeEvent]) * * - [actor] 戳了戳 [this] 的 XXXX */ @MockActionsDsl public suspend fun MockUserOrBot.nudgedBy(actor: MockUserOrBot, block: NudgeDsl.() -> Unit = {}) { actor.nudged0(this, NudgeDsl().also(block)) } /** * 广播 [target] 被 [this] 戳了的事件([NudgeEvent]) * * - [this] 戳了戳 [target] 的 XXXX */ @MockActionsDsl public suspend fun MockUserOrBot.nudges(target: MockUserOrBot, block: NudgeDsl.() -> Unit = {}) { nudged0(target, NudgeDsl().also(block)) } /** * @see [MockUser.says] */ @MockActionsDsl public suspend infix fun MockUser.says(block: MessageChainBuilder.() -> Unit): MessageChain { return says(buildMessageChain(block)) } /** * @see [MockUser.says] */ @MockActionsDsl public suspend infix fun MockUser.saysMessage(block: () -> Message): MessageChain { // no contract because compiler error // contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return says(block()) } /** * 令消息原作者撤回一条消息 */ @MockActionsDsl public suspend fun MessageChain.recalledBySender() { return MockActions.fireMessageRecalled(this, null) } /** * 令消息原作者撤回一条消息 */ @MockActionsDsl public suspend fun MessageSource.recalledBySender() { return MockActions.fireMessageRecalled(this, null) } /** * 令消息原作者撤回一条消息 */ @MockActionsDsl public suspend fun MessageReceipt<*>.recalledBySender() { this.source.recalledBy(null) } /** * 令 [operator] 撤回一条消息 * * @param operator 当 [operator] 为 null 时代表是发送者自己撤回 */ @MockActionsDsl public suspend infix fun MessageChain.recalledBy(operator: User?) { return MockActions.fireMessageRecalled(this, operator) } /** * 令 [operator] 撤回一条消息 * * @param operator 当 [operator] 为 null 时代表是发送者自己撤回 */ @MockActionsDsl public suspend infix fun MessageSource.recalledBy(operator: User?) { return MockActions.fireMessageRecalled(this, operator) } /** * 令 [operator] 撤回一条消息 * * @param operator 当 [operator] 为 null 时代表是发送者自己撤回 */ @MockActionsDsl public suspend infix fun MessageReceipt<*>.recalledBy(operator: User?) { this.source.recalledBy(operator) } } ================================================ FILE: mirai-core-mock/src/utils/MockConversions.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("MockConversions") package net.mamoe.mirai.mock.utils import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.message.data.OnlineAudio import net.mamoe.mirai.mock.MockBot import net.mamoe.mirai.mock.MockBotDSL import net.mamoe.mirai.mock.contact.* import net.mamoe.mirai.utils.ExternalResource import kotlin.contracts.contract public fun Bot.mock(): MockBot { contract { returns() implies (this@mock is MockBot) } return this as MockBot } public fun Group.mock(): MockGroup { contract { returns() implies (this@mock is MockGroup) } return this as MockGroup } public fun NormalMember.mock(): MockNormalMember { contract { returns() implies (this@mock is MockNormalMember) } return this as MockNormalMember } public fun Contact.mock(): MockContact { contract { returns() implies (this@mock is MockContact) } return this as MockContact } public fun AnonymousMember.mock(): MockAnonymousMember { contract { returns() implies (this@mock is MockAnonymousMember) } return this as MockAnonymousMember } public fun Friend.mock(): MockFriend { contract { returns() implies (this@mock is MockFriend) } return this as MockFriend } public fun Member.mock(): MockMember { contract { returns() implies (this@mock is MockMember) } return this as MockMember } public fun OtherClient.mock(): MockOtherClient { contract { returns() implies (this@mock is MockOtherClient) } return this as MockOtherClient } public fun Stranger.mock(): MockStranger { contract { returns() implies (this@mock is MockStranger) } return this as MockStranger } /** * @see MockBot.uploadOnlineAudio */ @MockBotDSL public suspend fun ExternalResource.mockUploadAsOnlineAudio(bot: MockBot): OnlineAudio { return bot.uploadOnlineAudio(this) } ================================================ FILE: mirai-core-mock/src/utils/NameGenerator.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.utils import java.util.concurrent.atomic.AtomicInteger /** * 名称生成器 * * 部分事件没有 `nick`, `name` 等相关的字段以确定名字, * [NameGenerator] 的作用就是在无法确定一个准确的名字的时候生成一个默认的名字 */ public interface NameGenerator { public fun nextGroupName(): String public fun nextFriendName(): String public companion object { private val DEFAULT: NameGenerator = SimpleNameGenerator() @JvmStatic public fun getDefault(): NameGenerator = DEFAULT } } public open class SimpleNameGenerator : NameGenerator { private val groupCounter = AtomicInteger(0) private val friendCounter = AtomicInteger(0) override fun nextGroupName(): String = "Testing Group #" + groupCounter.getAndIncrement() override fun nextFriendName(): String = "Testing Friend #" + friendCounter.getAndIncrement() } ================================================ FILE: mirai-core-mock/src/utils/NudgeDsl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock.utils import net.mamoe.mirai.Bot import net.mamoe.mirai.contact.* import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.NudgeEvent import net.mamoe.mirai.mock.contact.MockUserOrBot import net.mamoe.mirai.utils.MiraiInternalApi /** * 构造 Nudge 的 DSL * * @see MockActionsScope.nudgedBy */ public class NudgeDsl { @set:JvmSynthetic public var action: String = "戳了戳" @set:JvmSynthetic public var suffix: String = "" @MockActionsDsl public fun action(value: String): NudgeDsl = apply { action = value } @MockActionsDsl public fun suffix(value: String): NudgeDsl = apply { suffix = value } } @OptIn(MiraiInternalApi::class) @PublishedApi internal suspend fun MockUserOrBot.nudged0(target: MockUserOrBot, dsl: NudgeDsl) { when { this is Member && target is Member -> { if (this.group != target.group) error("Cross group nudging") } this is AnonymousMember -> error("anonymous member can't starting a nudge action") target is AnonymousMember -> error("anonymous member is not nudgeable") this is Bot && target is Bot -> error("Not yet support bot nudging bot") } val subject: Contact = when { this is Member -> this.group target is Member -> target.group this is Friend -> this target is Friend -> target this is Stranger -> this target is Stranger -> target else -> error("Not yet support $target nudging $this") } NudgeEvent( from = this, target = target, subject = subject, action = dsl.action, suffix = dsl.suffix, ).broadcast() } ================================================ FILE: mirai-core-mock/src/utils/event.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.utils import kotlinx.coroutines.runBlocking import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.broadcast public fun <T : Event> T.broadcastBlocking(): T = apply { runBlocking { broadcast() } } ================================================ FILE: mirai-core-mock/src/utils/http.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.mock.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName public fun String.plusHttpSubpath(subpath: String): String { if (this[this.lastIndex] == '/') return this + subpath return "$this/$subpath" } ================================================ FILE: mirai-core-mock/src/utils/image.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.utils import net.mamoe.mirai.message.data.Image import java.awt.Color import java.awt.image.BufferedImage import java.io.ByteArrayOutputStream import javax.imageio.ImageIO internal fun randomImage(): BufferedImage { val width = (500..800).random() val height = (500..800).random() val image = BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) val graphics = image.createGraphics() for (x in 0 until width) { for (y in 0 until height) { graphics.color = Color( (0..0xFFFFFF).random() ) graphics.drawRect(x, y, 1, 1) } } graphics.dispose() return image } internal fun BufferedImage.saveToBytes(): ByteArray = ByteArrayOutputStream().apply { ImageIO.write(this@saveToBytes, "png", this) }.toByteArray() public fun Image.Key.randomImageContent(): ByteArray = randomImage().saveToBytes() ================================================ FILE: mirai-core-mock/src/utils/mockdsl.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") package net.mamoe.mirai.mock.utils @DslMarker public annotation class MockActionsDsl ================================================ FILE: mirai-core-mock/test/AbsoluteFileTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock.test import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import kotlinx.coroutines.flow.count import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.toList import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.GroupMessageEvent import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.mock.internal.contact.mockUploadAudio import net.mamoe.mirai.mock.internal.remotefile.absolutefile.MockRemoteFiles import net.mamoe.mirai.mock.internal.serverfs.MockServerFileSystemImpl import net.mamoe.mirai.mock.utils.simpleMemberInfo import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.cast import net.mamoe.mirai.utils.md5 import net.mamoe.mirai.utils.runBIO import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import java.nio.file.FileSystem import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertNotEquals internal class AbsoluteFileTest : MockBotTestBase() { private val tmpfs: FileSystem = Jimfs.newFileSystem(Configuration.unix()) private val disk = bot.tmpResourceServer.mockServerFileDisk private val group = bot.addGroup(11L, "a").also { println(it.owner) } private val fsys = MockServerFileSystemImpl(disk.cast()) private val files = MockRemoteFiles(group, fsys) @AfterEach internal fun release() { tmpfs.close() } @Test internal fun listFileAndFolder() = runTest { val folder = files.root.createFolder("test1") files.root.createFolder("test2") val file = folder.uploadNewFile("test.txt", "cc".toByteArray().toExternalResource().toAutoCloseable()) folder.uploadNewFile("test.txt", "cac".toByteArray().toExternalResource().toAutoCloseable()) println(files.root.folders().toList()) println(files.root.resolveFolder("test1")!!.files().toList()) assertEquals(2, files.root.folders().toList().size) assertEquals(2, files.root.resolveFolder("test1")!!.files().toList().size) assertEquals("test.txt", files.root.resolveFolder("test1")!!.files().toList()[0].name) assertEquals("test1", files.root.resolveFolderById(folder.id)!!.name) assertEquals("test.txt", files.root.resolveFileById(file.id, true)!!.name) } @Test internal fun testDeleteAndMoveTo() = runTest { val f = files.root.createFolder("test") val ff = f.uploadNewFile("test.txt", "ccc".toByteArray().toExternalResource()) val fff = files.root.resolveFileById(ff.id, true)!! assertEquals(fff, ff) f.renameTo("test2") assertEquals("test2", files.root.folders().first().name) fff.refresh() assertEquals(f.absolutePath + "/" + fff.name, fff.absolutePath) fff.moveTo(files.root) assertEquals("/${fff.name}", fff.absolutePath) assertEquals(files.root, fff.parent) fff.delete() assertEquals(false, fff.exists()) assertEquals(null, files.root.resolveFileById(fff.id)) } @Test internal fun testSendAndDownload() = runTest { val f = files.root.uploadNewFile("test.txt", "c".toByteArray().toExternalResource()) println(files.fileSystem.findByPath("/test.txt").first().path) runAndReceiveEventBroadcast { group.addMember(simpleMemberInfo(222, "bb", permission = MemberPermission.MEMBER)) .saysMessage { f.toMessage() } }.let { events -> assertEquals(1, events.size) assertEquals(true, events[0].cast<GroupMessageEvent>().message.contains(FileMessage)) } assertEquals("c", f.getUrl()!!.toUrl().readText()) } @Test fun testRename() = runTest { val folder = files.root.createFolder("test1") val file = folder.uploadNewFile("test.txt", "content".toByteArray().toExternalResource().toAutoCloseable()) assertEquals(file.id, folder.resolveFiles("test.txt").first().id) folder.renameTo("test2") file.refresh() assertEquals(true, file.exists()) assertNotEquals(null, folder.resolveFiles("test.txt").firstOrNull()) } @Test fun testMD5() = runTest { val bytes = "test".toByteArray() val file = bytes.toExternalResource().use { res -> files.root.uploadNewFile("/test.txt", res) } assertContentEquals(bytes.md5(), file.md5) } @Test fun testMD5WithResolve() = runTest { val bytes = "test".toByteArray() bytes.toExternalResource().use { res -> files.root.uploadNewFile("/test.txt", res) } val file = files.root.resolveFiles("/test.txt").toList() assertEquals(1, file.size) assertContentEquals(bytes.md5(), file[0].md5) } @Test fun testMD5WithIDResolve() = runTest { val bytes = "test".toByteArray() val absFile = bytes.toExternalResource().use { res -> files.root.uploadNewFile("/test.txt", res) } val file = files.root.resolveFileById(absFile.id, true)!! assertContentEquals(bytes.md5(), file.md5) } @Test fun testResolveFiles() = runTest { val file = runBIO { kotlin.io.path.createTempFile("test", ".txt").toFile().apply { writeText("test") deleteOnExit() } } file.toExternalResource().use { group.files.root.uploadNewFile("/a/test.txt", it) } assertEquals(0, group.files.root.resolveFiles("/a").count()) } @Test @Suppress("INVISIBLE_REFERENCE") fun testMockUploadAudio() = runTest { val file = runBIO { kotlin.io.path.createTempFile("test", ".txt").toFile().apply { writeText("test") deleteOnExit() } } file.toExternalResource().use { assertIsInstance<net.mamoe.mirai.internal.utils.ExternalResourceImplByFile>(it) it.mockUploadAudio(bot) } } } ================================================ FILE: mirai-core-mock/test/DslTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.mock.MockActions import net.mamoe.mirai.mock.MockBotFactory import net.mamoe.mirai.mock.userprofile.MockMemberInfoBuilder import net.mamoe.mirai.mock.utils.NudgeDsl import net.mamoe.mirai.mock.utils.broadcastMockEvents import net.mamoe.mirai.mock.utils.mockUploadAsOnlineAudio import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import java.io.File /* * This file only for showing MockDSL and how to use mock bot. * Not included in testing running */ @Suppress("unused") internal suspend fun dslTest() { val bot = MockBotFactory.newMockBotBuilder().create() bot.addFriend(5, "OhMyFriend") bot.addGroup(1, "").apply { addMember( MockMemberInfoBuilder.create { uin(541) nameCard("Dmo") permission(MemberPermission.OWNER) } ) } bot.addGroup(7, "") .appendMember(MockMemberInfoBuilder.create { // Kotlin uin(571) nameCard("Hi") permission(MemberPermission.ADMINISTRATOR) }) .appendMember( MockMemberInfoBuilder.invoke() // Java, MockMemberInfoBuilder.builder() in java .uin(1654441) .nameCard("60") .permission(MemberPermission.MEMBER) .specialTitle("ST") .build() ) // 群成员 70 说了一句话 bot.getGroupOrFail(50).getOrFail(70).says("0") // 群成员 1 发了一条语音 bot.getGroupOrFail(1).getOrFail(1).says { // Kotlin +File("helloworld.amr").toExternalResource().toAutoCloseable().mockUploadAsOnlineAudio(bot) } /* Java: bot.getGroupOrFail(1).getOrFail(1).says(() -> { return bot.uploadOnlineAudio( ExternalResource.toExternalResource(new File("")).toAutoCloseable() ); }); */ broadcastMockEvents { // Required for kotlin // 50 拍了拍 bot 的 sys32 bot.getGroupOrFail(5).getOrFail(50).nudges(bot) { action("拍了拍") suffix("sys32") } MockActions.fireNudge( // Java bot.getGroupOrFail(5).getOrFail(50), bot, /*new*/ NudgeDsl().action("拍了拍").suffix("sys32") ) // 1 拍了拍 bot 的 sys32 bot.nudgedBy(bot.getGroupOrFail(1).getOrFail(1)) { action("拍了拍") suffix("sys32") } // 群成员 2 修改了群名片 bot.getGroupOrFail(1).getOrFail(2) nameCardChangesTo "Test" MockActions.fireNameCardChanged( // Java bot.getGroupOrFail(1).getOrFail(2), "Test" ) // 群成员 2 被群主修改了头衔 bot.getGroupOrFail(1).getOrFail(2) specialTitleChangesTo "管埋员" MockActions.fireSpecialTitleChanged( // Java bot.getGroupOrFail(1).getOrFail(2), "管埋员" ) // 群主修改了群成员 2 的权限为 Administrator bot.getGroupOrFail(1).getOrFail(2) permissionChangesTo MemberPermission.ADMINISTRATOR MockActions.firePermissionChanged( // Java bot.getGroupOrFail(1).getOrFail(2), MemberPermission.ADMINISTRATOR ) // 群主撤回了一条群员消息 bot.getGroupOrFail(1).owner.recallMessage( // Kotlin & Java bot.getGroupOrFail(1).getOrFail(1) says { append("SB") } ) } // 新的入群申请 bot.getGroupOrFail(50).broadcastNewMemberJoinRequestEvent( requester = 3, requesterName = "Him188moe", message = "Hi!", ).reject(message = "Hello!") // 新的好友申请 bot.broadcastNewFriendRequestEvent( requester = 1, requesterNick = "Karlatemp", fromGroup = 0, message = "さくらが落ちる", ).accept() bot.broadcastNewFriendRequestEvent(9, "", 0, "").reject() } ================================================ FILE: mirai-core-mock/test/FsServerTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("DEPRECATION", "DEPRECATION_ERROR") package net.mamoe.mirai.mock.test import kotlinx.coroutines.runBlocking import net.mamoe.mirai.mock.resserver.TmpResourceServer import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.mkParentDirs import org.junit.jupiter.api.Test import kotlin.io.path.writeText import kotlin.test.assertEquals @Suppress("RemoveExplicitTypeArguments") internal class FsServerTest { @Test fun testFsServer() = runBlocking<Unit> { val fsServer = TmpResourceServer.newInMemoryTmpResourceServer() fsServer.startupServer() val testFile = "Test".toByteArray().toExternalResource() val resourceId = fsServer.uploadResource(testFile) val response = fsServer.resolveHttpUrl(resourceId).toURL().readText() assertEquals("Test", response) val pt0 = fsServer.storageRoot.resolve("/rand/etc/randrand/somedata") pt0.mkParentDirs() pt0.writeText("Test") assertEquals("Test", fsServer.resolveHttpUrlByPath(pt0).toURL().readText()) fsServer.close() } } ================================================ FILE: mirai-core-mock/test/ImageUploadTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test import kotlinx.coroutines.runBlocking import net.mamoe.mirai.message.data.Image import net.mamoe.mirai.message.data.Image.Key.isUploaded import net.mamoe.mirai.message.data.Image.Key.queryUrl import net.mamoe.mirai.mock.MockBotFactory import net.mamoe.mirai.mock.utils.randomImageContent import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.getRandomByteArray import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance import java.net.URL import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertNotEquals import kotlin.test.assertTrue @TestInstance(TestInstance.Lifecycle.PER_METHOD) internal class ImageUploadTest { internal val bot = MockBotFactory.newMockBotBuilder() .id(1234567890) .nick("Sakura") .create() @AfterEach internal fun botDestroy() { bot.close() } @Test fun testImageUpload() = runBlocking<Unit> { val data = Image.randomImageContent() val img = bot.asFriend.uploadImage( data.toExternalResource().toAutoCloseable() ) println(img.imageId) assertTrue { data.contentEquals(URL(img.queryUrl()).readBytes()) } assertNotEquals(0, img.size) } @Test fun testSameImageMultiUpload() = runBlocking<Unit> { Image.randomImageContent().toExternalResource().use { imgData -> val img1 = bot.asFriend.uploadImage(imgData) val img2 = bot.asFriend.uploadImage(imgData) assertEquals(img1, img2) } } @Test fun testImageIsUploaded(): Unit = runBlocking { val img = Image.randomImageContent().toExternalResource().use { imgData -> bot.asFriend.uploadImage(imgData) } assertTrue { img.isUploaded(bot) } } @Test @Suppress("RemoveRedundantQualifierName") fun testImageIsUploadedNotTrue(): Unit = runBlocking { assertFalse { Image.isUploaded(bot, getRandomByteArray(16), 10) } val img = Image.randomImageContent().toExternalResource().use { imgData -> bot.asFriend.uploadImage(imgData) } assertFalse { Image.isUploaded(bot, img.md5, img.size + 5) } } } ================================================ FILE: mirai-core-mock/test/MockBotTestBase.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.GlobalEventChannel import net.mamoe.mirai.message.data.MessageSource import net.mamoe.mirai.mock.MockBotFactory import net.mamoe.mirai.mock.database.queryMessageInfo import net.mamoe.mirai.mock.internal.MockBotImpl import net.mamoe.mirai.mock.utils.MockActionsScope import net.mamoe.mirai.mock.utils.broadcastMockEvents import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.TestInstance import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.test.fail @TestInstance(TestInstance.Lifecycle.PER_METHOD) internal open class MockBotTestBase : TestBase() { internal val bot = MockBotFactory.newMockBotBuilder() .id((100000000L..321111111L).random()) .nick("Kafusumi") .create() @AfterEach internal fun `$$bot dispose`() { bot.close() } internal suspend fun runAndReceiveEventBroadcast( action: suspend MockActionsScope.() -> Unit ): List<Event> { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } val result = mutableListOf<Event>() val listener = GlobalEventChannel.subscribeAlways<Event> { result.add(this) } broadcastMockEvents { action() } (bot as MockBotImpl).joinEventBroadcast() listener.cancel() return result } internal fun assertMessageNotAvailable(source: MessageSource) { if (bot.msgDatabase.queryMessageInfo(source.ids, source.internalIds) != null) { fail("Require message $source no longer available.") } } internal fun assertMessageAvailable(source: MessageSource) { if (bot.msgDatabase.queryMessageInfo(source.ids, source.internalIds) == null) { fail("Require message $source available.") } } } ================================================ FILE: mirai-core-mock/test/MsgDbTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test import net.mamoe.mirai.message.data.MessageSourceKind import net.mamoe.mirai.message.data.messageChainOf import net.mamoe.mirai.mock.database.MessageDatabase import net.mamoe.mirai.mock.database.MessageInfo import net.mamoe.mirai.mock.database.mockMsgDatabaseId import org.junit.jupiter.api.Test import kotlin.random.Random import kotlin.test.assertEquals internal class MsgDbTest { @Test fun testIdConversion() { repeat(50) { val id1 = Random.nextInt() val id2 = Random.nextInt() val msgInfo = MessageInfo( mixinedMsgId = mockMsgDatabaseId(id1, id2), sender = 0, subject = 0, kind = MessageSourceKind.FRIEND, time = 0, messageChainOf() ) assertEquals(id1, msgInfo.id) assertEquals(id2, msgInfo.internal) } } @Test fun testDatabase() { val db = MessageDatabase.newDefaultDatabase() db.connect() repeat(90) { val info = db.newMessageInfo(Random.nextLong(), Random.nextLong(), MessageSourceKind.FRIEND, 0, messageChainOf()) assertEquals(info, db.queryMessageInfo(info.mixinedMsgId)) } db.disconnect() } } ================================================ FILE: mirai-core-mock/test/TestBase.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking import net.mamoe.mirai.event.Event import net.mamoe.mirai.event.events.MessageEvent import net.mamoe.mirai.event.events.MessagePostSendEvent import net.mamoe.mirai.event.events.MessagePreSendEvent import org.junit.jupiter.api.DynamicContainer import org.junit.jupiter.api.DynamicNode import org.junit.jupiter.api.DynamicTest import org.junit.jupiter.api.fail import java.net.URL import kotlin.reflect.jvm.jvmName import kotlin.test.assertFails internal open class TestBase { internal inline fun <reified T> assertIsInstance(value: Any?, block: T.() -> Unit = {}) { if (value !is T) { fail { "Actual value $value (${value?.javaClass}) is not instanceof ${T::class.jvmName}" } } block(value) } internal fun runTest(action: suspend CoroutineScope.() -> Unit) { runBlocking(block = action) } internal fun List<Event>.dropMessagePrePost() = filterNot { it is MessagePreSendEvent || it is MessagePostSendEvent<*> } internal fun List<Event>.dropMsgChat() = filterNot { it is MessageEvent || it is MessagePreSendEvent || it is MessagePostSendEvent<*> } internal fun String.toUrl(): URL = URL(this) internal inline fun <T> T.runAndAssertFails(block: T.() -> Unit) { assertFails { block() } } internal fun dynamicTest(displayName: String, action: suspend CoroutineScope.() -> Unit): DynamicTest { return DynamicTest.dynamicTest(displayName) { runBlocking(block = action) } } internal fun dynamicContainer( displayName: String, action: suspend CoroutineScope.() -> Iterable<DynamicNode> ): DynamicContainer { return DynamicContainer.dynamicContainer(displayName, runBlocking(block = action)) } } ================================================ FILE: mirai-core-mock/test/TxFsDiskTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test import com.google.common.jimfs.Configuration import com.google.common.jimfs.Jimfs import net.mamoe.mirai.mock.resserver.MockServerFileDisk import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import org.junit.jupiter.api.AfterEach import org.junit.jupiter.api.Test import java.nio.file.Files import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.assertFalse import kotlin.test.assertTrue internal class TxFsDiskTest { val tmpfs = Jimfs.newFileSystem(Configuration.unix()) val disk = MockServerFileDisk.newFileDisk(tmpfs.getPath("/disk")) private fun splitLine() = println("==================================================================") @AfterEach fun release() { println("===================[ FILE SYSTEM STRUCT DUMP ]========================") Files.walk(tmpfs.getPath("/")).use { s -> s.forEach { pt -> println(pt) } } println("===================[ ]========================") tmpfs.close() } @Test fun testDisk() { val system = disk.newFsSystem() val root = system.root println(root) println(root.fileInfo) splitLine() kotlin.run { val subdir = root.mksubdir("a-dir", 0) println(subdir) println(subdir.fileInfo) assertEquals("/a-dir", subdir.path) assertFails { root.moveTo(subdir) } val children = root.listFiles()!!.onEach { println(it) }.toList() assertEquals(1, children.size) assertEquals(subdir, children[0]) assertEquals(root, subdir.parent) subdir.delete() println(subdir) assertFalse { subdir.exists } assertFalse { subdir.isFile } assertFalse { subdir.isDirectory } assertTrue { subdir.toString().startsWith("<not exists>") } assertFails { subdir.fileInfo } } splitLine() kotlin.run { val newFile = root.uploadFile( "test.txt", """A""".toByteArray().toExternalResource().toAutoCloseable(), 5 ) val newFileInfo = newFile.fileInfo assertEquals(5, newFileInfo.creator) assertEquals(root, newFile.parent) assertEquals("test.txt", newFile.name) assertEquals("/test.txt", newFile.path) newFile.rename("hello world.bin") assertEquals("hello world.bin", newFile.name) val children = root.listFiles()!!.onEach { println(it) }.toList() assertEquals(1, children.size) assertEquals(children[0], newFile) val subdir = root.mksubdir("1", 3) newFile.moveTo(subdir) assertEquals("/1/hello world.bin", newFile.path) assertEquals(subdir, newFile.parent) val children1 = subdir.listFiles()!!.toList() assertEquals(1, children1.size) assertEquals(newFile, children1[0]) val children2 = root.listFiles()!!.toList() assertEquals(1, children2.size) assertEquals(subdir, children2[0]) assertEquals(newFile, system.findByPath("/1/hello world.bin").firstOrNull()) println("TEST SUB DIR: $subdir") // TODO: Download content } } } ================================================ FILE: mirai-core-mock/test/mock/MessageSerializationTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import kotlinx.serialization.* import kotlinx.serialization.json.Json import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.plus import net.mamoe.mirai.message.MessageSerializers import net.mamoe.mirai.message.data.* import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.mock.utils.randomImageContent import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import org.junit.jupiter.api.Assertions import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertFalse internal class MessageSerializationTest : MockBotTestBase() { @Suppress("DEPRECATION_ERROR") private val module get() = MessageSerializers.serializersModule private val format get() = Json { serializersModule = module useArrayPolymorphism = false ignoreUnknownKeys = false } private inline fun <reified T : Any> T.serialize(serializer: KSerializer<T> = module.serializer()): String { return format.encodeToString(serializer, this) } private inline fun <reified T : Any> String.deserialize(serializer: KSerializer<T> = module.serializer()): T { return format.decodeFromString(serializer, this) } private inline fun <reified T : Any> testSerialization(t: T, serializer: KSerializer<T> = module.serializer()) { val deserialized = kotlin.runCatching { println("Testing ${t::class.simpleName} with serializer $serializer") val serialized = t.serialize(serializer) println("Result: ${serializer.descriptor.serialName} $serialized") serialized.deserialize(serializer) }.getOrElse { throw IllegalStateException("Failed to serialize $t", it) } val msg = "serialized string: ${t.serialize(serializer)}\ndeserialized string: ${ deserialized.serialize( serializer ) }\n" assert1( t, deserialized, msg ) if (t is SingleMessage) { PolymorphicWrapperContent(t) .serialize(PolymorphicWrapperContent.serializer()) .deserialize(PolymorphicWrapperContent.serializer()) .let { assert1(t, it.message, msg) } } } private fun assert1(t: Any, deserialized: Any, msg: String) { if (deserialized is MessageSource && t is MessageSource) { assertSource(t, deserialized, msg) return } if (t is MessageChain && deserialized is MessageChain) { assertEquals(t.size, deserialized.size) val iter1 = t.iterator() val iter2 = deserialized.iterator() repeat(t.size) { assert1(iter1.next(), iter2.next(), msg) } assertFalse(iter1.hasNext(), msg) assertFalse(iter2.hasNext(), msg) return } assertEquals(t, deserialized, msg) } private fun assertSource(t: MessageSource, deserialized: MessageSource, msg: String) { assertEquals(t.kind, deserialized.kind, msg) assertEquals(t.botId, deserialized.botId, msg) assertEquals(t.fromId, deserialized.fromId, msg) assertEquals(t.targetId, deserialized.targetId, msg) assertEquals(t.time, deserialized.time, msg) Assertions.assertArrayEquals(t.ids, deserialized.ids, msg) Assertions.assertArrayEquals(t.internalIds, deserialized.internalIds, msg) assertEquals(t.originalMessage, deserialized.originalMessage, msg) } @Test fun testSerializersModulePlus() { MessageSerializers.serializersModule + EmptySerializersModule() } @Test fun testMockMessageSources() = runTest { testSerialization(bot.addFriend(1, "").says("")) testSerialization(bot.addStranger(2, "").says("")) bot.addGroup(3, "").let { group -> group.sendMessage("AWA").source.let { testSerialization(messageChainOf(it)) } group.addMember(6, "").says("A").let { testSerialization(it) } } } @Test fun testMockResources() = runTest { testSerialization(bot.uploadMockImage(Image.randomImageContent().toExternalResource().toAutoCloseable())) "1".toByteArray().toExternalResource().use { data0 -> testSerialization(bot.uploadOnlineAudio(data0)) testSerialization(bot.asFriend.uploadAudio(data0)) } } @Serializable data class PolymorphicWrapperImage( val message: @Polymorphic Image ) @Serializable data class PolymorphicWrapperContent( val message: @Polymorphic SingleMessage ) @Test @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "UNRESOLVED_REFERENCE") fun `test serialization for MockImage`() = runTest { val img = this@MessageSerializationTest.bot.uploadMockImage( Image.randomImageContent().toExternalResource().toAutoCloseable() ) PolymorphicWrapperImage(img) .serialize(PolymorphicWrapperImage.serializer()) .also { println(it) } .deserialize(PolymorphicWrapperImage.serializer()) } // https://github.com/mamoe/mirai/pull/2414#issuecomment-1386253123 @Test fun `test 2414-1386253123`() = runTest { // event val img = this@MessageSerializationTest.bot.uploadMockImage( Image.randomImageContent().toExternalResource().toAutoCloseable() ) val msg = buildMessageChain { add("imgUploaded") add(img) } val s = format.encodeToString(msg) println(s) println(format.decodeFromString<MessageChain>(s)) testSerialization(msg) } } ================================================ FILE: mirai-core-mock/test/mock/MessagingTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import kotlinx.coroutines.flow.toList import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.MessageSource.Key.recall import net.mamoe.mirai.mock.MockActions.mockFireRecalled import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.mock.userprofile.MockMemberInfoBuilder import net.mamoe.mirai.mock.utils.broadcastMockEvents import net.mamoe.mirai.mock.utils.simpleMemberInfo import org.junit.jupiter.api.DynamicNode import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestFactory import kotlin.test.* internal class MessagingTest : MockBotTestBase() { @Test internal fun testMessageEventBroadcast() = runTest { runAndReceiveEventBroadcast { bot.addGroup(5597122, "testing!") .addMember(simpleMemberInfo(5971, "test", permission = MemberPermission.OWNER)) .says("Hello World") bot.addFriend(9815, "tester").says("Msg By TestFriend") bot.addStranger(987166, "sudo").says("How are you") bot.getGroupOrFail(5597122).sendMessage("Testing message") bot.getFriendOrFail(9815).sendMessage("Hi my friend") bot.getStrangerOrFail(987166).sendMessage("How are you") }.let { events -> assertEquals(9, events.size) assertIsInstance<GroupMessageEvent>(events[0]) { assertEquals("Hello World", message.contentToString()) assertEquals("test", senderName) assertEquals(5971, sender.id) assertEquals(5597122, group.id) assertIsInstance<OnlineMessageSource.Incoming.FromGroup>(message.source) } assertIsInstance<FriendMessageEvent>(events[1]) { assertEquals("Msg By TestFriend", message.contentToString()) assertEquals("tester", senderName) assertEquals(9815, sender.id) assertIsInstance<OnlineMessageSource.Incoming.FromFriend>(message.source) } assertIsInstance<StrangerMessageEvent>(events[2]) { assertEquals("How are you", message.contentToString()) assertEquals("sudo", senderName) assertEquals(987166, sender.id) assertIsInstance<OnlineMessageSource.Incoming.FromStranger>(message.source) } assertIsInstance<GroupMessagePreSendEvent>(events[3]) assertIsInstance<GroupMessagePostSendEvent>(events[4]) { assertIsInstance<OnlineMessageSource.Outgoing.ToGroup>(receipt!!.source) } assertIsInstance<FriendMessagePreSendEvent>(events[5]) assertIsInstance<FriendMessagePostSendEvent>(events[6]) { assertIsInstance<OnlineMessageSource.Outgoing.ToFriend>(receipt!!.source) } assertIsInstance<StrangerMessagePreSendEvent>(events[7]) assertIsInstance<StrangerMessagePostSendEvent>(events[8]) { assertIsInstance<OnlineMessageSource.Outgoing.ToStranger>(receipt!!.source) } } } @Test internal fun testNudge() = runTest { val group = bot.addGroup(1, "1") val nudgeSender = group.addMember(simpleMemberInfo(3, "3", permission = MemberPermission.MEMBER)) val nudged = group.addMember(simpleMemberInfo(4, "4", permission = MemberPermission.MEMBER)) val myFriend = bot.addFriend(1, "514") val myStranger = bot.addStranger(2, "awef") runAndReceiveEventBroadcast { nudged.nudgedBy(nudgeSender) nudged.nudge().sendTo(group) myFriend.nudges(bot) myStranger.nudges(bot) myFriend.nudgedBy(bot) myStranger.nudgedBy(bot) }.let { events -> assertEquals(6, events.size) assertIsInstance<NudgeEvent>(events[0]) { assertSame(nudgeSender, this.from) assertSame(nudged, this.target) assertSame(group, this.subject) } assertIsInstance<NudgeEvent>(events[1]) { assertSame(bot, this.from) assertSame(nudged, this.target) assertSame(group, this.subject) } assertIsInstance<NudgeEvent>(events[2]) { assertSame(myFriend, this.from) assertSame(bot, this.target) assertSame(myFriend, this.subject) } assertIsInstance<NudgeEvent>(events[3]) { assertSame(myStranger, this.from) assertSame(bot, this.target) assertSame(myStranger, this.subject) } assertIsInstance<NudgeEvent>(events[4]) { assertSame(bot, this.from) assertSame(myFriend, this.target) assertSame(myFriend, this.subject) } assertIsInstance<NudgeEvent>(events[5]) { assertSame(bot, this.from) assertSame(myStranger, this.target) assertSame(myStranger, this.subject) } } } @Test internal fun testRoamingMessages() = runTest { val mockFriend = bot.addFriend(1, "1") val allSent = mutableListOf<MessageSource>() fun MutableList<MessageSource>.add(msg: MessageChain) { add(msg.source) } fun MutableList<MessageSource>.convertToOffline() { replaceAll { src -> bot.buildMessageSource(src.kind) { allFrom(src) } } } broadcastMockEvents { allSent.add(mockFriend says { append("Testing!") }) allSent.add(mockFriend says { append("Test2!") }) } allSent.add(mockFriend.sendMessage("Pong!").source) allSent.convertToOffline() mockFriend.roamingMessages.getAllMessages().toList().let { messages -> assertEquals(3, messages.size) assertEquals(messageChainOf(allSent[0] + PlainText("Testing!")), messages[0]) assertEquals(messageChainOf(allSent[1] + PlainText("Test2!")), messages[1]) assertEquals(messageChainOf(allSent[2] + PlainText("Pong!")), messages[2]) } allSent.clear() val mockGroup = bot.addGroup(2, "2") val mockGroupMember1 = mockGroup.addMember(123, "123") val mockGroupMember2 = mockGroup.addMember(124, "124") val mockGroupMember3 = mockGroup.addMember(125, "125") broadcastMockEvents { allSent.add(mockGroupMember1 says { append("msg1") }) allSent.add(mockGroupMember2 says { append("msg2") }) allSent.add(mockGroupMember3 says { append("msg3") }) } allSent.convertToOffline() with(mockGroup.roamingMessages.getAllMessages().toList()) { assertEquals(3, size) assertEquals(messageChainOf(allSent[0] + PlainText("msg1")), get(0)) assertEquals(messageChainOf(allSent[1] + PlainText("msg2")), get(1)) assertEquals(messageChainOf(allSent[2] + PlainText("msg3")), get(2)) } } @Test internal fun testMessageRecallEventBroadcast() = runTest { val group = bot.addGroup(8484846, "g") val admin = group.addMember(simpleMemberInfo(945474, "admin", permission = MemberPermission.ADMINISTRATOR)) val sender = group.addMember(simpleMemberInfo(178711, "usr", permission = MemberPermission.MEMBER)) runAndReceiveEventBroadcast { sender.says("Test").recalledBySender() sender.says("Admin recall").recalledBy(admin) mockFireRecalled(group.sendMessage("Hello world"), admin) sender.says("Hi").recall() admin.says("I'm admin").let { resp -> resp.recall() assertFails { resp.recall() }.let(::println) } }.dropMsgChat().let { events -> assertEquals(5, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(sender, operator) assertSame(sender, author) } assertIsInstance<MessageRecallEvent.GroupRecall>(events[1]) { assertSame(admin, operator) assertSame(sender, author) } assertIsInstance<MessageRecallEvent.GroupRecall>(events[2]) { assertSame(admin, operator) assertSame(group.botAsMember, author) } assertIsInstance<MessageRecallEvent.GroupRecall>(events[3]) { assertSame(null, operator) assertSame(sender, author) } assertIsInstance<MessageRecallEvent.GroupRecall>(events[4]) { assertSame(null, operator) assertSame(admin, author) } } val root = group.addMember(simpleMemberInfo(54986565, "root", permission = MemberPermission.OWNER)) runAndReceiveEventBroadcast { sender.says("0").runAndAssertFails { recall() } admin.says("0").runAndAssertFails { recall() } root.says("0").runAndAssertFails { recall() } group.sendMessage("Hi").recall() }.dropMsgChat().let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertEquals(group.botAsMember, author) assertEquals(null, operator) } } } @Suppress("ComplexRedundantLet") @Nested internal inner class MessageRecalling { @TestFactory fun `friend messaging`(): Iterable<DynamicNode> { val myFriend = bot.addFriend(1, "f") return listOf<DynamicNode>( dynamicTest("bot recalling") { val msgBot = myFriend.sendMessage("2") runAndReceiveEventBroadcast { msgBot.recall() }.let { events -> assertEquals(0, events.size) } assertMessageNotAvailable(msgBot.source) }, dynamicTest("friend recalling") { val msgFriend = myFriend.says("1") runAndReceiveEventBroadcast { msgFriend.recalledBySender() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.FriendRecall>(events[0]) { assertEquals(myFriend, this.operator) assertContentEquals(msgFriend.source.ids, this.messageIds) assertContentEquals(msgFriend.source.internalIds, this.messageInternalIds) } assertMessageNotAvailable(msgFriend.source) } }, dynamicTest("bot recall friend msg failed") { val msg = myFriend.says("1") assertFails { msg.recall() } assertMessageAvailable(msg.source) }, ) } @TestFactory fun `stranger messaging`(): Iterable<DynamicNode> { val myStranger = bot.addStranger(2, "s") return listOf<DynamicNode>( dynamicTest("stranger recalling") { val msg = myStranger.says("1") runAndReceiveEventBroadcast { msg.recalledBySender() }.let { events -> assertEquals(0, events.size) } assertMessageNotAvailable(msg.source) }, dynamicTest("bot recalling") { val msg = myStranger.sendMessage("1") runAndReceiveEventBroadcast { msg.recall() }.let { events -> assertEquals(0, events.size) } assertMessageNotAvailable(msg.source) }, dynamicTest("bot recall stranger failed") { val msg = myStranger.says("1") assertFails { msg.recall() } assertMessageAvailable(msg.source) }, ) } @TestFactory fun `group messaging`(): Iterable<DynamicNode> = listOf( dynamicContainer("Normal messaging test") { val group = bot.addGroup(18451444229, "owner group") group.addMember(MockMemberInfoBuilder.create { uin(184554).permission(MemberPermission.OWNER) }) val administrator = group.addMember(MockMemberInfoBuilder.create { uin(184).permission(MemberPermission.ADMINISTRATOR) }) val member = group.addMember(7777, "wapeog") group.botAsMember.mockApi.permission = MemberPermission.MEMBER return@dynamicContainer listOf<DynamicNode>( dynamicTest("member self recalling") { val msg = member.says("1") runAndReceiveEventBroadcast { msg.recalledBySender() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(member, this.author) assertSame(member, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, dynamicTest("member msg recalled by others") { val msg = member.says("1") runAndReceiveEventBroadcast { msg.recalledBy(administrator) }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(member, this.author) assertSame(administrator, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, dynamicTest("member msg forced recalled by bot") { val msg = member.says("1") runAndReceiveEventBroadcast { msg.recalledBy(group.botAsMember) }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(member, this.author) assertSame(null, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, ) }, dynamicContainer("bot is owner") { val group = bot.addGroup(8412, "owner group") val administrator = group.addMember(MockMemberInfoBuilder.create { uin(184).permission(MemberPermission.ADMINISTRATOR) }) val member = group.addMember(7777, "wapeog") assertEquals(group.botPermission, MemberPermission.OWNER) return@dynamicContainer listOf<DynamicNode>( dynamicTest("Bot can recall itself message") { val msg = group.sendMessage("1") runAndReceiveEventBroadcast { msg.recall() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(group.botAsMember, this.author) assertSame(null, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, dynamicTest("Bot can recall administrator message") { val msg = administrator.says("1") runAndReceiveEventBroadcast { msg.recall() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(administrator, this.author) assertSame(null, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, dynamicTest("Bot can recall member message") { val msg = member.says("1") runAndReceiveEventBroadcast { msg.recall() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(member, this.author) assertSame(null, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, ) }, dynamicContainer("bot is administrator") { val group = bot.addGroup(7517, "owner group") val owner = group.addMember(MockMemberInfoBuilder.create { uin(96451).permission(MemberPermission.OWNER) }) val administrator = group.addMember(MockMemberInfoBuilder.create { uin(184).permission(MemberPermission.ADMINISTRATOR) }) val member = group.addMember(7777, "wapeog") group.botAsMember.mockApi.permission = MemberPermission.ADMINISTRATOR return@dynamicContainer listOf<DynamicNode>( dynamicTest("Bot can recall itself message") { val msg = group.sendMessage("1") runAndReceiveEventBroadcast { msg.recall() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(group.botAsMember, this.author) assertSame(null, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, dynamicTest("Bot cannot recall owner message") { val msg = owner.says("1") assertFails { msg.recall() } assertMessageAvailable(msg.source) }, dynamicTest("Bot cannot recall administrator message") { val msg = administrator.says("1") assertFails { msg.recall() } assertMessageAvailable(msg.source) }, dynamicTest("Bot can recall member message") { val msg = member.says("1") runAndReceiveEventBroadcast { msg.recall() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(member, this.author) assertSame(null, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, ) }, dynamicContainer("bot is member") { val group = bot.addGroup(8441117, "owner group") val owner = group.addMember(MockMemberInfoBuilder.create { uin(98171).permission(MemberPermission.OWNER) }) val administrator = group.addMember(MockMemberInfoBuilder.create { uin(184).permission(MemberPermission.ADMINISTRATOR) }) val member = group.addMember(7777, "wapeog") group.botAsMember.mockApi.permission = MemberPermission.MEMBER return@dynamicContainer listOf<DynamicNode>( dynamicTest("Bot can recall itself message") { val msg = group.sendMessage("1") runAndReceiveEventBroadcast { msg.recall() }.let { events -> assertEquals(1, events.size) assertIsInstance<MessageRecallEvent.GroupRecall>(events[0]) { assertSame(group.botAsMember, this.author) assertSame(null, operator) assertContentEquals(msg.source.ids, this.messageIds) assertContentEquals(msg.source.internalIds, this.messageInternalIds) } } assertMessageNotAvailable(msg.source) }, dynamicTest("Bot cannot recall owner message") { val msg = owner.says("1") assertFails { msg.recall() } assertMessageAvailable(msg.source) }, dynamicTest("Bot cannot recall administrator message") { val msg = administrator.says("1") assertFails { msg.recall() } assertMessageAvailable(msg.source) }, dynamicTest("Bot cannot recall member message") { val msg = member.says("1") assertFails { msg.recall() } assertMessageAvailable(msg.source) }, ) }, ) } } ================================================ FILE: mirai-core-mock/test/mock/MockBotBaseTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.MemberPermissionChangeEvent import net.mamoe.mirai.mock.contact.MockNormalMember import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.mock.userprofile.buildUserProfile import net.mamoe.mirai.mock.utils.simpleMemberInfo import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertSame import kotlin.test.assertTrue internal class MockBotBaseTest : MockBotTestBase() { @Test internal fun testMockBotMocking() = runTest { repeat(50) { i -> bot.addFriend(20000L + i, "usr$i") bot.addStranger(10000L + i, "stranger$i") bot.addGroup(798100000L + i, "group$i") } assertEquals(50, bot.friends.size) assertEquals(50, bot.strangers.size) assertEquals(50, bot.groups.size) repeat(50) { i -> assertEquals("usr$i", bot.getFriendOrFail(20000L + i).nick) assertEquals("stranger$i", bot.getStrangerOrFail(10000L + i).nick) val group = bot.getGroupOrFail(798100000L + i) assertEquals("group$i", group.name) assertSame(group.botAsMember, group.owner) assertSame(MemberPermission.OWNER, group.botPermission) assertEquals(0, group.members.size) } val mockGroup = bot.getGroupOrFail(798100000L) repeat(50) { i -> mockGroup.appendMember(simpleMemberInfo(3700000L + i, "member$i", permission = MemberPermission.MEMBER)) } repeat(50) { i -> val member = mockGroup.getOrFail(3700000L + i) assertEquals(MemberPermission.MEMBER, member.permission) assertEquals("member$i", member.nick) assertTrue(member.nameCard.isEmpty()) assertEquals(MemberPermission.OWNER, mockGroup.botPermission) } val newOwner: MockNormalMember runAndReceiveEventBroadcast { newOwner = mockGroup.addMember(simpleMemberInfo(84485417, "root", permission = MemberPermission.OWNER)) }.let { events -> assertEquals(0, events.size) } assertEquals(MemberPermission.OWNER, newOwner.permission) assertEquals(MemberPermission.MEMBER, mockGroup.botPermission) assertSame(newOwner, mockGroup.owner) val newNewOwner = mockGroup.getOrFail(3700000L) runAndReceiveEventBroadcast { mockGroup.changeOwner(newNewOwner) }.let { events -> assertEquals(2, events.size) assertIsInstance<MemberPermissionChangeEvent>(events[0]) { assertSame(newNewOwner, member) assertSame(MemberPermission.OWNER, new) assertSame(MemberPermission.MEMBER, origin) } assertIsInstance<MemberPermissionChangeEvent>(events[1]) { assertSame(newOwner, member) assertSame(MemberPermission.OWNER, origin) assertSame(MemberPermission.MEMBER, new) } } assertEquals(MemberPermission.OWNER, newNewOwner.permission) assertEquals(MemberPermission.MEMBER, newOwner.permission) assertEquals(MemberPermission.MEMBER, mockGroup.botPermission) assertSame(newNewOwner, mockGroup.owner) } @Test internal fun testQueryProfile() = runTest { val service = bot.userProfileService val profile = buildUserProfile { nickname("Test0") } service.putUserProfile(1, profile) assertSame(profile, Mirai.queryProfile(bot, 1)) } } ================================================ FILE: mirai-core-mock/test/mock/MockBotEventTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import net.mamoe.mirai.event.events.* import net.mamoe.mirai.mock.test.MockBotTestBase import org.junit.jupiter.api.Test import kotlin.test.assertEquals internal class MockBotEventTest : MockBotTestBase() { @Test fun testBotOnlineEvent() = runTest { runAndReceiveEventBroadcast { bot.login() }.let { events -> assertEquals(1, events.size) assertIsInstance<BotOnlineEvent>(events[0]) } } @Test fun testBotOfflineEvent() = runTest { runAndReceiveEventBroadcast { bot.broadcastOfflineEvent() }.let { events -> assertEquals(1, events.size) assertIsInstance<BotOfflineEvent>(events[0]) } } @Test fun testBotRelogin() = runTest { bot.login() runAndReceiveEventBroadcast { bot.login() }.let { events -> assertEquals(2, events.size) assertIsInstance<BotOnlineEvent>(events[0]) assertIsInstance<BotReloginEvent>(events[1]) } } @Test fun testMockAvatarChange() = runTest { assertEquals("http://q.qlogo.cn/g?b=qq&nk=${bot.id}&s=640", bot.avatarUrl) runAndReceiveEventBroadcast { bot.avatarUrl = "http://localhost/test.png" assertEquals("http://localhost/test.png", bot.avatarUrl) }.let { events -> assertEquals(1, events.size) assertIsInstance<BotAvatarChangedEvent>(events[0]) } } @Test fun testBotNickChangedEvent() = runTest { runAndReceiveEventBroadcast { bot.nickNoEvent = "HiHi" bot.nick = "AAAA" bot nickChangesTo "BBBB" }.let { events -> assertEquals(2, events.size) assertIsInstance<BotNickChangedEvent>(events[0]) { assertEquals("HiHi", from) assertEquals("AAAA", to) } assertIsInstance<BotNickChangedEvent>(events[1]) { assertEquals("AAAA", from) assertEquals("BBBB", to) } } } } ================================================ FILE: mirai-core-mock/test/mock/MockFriendGroupsTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import net.mamoe.mirai.mock.test.MockBotTestBase import org.junit.jupiter.api.Test import kotlin.test.assertEquals internal class MockFriendGroupsTest : MockBotTestBase() { @Test internal fun testFriendGroupsDefaultEmpty() = runTest { assertEquals(1, bot.friendGroups.asCollection().size) assertEquals(bot.friendGroups.default, bot.friendGroups[0]) assertEquals(bot.friendGroups.default, bot.friendGroups.asCollection().iterator().next()) } @Test internal fun testFriendGroupCreating() = runTest { val group = bot.friendGroups.create("Test") println(group.id) assertEquals(2, bot.friendGroups.asCollection().size) assertEquals(group, bot.friendGroups[group.id]) } @Test internal fun testFriendGroupReferences() = runTest { val group = bot.friendGroups.create("Test") val friend = bot.addFriend(5, "Test") assertEquals(bot.friendGroups.default, friend.friendGroup) assertEquals(0, friend.mockApi.friendGroupId) group.moveIn(friend) assertEquals(group, friend.friendGroup) assertEquals(group.id, friend.mockApi.friendGroupId) group.delete() assertEquals(bot.friendGroups.default, friend.friendGroup) assertEquals(0, friend.mockApi.friendGroupId) } } ================================================ FILE: mirai-core-mock/test/mock/MockFriendTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import net.mamoe.mirai.event.events.* import net.mamoe.mirai.mock.internal.contact.MockImage import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.utils.cast import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertSame import kotlin.test.assertTrue internal class MockFriendTest : MockBotTestBase() { @Test internal fun testNewFriendRequest() = runTest { runAndReceiveEventBroadcast { bot.broadcastNewFriendRequestEvent( 1, "Hi", 0, "Hello!" ).reject() }.let { events -> assertEquals(1, events.size) assertIsInstance<NewFriendRequestEvent>(events[0]) { assertEquals(1, fromId) assertEquals("Hi", fromNick) assertEquals(0, fromGroupId) assertEquals("Hello!", message) } assertEquals(bot.friends.size, 0) } runAndReceiveEventBroadcast { bot.broadcastNewFriendRequestEvent( 1, "Hi", 0, "Hello!" ).accept() }.let { events -> assertEquals(2, events.size, events.toString()) assertIsInstance<NewFriendRequestEvent>(events[0]) { assertEquals(1, fromId) assertEquals("Hi", fromNick) assertEquals(0, fromGroupId) assertEquals("Hello!", message) } assertIsInstance<FriendAddEvent>(events[1]) { assertEquals(1, friend.id) assertEquals("Hi", friend.nick) assertSame(friend, bot.getFriend(friend.id)) } assertEquals(1, bot.friends.size) } } @Test fun testFriendAvatarChangedEvent() = runTest { runAndReceiveEventBroadcast { bot.addFriend(111, "a").changeAvatarUrl(MockImage.random(bot).getUrl(bot)) bot.addFriend(222, "b") }.let { events -> assertIsInstance<FriendAvatarChangedEvent>(events[0]) assertEquals(111, events[0].cast<FriendAvatarChangedEvent>().friend.id) assertNotEquals("", bot.getFriend(111)!!.avatarUrl) assertNotEquals("", bot.getFriend(222)!!.avatarUrl) assertNotEquals("", bot.getFriend(222)!!.avatarUrl.toUrl().readText()) } } @Test fun testFriendRemarkChangeEvent() = runTest { runAndReceiveEventBroadcast { bot.addFriend(1, "").remark = "Test" }.let { events -> assertEquals(1, events.size) assertIsInstance<FriendRemarkChangeEvent>(events[0]) { assertEquals(1, this.friend.id) assertEquals("", oldRemark) assertEquals("Test", newRemark) } } } @Test fun testFriendRequestAndAddEvent() = runTest { runAndReceiveEventBroadcast { bot.broadcastNewFriendRequestEvent( 1, "Test", 0, "Hi" ).accept() bot.broadcastNewFriendRequestEvent( 2, "Hi", 1, "0" ).reject() }.let { events -> assertEquals(3, events.size) assertIsInstance<NewFriendRequestEvent>(events[0]) { assertEquals(1, fromId) assertEquals("Test", fromNick) assertEquals(0, fromGroupId) assertEquals("Hi", message) } assertIsInstance<FriendAddEvent>(events[1]) { assertEquals(1, friend.id) assertEquals("Test", friend.nick) } assertIsInstance<NewFriendRequestEvent>(events[2]) { assertEquals(2, fromId) assertEquals("Hi", fromNick) assertEquals(1, fromGroupId) assertEquals("0", message) } } } @Test fun testFriendNickChangedEvent() = runTest { runAndReceiveEventBroadcast { bot.addFriend(0, "Old").nick = "Test" }.let { events -> assertEquals(1, events.size) assertIsInstance<FriendNickChangedEvent>(events[0]) { assertEquals("Old", from) assertEquals("Test", to) } } } @Test fun testFriendInputStatusChangedEvent() = runTest { runAndReceiveEventBroadcast { bot.addFriend(1, "a").broadcastFriendInputStateChange(true) }.let { events -> assertEquals(1, events.size) assertIsInstance<FriendInputStatusChangedEvent>(events[0]) { assertTrue(inputting) assertSame(bot.getFriend(1), friend) } } } } ================================================ FILE: mirai-core-mock/test/mock/MockGroupTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import kotlinx.coroutines.delay import kotlinx.coroutines.flow.toList import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.contact.announcement.AnnouncementParametersBuilder import net.mamoe.mirai.contact.isBotMuted import net.mamoe.mirai.data.GroupHonorType import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.data.FileMessage import net.mamoe.mirai.mock.contact.announcement.MockOnlineAnnouncement import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.mock.userprofile.MockMemberInfoBuilder import net.mamoe.mirai.mock.utils.simpleMemberInfo import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource import net.mamoe.mirai.utils.cast import org.junit.jupiter.api.Test import kotlin.test.* internal class MockGroupTest : MockBotTestBase() { @Test internal fun testMockGroupJoinRequest() = runTest { val group = bot.addGroup(9875555515, "test") runAndReceiveEventBroadcast { group.broadcastNewMemberJoinRequestEvent( 100000000, "demo", "msg" ).accept() }.let { events -> assertEquals(2, events.size) assertIsInstance<MemberJoinRequestEvent>(events[0]) { assertEquals(100000000, fromId) assertEquals("demo", fromNick) assertEquals("msg", message) } assertIsInstance<MemberJoinEvent>(events[1]) { assertEquals(100000000, member.id) assertEquals("demo", member.nick) } } val member = group.getOrFail(100000000) assertEquals(MemberPermission.MEMBER, member.permission) } @Test internal fun testMockBotJoinGroupRequest() = runTest { val invitor = bot.addFriend(5710, "demo") runAndReceiveEventBroadcast { invitor.broadcastInviteBotJoinGroupRequestEvent(999999999, "test") .accept() }.let { events -> assertEquals(2, events.size) assertIsInstance<BotInvitedJoinGroupRequestEvent>(events[0]) { assertEquals(5710, invitorId) assertEquals("demo", invitorNick) assertEquals(999999999, groupId) assertEquals("test", groupName) } assertIsInstance<BotJoinGroupEvent>(events[1]) { assertNotSame(group.botAsMember, group.owner) assertEquals(MemberPermission.MEMBER, group.botPermission) assertEquals(999999999, group.id) assertEquals(MemberPermission.OWNER, group.owner.permission) } } } @Test internal fun testGroupAnnouncements() = runTest { val group = bot.addGroup(8484541, "87") runAndReceiveEventBroadcast { group.announcements.publish( MockOnlineAnnouncement( content = "dlroW olleH", parameters = AnnouncementParametersBuilder().apply { this.sendToNewMember = true }.build(), senderId = 9711221, allConfirmed = false, confirmedMembersCount = 0, publicationTime = 0 ) ) group.announcements.publish( MockOnlineAnnouncement( content = "Hello World", parameters = AnnouncementParametersBuilder().apply { this.sendToNewMember = true }.build(), senderId = 971121, allConfirmed = false, confirmedMembersCount = 0, publicationTime = 0 ) ) }.let { events -> assertEquals(0, events.size) } val anc = group.announcements.asFlow().toList() assertEquals(1, anc.size) assertEquals("Hello World", anc[0].content) assertFalse(anc[0].fid.isEmpty()) assertEquals(anc[0], group.announcements.get(anc[0].fid)) } @Test internal fun testLeave() = runTest { runAndReceiveEventBroadcast { bot.addGroup(1, "1").quit() bot.addFriend(2, "2").delete() bot.addStranger(3, "3").delete() bot.addGroup(4, "4") .addMember(simpleMemberInfo(5, "5", permission = MemberPermission.MEMBER)) .broadcastMemberLeave() bot.addGroup(6, "6") .addMember(simpleMemberInfo(7, "7", permission = MemberPermission.OWNER)) .broadcastKickBot() }.let { events -> assertEquals(5, events.size) assertIsInstance<BotLeaveEvent.Active>(events[0]) { assertEquals(1, group.id) } assertIsInstance<FriendDeleteEvent>(events[1]) { assertEquals(2, friend.id) } assertIsInstance<StrangerRelationChangeEvent.Deleted>(events[2]) { assertEquals(3, stranger.id) } assertIsInstance<MemberLeaveEvent>(events[3]) { assertEquals(4, group.id) assertEquals(5, member.id) } assertIsInstance<BotLeaveEvent.Kick>(events[4]) { assertEquals(6, group.id) assertEquals(7, operator.id) } } } @Suppress("DEPRECATION", "DEPRECATION_ERROR") @Test internal fun testGroupFileV1() = runTest { val fsroot = bot.addGroup(5417, "58aw").filesRoot fsroot.resolve("helloworld.txt").uploadAndSend( "HelloWorld".toByteArray().toExternalResource().toAutoCloseable() ) assertEquals(1, fsroot.listFilesCollection().size) assertEquals( "HelloWorld", fsroot.resolve("helloworld.txt") .getDownloadInfo()!! .url.toUrl() .also { println("Mock file url: $it") } .readText() ) fsroot.resolve("helloworld.txt").delete() assertEquals(0, fsroot.listFilesCollection().size) } @Test internal fun testGroupFileUpload() = runTest { val files = bot.addGroup(111, "aaa").files val file = files.uploadNewFile("aaa", "ccc".toByteArray().toExternalResource().toAutoCloseable()) assertEquals("ccc", file.getUrl()!!.toUrl().readText()) runAndReceiveEventBroadcast { bot.getGroup(111)!!.addMember(simpleMemberInfo(222, "bbb", permission = MemberPermission.ADMINISTRATOR)) .says(file.toMessage()) }.let { events -> assertTrue(events[0].cast<GroupMessageEvent>().message.contains(FileMessage)) } } @Test internal fun testAvatar() = runTest { assertNotEquals("", bot.addGroup(111, "aaa").avatarUrl.toUrl().readText()) } @Test internal fun testBotLeaveGroup() = runTest { runAndReceiveEventBroadcast { bot.addGroup(1, "A").quit() bot.addGroup(2, "B") .addMember(MockMemberInfoBuilder.create { uin(3).nick("W") permission(MemberPermission.ADMINISTRATOR) }).broadcastKickBot() // TODO: BotLeaveEvent.Disband }.let { events -> assertEquals(2, events.size) assertIsInstance<BotLeaveEvent.Active>(events[0]) { assertEquals(1, group.id) assertEquals("A", group.name) } assertIsInstance<BotLeaveEvent.Kick>(events[1]) { assertEquals(2, group.id) assertEquals("B", group.name) assertEquals(3, operator.id) assertEquals("W", operator.nick) } assertNull(bot.getGroup(1)) assertNull(bot.getGroup(2)) } } @Test fun testBotGroupPermissionChangeEvent() = runTest { runAndReceiveEventBroadcast { bot.addGroup(1, "") .appendMember(MockMemberInfoBuilder.create { uin(1).nick("o") permission(MemberPermission.OWNER) }) .botAsMember permissionChangesTo MemberPermission.ADMINISTRATOR bot.addGroup(2, "") .appendMember(MockMemberInfoBuilder.create { uin(1).nick("o") permission(MemberPermission.OWNER) }) .let { it.changeOwner(it.botAsMember) } }.let { events -> assertEquals(3, events.size) assertIsInstance<BotGroupPermissionChangeEvent>(events[0]) { assertEquals(MemberPermission.ADMINISTRATOR, new) assertEquals(MemberPermission.MEMBER, origin) } assertIsInstance<BotGroupPermissionChangeEvent>(events[1]) { assertEquals(MemberPermission.OWNER, new) assertEquals(MemberPermission.MEMBER, origin) } assertIsInstance<MemberPermissionChangeEvent>(events[2]) { assertEquals(1, member.id) assertEquals("o", member.nick) assertEquals(MemberPermission.MEMBER, new) assertEquals(MemberPermission.OWNER, origin) } } } @Test fun testMuteEvent() = runTest { runAndReceiveEventBroadcast { val group = bot.addGroup(1, "") .appendMember(2, "") group.botAsMember.let { it.broadcastMute(it, 2) assertTrue { it.isMuted } it.broadcastMute(it, 0) assertFalse { it.isMuted } it.broadcastMute(it, 5) assertTrue { group.isBotMuted } assertTrue { it.isMuted } } group.getOrFail(2).let { it.broadcastMute(it, 2) assertTrue { it.isMuted } it.broadcastMute(it, 0) assertFalse { it.isMuted } it.broadcastMute(it, 5) assertTrue { it.isMuted } } }.let { events -> assertEquals(6, events.size) assertIsInstance<BotMuteEvent>(events[0]) assertIsInstance<BotUnmuteEvent>(events[1]) assertIsInstance<BotMuteEvent>(events[2]) assertIsInstance<MemberMuteEvent>(events[3]) assertIsInstance<MemberUnmuteEvent>(events[4]) assertIsInstance<MemberMuteEvent>(events[5]) delay(6000L) assertFalse { bot.getGroupOrFail(1).isBotMuted } assertFalse { bot.getGroupOrFail(1).getOrFail(2).isMuted } } } @Test fun testGroupNameChangeEvent() = runTest { runAndReceiveEventBroadcast { val g = bot.addGroup(1, "").appendMember(7, "A") g.controlPane.groupName = "OOOOO" g.name = "Test" g.controlPane.withActor(g.getOrFail(7)).groupName = "Hi" }.let { events -> assertEquals(2, events.size) assertIsInstance<GroupNameChangeEvent>(events[0]) { assertEquals("OOOOO", origin) assertEquals("Test", new) assertEquals(1, group.id) assertNull(operator) } assertIsInstance<GroupNameChangeEvent>(events[1]) { assertEquals("Test", origin) assertEquals("Hi", new) assertEquals(1, group.id) assertEquals(7, operator!!.id) } } } @Test fun testGroupMuteAllEvent() = runTest { runAndReceiveEventBroadcast { val g = bot.addGroup(1, "").appendMember(7, "A") g.controlPane.isMuteAll = true g.settings.isMuteAll = false g.controlPane.withActor(g.getOrFail(7)).isMuteAll = true }.let { events -> assertEquals(2, events.size) assertIsInstance<GroupMuteAllEvent>(events[0]) { assertEquals(true, origin) assertEquals(false, new) assertEquals(1, group.id) assertNull(operator) } assertIsInstance<GroupMuteAllEvent>(events[1]) { assertEquals(false, origin) assertEquals(true, new) assertEquals(1, group.id) assertEquals(7, operator!!.id) } } } @Test fun testGroupAllowAnonymousChatEvent() = runTest { runAndReceiveEventBroadcast { val g = bot.addGroup(1, "").appendMember(7, "A") g.controlPane.isAnonymousChatAllowed = true g.settings.isAnonymousChatEnabled = false g.controlPane.withActor(g.getOrFail(7)).isAnonymousChatAllowed = true }.let { events -> assertEquals(2, events.size) assertIsInstance<GroupAllowAnonymousChatEvent>(events[0]) { assertEquals(true, origin) assertEquals(false, new) assertEquals(1, group.id) assertNull(operator) } assertIsInstance<GroupAllowAnonymousChatEvent>(events[1]) { assertEquals(false, origin) assertEquals(true, new) assertEquals(1, group.id) assertEquals(7, operator!!.id) } } } @Test fun testGroupAllowConfessTalkEvent() = runTest { runAndReceiveEventBroadcast { val g = bot.addGroup(1, "").appendMember(7, "A") g.controlPane.isAllowConfessTalk = true g.controlPane.withActor(g.botAsMember).isAllowConfessTalk = false g.controlPane.withActor(g.getOrFail(7)).isAllowConfessTalk = true }.let { events -> assertEquals(2, events.size) assertIsInstance<GroupAllowConfessTalkEvent>(events[0]) { assertEquals(true, origin) assertEquals(false, new) assertEquals(1, group.id) assertTrue(isByBot) } assertIsInstance<GroupAllowConfessTalkEvent>(events[1]) { assertEquals(false, origin) assertEquals(true, new) assertEquals(1, group.id) assertFalse(isByBot) } } } @Test fun testGroupAllowMemberInviteEvent() = runTest { runAndReceiveEventBroadcast { val g = bot.addGroup(1, "").appendMember(7, "A") g.controlPane.isAllowMemberInvite = true g.settings.isAllowMemberInvite = false g.controlPane.withActor(g.getOrFail(7)).isAllowMemberInvite = true }.let { events -> assertEquals(2, events.size) assertIsInstance<GroupAllowMemberInviteEvent>(events[0]) { assertEquals(true, origin) assertEquals(false, new) assertEquals(1, group.id) assertNull(operator) } assertIsInstance<GroupAllowMemberInviteEvent>(events[1]) { assertEquals(false, origin) assertEquals(true, new) assertEquals(1, group.id) assertEquals(7, operator!!.id) } } } @Test fun testMemberCardChangeEvent() = runTest { runAndReceiveEventBroadcast { bot.addGroup(1, "") .addMember(MockMemberInfoBuilder.create { uin(2) nameCard("Hi") }).nameCardChangesTo("Hello") }.let { events -> assertEquals(1, events.size) assertIsInstance<MemberCardChangeEvent>(events[0]) { assertEquals("Hi", origin) assertEquals("Hello", new) assertEquals(2, member.id) assertEquals(1, member.group.id) } } } @Test fun testMemberSpecialTitleChangeEvent() = runTest { runAndReceiveEventBroadcast { bot.addGroup(1, "").addMember(2, "") specialTitleChangesTo "Hello" }.let { events -> assertEquals(1, events.size) assertIsInstance<MemberSpecialTitleChangeEvent>(events[0]) { assertEquals("", origin) assertEquals("Hello", new) assertEquals(2, member.id) assertEquals(1, member.group.id) } } } @Test fun testHonorMember() = runTest { val group = bot.addGroup(1, "") val member1 = group.addMember(2, "") val member2 = group.addMember(3, "") assertEquals(emptyList(), group.active.queryHonorHistory(GroupHonorType.TALKATIVE).records) runAndReceiveEventBroadcast { group.active.changeHonorMember(member1, GroupHonorType.TALKATIVE) }.let { events -> assertEquals(1, events.size) assertIsInstance<MemberHonorChangeEvent.Achieve>(events[0]) { assertEquals(GroupHonorType.TALKATIVE, this.honorType) assertEquals(member1, this.member) assertEquals(group, this.group) } } assertEquals(member1, group.active.queryHonorHistory(GroupHonorType.TALKATIVE).current!!.member!!) assertEquals(emptyList(), group.active.queryHonorHistory(GroupHonorType.TALKATIVE).records) runAndReceiveEventBroadcast { group.active.changeHonorMember(member2, GroupHonorType.TALKATIVE) }.let { events -> assertEquals(3, events.size) assertIsInstance<GroupTalkativeChangeEvent>(events[0]) { assertEquals(member2, this.now) assertEquals(member1, this.previous) assertEquals(group, this.group) } assertIsInstance<MemberHonorChangeEvent.Lose>(events[1]) { assertEquals(GroupHonorType.TALKATIVE, this.honorType) assertEquals(member1, this.member) assertEquals(group, this.group) } assertIsInstance<MemberHonorChangeEvent.Achieve>(events[2]) { assertEquals(GroupHonorType.TALKATIVE, this.honorType) assertEquals(member2, this.member) assertEquals(group, this.group) } } assertEquals(member2, group.active.queryHonorHistory(GroupHonorType.TALKATIVE).current!!.member!!) // it.member must exist assertEquals( listOf(member1), group.active.queryHonorHistory(GroupHonorType.TALKATIVE).records.map { it.member!! }) runAndReceiveEventBroadcast { group.active.changeHonorMember(member1, GroupHonorType.TALKATIVE) }.let { events -> assertEquals(3, events.size) assertIsInstance<GroupTalkativeChangeEvent>(events[0]) { assertEquals(member1, this.now) assertEquals(member2, this.previous) assertEquals(group, this.group) } assertIsInstance<MemberHonorChangeEvent.Lose>(events[1]) { assertEquals(GroupHonorType.TALKATIVE, this.honorType) assertEquals(member2, this.member) assertEquals(group, this.group) } assertIsInstance<MemberHonorChangeEvent.Achieve>(events[2]) { assertEquals(GroupHonorType.TALKATIVE, this.honorType) assertEquals(member1, this.member) assertEquals(group, this.group) } } assertEquals(member1, group.active.queryHonorHistory(GroupHonorType.TALKATIVE).current!!.member!!) assertEquals( listOf(member1, member2), group.active.queryHonorHistory(GroupHonorType.TALKATIVE).records.map { it.member!! }) runAndReceiveEventBroadcast { group.active.changeHonorMember(member1, GroupHonorType.BRONZE) }.let { events -> assertEquals(1, events.size) assertIsInstance<MemberHonorChangeEvent.Achieve>(events[0]) { assertEquals(GroupHonorType.BRONZE, this.honorType) assertEquals(member1, this.member) assertEquals(group, this.group) } } assertEquals(member1, group.active.queryHonorHistory(GroupHonorType.BRONZE).current!!.member!!) assertEquals(emptyList(), group.active.queryHonorHistory(GroupHonorType.BRONZE).records) runAndReceiveEventBroadcast { group.active.changeHonorMember(member2, GroupHonorType.BRONZE) }.let { events -> assertEquals(2, events.size) assertIsInstance<MemberHonorChangeEvent.Lose>(events[0]) { assertEquals(GroupHonorType.BRONZE, this.honorType) assertEquals(member1, this.member) assertEquals(group, this.group) } assertIsInstance<MemberHonorChangeEvent.Achieve>(events[1]) { assertEquals(GroupHonorType.BRONZE, this.honorType) assertEquals(member2, this.member) assertEquals(group, this.group) } } assertEquals(member2, group.active.queryHonorHistory(GroupHonorType.BRONZE).current!!.member!!) assertEquals(listOf(member1), group.active.queryHonorHistory(GroupHonorType.BRONZE).records.map { it.member!! }) } } ================================================ FILE: mirai-core-mock/test/mock/MockMemberTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import net.mamoe.mirai.contact.MemberPermission import net.mamoe.mirai.event.events.BotGroupPermissionChangeEvent import net.mamoe.mirai.event.events.MemberPermissionChangeEvent import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.mock.utils.simpleMemberInfo import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals import kotlin.test.assertSame internal class MockMemberTest : MockBotTestBase() { @Test internal fun testAvatar() = runTest { val m = bot.addGroup(111, "aaa").addMember(simpleMemberInfo(222, "bbb", permission = MemberPermission.MEMBER)) assertNotEquals("", m.avatarUrl) } @Test internal fun changeOwner() = runTest { val group = bot.addGroup(111, "aaa") val member = group.addMember(simpleMemberInfo(222, "bbb", permission = MemberPermission.MEMBER)) val events = runAndReceiveEventBroadcast { group.changeOwner(member) assertSame(member, group.owner) assertSame(MemberPermission.OWNER, member.permission) } assertEquals(2, events.size) assertIsInstance<MemberPermissionChangeEvent>(events[0]) { assertSame(member, this.member) assertSame(MemberPermission.OWNER, new) assertSame(MemberPermission.MEMBER, origin) assertSame(group, this.group) } assertIsInstance<BotGroupPermissionChangeEvent>(events[1]) { assertSame(MemberPermission.MEMBER, new) assertSame(MemberPermission.OWNER, origin) assertSame(group, this.group) } } @Test internal fun modifyAdmin() = runTest { val group = bot.addGroup(111, "aaa") group.changeOwner(group.botAsMember) val m = group.addMember(simpleMemberInfo(222, "bbb", permission = MemberPermission.MEMBER)) val events = runAndReceiveEventBroadcast { m.modifyAdmin(true) assertEquals(MemberPermission.ADMINISTRATOR, m.permission) m.modifyAdmin(false) assertEquals(MemberPermission.MEMBER, m.permission) } assertEquals(2, events.size) assertIsInstance<MemberPermissionChangeEvent>(events[0]) { assertSame(m, member) assertSame(MemberPermission.MEMBER, origin) assertSame(MemberPermission.ADMINISTRATOR, new) assertSame(group, this.group) } assertIsInstance<MemberPermissionChangeEvent>(events[1]) { assertSame(m, member) assertSame(MemberPermission.ADMINISTRATOR, origin) assertSame(MemberPermission.MEMBER, new) assertSame(group, this.group) } } } ================================================ FILE: mirai-core-mock/test/mock/MockStrangerTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test.mock import net.mamoe.mirai.event.events.StrangerRelationChangeEvent import net.mamoe.mirai.mock.test.MockBotTestBase import net.mamoe.mirai.utils.cast import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertNotEquals internal class MockStrangerTest : MockBotTestBase() { @Test internal fun testStrangerRelationChangeEvent() = runTest { runAndReceiveEventBroadcast { bot.addStranger(111, "aa").addAsFriend() bot.addStranger(222, "bb").delete() }.let { events -> assertEquals(2, events.size) assertIsInstance<StrangerRelationChangeEvent.Friended>(events[0]) assertEquals(111, events[0].cast<StrangerRelationChangeEvent.Friended>().friend.id) assertIsInstance<StrangerRelationChangeEvent.Deleted>(events[1]) assertEquals(222, events[1].cast<StrangerRelationChangeEvent.Deleted>().stranger.id) assertNotEquals("", bot.getFriend(111)!!.avatarUrl) } } } ================================================ FILE: mirai-core-mock/test/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.mock.test ================================================ FILE: mirai-core-utils/README.md ================================================ # mirai-core-utils 内部工具类模块。提供二进制兼容而不提供源码兼容,但也不保证破坏性变更不会发生,因此若要使用该工具模块,请自行承担兼容后果。 ================================================ FILE: mirai-core-utils/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UNUSED_VARIABLE") import shadow.relocateImplementation plugins { kotlin("multiplatform") kotlin("plugin.serialization") id("kotlinx-atomicfu") id("me.him188.kotlin-jvm-blocking-bridge") // id("me.him188.maven-central-publish") `maven-publish` } description = "mirai-core utilities" kotlin { explicitApi() apply(plugin = "explicit-api") configureJvmTargetsHierarchical("net.mamoe.mirai.utils") sourceSets { val commonMain by getting { dependencies { api(kotlin("reflect")) api(`kotlinx-serialization-core`) api(`kotlinx-serialization-json`) api(`kotlinx-coroutines-core`) implementation(`kotlinx-serialization-protobuf`) relocateImplementation(`ktor-io_relocated`) } } val commonTest by getting { dependencies { api(yamlkt) implementation(`kotlinx-coroutines-test`) api(`junit-jupiter-api`) } } findByName("jvmBaseMain")?.apply { dependencies { implementation(`jetbrains-annotations`) } } findByName("androidMain")?.apply { dependencies { implementation(`androidx-annotation`) } } findByName("jvmMain")?.apply { } findByName("jvmTest")?.apply { dependencies { implementation(`kotlinx-coroutines-debug`) runtimeOnly(files("build/classes/kotlin/jvm/test")) // classpath is not properly set by IDE } } } } if (tasks.findByName("androidMainClasses") != null) { tasks.register("checkAndroidApiLevel") { doFirst { analyzes.AndroidApiLevelCheck.check( buildDir.resolve("classes/kotlin/android/main"), project.property("mirai.android.target.api.level")!!.toString().toInt(), project ) } group = "verification" this.mustRunAfter("androidMainClasses") } tasks.findByName("androidTest")?.dependsOn("checkAndroidApiLevel") } configureMppPublishing() //mavenCentralPublish { // artifactId = "mirai-core-utils" // githubProject("mamoe", "mirai") // developer("Mamoe Technologies", email = "support@mamoe.net", url = "https://github.com/mamoe") // licenseFromGitHubProject("AGPLv3", "dev") // publishPlatformArtifactsInRootModule = "jvm" //} ================================================ FILE: mirai-core-utils/src/androidInstrumentedTest/kotlin/AndroidUnwrapTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.Test import kotlin.test.assertIs class AndroidUnwrapTest { @Test fun test() { val e = IllegalStateException( OutOfMemoryError().initCause(VerifyError()).apply { addSuppressed(UnsatisfiedLinkError()) } ) val unwrapped = e.unwrap<IllegalStateException>() unwrapped.printStackTrace() assertIs<OutOfMemoryError>(unwrapped) assertIs<VerifyError>(unwrapped.cause) assertIs<UnsatisfiedLinkError>(unwrapped.suppressed.first()) assertIs<StacktraceException>(unwrapped.suppressed[1]) } } ================================================ FILE: mirai-core-utils/src/androidMain/AndroidManifest.xml ================================================ <?xml version="1.0" encoding="utf-8"?> <!-- ~ Copyright 2019-2023 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/dev/LICENSE --> <manifest package="net.mamoe.mirai" xmlns:android="http://schemas.android.com/apk/res/android"> <uses-permission android:name="android.permission.INTERNET"/> </manifest> ================================================ FILE: mirai-core-utils/src/androidMain/kotlin/Actuals.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass package net.mamoe.mirai.utils import android.os.Build import android.util.Base64 import androidx.annotation.RequiresApi public actual fun ByteArray.encodeBase64(): String { return Base64.encodeToString(this, Base64.NO_WRAP) } public actual fun String.decodeBase64(): ByteArray { return Base64.decode(this, Base64.NO_WRAP) } @RequiresApi(Build.VERSION_CODES.N) @PublishedApi internal class StacktraceException(override val message: String?, private val stacktrace: Array<StackTraceElement>) : Exception(message, null, true, false) { override fun fillInStackTrace(): Throwable = this override fun getStackTrace(): Array<StackTraceElement> = stacktrace } @PublishedApi internal class StacktraceExceptionBeforeN( override val message: String?, private val stacktrace: Array<StackTraceElement> ) : Exception(message, null) { override fun fillInStackTrace(): Throwable = this override fun getStackTrace(): Array<StackTraceElement> = stacktrace } public actual inline fun <reified E> Throwable.unwrap(addSuppressed: Boolean): Throwable { if (this !is E) return this return if (addSuppressed) { val e = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { StacktraceException("Unwrapped exception: $this", this.stackTrace) } else { StacktraceExceptionBeforeN("Unwrapped exception: $this", this.stackTrace) } for (throwable in this.suppressed) { e.addSuppressed(throwable) } this.findCause { it !is E } ?.also { it.addSuppressed(e) } ?: this } else { this.findCause { it !is E } ?: this } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Annotations.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.RequiresOptIn.Level.ERROR import kotlin.annotation.AnnotationTarget.* @RequiresOptIn("This can only be used in tests.", level = ERROR) @Target(CLASS, FUNCTION, PROPERTY, CLASS, CONSTRUCTOR, FUNCTION, PROPERTY_GETTER, PROPERTY_SETTER) public annotation class TestOnly /** * 标注 API 弃用记录, 用于在将来提升弃用等级. * * 注意, 在使用时必须使用 named arguments, 如: * ``` * @DeprecatedSinceMirai(warningSince = "2.9") * ``` * * @since 2.9.0-RC */ // https://github.com/mamoe/mirai/issues/1669 @Target( CLASS, PROPERTY, CONSTRUCTOR, FUNCTION, TYPEALIAS ) public annotation class DeprecatedSinceMirai( val warningSince: String = "", val errorSince: String = "", val hiddenSince: String = "", val internalSince: String = "", ) ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Arrays.kt ================================================ /* * 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 kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName public inline fun <A, reified B> Array<A>.mapToArray(block: (element: A) -> B): Array<B> { val result = arrayOfNulls<B>(size) this.forEachIndexed { index, element -> result[index] = block(element) } return result.cast() } public inline fun <A, reified B> Collection<A>.mapToArray(block: (element: A) -> B): Array<B> { val result = arrayOfNulls<B>(size) this.forEachIndexed { index, element -> result[index] = block(element) } return result.cast() } public inline fun <A> Collection<A>.mapToIntArray(block: (element: A) -> Int): IntArray { val result = IntArray(size) this.forEachIndexed { index, element -> result[index] = block(element) } return result } public inline fun <A> Collection<A>.mapToByteArray(block: (element: A) -> Byte): ByteArray { val result = ByteArray(size) this.forEachIndexed { index, element -> result[index] = block(element) } return result } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/AtomicInteger.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.atomicfu.atomic /** * Wraps a [atomic] to allow more complicated usages. */ @TestOnly public class AtomicInteger( value: Int ) { private val delegate = atomic(value) public var value: Int get() = delegate.value set(value) { delegate.value = value } public fun compareAndSet(expect: Int, update: Int): Boolean = delegate.compareAndSet(expect, update) public fun getAndIncrement(): Int = delegate.getAndIncrement() public fun incrementAndGet(): Int = delegate.incrementAndGet() } /** * Wraps a [atomic] to allow more complicated usages. */ @TestOnly public class AtomicBoolean( value: Boolean ) { private val delegate = atomic(value) public var value: Boolean get() = delegate.value set(value) { delegate.value = value } public fun compareAndSet(expect: Boolean, update: Boolean): Boolean = delegate.compareAndSet(expect, update) } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Base64.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils public expect fun ByteArray.encodeBase64(): String public expect fun String.decodeBase64(): ByteArray ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/ByteArrayOp.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("ByteArrayOpKt_common") package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlin.jvm.JvmName public expect val DEFAULT_BUFFER_SIZE: Int public fun String.md5(): ByteArray = toByteArray().md5() public expect fun ByteArray.md5(offset: Int = 0, length: Int = size - offset): ByteArray public fun String.sha1(): ByteArray = toByteArray().sha1() public expect fun ByteArray.sha1(offset: Int = 0, length: Int = size - offset): ByteArray public fun String.sha256(): ByteArray = toByteArray().sha256() public expect fun ByteArray.sha256(offset: Int = 0, length: Int = size - offset): ByteArray /////////////////////////////////////////////////////////////////////////// // How to choose 'inflate', 'inflateAllAvailable', 'InflateInput'? // // On JVM, performance 'inflateAllAvailable' > 'InflateInput' > 'inflate' // On Native, performance 'inflateAllAvailable' = 'InflateInput' > 'inflate' // // So you should use `inflateAllAvailable` if you need have an Input and you need a ByteArray. // If you have a ByteArray and you need an InputStream, use 'InflateInput'. // Use 'inflate' only if the input and desired output type are both ByteArray. // // Specially if you are using `.decodeToString()` after reading a ByteArray, then you'd prefer 'InflateInput' with 'readText()'. /////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////// // Processing ByteArray /////////////////////////////////////////////////////////////////////////// public expect fun ByteArray.gzip(offset: Int = 0, length: Int = size - offset): ByteArray public expect fun ByteArray.ungzip(offset: Int = 0, length: Int = size - offset): ByteArray public expect fun ByteArray.inflate(offset: Int = 0, length: Int = size - offset): ByteArray public expect fun ByteArray.deflate(offset: Int = 0, length: Int = size - offset): ByteArray /////////////////////////////////////////////////////////////////////////// // Consuming input /////////////////////////////////////////////////////////////////////////// /** * Input will be closed. */ public expect fun Input.gzipAllAvailable(): ByteArray /** * Input will be closed. */ public expect fun Input.ungzipAllAvailable(): ByteArray /** * Input will be closed. */ public expect fun Input.inflateAllAvailable(): ByteArray /** * Input will be closed. */ public expect fun Input.deflateAllAvailable(): ByteArray /////////////////////////////////////////////////////////////////////////// // Input adapters. /////////////////////////////////////////////////////////////////////////// //@Suppress("FunctionName") //public expect fun GzipCompressionInput(source: Input): Input // No GzipInputStream for decompression on JVM /** * [source] will be closed on returned [Input.close] */ @Suppress("FunctionName") public expect fun GzipDecompressionInput(source: Input): Input /** * @see GzipDecompressionInput */ public fun Input.gzipDecompressionInput(): Input = GzipDecompressionInput(this) /** * [source] will be closed on returned [Input.close] */ @Suppress("FunctionName") public expect fun InflateInput(source: Input): Input /** * @see InflateInput */ public fun Input.inflateInput(): Input = InflateInput(this) /** * [source] will be closed on returned [Input.close] */ @Suppress("FunctionName") public expect fun DeflateInput(source: Input): Input /** * @see DeflateInput */ public fun Input.deflateInput(): Input = DeflateInput(this) ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/ByteArrayPool.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.pool.* /** * 缓存 [ByteArray] 实例的 [ObjectPool] */ public object ByteArrayPool : DefaultPool<ByteArray>(128) { /** * 每一个 [ByteArray] 的大小 */ public const val BUFFER_SIZE: Int = 4096 override fun produceInstance(): ByteArray = ByteArray(BUFFER_SIZE) override fun clearInstance(instance: ByteArray): ByteArray = instance public fun checkBufferSize(size: Int) { require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } } public fun checkBufferSize(size: Long) { require(size <= BUFFER_SIZE) { "sizePerPacket is too large. Maximum buffer size=$BUFFER_SIZE" } } /** * 请求一个大小至少为 [requestedSize] 的 [ByteArray] 实例. */ // 不要写为扩展函数. 它需要优先于 kotlinx.io 的扩展函数 resolve public inline fun <R> useInstance(requestedSize: Int = 0, block: (ByteArray) -> R): R { if (requestedSize > BUFFER_SIZE) { return ByteArray(requestedSize).run(block) } val instance = borrow() try { return block(instance) } finally { recycle(instance) } } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Bytes.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmOverloads import kotlin.jvm.JvmSynthetic @JvmOverloads public fun generateImageId(md5: ByteArray, format: String? = null): String { return """{${generateUUID(md5)}}.${format ?: "mirai"}""" } @JvmOverloads public fun generateImageIdFromResourceId(resourceId: String, format: String = "mirai"): String? { // friend image id: /f8f1ab55-bf8e-4236-b55e-955848d7069f // friend image id: / f8f1ab55-bf8e-4236-b55e-955848d7069f // friend image id: /0000000000-3666252994-EFF4427CE3D27DB6B1D9A8AB72E7A29C // friend image id: /0000000000-3666252994-EFF4427C E3D2 7DB6 B1D9 A8AB72E7A29C // group image id: {EF42A82D-8DB6-5D0F-4F11-68961D8DA5CB}.png if (resourceId.isNotEmpty()) { if (resourceId[0] == '{') { // {EF42A82D-8DB6-5D0F-4F11-68961D8DA5CB if (resourceId.substringBefore('}', "").length == 37) { return resourceId } } } val md5String = resourceId.substringAfterLast("-").substringAfter("/").takeIf { it.length == 32 } ?: resourceId.replace("-", "").substringAfter('/').takeIf { it.length == 32 } ?: return null return "{${generateUUID(md5String)}}.$format" } public fun generateUUID(md5: ByteArray): String { return "${md5[0, 3]}-${md5[4, 5]}-${md5[6, 7]}-${md5[8, 9]}-${md5[10, 15]}" } public fun generateUUID(md5String: String): String { with(md5String) { check(length == 32) { "it should md5String.length == 32" } return "${substring(0, 8)}-${substring(8, 12)}-${substring(12, 16)}-${substring(16, 20)}-${substring(20, 32)}" } } @JvmSynthetic public operator fun ByteArray.get(rangeStart: Int, rangeEnd: Int): String = buildString { for (it in rangeStart..rangeEnd) { append(this@get[it].fixToString()) } } private fun Byte.fixToString(): String { return when (val b = this.toInt() and 0xff) { in 0..15 -> "0${this.toString(16).uppercase()}" else -> b.toString(16).uppercase() } } public fun ByteArray.toUHexString( separator: String = " ", offset: Int = 0, length: Int = this.size - offset ): String { this.checkOffsetAndLength(offset, length) if (length == 0) { return "" } val lastIndex = offset + length return buildString(length * 2) { this@toUHexString.forEachIndexed { index, it -> if (index in offset until lastIndex) { val ret = it.toUByte().toString(16).uppercase() if (ret.length == 1) append('0') append(ret) if (index < lastIndex - 1) append(separator) } } } } public fun ByteArray.checkOffsetAndLength(offset: Int, length: Int) { require(offset >= 0) { "offset shouldn't be negative: $offset" } require(length >= 0) { "length shouldn't be negative: $length" } require(offset + length <= this.size) { "offset ($offset) + length ($length) > array.size (${this.size})" } } @OptIn(ExperimentalUnsignedTypes::class) public fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, length: Int = this.size - offset): String { if (length == 0) { return "" } val lastIndex = offset + length return buildString(length * 2) { this@toUHexString.forEachIndexed { index, it -> if (index in offset until lastIndex) { val ret = it.toByte().toUByte().toString(16).uppercase() if (ret.length == 1) append('0') append(ret) if (index < lastIndex - 1) append(separator) } } } } public fun ByteArray.toReadPacket( offset: Int = 0, length: Int = this.size - offset, release: (ByteArray) -> Unit = {} ): ByteReadPacket = ByteReadPacket(this, offset = offset, length = length, block = release) public inline fun <R> ByteArray.read(t: ByteReadPacket.() -> R): R { contract { callsInPlace(t, InvocationKind.EXACTLY_ONCE) } return this.toReadPacket().use(t) } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/CheckableResult.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.Serializable import net.mamoe.mirai.utils.Either.Companion.fold import kotlin.jvm.JvmName import kotlin.reflect.KType @Suppress("PropertyName") public interface CheckableResponse { public val _errorCode: Int public val _errorMessage: String? } @Serializable public abstract class CheckableResponseA : CheckableResponse { public abstract val errorCode: Int public abstract val errorMessage: String? final override val _errorCode: Int get() = errorCode final override val _errorMessage: String? get() = errorMessage } @Serializable public abstract class CheckableResponseB : CheckableResponse { public abstract val result: Int @Suppress("SpellCheckingInspection") public abstract val errmsg: String final override val _errorCode: Int get() = result final override val _errorMessage: String get() = errmsg } public class DeserializationFailure( structType: KType, public val json: String, public val exception: Throwable ) : CheckableResponseA() { override val errorCode: Int get() = -1 override val errorMessage: String = "Failed to deserialize '$json' into $structType" public fun createException(): Exception { return IllegalStateException("Error code: $_errorCode, Error message: $_errorMessage", exception) } } /* * `check`: throws exception, or returns succeed value. * `checked`: do `check` and wrap result into an `Either`. */ public fun <T : CheckableResponse> T.check(): T { check(_errorCode == 0) { "Error code: $_errorCode, Error message: $_errorMessage" } return this } public open class FailureResponse( public val errorCode: Int, public val errorMessage: String, ) { public fun createException(): Exception { return IllegalStateException("Error code: $errorCode, Error message: $errorMessage") } } public inline fun <reified T : CheckableResponse> T.checked(): Either<FailureResponse, T> { if (_errorCode == 0) return Either<FailureResponse, T>(this) return Either(FailureResponse(_errorCode, _errorMessage.toString())) } public fun DeserializationFailure.check(): Nothing = throw this.createException() public fun FailureResponse.check(): Nothing = throw this.createException() public inline fun <reified T : CheckableResponse> Either<DeserializationFailure, T>.check(): T { return this.fold(onLeft = { it.check() }, onRight = { it.check() }) } public inline fun <reified T> Either<DeserializationFailure, T>.check(): T { return this.fold(onLeft = { it.check() }, onRight = { it }) } @JvmName("checkedFailureResponseT") public inline fun <reified T> Either<FailureResponse, T>.check(): T { return this.fold(onLeft = { it.check() }, onRight = { it }) } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Clock.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("ClockKt_common") package net.mamoe.mirai.utils import kotlin.jvm.JvmName public interface Clock { public fun currentTimeMillis(): Long public fun currentTimeSeconds(): Long = currentTimeMillis() / 1000 public object SystemDefault : Clock { override fun currentTimeMillis(): Long = net.mamoe.mirai.utils.currentTimeMillis() override fun currentTimeSeconds(): Long = net.mamoe.mirai.utils.currentTimeSeconds() } } public fun Clock.adjusted(diffMillis: Long): Clock = AdjustedClock(this, diffMillis) public class AdjustedClock( private val clock: Clock, private val diffMillis: Long, ) : Clock { override fun currentTimeMillis(): Long = clock.currentTimeMillis() + diffMillis override fun currentTimeSeconds(): Long = (clock.currentTimeMillis() + diffMillis) / 1000 } public expect inline fun measureTimeMillis(block: () -> Unit): Long ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Closeable.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("CloseableKt_common") package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import io.ktor.utils.io.errors.* import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmName public expect interface Closeable { @Throws(IOException::class) public fun close() } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public expect inline fun <C : Closeable, R> C.use(block: (C) -> R): R // overcome overload resolution ambiguity public inline fun <C : Input, R> C.use(block: (C) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return this.asMiraiCloseable().use { block(this) } } // overcome overload resolution ambiguity public inline fun <C : Output, R> C.use(block: (C) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return this.asMiraiCloseable().use { block(this) } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public inline fun <C : Closeable, R> C.withUse(block: C.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return use(block) } // `Input` does not implement `Closeable` in common public inline fun <C : Input, R> C.withUse(block: C.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return use(block) } // `Output` does not implement `Closeable` in common public inline fun <C : Output, R> C.withUse(block: C.() -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return use(block) } public inline fun <I : Closeable, O : Closeable, R> I.withOut(output: O, block: I.(output: O) -> R): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return use { output.use { block(this, output) } } } public expect fun Closeable.asKtorCloseable(): io.ktor.utils.io.core.Closeable public expect fun io.ktor.utils.io.core.Closeable.asMiraiCloseable(): Closeable ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/CollectionDiff.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils public class CollectionDiff<E> { private var save: Collection<E> = listOf() public fun save(collection: Collection<E>) { save = collection.toList() } public fun subtract(collection: Collection<E>): Collection<E> = collection subtract save public fun subtractAndSave(collection: Collection<E>): Collection<E> { return subtract(collection).also { save(collection) } } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Collections.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("CollectionsKt_common") package net.mamoe.mirai.utils import kotlin.jvm.JvmName import kotlin.reflect.KClass @Suppress("FunctionName") public expect fun <K : Any, V> ConcurrentHashMap(): MutableMap<K, V> @Suppress("FunctionName") public expect fun <E> ConcurrentLinkedDeque(): MutableDeque<E> @Suppress("FunctionName") public fun <E> ConcurrentLinkedQueue(): MutableQueue<E> = ConcurrentLinkedDeque() public expect class LinkedList<E> constructor() : MutableList<E> { public fun addLast(element: E) } public expect interface MutableQueue<E> : MutableCollection<E> { /** * Adds the specified element to the collection. * * @return `true` if the element has been added, `false` if the collection does not support duplicates * and the element is already contained in the collection. * @throws IllegalStateException if the queue is full. */ public override fun add(element: E): Boolean /** * Removes and returns the head of the queue, `null` otherwise. */ public fun poll(): E? /** * Adds an element into the queue. * @return `true` if the element has been added, `false` if queue is full. */ public fun offer(element: E): Boolean } public expect interface MutableDeque<E> : MutableQueue<E> { public fun addFirst(element: E) } @Suppress("FunctionName") public expect fun <K : Enum<K>, V> EnumMap(clazz: KClass<K>): MutableMap<K, V> @Suppress("FunctionName") public expect fun <E> ConcurrentSet(): MutableSet<E> @Deprecated("", ReplaceWith("getOrElse(key) { default }")) public fun <K, V : R, R> Map<K, V>.getOrDefault(key: K, default: R): R = getOrElse(key) { default } @Suppress("EXTENSION_SHADOWED_BY_MEMBER") // JDK 1.8 @Deprecated("", ReplaceWith("getOrPut(key) { value }")) public fun <K, V> MutableMap<K, V>.putIfAbsent(key: K, value: V): V = getOrPut(key) { value } /** * Returns a [List] that cannot be cast to [MutableList] to modify it. */ public expect fun <T> List<T>.asImmutable(): List<T> /** * Returns a [Collection] that cannot be cast to [MutableCollection] to modify it. */ public expect fun <T> Collection<T>.asImmutable(): Collection<T> public expect fun <T> Set<T>.asImmutable(): Set<T> ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Conversions.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE", "unused") @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName /* * 类型转换 Utils. * 这些函数为内部函数, 可能会改变 */ /** * Converts a Short to its hex representation in network order (big-endian). */ public fun Short.toByteArray(): ByteArray = with(toInt()) { byteArrayOf( (shr(8) and 0xFF).toByte(), (shr(0) and 0xFF).toByte() ) } /** * Converts an Int to its hex representation in network order (big-endian). */ public fun Int.toByteArray(): ByteArray = byteArrayOf( ushr(24).toByte(), ushr(16).toByte(), ushr(8).toByte(), ushr(0).toByte() ) /** * Converts a Long to its hex representation in network order (big-endian). */ public fun Long.toByteArray(): ByteArray = byteArrayOf( (ushr(56) and 0xFF).toByte(), (ushr(48) and 0xFF).toByte(), (ushr(40) and 0xFF).toByte(), (ushr(32) and 0xFF).toByte(), (ushr(24) and 0xFF).toByte(), (ushr(16) and 0xFF).toByte(), (ushr(8) and 0xFF).toByte(), (ushr(0) and 0xFF).toByte() ) /** * Converts an Int to its hex representation in network order (big-endian). */ public fun Int.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) /** * Converts an UShort to its hex representation in network order (big-endian). */ public fun UShort.toByteArray(): ByteArray = with(toUInt()) { byteArrayOf( (shr(8) and 255u).toByte(), (shr(0) and 255u).toByte() ) } /** * Converts a Short to its hex representation in network order (big-endian). */ public fun Short.toUHexString(separator: String = " "): String = this.toUShort().toUHexString(separator) /** * Converts an UShort to its hex representation in network order (big-endian). */ public fun UShort.toUHexString(separator: String = " "): String = this.toInt().shr(8).toUShort().toUByte().toUHexString() + separator + this.toUByte().toUHexString() /** * Converts an ULong to its hex representation in network order (big-endian). */ public fun ULong.toUHexString(separator: String = " "): String = this.toLong().toUHexString(separator) /** * Converts a Long to its hex representation in network order (big-endian). */ public fun Long.toUHexString(separator: String = " "): String = this.ushr(32).toUInt().toUHexString(separator) + separator + this.toUInt().toUHexString(separator) /** * Converts an UByte to its hex representation. */ public fun UByte.toUHexString(): String = this.toByte().toUHexString() /** * 255u -> 00 00 00 FF */ public fun UInt.toByteArray(): ByteArray = byteArrayOf( (shr(24) and 255u).toByte(), (shr(16) and 255u).toByte(), (shr(8) and 255u).toByte(), (shr(0) and 255u).toByte() ) /** * Converts an UInt to its hex representation in network order (big-endian). */ public fun UInt.toUHexString(separator: String = " "): String = this.toByteArray().toUHexString(separator) /** * 转无符号十六进制表示, 并补充首位 `0`. * 转换结果示例: `FF`, `0E` */ public fun Byte.toUHexString(): String = this.toUByte().fixToUHex() /** * 转无符号十六进制表示, 并补充首位 `0`. */ public fun Byte.fixToUHex(): String = this.toUByte().fixToUHex() /** * 转无符号十六进制表示, 并补充首位 `0`. */ public fun UByte.fixToUHex(): String = if (this.toInt() in 0..15) "0${this.toString(16).uppercase()}" else this.toString(16).uppercase() /** * Converts 4 bytes to an UInt in network order (big-endian). */ public fun ByteArray.toUInt(): UInt = (this[0].toUInt().and(255u) shl 24) .plus(this[1].toUInt().and(255u) shl 16) .plus(this[2].toUInt().and(255u) shl 8) .plus(this[3].toUInt().and(255u) shl 0) /** * Converts 2 bytes to an UShort in network order (big-endian). */ public fun ByteArray.toUShort(): UShort = ((this[0].toUInt().and(255u) shl 8) + (this[1].toUInt().and(255u) shl 0)).toUShort() /** * Converts 4 bytes to an Int in network order (big-endian). */ public fun ByteArray.toInt(offset: Int = 0): Int = this[offset + 0].toInt().and(255).shl(24) .plus(this[offset + 1].toInt().and(255).shl(16)) .plus(this[offset + 2].toInt().and(255).shl(8)) .plus(this[offset + 3].toInt().and(255).shl(0)) /** * Converts 8 bytes to an Long in network order (big-endian). */ public fun ByteArray.toLong(): Long { var rsp: Long = 0 rsp += this[0].toLong().and(255).shl(56) rsp += this[1].toLong().and(255).shl(48) rsp += this[2].toLong().and(255).shl(40) rsp += this[3].toLong().and(255).shl(32) rsp += this[4].toLong().and(255).shl(24) rsp += this[5].toLong().and(255).shl(16) rsp += this[6].toLong().and(255).shl(8) rsp += this[7].toLong().and(255).shl(0) return rsp } /////////////////////////////////////////////////////////////////////////// // hexToBytes /////////////////////////////////////////////////////////////////////////// private val byteStringCandidates = arrayOf('a'..'f', 'A'..'F', '0'..'9', ' '..' ') private const val CHUNK_SPACE = -1 public fun String.hexToBytes(): ByteArray { val array = ByteArray(countHexBytes()) forEachHexChunkIndexed { index, char1, char2 -> array[index] = Byte.parseFromHexChunk(char1, char2) } return array } @OptIn(ExperimentalUnsignedTypes::class) public fun String.hexToUBytes(): UByteArray { val array = UByteArray(countHexBytes()) forEachHexChunkIndexed { index, char1, char2 -> array[index] = Byte.parseFromHexChunk(char1, char2).toUByte() } return array } public fun Byte.Companion.parseFromHexChunk(char1: Char, char2: Char): Byte { return (char1.digitToInt(16).shl(SIZE_BITS / 2) or char2.digitToInt(16)).toByte() } private inline fun String.forEachHexChunkIndexed(block: (index: Int, char1: Char, char2: Char) -> Unit) { var index = 0 forEachHexChunk { char1: Char, char2: Char -> block(index++, char1, char2) } } private inline fun String.forEachHexChunk(block: (char1: Char, char2: Char) -> Unit) { var chunkSize = 0 var char1: Char = 0.toChar() for ((index, c) in this.withIndex()) { // compiler optimization if (c == ' ') { if (chunkSize != 0) { throw IllegalArgumentException("Invalid size of chunk at index ${index.minus(1)}") } continue } if (c in 'a'..'f' || c in 'A'..'F' || c in '0'..'9') { // compiler optimization when (chunkSize) { 0 -> { chunkSize = 1 char1 = c } 1 -> { block(char1, c) chunkSize = 0 } } } else { throw IllegalArgumentException("Invalid char '$c' at index $index") } } if (chunkSize != 0) { throw IllegalArgumentException("Invalid size of chunk at end of string") } } public fun String.countHexBytes(): Int { var chunkSize = 0 var count = 0 for ((index, c) in this.withIndex()) { if (c == ' ') { if (chunkSize != 0) { throw IllegalArgumentException("Invalid size of chunk at index ${index.minus(1)}") } continue } if (c in 'a'..'f' || c in 'A'..'F' || c in '0'..'9') { when (chunkSize) { 0 -> { chunkSize = 1 } 1 -> { count++ chunkSize = 0 } } } else { throw IllegalArgumentException("Invalid char '$c' at index $index") } } if (chunkSize != 0) { throw IllegalArgumentException("Invalid size of chunk at end of string") } return count } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/CoroutineUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("CoroutineUtilsKt_common") package net.mamoe.mirai.utils import kotlinx.coroutines.* import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.withPermit import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.jvm.JvmName public expect suspend inline fun <R> runBIO( noinline block: () -> R, ): R public expect suspend inline fun <T, R> T.runBIO( crossinline block: T.() -> R, ): R public inline fun CoroutineScope.launchWithPermit( semaphore: Semaphore, coroutineContext: CoroutineContext = EmptyCoroutineContext, crossinline block: suspend () -> Unit, ): Job { return launch(coroutineContext) { semaphore.withPermit { block() } } } /** * Creates a child scope of the receiver scope. */ public fun CoroutineScope.childScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, ): CoroutineScope = this.coroutineContext.childScope(coroutineContext) /** * Creates a child scope of the receiver context scope. */ public fun CoroutineContext.childScope( coroutineContext: CoroutineContext = EmptyCoroutineContext, ): CoroutineScope = CoroutineScope(this.childScopeContext(coroutineContext)) /** * Creates a child scope of the receiver context scope. */ public fun CoroutineContext.childScopeContext( coroutineContext: CoroutineContext = EmptyCoroutineContext, ): CoroutineContext { val ctx = this + coroutineContext val job = ctx[Job] ?: return ctx + SupervisorJob() return ctx + SupervisorJob(job) } public inline fun <E : U, U : CoroutineContext.Element> CoroutineContext.getOrElse( key: CoroutineContext.Key<E>, default: () -> U, ): U = this[key] ?: default() public inline fun <E : CoroutineContext.Element> CoroutineContext.addIfAbsent( key: CoroutineContext.Key<E>, default: () -> CoroutineContext.Element, ): CoroutineContext = if (this[key] == null) this + default() else this public inline fun CoroutineContext.addNameIfAbsent( name: () -> String, ): CoroutineContext = addIfAbsent(CoroutineName) { CoroutineName(name()) } public fun CoroutineContext.addNameHierarchically( name: String, ): CoroutineContext = this + CoroutineName(this[CoroutineName]?.name?.plus('.')?.plus(name) ?: name) public fun CoroutineContext.hierarchicalName( name: String, ): CoroutineName = CoroutineName(this[CoroutineName]?.name?.plus('.')?.plus(name) ?: name) public fun CoroutineScope.hierarchicalName( name: String, ): CoroutineName = this.coroutineContext.hierarchicalName(name) public fun CoroutineContext.newCoroutineContextWithSupervisorJob(name: String? = null): CoroutineContext = this + CoroutineName(name ?: "<unnamed>") + SupervisorJob(this[Job]) public fun CoroutineScope.childScope( name: String? = null, context: CoroutineContext = EmptyCoroutineContext ): CoroutineScope = CoroutineScope(this.childScopeContext(name, context)) public fun CoroutineContext.childScope( name: String? = null, context: CoroutineContext = EmptyCoroutineContext ): CoroutineScope = CoroutineScope(this.childScopeContext(name, context)) public fun CoroutineScope.childScopeContext( name: String? = null, context: CoroutineContext = EmptyCoroutineContext ): CoroutineContext = this.coroutineContext.childScopeContext(name, context) public fun CoroutineContext.childScopeContext( name: String? = null, context: CoroutineContext = EmptyCoroutineContext ): CoroutineContext = this.newCoroutineContextWithSupervisorJob(name) + context.let { if (name != null) it + CoroutineName(name) else it } public fun Throwable.unwrapCancellationException(addSuppressed: Boolean = true): Throwable = unwrap<CancellationException>(addSuppressed) /** * For code * ``` * try { * job(new) * } catch (e: Throwable) { * throw IllegalStateException("Exception in attached Job '$name'", e.unwrapCancellationException()) * } * ``` * * Original stacktrace, you mainly see `StateSwitchingException` which is useless to locate the code where real cause `ForceOfflineException` is thrown. * ``` * Exception in thread "DefaultDispatcher-worker-1 @BotInitProcessor.init#7" java.lang.IllegalStateException: Exception in attached Job 'BotInitProcessor.init' * at net.mamoe.mirai.internal.network.handler.state.JobAttachStateObserver$stateChanged0$1.invokeSuspend(JobAttachStateObserver.kt:40) * at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) * at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) * at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) * at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) * at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) * at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) * Caused by: StateSwitchingException(old=StateLoading, new=StateClosed, cause=net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@4abf6d30) * at net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.setStateImpl$mirai_core(NetworkHandlerSupport.kt:258) * at net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler.close(NettyNetworkHandler.kt:404) * ``` * * Real stacktrace (with [unwrapCancellationException]), you directly have `ForceOfflineException`, also you wont lose information of `StateSwitchingException` * ``` * Exception in thread "DefaultDispatcher-worker-2 @BotInitProcessor.init#7" java.lang.IllegalStateException: Exception in attached Job 'BotInitProcessor.init' * at net.mamoe.mirai.internal.network.handler.state.JobAttachStateObserver$stateChanged0$1.invokeSuspend(JobAttachStateObserver.kt:40) * at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) * at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) * at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571) * at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750) * at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678) * at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665) * Caused by: net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@62f65f94 * at net.mamoe.mirai.utils.MiraiUtils__CoroutineUtilsKt.unwrapCancellationException(CoroutineUtils.kt:141) * at net.mamoe.mirai.utils.MiraiUtils.unwrapCancellationException(Unknown Source) * ... 7 more * Suppressed: StateSwitchingException(old=StateLoading, new=StateClosed, cause=net.mamoe.mirai.internal.network.impl.netty.ForceOfflineException: Closed by MessageSvc.PushForceOffline: net.mamoe.mirai.internal.network.protocol.data.jce.RequestPushForceOffline@62f65f94) * at net.mamoe.mirai.internal.network.handler.NetworkHandlerSupport.setStateImpl$mirai_core(NetworkHandlerSupport.kt:258) * at net.mamoe.mirai.internal.network.impl.netty.NettyNetworkHandler.close(NettyNetworkHandler.kt:404) * ``` */ @Suppress("unused") public expect inline fun <reified E> Throwable.unwrap(addSuppressed: Boolean = true): Throwable public val CoroutineContext.coroutineName: String get() = this[CoroutineName]?.name ?: "unnamed" ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Either.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE", "unused") package net.mamoe.mirai.utils import kotlin.jvm.JvmField import kotlin.jvm.JvmInline import kotlin.jvm.JvmName /** * Safe union of two types. */ @JvmInline public value class Either<out L : Any, out R : Any?> private constructor( @PublishedApi @JvmField internal val value: Any?, ) { override fun toString(): String = value.toString() public companion object { /////////////////////////////////////////////////////////////////////////// // constructors /////////////////////////////////////////////////////////////////////////// @PublishedApi internal object CheckedTypes @PublishedApi internal fun <L : Any, R> CheckedTypes.new(value: Any?): Either<L, R> = Either(value) @PublishedApi internal inline fun <reified L, reified R> checkTypes(value: Any?): CheckedTypes { if (!(value is R).xor(value is L)) { throw IllegalArgumentException("value(${getTypeHint(value)}) must be either L(${getTypeHint<L>()}) or R(${getTypeHint<R>()}), and must not be both of them.") } return CheckedTypes } /** * Create a [Either] whose value is [left]. * @throws IllegalArgumentException if [left] satisfies both types [L] and [R]. */ @JvmName("left1") @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public inline operator fun <reified L : Any, reified R> invoke(left: L): Either<L, R> = checkTypes<L, R>(left).new(left) /** * Create a [Either] whose value is [right]. * @throws IllegalArgumentException if [right] satisfies both types [L] and [R]. */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution @JvmName("right1") public inline operator fun <reified L : Any, reified R> invoke(right: R): Either<L, R> = checkTypes<L, R>(right).new(right) /** * Create a [Either] whose value is [left]. * @throws IllegalArgumentException if [left] satisfies both types [L] and [R]. */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") public inline fun <reified L : Any, reified R> left(left: L): Either<L, R> = checkTypes<L, R>(left).new(left) /** * Create a [Either] whose value is [right]. * @throws IllegalArgumentException if [right] satisfies both types [L] and [R]. */ @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") public inline fun <reified L : Any, reified R> right(right: R): Either<L, R> = checkTypes<L, R>(right).new(right) /////////////////////////////////////////////////////////////////////////// // functions /////////////////////////////////////////////////////////////////////////// public inline val <L : Any, reified R> Either<L, R>.rightOrNull: R? get() = value.safeCast() public inline val <L : Any, reified R> Either<L, R>.right: R get() = value.cast() public inline val <reified L : Any, R> Either<L, R>.leftOrNull: L? get() = value.safeCast() public inline val <reified L : Any, R> Either<L, R>.left: L get() = value.cast() public inline val <reified L : Any, R> Either<L, R>.isLeft: Boolean get() = value is L public inline val <L : Any, reified R> Either<L, R>.isRight: Boolean get() = value is R public inline fun <reified L : Any, reified R, T> Either<L, R>.ifLeft(block: (L) -> T): T? = this.leftOrNull?.let(block) public inline fun <L : Any, reified R, T> Either<L, R>.ifRight(block: (R) -> T): T? = this.rightOrNull?.let(block) public inline fun <reified L : Any, reified R> Either<L, R>.onLeft(block: (L) -> Unit): Either<L, R> { this.leftOrNull?.let(block) return this } public inline fun <L : Any, reified R> Either<L, R>.onRight(block: (R) -> Unit): Either<L, R> { this.rightOrNull?.let(block) return this } public inline fun <reified L : Any, reified R, reified T : Any> Either<L, R>.mapLeft(block: (L) -> T): Either<T, R> { @Suppress("RemoveExplicitTypeArguments") return this.fold( onLeft = { invoke<T, R>(block(it)) }, onRight = { invoke<T, R>(it) } ) } public inline fun <reified L : Any, reified R, reified T : Any> Either<L, R>.mapRight(block: (R) -> T): Either<L, T> { @Suppress("RemoveExplicitTypeArguments") return this.fold( onLeft = { invoke<L, T>(it) }, onRight = { invoke<L, T>(it.let(block)) } ) } public inline fun <reified L : Any, reified R, T> Either<L, R>.fold( onLeft: (L) -> T, onRight: (R) -> T, ): T = leftOrNull?.let { onLeft(it) } ?: value.cast<R>().let(onRight) public inline fun <reified T> Either<Throwable, T>.toResult(): Result<T> = this.fold( onLeft = { Result.failure(it) }, onRight = { Result.success(it) } ) /** * Invoke and return [block] if the backing value of this [Either] is null. Returns `this` otherwise. */ public inline fun <reified L : SuperL, reified R : SuperR, SuperL : Any, SuperR> Either<L, R?>.flatMapNull( block: () -> Either<SuperL, SuperR?> ): Either<SuperL, SuperR?> { if (this.leftOrNull == null && this.rightOrNull == null) { return block() } return this } @PublishedApi internal fun getTypeHint(value: Any?): String { return if (value == null) "null" else value::class.run { simpleName ?: toString() } } @PublishedApi internal inline fun <reified T> getTypeHint(): String { return T::class.run { simpleName ?: toString() } } } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/ExceptionCollector.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("ExceptionCollectorKt_common") package net.mamoe.mirai.utils import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmName import kotlin.jvm.Synchronized import kotlin.jvm.Volatile public open class ExceptionCollector { public constructor() public constructor(initial: Throwable?) { collect(initial) } public constructor(vararg initials: Throwable?) { for (initial in initials) { collect(initial) } } protected open fun beforeCollect(throwable: Throwable) { } @Volatile private var last: Throwable? = null private val hashCodes = mutableSetOf<Long>() private val suppressedList = mutableListOf<Throwable>() /** * @return `true` if [e] is new. */ @Synchronized public fun collect(e: Throwable?): Boolean { if (e == null) return false if (!hashCodes.add(hash(e))) return false // filter out duplications // we can also check suppressed exceptions of [e] but actual influence would be slight. beforeCollect(e) this.last?.let { addSuppressed(e, it) } this.last = e return true } protected open fun addSuppressed(receiver: Throwable, e: Throwable) { suppressedList.add(e) // receiver.addSuppressed(e) } public fun collectGet(e: Throwable?): Throwable { this.collect(e) return getLast()!! } /** * Alias to [collect] to be used inside [withExceptionCollector] * @return `true` if [e] is new. */ public fun collectException(e: Throwable?): Boolean = collect(e) /** * Adds [suppressedList] to suppressed exceptions of [last] */ @Synchronized private fun bake() { last?.let { last -> for (suppressed in suppressedList.asReversed()) { last.addSuppressed(suppressed) } } suppressedList.clear() } public fun getLast(): Throwable? { bake() return last } @TerminalOperation // to give it a color for a clearer control flow public fun collectThrow(exception: Throwable): Nothing { collect(exception) throw getLast()!! } @TerminalOperation public fun throwLast(): Nothing { throw getLast() ?: error("Internal error: expected at least one exception collected.") } @DslMarker private annotation class TerminalOperation @TestOnly // very slow public fun asSequence(): Sequence<Throwable> { fun Throwable.itr(): Iterator<Throwable> { return (sequenceOf(this) + this.suppressedExceptions.asSequence() .flatMap { it.itr().asSequence() }).iterator() } val last = getLast() ?: return emptySequence() return Sequence { last.itr() } } @Synchronized public fun dispose() { // help gc this.last = null this.hashCodes.clear() this.suppressedList.clear() } public companion object { public fun compressExceptions(exceptions: Array<Throwable>): Throwable? { return ExceptionCollector(*exceptions).getLast() } public fun compressExceptions(exception: Throwable, vararg exceptions: Throwable): Throwable { return ExceptionCollector(exception, *exceptions).getLast()!! } } } /** * Run with a coverage of `throw`. All thrown exceptions will be caught and rethrown with [ExceptionCollector.collectThrow] */ public inline fun <R> withExceptionCollector(action: ExceptionCollector.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } return ExceptionCollector().run { withExceptionCollector(action).also { dispose() } } } /** * Run with a coverage of `throw`. All thrown exceptions will be caught and rethrown with [ExceptionCollector.collectThrow] */ public inline fun <R> ExceptionCollector.withExceptionCollector(action: ExceptionCollector.() -> R): R { contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } this.run { try { return action() } catch (e: Throwable) { collectThrow(e) } finally { dispose() } } } internal expect fun hash(e: Throwable): Long ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/File.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.* /** * Multiplatform implementation of file operations. */ public expect interface MiraiFile { /** * Name of this file or directory. Can be '.' and '..' if created by */ public val name: String /** * Parent of this file or directory. */ public val parent: MiraiFile? /** * Input path from [create]. */ public val path: String /** * Normalized absolute [path]. */ public val absolutePath: String public val length: Long public val isFile: Boolean public val isDirectory: Boolean public fun exists(): Boolean /** * Resolves a [MiraiFile] representing the [path] based on this [MiraiFile]. Result path is not guaranteed to be normalized. */ public fun resolve(path: String): MiraiFile public fun resolve(file: MiraiFile): MiraiFile public fun createNewFile(): Boolean public fun delete(): Boolean public fun mkdir(): Boolean public fun mkdirs(): Boolean public fun input(): Input public fun output(): Output public companion object { public fun create(path: String): MiraiFile public fun getWorkingDir(): MiraiFile } } public expect fun MiraiFile.deleteRecursively(): Boolean public fun MiraiFile.writeBytes(data: ByteArray) { return output().use { it.writeFully(data) } } public fun MiraiFile.writeText(text: String) { return output().use { it.writeText(text) } } public fun MiraiFile.readText(): String { return input().use { it.readAllText() } } public fun MiraiFile.readBytes(): ByteArray { return input().use { it.readBytes() } } public fun MiraiFile.createFileIfNotExists() { if (!this.exists()) { this.parent?.mkdirs() this.createNewFile() } } public fun MiraiFile.resolveCreateFile(relative: String): MiraiFile = this.resolve(relative).apply { createFileIfNotExists() } public fun MiraiFile.resolveCreateFile(relative: MiraiFile): MiraiFile = this.resolve(relative).apply { createFileIfNotExists() } public fun MiraiFile.resolveMkdir(relative: String): MiraiFile = this.resolve(relative).apply { mkdirs() } public fun MiraiFile.resolveMkdir(relative: MiraiFile): MiraiFile = this.resolve(relative).apply { mkdirs() } public fun MiraiFile.touch(): MiraiFile = apply { parent?.mkdirs() createNewFile() } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Files.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName private class FileType( signature: String, val requiredHeaderSize: Int, val formatName: String ) { val signatureRegex = Regex(signature, RegexOption.IGNORE_CASE) } /** * 文件头和文件类型列表 */ private val FILE_TYPES: List<FileType> = listOf( FileType("^FFD8FF", 3, "jpg"), FileType("^89504E47", 4, "png"), FileType("^47494638", 4, "gif"), FileType("^424D", 3, "bmp"), FileType("^2321414D52", 5, "amr"), FileType("^02232153494C4B5F5633", 10, "silk"), FileType("^([a-zA-Z0-9]{8})66747970", 8, "mp4"), //"49492A00" to "tif", // client doesn't support //"52494646" to "webp", // pc client doesn't support // "57415645" to "wav", // server doesn't support ) /** * 在 [getFileType] 需要的 [ByteArray] 长度 */ public val COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE: Int by lazy { FILE_TYPES.maxOf { it.requiredHeaderSize } } /* startsWith("FFD8") -> "jpg" startsWith("89504E47") -> "png" startsWith("47494638") -> "gif" startsWith("424D") -> "bmp" */ /** * 根据文件头获取文件类型 */ public fun getFileType(fileHeader: ByteArray): String? { val hex = fileHeader.toUHexString( "", length = COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE.coerceAtMost(fileHeader.size) ) FILE_TYPES.forEach { t -> if (hex.contains(t.signatureRegex)) { return t.formatName } } return null } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/HtmlEntity.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils @Suppress("RegExpRedundantEscape") private val STR_TO_CHAR_PATTERN = """\&(\#?[A-Za-z0-9]+?)\;""".toRegex() public fun String.decodeHtmlEscape(): String = replace(STR_TO_CHAR_PATTERN) { match -> STR_TO_CHAR_MAPPINGS[match.value]?.let { return@replace it } val match1 = match.groups[1]!!.value if (match1.length > 1 && match1[0] == '#') { if (match1.length > 2) { if (match1[1] == 'x') { // hex match1.substring(2).toIntOrNull(16)?.let { return@replace it.toChar().toString() } } } match1.substring(1).toIntOrNull()?.let { return@replace it.toChar().toString() } } match.value } public fun String.encodeHtmlEscape(): String = buildString(length) { this@encodeHtmlEscape.forEach { c -> if (needDoHtmlEscape(c)) { append("&#").append(c.code).append(';') } else { append(c) } } } private fun needDoHtmlEscape(c: Char): Boolean { if (c.code < 32) return true // Ascii control codes if (c in "#@!~$%^&*()<>/\\\"'") return true return false } private val STR_TO_CHAR_MAPPINGS: Map<String, String> by lazy { //<editor-fold defaultstate="collapsed" desc="Generated Code"> val result = HashMap<String, String>(223) result["&amp;"] = "\u0026" result["&lt;"] = "\u003c" result["&gt;"] = "\u003e" result["&nbsp;"] = "\u00a0" result["&iexcl;"] = "\u00a1" result["&cent;"] = "\u00a2" result["&pound;"] = "\u00a3" result["&curren;"] = "\u00a4" result["&yen;"] = "\u00a5" result["&brvbar;"] = "\u00a6" result["&sect;"] = "\u00a7" result["&uml;"] = "\u00a8" result["&copy;"] = "\u00a9" result["&ordf;"] = "\u00aa" result["&laquo;"] = "\u00ab" result["&not;"] = "\u00ac" result["&shy;"] = "\u00ad" result["&reg;"] = "\u00ae" result["&macr;"] = "\u00af" result["&deg;"] = "\u00b0" result["&plusmn;"] = "\u00b1" result["&sup2;"] = "\u00b2" result["&sup3;"] = "\u00b3" result["&acute;"] = "\u00b4" result["&micro;"] = "\u00b5" result["&para;"] = "\u00b6" result["&middot;"] = "\u00b7" result["&cedil;"] = "\u00b8" result["&sup1;"] = "\u00b9" result["&ordm;"] = "\u00ba" result["&raquo;"] = "\u00bb" result["&frac14;"] = "\u00bc" result["&frac12;"] = "\u00bd" result["&frac34;"] = "\u00be" result["&iquest;"] = "\u00bf" result["&Agrave;"] = "\u00c0" result["&Aacute;"] = "\u00c1" result["&Acirc;"] = "\u00c2" result["&Atilde;"] = "\u00c3" result["&Auml;"] = "\u00c4" result["&Aring;"] = "\u00c5" result["&AElig;"] = "\u00c6" result["&Ccedil;"] = "\u00c7" result["&Egrave;"] = "\u00c8" result["&Eacute;"] = "\u00c9" result["&Ecirc;"] = "\u00ca" result["&Euml;"] = "\u00cb" result["&Igrave;"] = "\u00cc" result["&Iacute;"] = "\u00cd" result["&Icirc;"] = "\u00ce" result["&Iuml;"] = "\u00cf" result["&ETH;"] = "\u00d0" result["&Ntilde;"] = "\u00d1" result["&Ograve;"] = "\u00d2" result["&Oacute;"] = "\u00d3" result["&Ocirc;"] = "\u00d4" result["&Otilde;"] = "\u00d5" result["&Ouml;"] = "\u00d6" result["&times;"] = "\u00d7" result["&Oslash;"] = "\u00d8" result["&Ugrave;"] = "\u00d9" result["&Uacute;"] = "\u00da" result["&Ucirc;"] = "\u00db" result["&Uuml;"] = "\u00dc" result["&Yacute;"] = "\u00dd" result["&THORN;"] = "\u00de" result["&szlig;"] = "\u00df" result["&agrave;"] = "\u00e0" result["&aacute;"] = "\u00e1" result["&acirc;"] = "\u00e2" result["&atilde;"] = "\u00e3" result["&auml;"] = "\u00e4" result["&aring;"] = "\u00e5" result["&aelig;"] = "\u00e6" result["&ccedil;"] = "\u00e7" result["&egrave;"] = "\u00e8" result["&eacute;"] = "\u00e9" result["&ecirc;"] = "\u00ea" result["&euml;"] = "\u00eb" result["&igrave;"] = "\u00ec" result["&iacute;"] = "\u00ed" result["&icirc;"] = "\u00ee" result["&iuml;"] = "\u00ef" result["&eth;"] = "\u00f0" result["&ntilde;"] = "\u00f1" result["&ograve;"] = "\u00f2" result["&oacute;"] = "\u00f3" result["&ocirc;"] = "\u00f4" result["&otilde;"] = "\u00f5" result["&ouml;"] = "\u00f6" result["&divide;"] = "\u00f7" result["&oslash;"] = "\u00f8" result["&ugrave;"] = "\u00f9" result["&uacute;"] = "\u00fa" result["&ucirc;"] = "\u00fb" result["&uuml;"] = "\u00fc" result["&yacute;"] = "\u00fd" result["&thorn;"] = "\u00fe" result["&yuml;"] = "\u00ff" result["&fnof;"] = "\u0192" result["&Alpha;"] = "\u0391" result["&Beta;"] = "\u0392" result["&Gamma;"] = "\u0393" result["&Delta;"] = "\u0394" result["&Epsilon;"] = "\u0395" result["&Zeta;"] = "\u0396" result["&Eta;"] = "\u0397" result["&Theta;"] = "\u0398" result["&Iota;"] = "\u0399" result["&Kappa;"] = "\u039a" result["&Lambda;"] = "\u039b" result["&Mu;"] = "\u039c" result["&Nu;"] = "\u039d" result["&Xi;"] = "\u039e" result["&Omicron;"] = "\u039f" result["&Pi;"] = "\u03a0" result["&Rho;"] = "\u03a1" result["&Sigma;"] = "\u03a3" result["&Tau;"] = "\u03a4" result["&Upsilon;"] = "\u03a5" result["&Phi;"] = "\u03a6" result["&Chi;"] = "\u03a7" result["&Psi;"] = "\u03a8" result["&Omega;"] = "\u03a9" result["&alpha;"] = "\u03b1" result["&beta;"] = "\u03b2" result["&gamma;"] = "\u03b3" result["&delta;"] = "\u03b4" result["&epsilon;"] = "\u03b5" result["&zeta;"] = "\u03b6" result["&eta;"] = "\u03b7" result["&theta;"] = "\u03b8" result["&iota;"] = "\u03b9" result["&kappa;"] = "\u03ba" result["&lambda;"] = "\u03bb" result["&mu;"] = "\u03bc" result["&nu;"] = "\u03bd" result["&xi;"] = "\u03be" result["&omicron;"] = "\u03bf" result["&pi;"] = "\u03c0" result["&rho;"] = "\u03c1" result["&sigmaf;"] = "\u03c2" result["&sigma;"] = "\u03c3" result["&tau;"] = "\u03c4" result["&upsilon;"] = "\u03c5" result["&phi;"] = "\u03c6" result["&chi;"] = "\u03c7" result["&psi;"] = "\u03c8" result["&omega;"] = "\u03c9" result["&thetasym;"] = "\u03d1" result["&upsih;"] = "\u03d2" result["&piv;"] = "\u03d6" result["&bull;"] = "\u2022" result["&hellip;"] = "\u2026" result["&prime;"] = "\u2032" result["&Prime;"] = "\u2033" result["&oline;"] = "\u203e" result["&frasl;"] = "\u2044" result["&weierp;"] = "\u2118" result["&image;"] = "\u2111" result["&real;"] = "\u211c" result["&trade;"] = "\u2122" result["&alefsym;"] = "\u2135" result["&larr;"] = "\u2190" result["&uarr;"] = "\u2191" result["&rarr;"] = "\u2192" result["&darr;"] = "\u2193" result["&harr;"] = "\u2194" result["&crarr;"] = "\u21b5" result["&lArr;"] = "\u21d0" result["&uArr;"] = "\u21d1" result["&rArr;"] = "\u21d2" result["&dArr;"] = "\u21d3" result["&hArr;"] = "\u21d4" result["&forall;"] = "\u2200" result["&part;"] = "\u2202" result["&exist;"] = "\u2203" result["&empty;"] = "\u2205" result["&nabla;"] = "\u2207" result["&isin;"] = "\u2208" result["&notin;"] = "\u2209" result["&ni;"] = "\u220b" result["&prod;"] = "\u220f" result["&sum;"] = "\u2211" result["&minus;"] = "\u2212" result["&lowast;"] = "\u2217" result["&radic;"] = "\u221a" result["&prop;"] = "\u221d" result["&infin;"] = "\u221e" result["&ang;"] = "\u2220" result["&and;"] = "\u2227" result["&or;"] = "\u2228" result["&cap;"] = "\u2229" result["&cup;"] = "\u222a" result["&int;"] = "\u222b" result["&there4;"] = "\u2234" result["&sim;"] = "\u223c" result["&cong;"] = "\u2245" result["&asymp;"] = "\u2248" result["&ne;"] = "\u2260" result["&equiv;"] = "\u2261" result["&le;"] = "\u2264" result["&ge;"] = "\u2265" result["&sub;"] = "\u2282" result["&sup;"] = "\u2283" result["&nsub;"] = "\u2284" result["&sube;"] = "\u2286" result["&supe;"] = "\u2287" result["&oplus;"] = "\u2295" result["&otimes;"] = "\u2297" result["&perp;"] = "\u22a5" result["&sdot;"] = "\u22c5" result["&lceil;"] = "\u2308" result["&rceil;"] = "\u2309" result["&lfloor;"] = "\u230a" result["&rfloor;"] = "\u230b" result["&lang;"] = "\u2329" result["&rang;"] = "\u232a" result["&loz;"] = "\u25ca" result["&spades;"] = "\u2660" result["&clubs;"] = "\u2663" result["&hearts;"] = "\u2665" result["&diams;"] = "\u2666" //</editor-fold> result } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/IO.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import io.ktor.utils.io.* import io.ktor.utils.io.charsets.* import io.ktor.utils.io.core.* import io.ktor.utils.io.core.internal.* import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic public val EMPTY_BYTE_ARRAY: ByteArray = ByteArray(0) public val DECRYPTER_16_ZERO: ByteArray = ByteArray(16) public val KEY_16_ZEROS: ByteArray = ByteArray(16) /** * It's caller's responsibility to close the input */ public inline fun <R> ByteReadPacket.useBytes( n: Int = remaining.toIntOrFail(), block: (data: ByteArray, length: Int) -> R ): R = ByteArrayPool.useInstance(n) { this.readFully(it, 0, n) block(it, n) } /** * It's caller's responsibility to close the input */ public inline fun <R> Input.useBytes( n: Int? = null, block: (data: ByteArray, length: Int) -> R ): R { return when { n != null -> { this.readBytes(n).let { block(it, it.size) } } this is ByteReadPacket -> { val count = this.remaining.toIntOrFail() ByteArrayPool.useInstance(count) { this.readFully(it, 0, count) block(it, count) } } else -> { this.readBytes().let { block(it, it.size) } } } } public fun Long.toIntOrFail(): Int { if (this >= Int.MAX_VALUE || this <= Int.MIN_VALUE) { throw IllegalArgumentException("$this does not fit in Int range") } return this.toInt() } public fun ByteReadPacket.readPacketExact( n: Int = remaining.toIntOrFail() ): ByteReadPacket = this.readBytes(n).toReadPacket() public fun Input.readAllText(): String = Charsets.UTF_8.newDecoder().decode(this) public fun Input.readString(length: Int, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length), charset = charset) // stdlib public fun Input.readString(length: Long, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) public fun Input.readString(length: Short, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) @JvmSynthetic public fun Input.readString(length: UShort, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) public fun Input.readString(length: Byte, charset: Charset = Charsets.UTF_8): String = String(this.readBytes(length.toInt()), charset = charset) public fun Input.readUShortLVString(): String = String(this.readUShortLVByteArray()) public fun Input.readUShortLVByteArray(): ByteArray = this.readBytes(this.readShort().toUShort().toInt()) public suspend fun Input.copyTo(output: ByteWriteChannel): Long { val buffer = ChunkBuffer.Pool.borrow() var copied = 0L try { do { buffer.resetForWrite() val rc = readAvailable(buffer) if (rc == -1) break copied += rc output.writeFully(buffer) } while (true) return copied } finally { buffer.release(ChunkBuffer.Pool) } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/JsonStruct.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.json.Json import kotlin.reflect.typeOf public interface JsonStruct @PublishedApi internal val defaultJson: Json = Json { isLenient = true ignoreUnknownKeys = true } public fun <T : JsonStruct> String.loadAs(deserializer: DeserializationStrategy<T>, json: Json = defaultJson): T { return json.decodeFromString(deserializer, this) } @OptIn(ExperimentalStdlibApi::class) public inline fun <reified T> String.loadSafelyAs( deserializer: DeserializationStrategy<T>, json: Json = defaultJson ): Either<DeserializationFailure, T> where T : JsonStruct { return try { Either<DeserializationFailure, T>(json.decodeFromString(deserializer, this)) } catch (e: Throwable) { // typeOf is used in ktor and coroutines so Kotlin will absolutely provide ABI guarantee for it. Either(DeserializationFailure(typeOf<T>(), this, e)) } } public fun <T : JsonStruct> T.toJsonString(serializer: SerializationStrategy<T>, json: Json = defaultJson): String = json.encodeToString(serializer, this) ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/LateinitMutableProperty.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.atomicfu.atomic import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty private val UNINITIALIZED: Any? = Symbol("UNINITIALIZED") /** * Creates a lazily initialized, atomic, mutable property. * * [initializer] will be called at most once for most of the time, but not always. * Multiple invocations may happen if property getter is called by multiple coroutines in single thread (implementation use reentrant lock). * Hence, you must not trust [initializer] to be called only once. * * If property setter is executed before any execution of getter, [initializer] will not be called. * While [initializer] is running, i.e. still calculating the value to set to the property, * calling property setter will *outdo* the initializer. That is, the setter always prevails on competition with [initializer]. */ public fun <T> lateinitMutableProperty(initializer: () -> T): ReadWriteProperty<Any?, T> = LateinitMutableProperty(initializer) private class LateinitMutableProperty<T>( initializer: () -> T ) : ReadWriteProperty<Any?, T>, SynchronizedObject() { private val value = atomic(UNINITIALIZED) private var initializer: (() -> T)? = initializer @Suppress("UNCHECKED_CAST") override fun getValue(thisRef: Any?, property: KProperty<*>): T { return when (val v = this.value.value) { UNINITIALIZED -> synchronized(this) { val initializer = initializer if (initializer != null && this.value.value === UNINITIALIZED) { val value = initializer() this.initializer = null // not used anymore, help gc this.value.compareAndSet(UNINITIALIZED, value) // setValue prevails this.value.value.let { check(it !== UNINITIALIZED) return it as T } } else this.value.value as T } else -> v as T } } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { this.value.value = value } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Numbers.kt ================================================ /* * 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 kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName public fun Int.toLongUnsigned(): Long = this.toLong().and(0xFFFF_FFFF) public fun Long.toLongUnsigned(): Long = this // for native unstable types public fun Short.toIntUnsigned(): Int = this.toUShort().toInt() public fun Byte.toIntUnsigned(): Int = toInt() and 0xFF public fun Int.concatAsLong(i2: Int): Long = this.toLongUnsigned().shl(Int.SIZE_BITS) or i2.toLongUnsigned() ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/RandomUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.math.absoluteValue import kotlin.random.Random import kotlin.random.nextInt /** * 生成长度为 [length], 元素为随机 `0..255` 的 [ByteArray] */ public fun getRandomByteArray(length: Int, random: Random = Random): ByteArray = ByteArray(length) { random.nextInt(0..255).toByte() } /** * 随机生成一个正整数 */ public fun getRandomUnsignedInt(): Int = Random.nextInt().absoluteValue /** * 随机生成长度为 [length] 的 [String]. */ public fun getRandomString(length: Int, random: Random = Random): String = getRandomString(length, *defaultRanges, random = random) private val defaultRanges: Array<CharRange> = arrayOf('a'..'z', 'A'..'Z', '0'..'9') private val intCharRanges: Array<CharRange> = arrayOf('0'..'9') /** * 根据所给 [charRange] 随机生成长度为 [length] 的 [String]. */ public fun getRandomString(length: Int, charRange: CharRange, random: Random = Random): String = CharArray(length) { charRange.random(random) }.concatToString() /** * 根据所给 [charRanges] 随机生成长度为 [length] 的 [String]. */ public fun getRandomString(length: Int, vararg charRanges: CharRange, random: Random = Random): String = CharArray(length) { charRanges[random.nextInt(0..charRanges.lastIndex)].random(random) }.concatToString() public fun getRandomIntString(length: Int, random: Random = Random): String = getRandomString(length, charRanges = intCharRanges, random = random) ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Resources.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.atomicfu.atomic public class ResourceAccessLock { public companion object { public const val LOCKED: Int = -2 public const val UNINITIALIZED: Int = -1 public const val INITIALIZED: Int = 0 } /* * status > 0 -> Number of holders using resource */ private val status = atomic(-1) /** * ``` * if (res.lock.tryToDispose()) { * res.internal.close() * } * ``` */ public fun tryDispose(): Boolean { return status.compareAndSet(0, -1) } /** * ``` * if (res.lock.tryInitialize()) { * res.internalRes = download() * } * ``` */ public fun tryInitialize(): Boolean { return status.compareAndSet(-1, 0) } public fun tryUse(): Boolean { val c = status while (true) { val v = c.value if (v < 0) return false if (c.compareAndSet(v, v + 1)) return true } } public fun lockIfNotUsing(): Boolean { val count = this.status while (true) { val value = count.value if (value != 0) return false if (count.compareAndSet(0, -2)) return true } } public fun release() { val count = this.status while (true) { val value = count.value if (value < 1) throw IllegalStateException("Current resource not in using") if (count.compareAndSet(value, value - 1)) return } } public fun unlock() { status.compareAndSet(LOCKED, INITIALIZED) } public fun setInitialized() { status.value = INITIALIZED } public fun setLocked() { status.value = LOCKED } public fun setDisposed() { setUninitialized() } public fun setUninitialized() { status.value = UNINITIALIZED } public fun currentStatus(): Int = status.value override fun toString(): String { return when (val status = status.value) { 0 -> "ResourceAccessLock(INITIALIZED)" -1 -> "ResourceAccessLock(UNINITIALIZED)" -2 -> "ResourceAccessLock(LOCKED)" else -> "ResourceAccessLock($status)" } } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/ResultExtensions.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName import kotlin.reflect.KClass @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly @kotlin.internal.LowPriorityInOverloadResolution public inline fun <R, T : R> Result<T>.recoverCatchingSuppressed(transform: (exception: Throwable) -> R): Result<R> { return when (val exception = exceptionOrNull()) { null -> this else -> { try { Result.success(transform(exception)) } catch (e: Throwable) { e.addSuppressed(exception) Result.failure(e) } } } } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly @kotlin.internal.LowPriorityInOverloadResolution public inline fun <R> retryCatching( n: Int, except: KClass<out Throwable>? = null, block: (count: Int, lastException: Throwable?) -> R, ): Result<R> { require(n >= 0) { "param n for retryCatching must not be negative" } var exception: Throwable? = null repeat(n) { try { return Result.success(block(it, exception)) } catch (e: Throwable) { if (except?.isInstance(e) == true) { return Result.failure(e) } exception?.addSuppressed(e) exception = e } } return Result.failure(exception!!) } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly @kotlin.internal.LowPriorityInOverloadResolution public inline fun <R> retryCatchingExceptions( n: Int, except: KClass<out Exception>? = null, block: (count: Int, lastException: Throwable?) -> R, ): Result<R> { require(n >= 0) { "param n for retryCatching must not be negative" } var exception: Throwable? = null repeat(n) { try { return Result.success(block(it, exception)) } catch (e: Exception) { if (except?.isInstance(e) == true) { return Result.failure(e) } exception?.addSuppressed(e) exception = e } } return Result.failure(exception!!) } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly public inline fun <R> retryCatching( n: Int, except: KClass<out Throwable>? = null, block: () -> R, ): Result<R> { require(n >= 0) { "param n for retryCatching must not be negative" } var exception: Throwable? = null repeat(n) { try { return Result.success(block()) } catch (e: Throwable) { if (except?.isInstance(e) == true) { return Result.failure(e) } exception?.addSuppressed(e) exception = e } } return Result.failure(exception!!) } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly public inline fun <R> retryCatchingExceptions( n: Int, except: KClass<out Exception>? = null, block: () -> R, ): Result<R> { require(n >= 0) { "param n for retryCatching must not be negative" } var exception: Throwable? = null repeat(n) { try { return Result.success(block()) } catch (e: Exception) { if (except?.isInstance(e) == true) { return Result.failure(e) } exception?.addSuppressed(e) exception = e } } return Result.failure(exception!!) } @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "RESULT_CLASS_IN_RETURN_TYPE") @kotlin.internal.InlineOnly public inline fun <R> runCatchingExceptions(block: () -> R): Result<R> { return try { Result.success(block()) } catch (e: Exception) { Result.failure(e) } } public inline fun <R> Result<R>.mapFailure( block: (Throwable) -> Throwable, ): Result<R> = onFailure { return Result.failure(block(it)) } public inline fun <R> Result<R>.onSuccessCatching(block: () -> Unit): Result<R> { if (isSuccess) { runCatching(block).onFailure { return@onSuccessCatching Result.failure(it) } } return this } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/SecretsProtection.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.KSerializer import kotlinx.serialization.Serializable import kotlin.jvm.JvmInline import kotlin.jvm.JvmStatic /** * 核心数据保护器 * * ### Why * * 有时候可能会发生 `OutOfMemoryError`, 如果存在 `-XX:+HeapDumpOnOutOfMemoryError`, 则 JVM 会生成一份系统内存打印以供 debug. * 该报告包含全部内存信息, 包括各种数据, 核心数据以及, 机密数据 (如密码) * * 该内存报告唯一没有包含的数据就是 Native层数据, 包括且不限于 * * - `sun.misc.Unsafe.allocate()` * - `java.nio.ByteBuffer.allocateDirect()` (Named `DirectByteBuffer`) * - C/C++ (或其他语言) 的数据 * * *试验数据来源 `openjdk version "17" 2021-09-14, 64-Bit Server VM (build 17+35-2724, mixed mode, sharing)`* * * ### How it works * * 因为 Heap Dump 不存在 `DirectByteBuffer` 的实际数据, 所以可以通过该类隐藏关键数据. 等需要的时候在读取出来. * 因为数据并没有直接存在于某个类字段中, 缺少数据关联, 也很难分析相关数据是什么数据 */ @Suppress("NOTHING_TO_INLINE", "UsePropertyAccessSyntax") //@MiraiExperimentalApi public object SecretsProtection { @JvmInline @Serializable(EscapedStringSerializer::class) public value class EscapedString( public val data: Any, ) { public val asString: String get() = SecretsProtectionPlatform.impl_asString(data) public constructor(data: ByteArray) : this(escape(data)) public constructor(data: String) : this(escape(data.encodeToByteArray())) } @JvmInline @Serializable(EscapedByteBufferSerializer::class) public value class EscapedByteBuffer( public val data: Any, ) { public val size: Int get() = SecretsProtectionPlatform.impl_getSize(data) public val asByteArray: ByteArray get() = SecretsProtectionPlatform.impl_asByteArray(data) public constructor(data: ByteArray) : this(escape(data)) } @JvmStatic public fun escape(data: ByteArray): Any { return SecretsProtectionPlatform.escape(data) } public object EscapedStringSerializer : KSerializer<EscapedString> by SecretsProtectionPlatform.EscapedStringSerializer public object EscapedByteBufferSerializer : KSerializer<EscapedByteBuffer> by SecretsProtectionPlatform.EscapedByteBufferSerializer } internal expect object SecretsProtectionPlatform { fun impl_asString(data: Any): String fun impl_asByteArray(data: Any): ByteArray fun impl_getSize(data: Any): Int fun escape(data: ByteArray): Any object EscapedStringSerializer : KSerializer<SecretsProtection.EscapedString> object EscapedByteBufferSerializer : KSerializer<SecretsProtection.EscapedByteBuffer> } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Serialization.kt ================================================ /* * 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:JvmName("SerializationKt_common") package net.mamoe.mirai.utils import kotlinx.serialization.* import kotlinx.serialization.descriptors.* import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder import kotlin.jvm.JvmName public fun SerialDescriptor.copy(newName: String): SerialDescriptor = buildClassSerialDescriptor(newName) { takeElementsFrom(this@copy) } @OptIn(ExperimentalSerializationApi::class) // bad but there is no other solution public fun ClassSerialDescriptorBuilder.takeElementsFrom(descriptor: SerialDescriptor) { with(descriptor) { repeat(descriptor.elementsCount) { index -> element( elementName = getElementName(index), descriptor = getElementDescriptor(index), annotations = getElementAnnotations(index), isOptional = isElementOptional(index), ) } } } public inline fun <T, R> KSerializer<T>.map( resultantDescriptor: SerialDescriptor, crossinline deserialize: T.(T) -> R, crossinline serialize: R.(R) -> T, ): KSerializer<R> { return object : KSerializer<R> { override val descriptor: SerialDescriptor get() = resultantDescriptor override fun deserialize(decoder: Decoder): R = this@map.deserialize(decoder).let { deserialize(it, it) } override fun serialize(encoder: Encoder, value: R) = serialize(encoder, value.let { serialize(it, it) }) } } @OptIn(ExperimentalSerializationApi::class) public inline fun <T, R> KSerializer<T>.mapPrimitive( serialName: String, crossinline deserialize: (T) -> R, crossinline serialize: R.(R) -> T, ): KSerializer<R> { val kind = this@mapPrimitive.descriptor.kind check(kind is PrimitiveKind) { "kind must be PrimitiveKind but found $kind" } return object : KSerializer<R> { override fun deserialize(decoder: Decoder): R = this@mapPrimitive.deserialize(decoder).let(deserialize) override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(serialName, kind) override fun serialize(encoder: Encoder, value: R) = this@mapPrimitive.serialize(encoder, value.let { serialize(it, it) }) } } public fun <T> MiraiFile.loadNotBlankAs( serializer: DeserializationStrategy<T>, stringFormat: StringFormat, ): T? { if (!this.exists() || this.length == 0L) { return null } return try { stringFormat.decodeFromString(serializer, this.readText()) } catch (e: Throwable) { //broken file e.printStackTrace() null } } public fun <T> MiraiFile.loadNotBlankAs( serializer: DeserializationStrategy<T>, binaryFormat: BinaryFormat, ): T? { if (!this.exists() || this.length == 0L) { return null } return try { binaryFormat.decodeFromByteArray(serializer, this.readBytes()) } catch (e: Throwable) { //broken file e.printStackTrace() null } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Services.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmName("ServicesKt_common") package net.mamoe.mirai.utils import kotlinx.atomicfu.locks.reentrantLock import kotlinx.atomicfu.locks.withLock import kotlin.jvm.JvmName import kotlin.reflect.KClass public object Services { private val lock = reentrantLock() public fun <T : Any> qualifiedNameOrFail(clazz: KClass<out T>): String = clazz.qualifiedName ?: error("Could not find qualifiedName for $clazz") internal class Implementation( val implementationClass: String, val instance: Lazy<Any> ) private val registered: MutableMap<String, MutableList<Implementation>> = mutableMapOf() private val overrided: MutableMap<String, Implementation> = mutableMapOf() @Suppress("UNCHECKED_CAST") public fun <T : Any> getOverrideOrNull(clazz: KClass<out T>): T? { lock.withLock { return overrided[qualifiedNameOrFail(clazz)]?.instance?.value as T? } } public fun registerAsOverride(baseClass: String, implementationClass: String, implementation: () -> Any) { lock.withLock { overrided[baseClass] = Implementation(implementationClass, lazy(implementation)) } } public fun register(baseClass: String, implementationClass: String, implementation: () -> Any) { lock.withLock { registered.getOrPut(baseClass, ::mutableListOf) .add(Implementation(implementationClass, lazy(implementation))) } } public fun firstImplementationOrNull(baseClass: String): Any? { lock.withLock { overrided[baseClass]?.let { return it.instance.value } return registered[baseClass]?.firstOrNull()?.instance?.value } } public fun implementations(baseClass: String): Sequence<Lazy<Any>>? { lock.withLock { val implementations = registered[baseClass] val forced = overrided[baseClass] if (forced == null && implementations == null) return null val implementationsSnapshot = implementations?.toList().orEmpty() return sequence { if (forced != null) yield(forced.instance) implementationsSnapshot.forEach { yield(it.instance) } } } } internal fun implementationsDirectly(baseClass: String) = lock.withLock { registered[baseClass]?.toList().orEmpty() } public fun print(): String { lock.withLock { return registered.entries.joinToString { "${it.key}:${it.value}" } } } } public expect fun <T : Any> loadServiceOrNull(clazz: KClass<out T>, fallbackImplementation: String? = null): T? public expect fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: String? = null): T public expect fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T> public inline fun <reified T : Any> loadService(fallbackImplementation: String? = null): T = loadService(T::class, fallbackImplementation) // do not inline: T will be inferred to returning type of `fallbackImplementation` public fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: () -> T): T = loadServiceOrNull(clazz) ?: fallbackImplementation() ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/SizedCache.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.atomicfu.locks.ReentrantLock import kotlinx.atomicfu.locks.reentrantLock import kotlinx.atomicfu.locks.withLock @Suppress("unused", "UNCHECKED_CAST") public class SizedCache<T>(size: Int) : Iterable<T> { public val lock: ReentrantLock = reentrantLock() public val data: Array<Any?> = arrayOfNulls(size) public var filled: Boolean = false public var idx: Int = 0 public fun emit(v: T) { lock.withLock { data[idx] = v idx++ if (idx == data.size) { filled = true idx = 0 } } } override fun iterator(): Iterator<T> { if (filled) { return object : Iterator<T> { private var idx0: Int = idx private var floopend = false override fun hasNext(): Boolean = !floopend || idx0 != idx override fun next(): T { val rsp = data[idx0] as T idx0++ if (idx0 == data.size) { idx0 = 0 floopend = true } return rsp } } } return object : Iterator<T> { private var idx0: Int = 0 override fun hasNext(): Boolean = idx0 < idx override fun next(): T = data[idx0].also { idx0++ } as T } } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/StandardUtils.kt ================================================ /* * 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:JvmName("StandardUtilsKt_common") package net.mamoe.mirai.utils import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmName public inline fun <reified T> Any?.cast(): T { contract { returns() implies (this@cast is T) } return this as T } /** * Casts T to U where U : T. Safer than [cast] -- [castUp] only allow casting to upper types. */ public inline fun <reified U : T, T> T.castUp(): U { contract { returns() implies (this@castUp is U) } return this as U } public inline fun <reified T> Any?.safeCast(): T? { contract { returnsNotNull() implies (this@safeCast is T) } return this as? T } public inline fun <reified T> Any?.castOrNull(): T? { contract { returnsNotNull() implies (this@castOrNull is T) } return this as? T } @Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") public inline fun <T> Any?.uncheckedCast(): T = this as T public inline fun <reified R> Iterable<*>.firstIsInstanceOrNull(): R? { for (it in this) { if (it is R) return it } return null } public inline fun <E> MutableList<E>.replaceAllKotlin(operator: (E) -> E) { val li: MutableListIterator<E> = this.listIterator() while (li.hasNext()) { li.set(operator(li.next())) } } public fun Throwable.getRootCause(maxDepth: Int = 20): Throwable { var depth = 0 var rootCause: Throwable? = this while (rootCause?.cause != null) { rootCause = rootCause.cause if (depth++ >= maxDepth) break } return rootCause ?: this } /** * Use [findCause] instead for better performance. */ @TestOnly public fun Throwable.causes(maxDepth: Int = 20): Sequence<Throwable> = sequence { var depth = 0 var rootCause: Throwable? = this@causes while (rootCause?.cause != null) { yield(rootCause.cause!!) rootCause = rootCause.cause if (depth++ >= maxDepth) break } } public inline fun Throwable.findCause(maxDepth: Int = 20, filter: (Throwable) -> Boolean): Throwable? { var depth = 0 var curr: Throwable? = this while (true) { if (curr == null) return null val cause = curr.cause ?: return null if (filter(cause)) return cause if (curr.cause === curr) return null // circular reference curr = curr.cause if (depth++ >= maxDepth) return null } } /** * Run [block] and do [finally], catching exception thrown in [finally] and add it to the exception from [block]. */ public inline fun <R> trySafely( block: () -> R, finally: () -> Unit, ): R { // contract { // callsInPlace(block, InvocationKind.EXACTLY_ONCE) // callsInPlace(finally, InvocationKind.EXACTLY_ONCE) // } var eInBlock: Throwable? = null try { return block() } catch (e: Throwable) { eInBlock = e } finally { try { finally() } catch (eInFinally: Throwable) { if (eInBlock != null) { eInBlock.addSuppressed(eInFinally) throw eInBlock } else throw eInFinally } if (eInBlock != null) throw eInBlock } throw AssertionError() } public inline fun Throwable.findCauseOrSelf(maxDepth: Int = 20, filter: (Throwable) -> Boolean): Throwable = findCause(maxDepth, filter) ?: this public fun String.capitalize(): String { return replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() } } public fun String.truncated(length: Int, truncated: String = "..."): String { return if (this.length > length) { this.take(10) + truncated } else this } /** * Similar to [run] bot with [Unit] return type. * * You should not reference to [T] directly in the [block]. */ // can convert to contextual receiver in the future, or there might be a stdlib function which we can delegate to. public inline fun <T> T.context(block: T.() -> Unit) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return block() } public fun assertUnreachable(hint: String? = null): Nothing = error("This clause should not be reached. " + hint.orEmpty()) public fun isSameClass(object1: Any?, object2: Any?): Boolean { if (object1 == null || object2 == null) { return object1 == null && object2 == null } return isSameClassPlatform(object1, object2) } internal expect fun isSameClassPlatform(object1: Any, object2: Any): Boolean public inline fun <reified T> isSameType(thisObject: T, other: Any?): Boolean { contract { returns(true) implies (other is T) } if (other == null) return false if (other !is T) return false return isSameClass(thisObject, other) } public expect fun availableProcessors(): Int /** * Localhost 解析 */ public expect fun localIpAddress(): String ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Strings.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName // region Emoji pattern. <Licenced under the MIT LICENSE> // // https://github.com/mathiasbynens/emoji-test-regex-pattern // https://github.com/mathiasbynens/emoji-test-regex-pattern/blob/main/dist/latest/java.txt // @Suppress("RegExpSingleCharAlternation", "RegExpRedundantEscape") private val EMOJI_PATTERN: Regex? = runCatching { """\x{1F3F4}\x{E0067}\x{E0062}(?:\x{E0077}\x{E006C}\x{E0073}|\x{E0073}\x{E0063}\x{E0074}|\x{E0065}\x{E006E}\x{E0067})\x{E007F}|(?:\x{1F9D1}\x{1F3FF}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FF}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FF}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FE}]|(?:\x{1F9D1}\x{1F3FE}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FE}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FE}\x{200D}\x{1FAF2})[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FD}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FD}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FD}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FC}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FC}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FC}\x{200D}\x{1FAF2})[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|(?:\x{1F9D1}\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F9D1}|\x{1F469}\x{1F3FB}\x{200D}\x{1F91D}\x{200D}[\x{1F468}\x{1F469}]|\x{1FAF1}\x{1F3FB}\x{200D}\x{1FAF2})[\x{1F3FC}-\x{1F3FF}]|\x{1F468}(?:\x{1F3FB}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{1F91D}\x{200D}\x{1F468}[\x{1F3FC}-\x{1F3FF}]|[\x{2695}\x{2696}\x{2708}]\x{FE0F}|[\x{2695}\x{2696}\x{2708}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]))?|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}])|\x{200D}(?:\x{1F48B}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FF}]|\x{1F468}[\x{1F3FB}-\x{1F3FF}]))|\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D})?|\x{200D}(?:\x{1F48B}\x{200D})?)\x{1F468}|[\x{1F468}\x{1F469}]\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FE}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FE}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}-\x{1F3FD}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FD}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FC}\x{1F3FE}\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FC}\x{200D}(?:\x{1F91D}\x{200D}\x{1F468}[\x{1F3FB}\x{1F3FD}-\x{1F3FF}]|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])\x{FE0F}|\x{200D}(?:[\x{1F468}\x{1F469}]\x{200D}[\x{1F466}\x{1F467}]|[\x{1F466}\x{1F467}])|\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{200D}[\x{2695}\x{2696}\x{2708}])?|(?:\x{1F469}(?:\x{1F3FB}\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F3FC}-\x{1F3FF}]\x{200D}\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])))|\x{1F9D1}[\x{1F3FB}-\x{1F3FF}]\x{200D}\x{1F91D}\x{200D}\x{1F9D1})[\x{1F3FB}-\x{1F3FF}]|\x{1F469}\x{200D}\x{1F469}\x{200D}(?:\x{1F466}\x{200D}\x{1F466}|\x{1F467}\x{200D}[\x{1F466}\x{1F467}])|\x{1F469}(?:\x{200D}(?:\x{2764}(?:\x{FE0F}\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}])|\x{200D}(?:\x{1F48B}\x{200D}[\x{1F468}\x{1F469}]|[\x{1F468}\x{1F469}]))|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F9D1}(?:\x{200D}(?:\x{1F91D}\x{200D}\x{1F9D1}|[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F3FF}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FE}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FD}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FC}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}]|\x{1F3FB}\x{200D}[\x{1F33E}\x{1F373}\x{1F37C}\x{1F384}\x{1F393}\x{1F3A4}\x{1F3A8}\x{1F3EB}\x{1F3ED}\x{1F4BB}\x{1F4BC}\x{1F527}\x{1F52C}\x{1F680}\x{1F692}\x{1F9AF}-\x{1F9B3}\x{1F9BC}\x{1F9BD}])|\x{1F469}\x{200D}\x{1F466}\x{200D}\x{1F466}|\x{1F469}\x{200D}\x{1F469}\x{200D}[\x{1F466}\x{1F467}]|\x{1F469}\x{200D}\x{1F467}\x{200D}[\x{1F466}\x{1F467}]|(?:\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2757}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}])\x{FE0F}|\x{1F441}\x{FE0F}?\x{200D}\x{1F5E8}|\x{1F9D1}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F469}(?:\x{1F3FF}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FE}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FD}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FC}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{1F3FB}\x{200D}[\x{2695}\x{2696}\x{2708}]|\x{200D}[\x{2695}\x{2696}\x{2708}])|\x{1F3F3}\x{FE0F}?\x{200D}\x{1F308}|\x{1F469}\x{200D}\x{1F467}|\x{1F469}\x{200D}\x{1F466}|\x{1F636}\x{200D}\x{1F32B}|\x{1F3F3}\x{FE0F}?\x{200D}\x{26A7}|\x{1F635}\x{200D}\x{1F4AB}|\x{1F62E}\x{200D}\x{1F4A8}|\x{1F415}\x{200D}\x{1F9BA}|\x{1FAF1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F9D1}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F469}(?:\x{1F3FF}|\x{1F3FE}|\x{1F3FD}|\x{1F3FC}|\x{1F3FB})?|\x{1F43B}\x{200D}\x{2744}|(?:[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}])\x{200D}[\x{2640}\x{2642}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}](?:[\x{FE0F}\x{1F3FB}-\x{1F3FF}]\x{200D}[\x{2640}\x{2642}]|\x{200D}[\x{2640}\x{2642}])|\x{1F3F4}\x{200D}\x{2620}|\x{1F1FD}\x{1F1F0}|\x{1F1F6}\x{1F1E6}|\x{1F1F4}\x{1F1F2}|\x{1F408}\x{200D}\x{2B1B}|\x{2764}(?:\x{FE0F}\x{200D}[\x{1F525}\x{1FA79}]|\x{200D}[\x{1F525}\x{1FA79}])|\x{1F441}\x{FE0F}?|\x{1F3F3}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93C}-\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]\x{200D}[\x{2640}\x{2642}]|\x{1F1FF}[\x{1F1E6}\x{1F1F2}\x{1F1FC}]|\x{1F1FE}[\x{1F1EA}\x{1F1F9}]|\x{1F1FC}[\x{1F1EB}\x{1F1F8}]|\x{1F1FB}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1EE}\x{1F1F3}\x{1F1FA}]|\x{1F1FA}[\x{1F1E6}\x{1F1EC}\x{1F1F2}\x{1F1F3}\x{1F1F8}\x{1F1FE}\x{1F1FF}]|\x{1F1F9}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1ED}\x{1F1EF}-\x{1F1F4}\x{1F1F7}\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FF}]|\x{1F1F8}[\x{1F1E6}-\x{1F1EA}\x{1F1EC}-\x{1F1F4}\x{1F1F7}-\x{1F1F9}\x{1F1FB}\x{1F1FD}-\x{1F1FF}]|\x{1F1F7}[\x{1F1EA}\x{1F1F4}\x{1F1F8}\x{1F1FA}\x{1F1FC}]|\x{1F1F5}[\x{1F1E6}\x{1F1EA}-\x{1F1ED}\x{1F1F0}-\x{1F1F3}\x{1F1F7}-\x{1F1F9}\x{1F1FC}\x{1F1FE}]|\x{1F1F3}[\x{1F1E6}\x{1F1E8}\x{1F1EA}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F4}\x{1F1F5}\x{1F1F7}\x{1F1FA}\x{1F1FF}]|\x{1F1F2}[\x{1F1E6}\x{1F1E8}-\x{1F1ED}\x{1F1F0}-\x{1F1FF}]|\x{1F1F1}[\x{1F1E6}-\x{1F1E8}\x{1F1EE}\x{1F1F0}\x{1F1F7}-\x{1F1FB}\x{1F1FE}]|\x{1F1F0}[\x{1F1EA}\x{1F1EC}-\x{1F1EE}\x{1F1F2}\x{1F1F3}\x{1F1F5}\x{1F1F7}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1EF}[\x{1F1EA}\x{1F1F2}\x{1F1F4}\x{1F1F5}]|\x{1F1EE}[\x{1F1E8}-\x{1F1EA}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}]|\x{1F1ED}[\x{1F1F0}\x{1F1F2}\x{1F1F3}\x{1F1F7}\x{1F1F9}\x{1F1FA}]|\x{1F1EC}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EE}\x{1F1F1}-\x{1F1F3}\x{1F1F5}-\x{1F1FA}\x{1F1FC}\x{1F1FE}]|\x{1F1EB}[\x{1F1EE}-\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1F7}]|\x{1F1EA}[\x{1F1E6}\x{1F1E8}\x{1F1EA}\x{1F1EC}\x{1F1ED}\x{1F1F7}-\x{1F1FA}]|\x{1F1E9}[\x{1F1EA}\x{1F1EC}\x{1F1EF}\x{1F1F0}\x{1F1F2}\x{1F1F4}\x{1F1FF}]|\x{1F1E8}[\x{1F1E6}\x{1F1E8}\x{1F1E9}\x{1F1EB}-\x{1F1EE}\x{1F1F0}-\x{1F1F5}\x{1F1F7}\x{1F1FA}-\x{1F1FF}]|\x{1F1E7}[\x{1F1E6}\x{1F1E7}\x{1F1E9}-\x{1F1EF}\x{1F1F1}-\x{1F1F4}\x{1F1F6}-\x{1F1F9}\x{1F1FB}\x{1F1FC}\x{1F1FE}\x{1F1FF}]|\x{1F1E6}[\x{1F1E8}-\x{1F1EC}\x{1F1EE}\x{1F1F1}\x{1F1F2}\x{1F1F4}\x{1F1F6}-\x{1F1FA}\x{1F1FC}\x{1F1FD}\x{1F1FF}]|[#\*0-9]\x{FE0F}?\x{20E3}|\x{1F93C}[\x{1F3FB}-\x{1F3FF}]|\x{2764}\x{FE0F}?|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}][\x{1F3FB}-\x{1F3FF}]|[\x{26F9}\x{1F3CB}\x{1F3CC}\x{1F575}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]?|\x{1F3F4}|[\x{270A}\x{270B}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F57A}\x{1F595}\x{1F596}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}][\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270C}\x{270D}\x{1F574}\x{1F590}][\x{FE0F}\x{1F3FB}-\x{1F3FF}]|[\x{261D}\x{270A}-\x{270D}\x{1F385}\x{1F3C2}\x{1F3C7}\x{1F408}\x{1F415}\x{1F43B}\x{1F442}\x{1F443}\x{1F446}-\x{1F450}\x{1F466}\x{1F467}\x{1F46B}-\x{1F46D}\x{1F472}\x{1F474}-\x{1F476}\x{1F478}\x{1F47C}\x{1F483}\x{1F485}\x{1F48F}\x{1F491}\x{1F4AA}\x{1F574}\x{1F57A}\x{1F590}\x{1F595}\x{1F596}\x{1F62E}\x{1F635}\x{1F636}\x{1F64C}\x{1F64F}\x{1F6C0}\x{1F6CC}\x{1F90C}\x{1F90F}\x{1F918}-\x{1F91F}\x{1F930}-\x{1F934}\x{1F936}\x{1F93C}\x{1F977}\x{1F9B5}\x{1F9B6}\x{1F9BB}\x{1F9D2}\x{1F9D3}\x{1F9D5}\x{1FAC3}-\x{1FAC5}\x{1FAF0}\x{1FAF2}-\x{1FAF6}]|[\x{1F3C3}\x{1F3C4}\x{1F3CA}\x{1F46E}\x{1F470}\x{1F471}\x{1F473}\x{1F477}\x{1F481}\x{1F482}\x{1F486}\x{1F487}\x{1F645}-\x{1F647}\x{1F64B}\x{1F64D}\x{1F64E}\x{1F6A3}\x{1F6B4}-\x{1F6B6}\x{1F926}\x{1F935}\x{1F937}-\x{1F939}\x{1F93D}\x{1F93E}\x{1F9B8}\x{1F9B9}\x{1F9CD}-\x{1F9CF}\x{1F9D4}\x{1F9D6}-\x{1F9DD}]|[\x{1F46F}\x{1F9DE}\x{1F9DF}]|[\xA9\xAE\x{203C}\x{2049}\x{2122}\x{2139}\x{2194}-\x{2199}\x{21A9}\x{21AA}\x{231A}\x{231B}\x{2328}\x{23CF}\x{23ED}-\x{23EF}\x{23F1}\x{23F2}\x{23F8}-\x{23FA}\x{24C2}\x{25AA}\x{25AB}\x{25B6}\x{25C0}\x{25FB}\x{25FC}\x{25FE}\x{2600}-\x{2604}\x{260E}\x{2611}\x{2614}\x{2615}\x{2618}\x{2620}\x{2622}\x{2623}\x{2626}\x{262A}\x{262E}\x{262F}\x{2638}-\x{263A}\x{2640}\x{2642}\x{2648}-\x{2653}\x{265F}\x{2660}\x{2663}\x{2665}\x{2666}\x{2668}\x{267B}\x{267E}\x{267F}\x{2692}\x{2694}-\x{2697}\x{2699}\x{269B}\x{269C}\x{26A0}\x{26A7}\x{26AA}\x{26B0}\x{26B1}\x{26BD}\x{26BE}\x{26C4}\x{26C8}\x{26CF}\x{26D1}\x{26D3}\x{26E9}\x{26F0}-\x{26F5}\x{26F7}\x{26F8}\x{26FA}\x{2702}\x{2708}\x{2709}\x{270F}\x{2712}\x{2714}\x{2716}\x{271D}\x{2721}\x{2733}\x{2734}\x{2744}\x{2747}\x{2757}\x{2763}\x{27A1}\x{2934}\x{2935}\x{2B05}-\x{2B07}\x{2B1B}\x{2B1C}\x{2B55}\x{3030}\x{303D}\x{3297}\x{3299}\x{1F004}\x{1F170}\x{1F171}\x{1F17E}\x{1F17F}\x{1F202}\x{1F237}\x{1F321}\x{1F324}-\x{1F32C}\x{1F336}\x{1F37D}\x{1F396}\x{1F397}\x{1F399}-\x{1F39B}\x{1F39E}\x{1F39F}\x{1F3CD}\x{1F3CE}\x{1F3D4}-\x{1F3DF}\x{1F3F5}\x{1F3F7}\x{1F43F}\x{1F4FD}\x{1F549}\x{1F54A}\x{1F56F}\x{1F570}\x{1F573}\x{1F576}-\x{1F579}\x{1F587}\x{1F58A}-\x{1F58D}\x{1F5A5}\x{1F5A8}\x{1F5B1}\x{1F5B2}\x{1F5BC}\x{1F5C2}-\x{1F5C4}\x{1F5D1}-\x{1F5D3}\x{1F5DC}-\x{1F5DE}\x{1F5E1}\x{1F5E3}\x{1F5E8}\x{1F5EF}\x{1F5F3}\x{1F5FA}\x{1F6CB}\x{1F6CD}-\x{1F6CF}\x{1F6E0}-\x{1F6E5}\x{1F6E9}\x{1F6F0}\x{1F6F3}]|[\x{23E9}-\x{23EC}\x{23F0}\x{23F3}\x{25FD}\x{2693}\x{26A1}\x{26AB}\x{26C5}\x{26CE}\x{26D4}\x{26EA}\x{26FD}\x{2705}\x{2728}\x{274C}\x{274E}\x{2753}-\x{2755}\x{2795}-\x{2797}\x{27B0}\x{27BF}\x{2B50}\x{1F0CF}\x{1F18E}\x{1F191}-\x{1F19A}\x{1F201}\x{1F21A}\x{1F22F}\x{1F232}-\x{1F236}\x{1F238}-\x{1F23A}\x{1F250}\x{1F251}\x{1F300}-\x{1F320}\x{1F32D}-\x{1F335}\x{1F337}-\x{1F37C}\x{1F37E}-\x{1F384}\x{1F386}-\x{1F393}\x{1F3A0}-\x{1F3C1}\x{1F3C5}\x{1F3C6}\x{1F3C8}\x{1F3C9}\x{1F3CF}-\x{1F3D3}\x{1F3E0}-\x{1F3F0}\x{1F3F8}-\x{1F407}\x{1F409}-\x{1F414}\x{1F416}-\x{1F43A}\x{1F43C}-\x{1F43E}\x{1F440}\x{1F444}\x{1F445}\x{1F451}-\x{1F465}\x{1F46A}\x{1F479}-\x{1F47B}\x{1F47D}-\x{1F480}\x{1F484}\x{1F488}-\x{1F48E}\x{1F490}\x{1F492}-\x{1F4A9}\x{1F4AB}-\x{1F4FC}\x{1F4FF}-\x{1F53D}\x{1F54B}-\x{1F54E}\x{1F550}-\x{1F567}\x{1F5A4}\x{1F5FB}-\x{1F62D}\x{1F62F}-\x{1F634}\x{1F637}-\x{1F644}\x{1F648}-\x{1F64A}\x{1F680}-\x{1F6A2}\x{1F6A4}-\x{1F6B3}\x{1F6B7}-\x{1F6BF}\x{1F6C1}-\x{1F6C5}\x{1F6D0}-\x{1F6D2}\x{1F6D5}-\x{1F6D7}\x{1F6DD}-\x{1F6DF}\x{1F6EB}\x{1F6EC}\x{1F6F4}-\x{1F6FC}\x{1F7E0}-\x{1F7EB}\x{1F7F0}\x{1F90D}\x{1F90E}\x{1F910}-\x{1F917}\x{1F920}-\x{1F925}\x{1F927}-\x{1F92F}\x{1F93A}\x{1F93F}-\x{1F945}\x{1F947}-\x{1F976}\x{1F978}-\x{1F9B4}\x{1F9B7}\x{1F9BA}\x{1F9BC}-\x{1F9CC}\x{1F9D0}\x{1F9E0}-\x{1F9FF}\x{1FA70}-\x{1FA74}\x{1FA78}-\x{1FA7C}\x{1FA80}-\x{1FA86}\x{1FA90}-\x{1FAAC}\x{1FAB0}-\x{1FABA}\x{1FAC0}-\x{1FAC2}\x{1FAD0}-\x{1FAD9}\x{1FAE0}-\x{1FAE7}]""".toRegex() }.getOrNull() // May some java runtime unsupported public fun String.dropEmoji(): String { EMOJI_PATTERN?.let { regex -> return replace(regex, "") } return this } // endregion public fun CharSequence.chineseLength(upTo: Int = Int.MAX_VALUE): Int { return this.sumUpTo(upTo) { it.chineseLength } } public val Char.chineseLength: Int get() { return when (this) { in '\u0000'..'\u007F' -> 1 in '\u0080'..'\u07FF' -> 2 in '\u0800'..'\uFFFF' -> 3 else -> 4 } } public inline fun CharSequence.sumUpTo(upTo: Int, selector: (Char) -> Int): Int { var sum = 0 for (element in this) { sum += selector(element) if (sum >= upTo) { return sum } } return sum } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/StructureToStringTransformer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("StructureToStringTransformerKt_common") package net.mamoe.mirai.utils import kotlin.jvm.JvmName public interface StructureToStringTransformer { public fun transform(any: Any?): String public fun transformAndDesensitize(any: Any?): String public companion object { private class ObjectToStringStructureToStringTransformer : StructureToStringTransformer { override fun transform(any: Any?): String = any.toString() override fun transformAndDesensitize(any: Any?): String = any.toString() } public val instance: StructureToStringTransformer by lazy { loadService(StructureToStringTransformer::class) { getPlatformDefaultStructureToStringTransformer() ?: ObjectToStringStructureToStringTransformer() } } public val available: Boolean = instance !is ObjectToStringStructureToStringTransformer } } internal expect fun getPlatformDefaultStructureToStringTransformer(): StructureToStringTransformer? /** * Do not call this inside [Any.toString]. `StackOverflowError` may happen. */ public fun Any?.structureToString(): String = StructureToStringTransformer.instance.transform(this) public fun Any?.structureToStringIfAvailable(): String? { return if (StructureToStringTransformer.available) { StructureToStringTransformer.instance.transform(this) } else null } public fun Any?.structureToStringAndDesensitize(): String = StructureToStringTransformer.instance.transformAndDesensitize(this) public fun Any?.structureToStringAndDesensitizeIfAvailable(): String? { return if (StructureToStringTransformer.available) { StructureToStringTransformer.instance.transformAndDesensitize(this) } else null } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/Symbol.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.jvm.JvmName /** * An object with name for debugging purposes. Handy for states. */ public class Symbol private constructor(name: String) { private val str = "Symbol($name)" override fun toString(): String = str public companion object { @Suppress("RedundantNullableReturnType") @JvmName("create") public operator fun invoke(name: String): Any? = Symbol(name) // calls constructor } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/TimeUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmName("TimeUtilsKt_common") package net.mamoe.mirai.utils import kotlin.jvm.JvmName import kotlin.jvm.JvmSynthetic import kotlin.math.floor import kotlin.time.Duration import kotlin.time.DurationUnit import kotlin.time.ExperimentalTime public expect fun currentTimeMillis(): Long /** * 时间戳到秒 */ public fun currentTimeSeconds(): Long = currentTimeMillis() / 1000 public fun currentTimeFormatted(format: String? = null): String { return formatTime(currentTimeMillis(), format) } public expect fun formatTime(epochTimeMillis: Long, format: String?): String // 临时使用, 待 Kotlin Duration 稳定后使用 Duration. // 内联属性, 则将来删除这些 API 将不会导致二进制不兼容. @get:JvmSynthetic public inline val Int.secondsToMillis: Long get() = this * 1000L @get:JvmSynthetic public inline val Int.minutesToMillis: Long get() = this * 60.secondsToMillis @get:JvmSynthetic public inline val Int.hoursToMillis: Long get() = this * 60.minutesToMillis @get:JvmSynthetic public inline val Int.daysToMillis: Long get() = this * 24.hoursToMillis @get:JvmSynthetic public inline val Int.weeksToMillis: Long get() = this * 7.daysToMillis @get:JvmSynthetic public inline val Int.monthsToMillis: Long get() = this * 30.daysToMillis @get:JvmSynthetic public inline val Int.millisToSeconds: Long get() = (this / 1000).toLong() @get:JvmSynthetic public inline val Int.minutesToSeconds: Long get() = (this * 60).toLong() @get:JvmSynthetic public inline val Int.hoursToSeconds: Long get() = this * 60.minutesToSeconds @get:JvmSynthetic public inline val Int.daysToSeconds: Long get() = this * 24.hoursToSeconds @get:JvmSynthetic public inline val Int.weeksToSeconds: Long get() = this * 7.daysToSeconds @get:JvmSynthetic public inline val Int.monthsToSeconds: Long get() = this * 30.daysToSeconds // @MiraiExperimentalApi @Deprecated("Do not use unstable API", level = DeprecationLevel.HIDDEN) @ExperimentalTime @DeprecatedSinceMirai(errorSince = "2.7", hiddenSince = "2.10") // maybe 2.7 public fun Duration.toHumanReadableString(): String { val days = toInt(DurationUnit.DAYS) val hours = toInt(DurationUnit.HOURS) % 24 val minutes = toInt(DurationUnit.MINUTES) % 60 val s = floor(toDouble(DurationUnit.SECONDS) % 60 * 1000) / 1000 return buildString { if (days != 0) append("${days}d ") if (hours != 0) append("${hours}h ") if (minutes != 0) append("${minutes}min ") append("${s}s") } } // since 2.7 public fun Int.millisToHumanReadableString(): String = toLongUnsigned().millisToHumanReadableString() public fun Long.millisToHumanReadableString(): String { val days = this / 1000 / 3600 / 24 val hours = this / 1000 / 3600 % 24 val minutes = this / 1000 / 60 % 60 val s = floor(this.toDouble() / 1000 % 60 * 1000) / 1000 return buildString { if (days != 0L) append("${days}d ") if (hours != 0L) append("${hours}h ") if (minutes != 0L) append("${minutes}min ") append("${s}s") } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/TlvMap.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlin.jvm.JvmField public typealias TlvMap = MutableMap<Int, ByteArray> public fun TlvMap(): TlvMap = linkedMapOf() @Suppress("FunctionName") public fun Output._writeTlvMap( tagSize: Int, includeCount: Boolean = true, map: TlvMap, ) { if (includeCount) { when (tagSize) { 1 -> writeByte(map.size.toByte()) 2 -> writeShort(map.size.toShort()) 4 -> writeInt(map.size) else -> error("Unsupported tag size: $tagSize") } } map.forEach { (key, value) -> when (tagSize) { 1 -> writeByte(key.toByte()) 2 -> writeShort(key.toShort()) 4 -> writeInt(key) else -> error("Unsupported tag size: $tagSize") } writeShort(value.size.toShort()) writeFully(value) } } @Suppress("MemberVisibilityCanBePrivate") public class TlvMapWriter internal constructor( private val tagSize: Int, ) { @JvmField internal val buffer = BytePacketBuilder() @JvmField internal var counter: Int = 0 @PublishedApi @JvmField internal var isWriting: Boolean = false private fun writeKey(key: Int) { when (tagSize) { 1 -> buffer.writeByte(key.toByte()) 2 -> buffer.writeShort(key.toShort()) 4 -> buffer.writeInt(key) else -> error("Unsupported tag size: $tagSize") } counter++ } @PublishedApi internal fun ensureNotWriting() { if (isWriting) error("Cannot write a new Tlv when writing Tlv") } public fun tlv(key: Int, data: ByteArray) { ensureNotWriting() tlv0(key, data) } private fun tlv0(key: Int, data: ByteArray) { writeKey(key) buffer.writeShort(data.size.toShort()) buffer.writeFully(data) // println("Writing [${key.toUHexString()}](${data.size}) => " + data.toUHexString()) } public fun tlv(key: Int, data: ByteReadPacket) { ensureNotWriting() tlv0(key, data) } @PublishedApi internal fun tlv0(key: Int, data: ByteReadPacket) { writeKey(key) buffer.writeShort(data.remaining.toShort()) // println("Writing [${key.toUHexString()}](${data.remaining}) => " + data.copy() // .use { d1 -> d1.readBytes().toUHexString() }) buffer.writePacket(data) } public inline fun tlv( key: Int, crossinline builder: BytePacketBuilder.() -> Unit, ) { ensureNotWriting() try { isWriting = true buildPacket(builder).use { tlv0(key, it) } } finally { isWriting = false } } } public fun Output._writeTlvMap( tagSize: Int = 2, includeCount: Boolean = true, block: TlvMapWriter.() -> Unit ) { val writer = TlvMapWriter(tagSize) try { block(writer) if (includeCount) { when (tagSize) { 1 -> writeByte(writer.counter.toByte()) 2 -> writeShort(writer.counter.toShort()) 4 -> writeInt(writer.counter) else -> error("Unsupported tag size: $tagSize") } } writer.buffer.build().use { // println(it.copy().use { it.readBytes().toUHexString() }) writePacket(it) } } finally { writer.buffer.release() } } public fun TlvMap.getOrFail(tag: Int): ByteArray { return this[tag] ?: error("cannot find tlv 0x${tag.toUHexString("")}($tag)") } public fun TlvMap.getOrFail(tag: Int, lazyMessage: (tag: Int) -> String): ByteArray { return this[tag] ?: error(lazyMessage(tag)) } @Suppress("FunctionName") public fun Input._readTLVMap(tagSize: Int = 2, suppressDuplication: Boolean = true): TlvMap = _readTLVMap(true, tagSize, suppressDuplication) @Suppress("DuplicatedCode", "FunctionName") public fun Input._readTLVMap( expectingEOF: Boolean = true, tagSize: Int, suppressDuplication: Boolean = true ): TlvMap { val map = linkedMapOf<Int, ByteArray>() var key = 0 while (kotlin.run { try { key = when (tagSize) { 1 -> readByte().toUByte().toInt() 2 -> readShort().toUShort().toInt() 4 -> readInt() else -> error("Unsupported tag size: $tagSize") } } catch (e: Exception) { // java.nio.BufferUnderflowException is not a EOFException... if (expectingEOF) { return map } throw e } key }.toUByte() != UByte.MAX_VALUE) { if (map.containsKey(key)) { // println("reading ${key.toUHexString()}") if (!suppressDuplication) { /* @Suppress("DEPRECATION") MiraiLogger.error( @Suppress("IMPLICIT_CAST_TO_ANY") """ Error readTLVMap: duplicated key ${when (tagSize) { 1 -> key.toByte() 2 -> key.toShort() 4 -> key else -> error("unreachable") }.contentToString()} map=${map.contentToString()} duplicating value=${this.readUShortLVByteArray().toUHexString()} """.trimIndent() )*/ } else { this.discardExact(this.readShort().toInt() and 0xffff) } } else { try { val len = readShort().toUShort().toInt() val data = this.readBytes(len) // println("Writing [${key.toUHexString()}]($len) => ${data.toUHexString()}") map[key] = data } catch (e: Exception) { // BufferUnderflowException, java.io.EOFException // if (expectingEOF) { // return map // } throw e } } } return map } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/TypeSafeMap.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.utils import kotlinx.serialization.Serializable import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.jvm.JvmInline import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty /** * 携带值类型信息的键. * @see TypeSafeMap */ @Serializable @JvmInline public value class TypeKey<T>(public val name: String) { override fun toString(): String = "Key($name)" public inline infix fun to(value: T): TypeSafeMap = buildTypeSafeMap { set(this@TypeKey, value) } } /** * 类型安全的表. [TypeSafeMap] 使用带有值类型信息的 [TypeKey] 作为键, 可放入不同类型的值, 并且在取出时能获得期望的安全类型. * * **注意: 类型仅在编译期检查. 进行未受检类型转换将可能导致运行时问题.** * * 与 [Map] 类似, [TypeSafeMap] 是只读的实现. * 使用 [buildTypeSafeMap] 以构建一个表. * * @see buildTypeSafeMap * @see MutableTypeSafeMap */ public sealed interface TypeSafeMap { public val size: Int /** * @throws NoSuchElementException */ public operator fun <T> get(key: TypeKey<T>): T public operator fun <T : S, S> get(key: TypeKey<T>, defaultValue: S): S public operator fun <T> contains(key: TypeKey<T>): Boolean = get(key) != null /** * 创建键为包装类型 [TypeKey] 的 [Map]. 此方法将会包装全部键因而性能低下, 尽可能使用 [toMap] 替代. */ public fun toMapBoxed(): Map<TypeKey<*>, Any> /** * 得到实际数据表. */ public fun toMap(): Map<String, Any> public operator fun <T> provideDelegate(thisRef: Any?, property: KProperty<*>): ReadOnlyProperty<Any?, T> { val typeKey = TypeKey<T>(property.name) return ReadOnlyProperty { _, _ -> get(typeKey) } } public companion object { public val EMPTY: TypeSafeMap = TypeSafeMapImpl(emptyMap()) } } public fun <T> TypeSafeMap.property(name: String): ReadOnlyProperty<Any?, T> { return property(TypeKey(name)) } public fun <T> TypeSafeMap.property(typeKey: TypeKey<T>): ReadOnlyProperty<Any?, T> { return ReadOnlyProperty { _, _ -> get(typeKey) } } public operator fun TypeSafeMap.plus(other: TypeSafeMap): TypeSafeMap { return when { other.size == 0 -> this this.size == 0 -> other else -> buildTypeSafeMap { setAll(this@plus) setAll(other) } } } public sealed interface MutableTypeSafeMap : TypeSafeMap { public operator fun <T> set(key: TypeKey<T>, value: T) public fun <T> remove(key: TypeKey<T>): T? public fun setAll(other: TypeSafeMap) public override operator fun <T> provideDelegate( thisRef: Any?, property: KProperty<*> ): ReadWriteProperty<Any?, T> { val typeKey = TypeKey<T>(property.name) return object : ReadWriteProperty<Any?, T> { override fun getValue(thisRef: Any?, property: KProperty<*>): T { return get(typeKey) } override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) { set(typeKey, value) } } } } private val NULL: Any = Symbol("NULL")!! @PublishedApi internal open class TypeSafeMapImpl( @PublishedApi internal open val map: Map<String, Any> = ConcurrentHashMap() ) : TypeSafeMap { override val size: Int get() = map.size override fun equals(other: Any?): Boolean { return other is TypeSafeMapImpl && other.map == this.map } override fun hashCode(): Int { return map.hashCode() } override fun toString(): String { return "TypeSafeMapImpl(map=$map)" } override operator fun <T> get(key: TypeKey<T>): T { val value = map[key.name] if (value === NULL) { return null.uncheckedCast() } return value?.uncheckedCast() ?: throw NoSuchElementException(key.toString()) } override operator fun <T : S, S> get(key: TypeKey<T>, defaultValue: S): S { val value = map[key.name] if (value === NULL) return defaultValue return value?.uncheckedCast() ?: defaultValue } override operator fun <T> contains(key: TypeKey<T>): Boolean = map.containsKey(key.name) override fun toMapBoxed(): Map<TypeKey<*>, Any> = map.mapKeys { TypeKey<Any?>(it.key) } override fun toMap(): Map<String, Any> = map } @PublishedApi internal class MutableTypeSafeMapImpl( @PublishedApi override val map: MutableMap<String, Any> = ConcurrentHashMap() ) : TypeSafeMap, MutableTypeSafeMap, TypeSafeMapImpl(map) { override fun equals(other: Any?): Boolean { return other is MutableTypeSafeMapImpl && other.map == this.map } override fun hashCode(): Int { return map.hashCode() } override fun toString(): String { return "MutableTypeSafeMapImpl(map=$map)" } override operator fun <T> set(key: TypeKey<T>, value: T) { if (value == null) { map[key.name] = NULL } else { map[key.name] = value } } override fun setAll(other: TypeSafeMap) { if (other is TypeSafeMapImpl) { map.putAll(other.map) } else { map.putAll(other.toMap()) } } override fun <T> remove(key: TypeKey<T>): T? { val value = map.remove(key.name) return if (value == NULL) { null } else { value?.uncheckedCast() } } } public fun TypeSafeMap.toMutableTypeSafeMap(): MutableTypeSafeMap = createMutableTypeSafeMap(this.toMap()) public inline fun createMutableTypeSafeMap(): MutableTypeSafeMap = MutableTypeSafeMapImpl() public inline fun createMutableTypeSafeMap(map: Map<String, Any>): MutableTypeSafeMap = MutableTypeSafeMapImpl().also { it.map.putAll(map) } public inline fun createTypeSafeMap(): TypeSafeMap = TypeSafeMap.EMPTY public inline fun buildTypeSafeMap(block: MutableTypeSafeMap.() -> Unit): MutableTypeSafeMap { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return MutableTypeSafeMapImpl().apply(block) } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/UnsafeMutableNonNullProperty.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.jvm.JvmField public fun <T : Any> unsafeMutableNonNullPropertyOf( name: String = "<unknown>" ): UnsafeMutableNonNullProperty<T> { return UnsafeMutableNonNullProperty(name) } @Suppress("NOTHING_TO_INLINE") public class UnsafeMutableNonNullProperty<T : Any>( private val propertyName: String = "<unknown>" ) { @JvmField public var value0: T? = null public val isInitialized: Boolean get() = value0 !== null public var value: T get() = value0 ?: throw IllegalStateException("Property `$propertyName` not initialized") set(value) { value0 = value } public fun clear() { value0 = null } public inline operator fun getValue(thiz: Any?, property: Any?): T = value public inline operator fun setValue(thiz: Any?, property: Any?, value: T) { value0 = value } override fun toString(): String { return value0?.toString() ?: "<uninitialized>" } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/UtilsLogger.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils /** * Mirror of `MiraiLogger`, to be used in utils module. */ public interface UtilsLogger { /** * 当 VERBOSE 级别的日志启用时返回 `true`. */ public val isVerboseEnabled: Boolean /** * 当 DEBUG 级别的日志启用时返回 `true` */ public val isDebugEnabled: Boolean /** * 当 INFO 级别的日志启用时返回 `true` */ public val isInfoEnabled: Boolean /** * 当 WARNING 级别的日志启用时返回 `true` */ public val isWarningEnabled: Boolean /** * 当 ERROR 级别的日志启用时返回 `true` */ public val isErrorEnabled: Boolean /** * 记录一个 `verbose` 级别的日志. * 无关紧要的, 经常大量输出的日志应使用它. */ public fun verbose(message: String?, e: Throwable? = null) /** * 记录一个 _调试_ 级别的日志. */ public fun debug(message: String?, e: Throwable? = null) /** * 记录一个 _信息_ 级别的日志. */ public fun info(message: String?, e: Throwable? = null) /** * 记录一个 _警告_ 级别的日志. */ public fun warning(message: String?, e: Throwable? = null) /** * 记录一个 _错误_ 级别的日志. */ public fun error(message: String?, e: Throwable? = null) public companion object { @OptIn(TestOnly::class) private val noop: UtilsLogger by lazy { SimpleUtilsLogger().apply { isDebugEnabled = false isErrorEnabled = false isInfoEnabled = false isWarningEnabled = false isVerboseEnabled = false } } public fun noop(): UtilsLogger = noop } } public fun UtilsLogger.info(e: Throwable?) { info(null, e) } public fun UtilsLogger.error(e: Throwable?) { error(null, e) } public fun UtilsLogger.warning(e: Throwable?) { warning(null, e) } public fun UtilsLogger.debug(e: Throwable?) { debug(null, e) } public fun UtilsLogger.verbose(e: Throwable?) { verbose(null, e) } public inline fun UtilsLogger.verbose(message: () -> String) { if (isVerboseEnabled) verbose(message()) } public inline fun UtilsLogger.verbose(message: () -> String, e: Throwable?) { if (isVerboseEnabled) verbose(message(), e) } public inline fun UtilsLogger.debug(message: () -> String?) { if (isDebugEnabled) debug(message()) } public inline fun UtilsLogger.debug(message: () -> String?, e: Throwable?) { if (isDebugEnabled) debug(message(), e) } public inline fun UtilsLogger.info(message: () -> String?) { if (isInfoEnabled) info(message()) } public inline fun UtilsLogger.info(message: () -> String?, e: Throwable?) { if (isInfoEnabled) info(message(), e) } public inline fun UtilsLogger.warning(message: () -> String?) { if (isWarningEnabled) warning(message()) } public inline fun UtilsLogger.warning(message: () -> String?, e: Throwable?) { if (isWarningEnabled) warning(message(), e) } public inline fun UtilsLogger.error(message: () -> String?) { if (isErrorEnabled) error(message()) } public inline fun UtilsLogger.error(message: () -> String?, e: Throwable?) { if (isErrorEnabled) error(message(), e) } @TestOnly public open class SimpleUtilsLogger @TestOnly public constructor() : UtilsLogger { override var isVerboseEnabled: Boolean = true override var isDebugEnabled: Boolean = true override var isInfoEnabled: Boolean = true override var isWarningEnabled: Boolean = true override var isErrorEnabled: Boolean = true override fun verbose(message: String?, e: Throwable?) { if (!isVerboseEnabled) return println("[V] $message") e?.printStackTrace() } override fun debug(message: String?, e: Throwable?) { if (!isDebugEnabled) return println("[D] $message") e?.printStackTrace() } override fun info(message: String?, e: Throwable?) { if (!isInfoEnabled) return println("[I] $message") e?.printStackTrace() } override fun warning(message: String?, e: Throwable?) { if (!isWarningEnabled) return println("[W] $message") e?.printStackTrace() } override fun error(message: String?, e: Throwable?) { if (!isErrorEnabled) return println("[E] $message") e?.printStackTrace() } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/annotations/Range.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils.annotations @OptIn(ExperimentalMultiplatform::class) @Target(AnnotationTarget.VALUE_PARAMETER, AnnotationTarget.PROPERTY, AnnotationTarget.TYPE) @Retention(AnnotationRetention.BINARY) @OptionalExpectation public expect annotation class Range(val from: Long, val to: Long) ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/channels/ChannelState.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils.channels import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Deferred import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlin.coroutines.CoroutineContext /** * Producer states. */ internal sealed interface ChannelState<T, V> { /* * 可变更状态的函数: [emit], [receiveOrNull], [expectMore], [finish], [finishExceptionally] * * [emit] 和 [receiveOrNull] 为 suspend 函数, 在图中 "(suspend)" 表示挂起它们的协程, "(resume)" 表示恢复它们的协程. * * "A ~~~~~~> B" 表示在切换为状态 A 后, 会挂起或恢复协程 B. * * * * * JustInitialized * | * | 调用 [expectMore] * | * V * ProducerReady (从此用户协程作为 producer 在后台运行) * | * | * | <-------------------------------------------------- * | \ * V | * Producing ([expectMore] 结束) | * | \ | * 调用 | \ | * [receiveOrNull] | \ 调用 [emit] | * / \ | * / \ | * / \ | * | \ | * | \ | * | |------------- | * | | \ | * | | | | * | \ | | * | \ | | * | \ | | * | | | | * V (resume) V | | * ([receiveOrNull] suspend) <~~~~~~~~~~~~ Consuming | | * | / | | * | / | | * | /---------------/ | | * | / 调用 [receiveOrNull] | | * | / | | * |/ | | * | | | * | | | * V | | * ([receiveOrNull] 结束) Consumed | | * | | | * | 调用 [expectMore] | | * | | | * V (resume) V | * ProducerReady ~~~~~~~~~~~~~~~~> ([emit] suspend) | * | | | * | | | * | V | * | ([emit] 结束) | * | | * |------------------------------------------------------------+ * (返回顶部 Producing) * * * * 在任意状态调用 [finish] 以及 [finishExceptionally], 可将状态转移到最终状态 [Finished]. * * 在一个状态中调用图中未说明的函数会抛出 [IllegalProducerStateException]. */ /** * Override this function to produce good debug information */ abstract override fun toString(): String class JustInitialized<T, V> : ChannelState<T, V> { override fun toString(): String = "JustInitialized" } sealed interface HasProducer<T, V> : ChannelState<T, V> { val producer: OnDemandSendChannel<T, V> } // Producer is not running until `expectMore`. `emit` and `receiveOrNull` not allowed. class ProducerReady<T, V>( launchProducer: () -> OnDemandSendChannel<T, V>, ) : HasProducer<T, V> { // Lazily start the producer job since it's on-demand override val producer: OnDemandSendChannel<T, V> by lazy(launchProducer) // `lazy` is synchronized override fun toString(): String = "ProducerReady" } // Producer is running. `emit` and `receiveOrNull` both allowed. class Producing<T, V>( override val producer: OnDemandSendChannel<T, V>, parentJob: Job, ) : HasProducer<T, V> { val deferred: CompletableDeferred<V> by lazy { CompletableDeferred<V>(parentJob) } override fun toString(): String = "Producing(deferred.completed=${deferred.isCompleted})" } // Producer is suspended because it called `emit`. Expecting `receiveOrNull`. class Consuming<T, V>( override val producer: OnDemandSendChannel<T, V>, val value: Deferred<V>, parentCoroutineContext: CoroutineContext, ) : HasProducer<T, V> { val producerLatch: CompletableDeferred<T> = CompletableDeferred(parentCoroutineContext[Job]) override fun toString(): String { @OptIn(ExperimentalCoroutinesApi::class) val completed = value.runCatching { getCompleted().toString() }.getOrNull() // getCompleted() is experimental return "Consuming(value=$completed)" } } // Producer is suspended. `expectMore` will resume producer with a ticket. class Consumed<T, V>( override val producer: OnDemandSendChannel<T, V>, val producerLatch: CompletableDeferred<T> ) : HasProducer<T, V> { override fun toString(): String = "Consumed($producerLatch)" } class Finished<T, V>( private val previousState: ChannelState<T, V>, val exception: Throwable?, ) : ChannelState<T, V> { val isSuccess: Boolean get() = exception == null fun createAlreadyFinishedException(cause: Throwable?): IllegalChannelStateException { val exception = exception val causeMessage = if (cause == null) { "" } else { ", but attempting to finish with the cause $cause" } return if (exception == null) { IllegalChannelStateException( this, "Producer has already finished normally$causeMessage. Previous state was: $previousState", cause = cause ) } else { IllegalChannelStateException( this, "Producer has already finished with the suppressed exception$causeMessage. Previous state was: $previousState", cause = cause ).apply { addSuppressed(exception) } } } override fun toString(): String = "Finished($previousState, $exception)" } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/channels/IllegalChannelStateException.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils.channels // An internal error exception public class IllegalChannelStateException internal constructor( private val state: ChannelState<*, *>, message: String? = state.toString(), cause: Throwable? = null, ) : IllegalStateException(message, cause) { public val lastStateWasSucceed: Boolean get() = (state is ChannelState.Finished) && state.isSuccess } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/channels/OnDemandChannelImpl.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils.channels import kotlinx.atomicfu.AtomicRef import kotlinx.atomicfu.atomic import kotlinx.atomicfu.loop import kotlinx.coroutines.* import net.mamoe.mirai.utils.TestOnly import net.mamoe.mirai.utils.UtilsLogger import net.mamoe.mirai.utils.childScope import net.mamoe.mirai.utils.debug import kotlin.coroutines.CoroutineContext internal class CoroutineOnDemandReceiveChannel<T, V>( parentCoroutineContext: CoroutineContext, private val logger: UtilsLogger, private val producerCoroutine: suspend OnDemandSendChannel<T, V>.(initialTicket: T) -> Unit, ) : OnDemandReceiveChannel<T, V> { private val coroutineScope = parentCoroutineContext.childScope("CoroutineOnDemandReceiveChannel") @TestOnly internal fun getScope() = coroutineScope private val state: AtomicRef<ChannelState<T, V>> = atomic(ChannelState.JustInitialized()) @TestOnly internal fun getState() = state.value inner class Producer( private val initialTicket: T, ) : OnDemandSendChannel<T, V> { init { // `UNDISPATCHED` with `yield()`: start the coroutine immediately in current thread, // attaching Job to the coroutineScope, then `yield` the thread back, to complete `launch`. coroutineScope.launch(start = CoroutineStart.UNDISPATCHED) { yield() try { producerCoroutine(initialTicket) } catch (e: Throwable) { // close exceptionally val r = emitImpl(Result.failure(e)) check(r == null) // assertion return@launch } close() } } override suspend fun emit(value: V): T = emitImpl(Result.success(value))!! private suspend inline fun emitImpl(value: Result<V>): T? { state.loop { state -> when (state) { is ChannelState.Finished -> { if (value.isFailure) { return null } else { throw state.createAlreadyFinishedException(null) } } is ChannelState.Producing -> { val deferred = state.deferred val consumingState = ChannelState.Consuming( state.producer, state.deferred, coroutineScope.coroutineContext ) if (compareAndSetState(state, consumingState)) { deferred.completeWith(value) // produce a value return consumingState.producerLatch.await() // wait for producer to consume the previous value. } // failed race, try again } is ChannelState.ProducerReady -> { // This implies another coroutine is running `expectMore`, // and we are a bit faster than it! setStateProducing(state) } else -> throw IllegalChannelStateException( state, if (value.isFailure) "Producer threw an exception (see cause), so completing with the exception, but current state is not Producing" else "Producer is emitting an value, but current state is not Producing", value.exceptionOrNull() ) } } } } private fun setStateProducing(state: ChannelState.ProducerReady<T, V>): Boolean { return compareAndSetState(state, ChannelState.Producing(state.producer, coroutineScope.coroutineContext.job)) } private fun setStateFinished( currState: ChannelState<T, V>, message: String, exception: ProducerFailureException? ): Boolean { if (compareAndSetState(currState, ChannelState.Finished(currState, exception))) { val cancellationException = CancellationException(message, exception) coroutineScope.cancel(cancellationException) return true } return false } private fun compareAndSetState(state: ChannelState<T, V>, newState: ChannelState<T, V>): Boolean { return this.state.compareAndSet(state, newState).also { logger.debug { "CAS: $state -> $newState: $it" } } } override val isClosed: Boolean get() = state.value is ChannelState.Finished override suspend fun receiveOrNull(): V? { // don't use atomicfu `.loop`: // java.lang.VerifyError: Bad type on operand stack // net/mamoe/mirai/utils/channels/CoroutineOnDemandReceiveChannel.receiveOrNull(Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @103: getfield while (true) { when (val state = state.value) { is ChannelState.Consuming -> { // value is ready, now we try to consume the value if (compareAndSetState(state, ChannelState.Consumed(state.producer, state.producerLatch))) { // value is now reserved for us, no contention is possible, safe to retrieve // This actually won't suspend (there are tests ensuring this point), // since the value is already completed. // Just to be error-tolerating and re-throwing exceptions. // (Also because `Deferred.getCompleted()` is not stable yet (coroutines 1.6)) return awaitValueSafe(state.value) } } // note: actually, this case should be the first case (for code consistency) in `when`, // but atomicfu 1.8.10 fails on this. is ChannelState.Producing<T, V> -> { // still producing value // Wait for value and throw exception caused by the producer if there is one. awaitValueSafe(state.deferred) // this may or may not suspend. // Now deferred is complete, and we will be in the Consuming state, but we can't use the value here. // We must ensure only one thread gets the value, and state should then be Consumed // So we loop again and do this in the Consuming state. } is ChannelState.Finished -> { // see public API docs for behavior return null } else -> // internal error throw IllegalChannelStateException(state) } } } private suspend inline fun awaitValueSafe(deferred: Deferred<V>) = try { deferred.await() } catch (e: Throwable) { // Producer failed to produce the previous value with exception val producerFailureException = ProducerFailureException(cause = e) setStateFinished( this.state.value, "OnDemandChannel is closed because producer failed to produce value, see cause", producerFailureException ) throw producerFailureException } override fun expectMore(ticket: T): Boolean { state.loop { state -> when (state) { is ChannelState.JustInitialized -> { // start producer atomically val ready = ChannelState.ProducerReady { Producer(ticket) } compareAndSetState(state, ready) // loop again } is ChannelState.ProducerReady -> { if (setStateProducing(state)) { return true } // lost race, try again } is ChannelState.Producing, is ChannelState.Consuming -> throw IllegalChannelStateException(state) // a value is already ready is ChannelState.Consumed -> { if (compareAndSetState(state, ChannelState.ProducerReady { state.producer })) { // wake up producer async. state.producerLatch.complete(ticket) // loop again to switch state atomically to Producing. // Do not do switch state directly here — async producer may race with you! } } is ChannelState.Finished -> return false } } } override fun close() { state.loop { state -> when (state) { is ChannelState.Finished -> return else -> if (setStateFinished(state, "OnDemandChannel is closed normally", null)) return } } } } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/channels/OnDemandSendChannel.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils.channels import kotlinx.coroutines.Deferred import kotlinx.coroutines.Job import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.channels.SendChannel import net.mamoe.mirai.utils.UtilsLogger import kotlin.coroutines.Continuation import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.cancellation.CancellationException /** * 按需供给的 [SendChannel]. * * @param T 令牌类型. * @param V 值类型. */ public interface OnDemandSendChannel<T, V> { /** * 挂起协程, 直到 [OnDemandReceiveChannel] [期望接收][OnDemandReceiveChannel.receiveOrNull]一个 [V], * 届时将 [value] 传递给 [OnDemandReceiveChannel.receiveOrNull], 成为其返回值. * * 若在调用 [emit] 时已经有 [OnDemandReceiveChannel.receiveOrNull] 正在等待, 则该协程会立即[恢复][Continuation.resumeWith], [emit] 不会挂起. * * 若 [OnDemandReceiveChannel] 已经[完结][OnDemandReceiveChannel.close], [OnDemandSendChannel.emit] 会抛出 [IllegalChannelStateException]. * * @see OnDemandReceiveChannel.receiveOrNull * * @param value 需要传递给 [OnDemandReceiveChannel.receiveOrNull] 的值 * @return 下一个 ticket [T]. * * @throws CancellationException 当此协程被取消时抛出 */ public suspend fun emit(value: V): T } /** * 线程安全的按需接收通道. * * 与 [ReceiveChannel] 不同, [OnDemandReceiveChannel] 只有在调用 [expectMore] 后才会让[生产者][OnDemandSendChannel] 开始生产下一个 [V]. */ public interface OnDemandReceiveChannel<T, V> { /** * 当此 [OnDemandReceiveChannel] 已经关闭, 即不再期望更多值时返回 `true`, * 无论是调用了 [close] (主动关闭) 还是 [OnDemandSendChannel] 没有更多值了 (被动关闭). */ public val isClosed: Boolean /** * 尝试从 [OnDemandSendChannel] [接收][OnDemandSendChannel.emit]一个 [V]. * 当且仅当在 [OnDemandSendChannel] 已经正常结束时返回 `null`. * * 若目前已有 [V], 此函数立即返回该 [V], 不会挂起. * 否则, 此函数将会挂起直到 [OnDemandSendChannel.emit]. * * 当此函数被多个协程 (线程) 同时调用时, 只有一个协程会获得 [V], 其他协程将会挂起. * * 若在等待过程中 [OnDemandSendChannel] 异常结束, * 本函数会立即恢复并抛出 [ProducerFailureException], 其 `cause` 为令 [OnDemandSendChannel] 的异常. * * 此挂起函数可被取消. * 如果在此函数挂起时当前协程的 [Job] 被取消或完结, 此函数会立即恢复并抛出 [CancellationException]. 此行为与 [Deferred.await] 相同. * * @throws ProducerFailureException 当 [OnDemandSendChannel] 产生了一个异常时抛出. * @throws CancellationException 当协程被取消时抛出 * @throws IllegalChannelStateException 当状态异常, 如未调用 [expectMore] 时抛出 */ @Throws(ProducerFailureException::class, CancellationException::class) public suspend fun receiveOrNull(): V? /** * 期待 [OnDemandSendChannel] 再生产一个 [V]. * 期望生产后必须在之后调用 [receiveOrNull] 或 [close] 来消耗生产的 [V]. * 不可连续重复调用 [expectMore]. * * 在成功发起期待后返回 `true`; 在 [OnDemandSendChannel] 已经[完结][OnDemandSendChannel.finish] 时返回 `false`. * * @throws IllegalChannelStateException 当 [expectMore] 被调用后, 没有调用 [receiveOrNull] 就又调用了 [expectMore] 时抛出 */ public fun expectMore(ticket: T): Boolean /** * 标记此 [OnDemandSendChannel] 已经不再需要更多的值. * * 如果 [OnDemandSendChannel] 仍在运行 (无论是挂起中还是正在计算下一个值), 都会正常地[取消][Job.cancel] [OnDemandSendChannel]. * * 若此 [OnDemandSendChannel] 已经被关闭, 则此函数不会进行任何操作. * * 在 [close] 之后若尝试调用 [OnDemandSendChannel.emit], [OnDemandReceiveChannel.receiveOrNull] 或 [OnDemandReceiveChannel.expectMore] 都会导致 [IllegalStateException]. */ public fun close() } @Suppress("FunctionName") public fun <T, V> OnDemandChannel( parentCoroutineContext: CoroutineContext = EmptyCoroutineContext, logger: UtilsLogger = UtilsLogger.noop(), producerCoroutine: suspend OnDemandSendChannel<T, V>.(initialTicket: T) -> Unit, ): OnDemandReceiveChannel<T, V> = CoroutineOnDemandReceiveChannel(parentCoroutineContext, logger, producerCoroutine) ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/channels/ProducerFailureException.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils.channels public class ProducerFailureException( override val message: String? = "Producer failed to produce a value, see cause", override var cause: Throwable? ) : Exception() { private val unwrapped: Throwable by lazy { val cause = cause ?: return@lazy this this.cause = null cause.also { addSuppressed(this) } } public fun unwrap(): Throwable = unwrapped } ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/package.kt ================================================ /* * 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.utils ================================================ FILE: mirai-core-utils/src/commonMain/kotlin/systemProp.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName internal expect fun getProperty(name: String, default: String): String? internal expect fun setProperty(name: String, value: String) public fun setSystemProp(name: String, value: String): Unit = setProperty(name, value) public fun systemProp(name: String, default: String): String = getProperty(name, default) ?: default public fun systemProp(name: String, default: Boolean): Boolean = getProperty(name, default.toString())?.toBoolean() ?: default public fun systemProp(name: String, default: Long): Long = getProperty(name, default.toString())?.toLongOrNull() ?: default private val debugProps = ConcurrentHashMap<String, Boolean>() public fun Any?.toDebugString(prop: String, default: Boolean = false): String { if (this == null) return "null" val debug = debugProps.getOrPut(prop) { systemProp(prop, default) } return if (debug) { "${this::class.simpleName}($this)" } else { "${this::class.simpleName}" } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/CommonByteArrayOpTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(TestOnly::class) package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertTrue internal expect class ByteArrayOpTest() : CommonByteArrayOpTest // will run all tests in CommonByteArrayOpTest again but fine internal open class CommonByteArrayOpTest { protected val sampleLongText = // 574 chars "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum." @Test fun testAvailableProcessors() { val processors = availableProcessors() assertTrue(processors.toString()) { processors > 0 } } @Test fun testMd5() { val str = getRandomString(10, Random(1)) println(str) val hash = str.md5() assertContentEquals( "30 3B 36 B3 42 00 39 E2 EC 18 22 79 10 32 05 48".hexToBytes(), hash, message = hash.toUHexString() ) } @Test fun testMd5WithOffset() { val str = getRandomString(10, Random(1)) println(str) val hash = (byteArrayOf(1) + str.toByteArray()).md5(1) assertContentEquals( "30 3B 36 B3 42 00 39 E2 EC 18 22 79 10 32 05 48".hexToBytes(), hash, message = hash.toUHexString() ) } @Test fun testSha1() { val str = getRandomString(10, Random(1)) println(str) val hash = str.sha1() assertContentEquals( "54 98 CD 62 6C DE E3 9B 96 D4 34 5E 13 51 48 BB FC 32 1C 48".hexToBytes(), hash, message = hash.toUHexString() ) } @Test fun testDeflate() { val str = "qGnJ1RrFC9" println(str) val hash = str.toByteArray().deflate() assertContentEquals( // if we change "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes(), hash, message = hash.toUHexString() ) assertEquals(str, str.toByteArray().deflate().inflate().decodeToString()) } @Test fun testInflate() { val result = "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes() .inflate().decodeToString() assertEquals( "qGnJ1RrFC9", result, message = result ) } @Test fun testInflateRealLongData() { val result = "78 DA ED D9 6D 50 DB E6 1D 00 70 49 E6 45 D6 B5 19 73 9B 8E B0 2F 9A 3E AC 59 EF 8A 1F C9 06 D9 5E DC DD B1 DB 5D BB 6B EF F6 65 B7 DE F6 21 A3 AE 21 4E 8C 63 C0 64 D9 3E 51 F2 E6 40 78 33 64 25 6F 84 92 00 CE 08 85 00 0D 0A 25 90 B6 59 E9 BA 8C D2 A4 D7 5E 9B A4 C9 06 B6 43 5E 69 93 90 34 69 93 3D B2 1C 59 C6 12 71 81 72 C7 45 3E CE 77 92 2D F1 7F FE CF EF F9 DF 5F 7E 70 44 87 64 50 69 DE E3 57 EA 53 D2 EB 1B 0E B7 13 64 5F EB 58 39 B1 BC A2 F9 DE 69 F4 E5 F2 3B 1F 06 B4 7F 5E EA 46 48 F4 99 F2 35 F8 7D F8 FA 51 5A 5F EB BE 1B 58 3A 4A ED 46 89 E5 78 F8 20 ED EC 57 BE BE D4 F4 33 C9 24 B2 BC A5 0C 05 9F 1E 1A A9 4F 31 9D D9 E4 DD 50 0A 5F 18 87 22 3A 1C 47 D2 90 74 84 44 32 F2 08 44 F7 4A A6 F7 14 FB 63 E4 4F 7F FC 4D 46 06 7E AB 96 4C FB 84 BF 22 BD BB 31 38 A1 81 F7 40 C0 77 0D 9B 9B 52 F0 F0 FB 33 18 8E 00 C4 84 FC 76 19 8E EB 96 06 76 35 8D 73 FD C1 DD 5B 27 EA 77 04 BC ED 81 8E D7 C7 4A CB 38 CC 7F B7 E5 14 86 A7 92 E1 70 FC D8 ED 5A 92 DA 85 12 4F C7 87 37 F0 2C D8 D1 9D 78 74 CB F0 EF 60 74 F5 FC 15 E9 65 9F 85 56 85 83 1B BB F3 CD E7 04 1E 7E 17 83 CB C0 93 75 4F 05 BA 0E 05 1B 3A 2F 0C EE 1B 3F E2 0D B5 34 86 DA 1A 26 F7 36 54 71 D8 9D F3 5D 5D DA 68 74 F7 60 74 F2 C9 AB BF AD 01 57 0F 7F 9F E4 95 F9 C8 B4 CB FC 15 E9 97 A7 DA CE A4 CE 2A 79 BB BA 6A 7A 93 A2 E1 C1 5B 52 77 92 E5 C2 3B 7C 40 03 6E F4 09 E1 9D A8 90 09 EF BD 64 18 DF 60 32 1F E0 93 58 1F FE A4 70 CB 55 4F 9C 44 1B 93 F1 C7 D2 5A F0 F4 54 70 05 B7 3C 4E 68 5D 0E DB 9A 95 C5 9E 22 5A 87 48 0F 19 78 F8 14 81 97 38 5C C2 87 04 63 00 26 33 60 0C 46 56 72 9E 81 E7 19 36 0B B0 0C 0B 80 65 09 A1 F5 FC D5 6D 17 3E 40 81 E5 17 04 51 5C 92 97 E7 58 CF 9F D1 FD 34 B4 73 E3 44 47 7D B0 67 FB 44 C3 5B 81 EA 86 A0 B7 66 BC 7F 73 68 5B 7B C0 DF 60 79 9E 58 92 6B F3 38 D6 BA 56 3A 0A F2 57 96 14 39 75 D9 AB 3C 1E B7 45 AF F7 38 72 5D 85 25 B9 AE CC 7C 0F FC 28 D3 E6 D2 BB 4A 5E CD B7 DB F3 F2 EC 36 8F DE E1 B1 17 E8 59 BD CD E9 B0 BB 3C 99 F9 8E 3C CB 52 02 CF 75 7A 84 A0 B5 41 6F 0F FC 1F F0 DD F2 07 22 75 B5 5B 38 FB 22 7F E7 62 78 EB BF BD 92 B9 CE E1 CE 2C 2C CC B4 AD 2D D0 AF 63 F4 EE DC 7C 7B B1 70 FF 97 72 9D CE 5F AD FC CB 3A 2B F3 F3 DC 02 F7 2F 85 E0 5E 78 D5 0A C2 87 C2 7F 87 87 6C CE 07 D8 0A 18 99 9B CC 75 3A F2 5D 56 CA 06 E3 B0 17 51 CF 91 F0 B5 A2 B0 90 84 89 B2 52 D1 DC 51 A4 6D AD D3 4A D1 14 E9 2A B0 52 14 A9 17 BE 08 47 46 16 17 D9 AC D4 EC 06 4D 91 AB DD C2 B5 F3 35 2C 0A 8E C7 63 A5 C4 FC 51 7C A0 D1 F1 88 73 3E 7D 3C 1E 18 07 A0 22 A3 72 AD 2D 22 3D EB E1 6D 66 98 79 FE BB 2B F4 7C 06 9F FB 5D 55 E3 F1 6F 34 1C 76 7F 70 E3 AF FD 58 25 C4 7F 4D 16 FF D4 B7 18 B8 15 C1 7F 4C 0E 3F C7 E3 EF 89 E0 3F 28 C5 EF 13 F1 5F 9E 2D FE A5 12 FC 5A 13 9B 6D 30 18 0D 66 43 9C 7D 7D 8C FD 9F C1 0C 5C D8 C0 4D 6C F0 07 AA 6B 03 5B FA E1 F0 27 7C CD C1 3D 43 F0 7C 60 5F D7 C4 DE 1E CB 4F 08 22 B2 02 F8 0B B4 C1 8A 4A 3E F3 15 95 96 17 E3 96 86 65 66 25 C2 B7 05 25 B4 DE BE DE 5D 64 2F 2E 86 67 32 57 BB F3 E1 42 13 D7 81 75 2E 60 E8 9C 63 0A F0 13 53 9F 28 F9 99 06 33 77 F6 B4 10 8B 08 55 CC 7A 98 E5 83 81 88 B3 AC A8 3D 86 FA CC 13 2D 05 FF D5 40 F3 B5 54 0E 3B 3D D4 5E A7 F1 63 55 50 FC B0 AC F8 52 9F 06 DC 8D 88 9F 2C 97 11 5F C3 8B AF 88 88 DF 28 15 7F 31 69 76 E2 E9 6C 96 35 31 06 1A D0 D3 CA 7D 74 25 4C 27 9F 11 43 FE 31 7E C4 5D D5 A1 AD BB E0 5F 9C EE CA 6A 3E CF 95 D5 73 D3 6D FE A1 74 9B 73 0E 3E 4C 77 34 41 F3 A4 DB 3C EF BA CD D3 75 3F C8 7A 8C 6E E5 65 2A CF 5B 9C 54 A9 E4 A6 1B 3B 26 61 E9 AE EA DD 73 1D F5 63 D5 50 F2 78 B2 5C D7 77 9E 00 F7 22 90 0F CA 95 EE 37 79 C8 07 22 90 9B A5 90 BD 73 2F DD 31 90 25 C0 1F DE B7 04 FD 6F 04 1A 2B AE 0D 6C 0B 36 77 06 FA 5B 43 DE CD A1 32 0E E6 72 F1 55 ED B7 1F 99 AA AD BC 40 E5 5D 2B CD B1 94 F9 7F 83 F7 CB 92 38 AC EE 64 B8 60 F3 CC AF CA 32 1F 7D 02 6C E2 D4 0E 45 ED 50 16 7B 87 12 FA DF 7F CA 60 87 52 F7 E5 B1 2E AD 00 FE AE 6C 87 72 EA 04 0A BC 11 F1 A3 72 E2 8F F3 E2 87 22 E2 39 F5 81 54 7D 20 5D 04 0F A4 53 DF DE 3D 90 02 FB 73 DF D4 17 B0 AB A9 51 D4 1F 68 45 41 68 DF E8 82 EA 07 66 63 16 63 62 61 61 57 F5 7F 5F FD 62 EE 54 FD 33 E8 6F DB 7F E2 24 AC FD 17 CF 36 37 63 7E EC 9F 8A BF C7 34 0D A3 E0 62 44 FF 42 75 3B 51 FD 6A B7 93 70 B7 33 13 7B B5 DB 81 E2 CF FA 03 95 50 7C 7F C7 BF 8F E2 7E EC 7D C5 F6 FE 74 12 F0 1D 5E 58 F0 70 79 67 65 D1 2C C3 A8 E0 13 07 2F 26 4D 05 2F 0F BE AF FB DC 30 CA 61 7D 53 6D 47 53 FC D8 07 8A 25 7E E8 35 0D F8 E8 CA C2 8A 37 D1 34 63 64 0D 46 A0 8A 4F 5C BC 98 34 55 BC BC F8 DE ED 6F F7 26 71 D8 9E E1 E1 F3 B0 A5 FF 48 51 FC CD 33 18 38 AD 8A 57 C5 2F 7A F1 83 DB C6 7C 1A 0E 1B FD BA 76 E4 71 3F 36 0A C5 77 C8 36 35 FF F8 3D 38 D2 F6 69 18 FC 80 DC 1E D3 AD 24 08 FE EB 24 01 FC 1A 29 F8 8F 67 B9 C7 C4 18 69 B3 81 31 01 26 3B F6 19 D6 90 6D C8 CE 02 0C 30 31 71 E2 97 C4 88 47 E2 40 8B 0F 98 73 02 0D 7E 28 D0 20 A7 E9 A1 A0 C5 A4 CC 13 68 30 EF A0 C1 74 D0 D2 C7 52 71 20 D1 59 4C 40 B4 14 EC CD CB 2D 4E 0E 2B BF 79 96 EF 49 B6 D4 91 D4 29 D9 0A 7D EE B6 46 04 EB 95 AB D0 BB F9 0A FD 7A A4 42 D7 48 C1 4E CD 12 AC 04 66 EC 8F 2E 51 C8 D3 C1 92 31 60 75 FC 0A DD DF CB 6F 32 54 6D 85 39 BB 36 F0 DA E2 AB C9 DD 0F 23 AC 3C F3 8B AC 26 2B AF 45 85 AD D1 D8 A9 95 A2 FE 7B E9 C8 78 32 87 DD 38 32 D2 AE 15 54 77 CA AA EE E8 44 C1 97 11 D5 C7 E4 CA F0 6D BE 0C 5F 8F 94 61 A7 54 F5 C9 D9 6E F5 9B 58 93 89 36 19 4C B4 5A 86 A3 BB 87 62 52 1E D5 32 1C 1A 69 DD 92 CA 61 23 E5 93 6F 44 EA F0 9B B2 7D C3 78 1A 08 2D 2C 58 06 C0 C7 7A 90 C5 64 99 55 B0 92 5F F7 1E 24 E5 51 05 7B E9 52 DD 18 DF E8 1E EF D9 AF F1 63 5E 01 AC 4C 89 DD 54 8E 81 EB 0B DC E9 AA 62 55 B1 F1 62 3B BA 4B 21 D8 F7 EB FE F5 0E EC 09 B6 2A F6 04 9F F8 35 60 CF BB 0B 5B 62 59 9A 36 02 C6 90 C5 AA 60 25 BF 27 3F 48 CA A3 0A 76 43 E8 EA 25 D8 C5 4E DE 7F A7 19 F6 04 E5 75 4A 1B E2 C3 53 51 B1 0B B4 21 AE F8 6C 16 95 3C 5D EC D3 31 62 97 F1 BF AA 6C E4 2E 74 C2 1E 7E 67 D0 5B 1B F2 1E 1A 3F DA 34 B9 D7 B7 DD F2 42 9C 58 36 F1 9D 61 DA A8 B8 1F 3E DE DF 0D 67 07 BE 5B 5E 8E 2A 7E 69 2E F3 6F 88 DD 38 A6 8D 09 6D 88 CF F8 C0 36 CB 0D F1 98 61 CF 1D 76 DC C0 22 5B E2 62 0A A7 6D 89 2B 2E 55 C5 2D 71 F9 B9 97 EA 1F 6A DF 79 11 E5 B0 73 B5 1F 0E 0A FA 01 6A 42 73 90 E7 91 FF 03 32 BC E8 A3" .hexToBytes().inflate().toUHexString() println(result) } @Test fun testGzip() { val str = sampleLongText // val hash = str.toByteArray().gzip() // assertContentEquals( // "1F 8B 08 00 00 00 00 00 00 13 2B 74 CF F3 32 0C 1F 8B 08 00 00 00 00 00 00 13 2B 74 CF F3".hexToBytes(), // hash, // message = hash.toUHexString() // ) assertEquals(str, str.toByteArray().gzip().ungzip().decodeToString()) } @Test fun testUnGzip() { val result = "1F 8B 08 00 00 00 00 00 00 FF 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 A8 35 6D D9 0A 00 00 00".hexToBytes() .ungzip().decodeToString() assertEquals( "qGnJ1RrFC9", result, message = result ) } /////////////////////////////////////////////////////////////////////////// // Input consume all /////////////////////////////////////////////////////////////////////////// @Test fun testInputAllUnGzip() { var released = false val result = "1F 8B 08 00 00 00 00 00 00 FF 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 A8 35 6D D9 0A 00 00 00".hexToBytes() .toReadPacket(release = { released = true }).let { input -> input.ungzipAllAvailable().decodeToString().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } } assertEquals( "qGnJ1RrFC9", result, message = result ) } @Test fun testInputAllGzip() { var released = false val result = sampleLongText.toByteArray() .toReadPacket(release = { released = true }).let { input -> input.gzipAllAvailable().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } } println(result.toUHexString()) assertEquals(sampleLongText, result.ungzip().decodeToString()) } @Test fun testInputAllDeflate() { var released = false val str = "qGnJ1RrFC9" println(str) val hash = str.toByteArray().toReadPacket(release = { released = true }).let { input -> input.deflateAllAvailable().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } } assertContentEquals( // if we change "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes(), hash, message = hash.toUHexString() ) assertEquals(str, hash.inflate().decodeToString()) } @Test fun testInputAllInflate() { var released = false val result = "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes() .toReadPacket(release = { released = true }).let { input -> input.inflateAllAvailable().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } }.decodeToString() assertEquals( "qGnJ1RrFC9", result, message = result ) } /////////////////////////////////////////////////////////////////////////// // Input /////////////////////////////////////////////////////////////////////////// @Test fun testInputUnGzip() { var released = false val result = "1F 8B 08 00 00 00 00 00 00 FF 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 A8 35 6D D9 0A 00 00 00".hexToBytes() .toReadPacket(release = { released = true }).let { input -> GzipDecompressionInput(input).readAllText().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } } assertEquals( "qGnJ1RrFC9", result, message = result ) } // not supported on JVM // @Test // fun testInputGzip() { // var released = false // val result = // sampleLongText.toByteArray() // .toReadPacket(release = { released = true }).let { input -> // GzipCompressionInput(input).readBytes().also { // assertEquals(true, input.endOfInput) // assertEquals(true, released) // } // } // println(result.toUHexString()) // assertEquals(sampleLongText, result.ungzip().decodeToString()) // } @Test fun testInputDeflate() { var released = false val str = "qGnJ1RrFC9" println(str) val hash = str.toByteArray().toReadPacket(release = { released = true }).let { input -> DeflateInput(input).readBytes().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } } assertContentEquals( // if we change "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes(), hash, message = hash.toUHexString() ) assertEquals(str, hash.inflate().decodeToString()) } @Test fun testInputInflate() { var released = false val result = "78 9C 2B 74 CF F3 32 0C 2A 72 73 B6 04 00 12 82 03 28".hexToBytes() .toReadPacket(release = { released = true }).let { input -> InflateInput(input).readAllText().also { assertEquals(true, input.endOfInput) assertEquals(true, released) } } assertEquals( "qGnJ1RrFC9", result, message = result ) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/EitherTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:OptIn(ExperimentalStdlibApi::class) package net.mamoe.mirai.utils import net.mamoe.mirai.utils.Either.Companion.flatMapNull import net.mamoe.mirai.utils.Either.Companion.fold import net.mamoe.mirai.utils.Either.Companion.ifLeft import net.mamoe.mirai.utils.Either.Companion.ifRight import net.mamoe.mirai.utils.Either.Companion.left import net.mamoe.mirai.utils.Either.Companion.leftOrNull import net.mamoe.mirai.utils.Either.Companion.mapLeft import net.mamoe.mirai.utils.Either.Companion.mapRight import net.mamoe.mirai.utils.Either.Companion.onLeft import net.mamoe.mirai.utils.Either.Companion.onRight import net.mamoe.mirai.utils.Either.Companion.right import net.mamoe.mirai.utils.Either.Companion.rightOrNull import kotlin.reflect.KType import kotlin.reflect.typeOf import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertIs internal class EitherTest { @Test fun `type check`() { Either<CharSequence, Int>("") Either<CharSequence, Int>(1) assertFailsWith<IllegalArgumentException> { Either.invoke<CharSequence, String>("") } assertFailsWith<IllegalArgumentException> { Either.invoke<String, CharSequence>("") } } @Test fun `type variance`() { val p: Either<CharSequence, Int> = Either("") // type is <String, Int> assertIs<String>(p.left) } @Test fun `get left`() { assertEquals("", Either<CharSequence, Int>("").leftOrNull) assertEquals(null, Either<CharSequence, Int>(1).leftOrNull) assertFailsWith<ClassCastException> { Either<CharSequence, Int>(1).left } } @Test fun `get right`() { assertEquals(null, Either<CharSequence, Int>("").rightOrNull) assertEquals(1, Either<CharSequence, Int>(1).rightOrNull) assertFailsWith<ClassCastException> { Either<CharSequence, Int>("").right } } @Test fun `can fold`() { assertEquals( true, Either<CharSequence, Int>("").fold( onLeft = { true }, onRight = { false } ) ) assertEquals( false, Either<CharSequence, Int>(1).fold( onLeft = { true }, onRight = { false } ) ) } @Test fun `can map left`() { assertTypeIs(typeOf<Either<Boolean, Int>>(), Either<CharSequence, Int>("").mapLeft { true }) // left is not null, so block will be called assertEquals(true, Either<CharSequence, Int>("").mapLeft { true }.leftOrNull) assertEquals(null, Either<CharSequence, Int>("").mapLeft { true }.rightOrNull) // right is null, so map will also be null assertEquals( null, Either<CharSequence, Int>(1) .mapLeft<CharSequence, Int, Boolean> { throw AssertionError("should not be called") }.leftOrNull ) assertEquals( 1, Either<CharSequence, Int>(1) .mapLeft<CharSequence, Int, Boolean> { throw AssertionError("should not be called") }.rightOrNull ) } @Test fun `can map right`() { assertTypeIs(typeOf<Either<CharSequence, Boolean>>(), Either<CharSequence, Int>(1).mapRight { true }) // right is not null, so block will be called assertEquals(null, Either<CharSequence, Int>(1).mapRight { true }.leftOrNull) assertEquals(true, Either<CharSequence, Int>(1).mapRight { true }.rightOrNull) // right is null, so map will also be null assertEquals( null, Either<CharSequence, Int>("") .mapRight<CharSequence, Int, Boolean> { throw AssertionError("should not be called") }.rightOrNull ) assertEquals( "", Either<CharSequence, Int>("") .mapRight<CharSequence, Int, Boolean> { throw AssertionError("should not be called") }.leftOrNull ) } @Test fun `can flatMapNull`() { assertTypeIs(typeOf<Either<CharSequence, Int>>(), Either<CharSequence, Int>(1)) // not null types Either<CharSequence, Int>("left").run { val result = flatMapNull { throw AssertionError("Fail") } assertEquals(this, result) // don't assertSame: arguments boxed separately } Either<CharSequence, Int>(1).run { val result = flatMapNull { throw AssertionError("Fail") } assertEquals(this, result) // don't assertSame: arguments boxed separately } // nullable types Either<CharSequence, Int?>("left").run { val result = flatMapNull { throw AssertionError("Fail") } assertEquals(this, result) // don't assertSame: arguments boxed separately } Either<CharSequence, Int?>(1).run { val result = flatMapNull { throw AssertionError("Fail") } assertEquals(this, result) // don't assertSame: arguments boxed separately } // normal case Either<CharSequence, Int?>(null).run { val result = flatMapNull { right(true) } // can interlace type assertEquals(true, result.right) } } @Test fun `can call onRight`() { var called = false Either<CharSequence, Int>(1).onRight { called = true } assertEquals(true, called) Either<CharSequence, Int>("").onRight { throw AssertionError("should not be called") } } @Test fun `can call onLeft`() { var called = false Either<CharSequence, Int>("").onLeft { called = true } assertEquals(true, called) Either<CharSequence, Int>(1).onLeft { throw AssertionError("should not be called") } } @Test fun `can call ifRight`() { assertEquals(true, Either<CharSequence, Int>(1).ifRight { true }) @Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION") Either<CharSequence, Int>("").ifRight { throw AssertionError("should not be called") } } @Test fun `can call ifLeft`() { assertEquals(true, Either<CharSequence, Int>("").ifLeft { true }) @Suppress("IMPLICIT_NOTHING_TYPE_ARGUMENT_IN_RETURN_POSITION") Either<CharSequence, Int>(1).ifLeft { throw AssertionError("should not be called") } } private inline fun <reified V> assertTypeIs(expected: KType, @Suppress("UNUSED_PARAMETER") value: V) { assertEquals(expected, typeOf<V>()) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/ExceptionCollectorTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.* @OptIn(TestOnly::class) internal class ExceptionCollectorTest { @Test fun `can collect`() { val collector = ExceptionCollector() collector.collect(IllegalArgumentException()) assertTrue { collector.getLast() is IllegalArgumentException } assertEquals(1, collector.asSequence().count()) } @Test fun `can collect suppressed`() { val collector = ExceptionCollector() collector.collect(IllegalArgumentException()) collector.collect(IllegalStateException()) assertIs<IllegalStateException>(collector.getLast()) assertTrue { collector.getLast()!!.suppressedExceptions[0] is IllegalArgumentException } assertEquals(2, collector.asSequence().count()) } @Test fun `can collect suppressed nested`() { val collector = ExceptionCollector() collector.collect(IndexOutOfBoundsException()) collector.collect(IllegalArgumentException()) collector.collect(IllegalStateException()) assertIs<IllegalStateException>(collector.getLast()) assertTrue { collector.getLast()!!.suppressedExceptions[0] is IllegalArgumentException } assertTrue { collector.getLast()!!.suppressedExceptions[1] is IndexOutOfBoundsException } assertEquals(3, collector.asSequence().count()) } @Test fun `ignore same exception`() { val collector = ExceptionCollector() val exception = Exception() collector.collect(exception) collector.collect(exception) collector.collect(exception) assertSame(exception, collector.asSequence().last()) assertEquals(0, collector.getLast()!!.suppressedExceptions.size) assertEquals(1, collector.asSequence().count()) } @Test fun `ignore exception with same stacktrace and preserve first occurrence`() { val exceptions = mutableListOf<Exception>() repeat(5) { id -> exceptions.add(Exception("#$id")) } val collector = ExceptionCollector() exceptions.forEach { exception -> collector.collect(exception) } assertEquals(0, collector.getLast()!!.suppressedExceptions.size) assertEquals(1, collector.asSequence().count()) assertSame(exceptions.first(), collector.getLast()) assertEquals("#0", collector.getLast()!!.message) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/ExternalImageTest.kt ================================================ /* * Copyright 2019-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.utils import kotlin.test.Test import kotlin.test.assertEquals internal class ExternalImageTest { @Test fun testByteArrayGet() { assertEquals("0F", byteArrayOf(0x0f)[0, 0]) assertEquals("10", byteArrayOf(0x10)[0, 0]) assertEquals("0FFE", byteArrayOf(0x0F, 0xFE.toByte())[0, 1]) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/HexToBytesTest.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertFailsWith class HexToBytesTest { private fun Byte.Companion.parseFromHexChunk(char1: String): Byte = parseFromHexChunk(char1[0], char1[1]) @Test fun `Byte parseFromHexChunk`() { assertEquals(0xff.toByte(), Byte.parseFromHexChunk("FF")) assertEquals(0xff.toByte(), Byte.parseFromHexChunk("ff")) assertEquals(0xff.toByte(), Byte.parseFromHexChunk("fF")) assertEquals(0xff.toByte(), Byte.parseFromHexChunk("Ff")) assertEquals(0x00.toByte(), Byte.parseFromHexChunk("00")) assertEquals(0x0f.toByte(), Byte.parseFromHexChunk("0f")) assertEquals(0x34.toByte(), Byte.parseFromHexChunk("34")) assertEquals(0x7f.toByte(), Byte.parseFromHexChunk("7f")) } @Test fun `test countHexBytes`() { assertEquals(0, "".countHexBytes()) assertEquals(1, "01".countHexBytes()) assertEquals(1, "FF".countHexBytes()) assertEquals(1, "ff".countHexBytes()) assertEquals(1, "Ff".countHexBytes()) assertEquals(1, "fF".countHexBytes()) assertEquals(1, "0F".countHexBytes()) assertEquals(1, "F0".countHexBytes()) assertEquals(1, "0f".countHexBytes()) assertEquals(1, "f0".countHexBytes()) assertEquals(1, "01 ".countHexBytes()) assertEquals(1, "FF ".countHexBytes()) assertEquals(1, "ff ".countHexBytes()) assertEquals(1, "Ff ".countHexBytes()) assertEquals(1, "fF ".countHexBytes()) assertEquals(1, "0F ".countHexBytes()) assertEquals(1, "F0 ".countHexBytes()) assertEquals(1, "0f ".countHexBytes()) assertEquals(1, "f0 ".countHexBytes()) assertEquals(1, " 01 ".countHexBytes()) assertEquals(1, " FF ".countHexBytes()) assertEquals(1, " ff ".countHexBytes()) assertEquals(1, " Ff ".countHexBytes()) assertEquals(1, " fF ".countHexBytes()) assertEquals(1, " 0F ".countHexBytes()) assertEquals(1, " F0 ".countHexBytes()) assertEquals(1, " 0f ".countHexBytes()) assertEquals(1, " f0 ".countHexBytes()) assertEquals(1, " 01 ".countHexBytes()) assertEquals(1, " FF ".countHexBytes()) assertEquals(1, " ff ".countHexBytes()) assertEquals(1, " Ff ".countHexBytes()) assertEquals(1, " fF ".countHexBytes()) assertEquals(1, " 0F ".countHexBytes()) assertEquals(1, " F0 ".countHexBytes()) assertEquals(1, " 0f ".countHexBytes()) assertEquals(1, " f0 ".countHexBytes()) assertEquals(2, " 01 01 ".countHexBytes()) assertEquals(2, " FF FF ".countHexBytes()) assertEquals(2, " ff ff ".countHexBytes()) assertEquals(2, " Ff Ff ".countHexBytes()) assertEquals(2, " fF fF ".countHexBytes()) assertEquals(2, " 0F 0F ".countHexBytes()) assertEquals(2, " F0 F0 ".countHexBytes()) assertEquals(2, " 0f 0f ".countHexBytes()) assertEquals(2, " f0 f0 ".countHexBytes()) assertEquals(2, " 0101 ".countHexBytes()) assertEquals(2, " FFFF ".countHexBytes()) assertEquals(2, " ffff ".countHexBytes()) assertEquals(2, " FfFf ".countHexBytes()) assertEquals(2, " fFfF ".countHexBytes()) assertEquals(2, " 0F0F ".countHexBytes()) assertEquals(2, " F0F0 ".countHexBytes()) assertEquals(2, " 0f0f ".countHexBytes()) assertEquals(2, " f0f0 ".countHexBytes()) assertFailsWith<IllegalArgumentException> { "1".countHexBytes() } assertFailsWith<IllegalArgumentException> { "0_1".countHexBytes() } assertFailsWith<IllegalArgumentException> { "0 1".countHexBytes() } assertFailsWith<IllegalArgumentException> { "g".countHexBytes() } assertFailsWith<IllegalArgumentException> { "_".countHexBytes() } assertFailsWith<IllegalArgumentException> { "123".countHexBytes() } assertFailsWith<IllegalArgumentException> { "0x12".countHexBytes() } assertFailsWith<IllegalArgumentException> { "12 3".countHexBytes() } } @Test fun `test hexToBytes`() { assertContentEquals(byteArrayOf(0xff.toByte()), "FF".hexToBytes()) assertContentEquals(byteArrayOf(0xff.toByte()), "ff".hexToBytes()) assertContentEquals(byteArrayOf(0xff.toByte()), "fF".hexToBytes()) assertContentEquals(byteArrayOf(0xff.toByte()), "Ff".hexToBytes()) assertContentEquals(byteArrayOf(0x00.toByte()), "00".hexToBytes()) assertContentEquals(byteArrayOf(0x0f.toByte()), "0f".hexToBytes()) assertContentEquals(byteArrayOf(0x34.toByte()), "34".hexToBytes()) assertContentEquals(byteArrayOf(0x7f.toByte()), "7f".hexToBytes()) assertContentEquals(byteArrayOf(0xff.toByte(), 0xff.toByte()), " FF FF ".hexToBytes()) assertContentEquals(byteArrayOf(0xff.toByte(), 0xff.toByte()), " ff ff ".hexToBytes()) assertContentEquals(byteArrayOf(0xff.toByte(), 0xff.toByte()), " fF fF ".hexToBytes()) assertContentEquals(byteArrayOf(0xff.toByte(), 0xff.toByte()), " Ff Ff ".hexToBytes()) assertContentEquals(byteArrayOf(0x00.toByte(), 0x00.toByte()), " 00 00 ".hexToBytes()) assertContentEquals(byteArrayOf(0x0f.toByte(), 0x0f.toByte()), " 0f 0f ".hexToBytes()) assertContentEquals(byteArrayOf(0x34.toByte(), 0x34.toByte()), " 34 34 ".hexToBytes()) assertContentEquals(byteArrayOf(0x7f.toByte(), 0x7f.toByte()), " 7f 7f ".hexToBytes()) } @Test fun `test hexToUBytes`() { // implementations of hexToBytes and hexToUBytes are very similar. assertContentEquals(ubyteArrayOf(0x7f.toUByte(), 0x7f.toUByte()), " 7f 7f ".hexToUBytes()) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/HtmlEscapeTest.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.Test import kotlin.test.assertEquals internal class HtmlEscapeTest { @Test fun testDecode() { val ALL = "&#xCF;&#xBD;&#8719;&#xAB;&uuml;&#8901;&Phi;&#8707;&#8804;&ccedil;&#x203E;&#x2209;&Eta;&#246;&#234;&#222;&#210;&Ocirc;&#xAA;&rceil;&icirc;&nsub;&#xCE;&#xBC;&#8805;&szlig;&and;&iacute;&#x3E;&macr;&#235;&#223;&#211;&#xBB;&#x3BA;&#247;&clubs;&#xDF;&zeta;&#xCD;&#8709;&#9002;&eta;&Prime;&infin;&#92;&Ograve;&ecirc;&Oslash;&#x2308;&#220;&Iuml;&#244;&#x3BB;&spades;&#232;&phi;&#xCC;&deg;&#xBA;&#xDE;&#9001;&ograve;&Otilde;&prod;&#x3C;&trade;&#x2190;&perp;&#x391;&#x2309;&#245;&#233;&#221;&#x3BC;&#xDD;&#8482;&#xCB;&ordm;&ang;&#xEF;&empty;&#242;&#230;&#919;&#169;&Upsilon;&#254;&#x2665;&#xEE;&Epsilon;&#8592;&#xDC;&#x2229;&#xCA;&#x2217;&#x2205;&cup;&hArr;&ntilde;&eth;&Delta;&#8472;&#8593;&#231;&#8230;&Acirc;&#8242;&#8254;&#918;&#255;&#x2666;&#243;&Eacute;&#xFF;&#xED;&#xDB;&acirc;&or;&uArr;&curren;&divide;&para;&thorn;&epsilon;&#60;&#8594;&hearts;&otimes;&#x192;&#252;&lowast;&#240;&#8243;&#929;&#179;&#917;&#167;&#xFE;&#x2207;&#xEC;&#xDA;&plusmn;&Tau;&egrave;&yen;&#8595;&#253;&tau;&#241;&asymp;&#168;&#928;&#916;&#x2208;&#x2329;&#xFD;&#xEB;&uarr;&otilde;&reg;&#219;&#207;&#982;&#xE1;&Oacute;&#8747;&#62;&Ecirc;&#8711;&#xF3;&Iota;&psi;&int;&#206;&#x396;&#208;&upsilon;&middot;&ge;&sim;&#xF2;&#xE0;&#x221D;&#8736;&#x220B;&#8712;&ucirc;&#8869;&piv;&ETH;&sube;&Lambda;&oslash;&#x397;&#9824;&#x2282;&uml;&#229;&eacute;&Psi;&#8713;&#x221E;&#8834;&#xF1;&pound;&alefsym;&Ccedil;&lang;&frac34;&cent;&Iacute;&#9827;&ouml;&#228;&Nu;&#216;&Alpha;&#x398;&#204;&#x2295;&#x2283;&#218;&#8835;&#xF0;&#x230A;&sub;&#8629;&#8968;&laquo;&sigmaf;&part;&Xi;&#x399;&#x25CA;&#217;&nu;&#205;&copy;&darr;&#xAF;&beta;&cap;&igrave;&#8800;&#8969;&#x2191;&#8715;&#8836;&omicron;&#8727;&frac12;&#x392;&#x223C;&#x222A;&Rho;&xi;&#202;&#x3BD;&#9829;&#238;&#226;&#214;&Mu;&#xAE;&#x3BF;&#8801;&#x211C;&#x2192;&AElig;&#8704;&Atilde;&#x393;&#x222B;&kappa;&#x3BE;&delta;&#239;&#227;&supe;&#215;&#203;&fnof;&mu;&#xBF;&#xAD;&#x2193;&rho;&sdot;&Omicron;&#8838;&aelig;&frac14;&#x394;&#x221A;&#224;&#212;&#200;&aring;&frasl;&#248;&lt;&#236;&Auml;&#xBE;&#xAC;&#x2194;&Gamma;&Omega;&#8706;&#8839;&nbsp;&#x232A;&#213;&euml;&ordf;&#201;&#8260;&#249;&#237;&#x395;&#225;&#954;&#x2022;&Aacute;&#x2264;&atilde;&#xA1;&brvbar;&#x3C2;&#xE9;&#193;&#xD7;&#181;&#xC5;&#xB3;&#x21D3;&rArr;&#967;&#955;&#965;&Zeta;&#953;&yacute;&#x2265;&#x3C3;&#xB2;&#xA0;&#x3B1;&lambda;&#194;&#182;&#xE8;&#170;&Igrave;&#xD6;&#xC4;&#x21D4;&#10;&diams;&sum;&sigma;&#978;&#966;&#964;&#9830;&#x39A;&#952;&#x3D6;&#x3B2;&rarr;&#x3C4;&#8656;&#xC3;&#xB1;&#x3A0;&Theta;&#xF9;&#xE7;&#8970;&#191;&#xD5;&#8764;&#8776;&amp;&upsih;&#977;&gamma;&dArr;&image;&#963;&#x2122;&#x39B;&#951;&#x3C5;&#x3A1;&#8657;&#x3B3;&#xD4;&#xC2;&#xB0;&#192;&#xF8;&#180;&#xE6;&#8971;&ugrave;&#x2135;&#x2026;&#x2284;&prime;&#x2260;&#962;&#950;&#8658;&#x230B;&lceil;&#xE5;&#xD3;&#xC1;&Ugrave;&#xF7;&#8730;&exist;&equiv;&sup;&loz;&#x2261;&#961;&not;&#x2297;&Ucirc;&#8659;&#8744;&#8756;&#190;&#xF6;&#xE4;&sect;&#xD2;&#x220F;&#xC0;&#x3D1;&oacute;&#38;&#x21D0;&#8501;&#8743;&real;&#x2032;&#209;&#x2044;&yuml;&#960;&#x2286;&lArr;&#8733;&#8745;&#x3D2;&#xF5;&#xE3;&#xD1;&#x3C0;&#x21D1;&#8853;&#402;&#x2033;&oline;&#x2287;&#xD0;&#8746;&Uuml;&#8722;&aacute;&#8734;&#8855;&#x3C1;&#xF4;&#xE2;&weierp;&#8721;&iota;&larr;&#x21D2;&lfloor;&nabla;&#x2200;&#x2660;&auml;&#9674;&theta;&#8596;&iquest;&#165;&#xA9;&#x3B8;&#250;&#x3A6;&#xEA;&gt;&#189;&#927;&#177;&#915;&forall;&iexcl;&#xFC;&crarr;&Uacute;&cong;&isin;&#x3A7;&#xA8;&#x3B9;&#251;&#8476;&Ouml;&#xFB;&#178;&#x2202;&#166;&#926;&thetasym;&#914;&acute;&times;&harr;&cedil;&Pi;&shy;&bull;&#187;&#175;&#xB9;&#x3A8;&#163;&#xA7;&#8465;&#xFA;&#x2118;&#x2203;&minus;&#x2663;&#949;&#199;&#937;&#925;&#913;&#x2227;&Icirc;&raquo;&omega;&Agrave;&#x22A5;&Chi;&Beta;&ne;&pi;&agrave;&#x3A9;&#8660;&#176;&#164;&THORN;&#xB8;&#xA6;&#188;&#948;&chi;&#936;&#924;&#x2228;&#x39C;&#x2111;&#x2220;&#x3B4;&#x3C6;&oplus;&Yacute;&#161;&#xA5;&uacute;&#197;&#185;&#xC9;&#173;&#xB7;&alpha;&#959;&#x2245;&#947;&rfloor;&Euml;&#935;&notin;&#923;&Aring;&ocirc;&#921;&#x39D;&Kappa;&#x3A3;&#x3B5;&#xB6;&#xA4;&sup3;&#8226;&#198;&Ntilde;&#186;&#174;&#x3C7;&#162;&#xC8;&Sigma;&#x2234;&iuml;&#958;&#946;&micro;&#934;&#922;&#x26;&#932;&#920;&hellip;&Egrave;&#x3A4;&#x21B5;&#x39E;&#183;&#xC7;&#171;&#xB5;&#xA3;&sup2;&#x3B6;&#195;&#xD9;&#x3C8;&#969;&#957;&#945;&#933;&#931;&rang;&#x2211;&prop;&ni;&radic;&#x22C5;&le;&#x39F;&#172;&#xD8;&#160;&sup1;&#xC6;&#xB4;&#xA2;&#x3C9;&#196;&#x3A5;&#8773;&#184;&#x3B7;&#x2248;&there4;&#x2212;&#968;&#956;" val RESP = "\u00cf\u00bd\u220f\u00ab\u00fc\u22c5\u03a6\u2203\u2264\u00e7\u203e\u2209\u0397\u00f6\u00ea\u00de\u00d2\u00d4\u00aa\u2309\u00ee\u2284\u00ce\u00bc\u2265\u00df\u2227\u00ed\u003e\u00af\u00eb\u00df\u00d3\u00bb\u03ba\u00f7\u2663\u00df\u03b6\u00cd\u2205\u232a\u03b7\u2033\u221e\u005c\u00d2\u00ea\u00d8\u2308\u00dc\u00cf\u00f4\u03bb\u2660\u00e8\u03c6\u00cc\u00b0\u00ba\u00de\u2329\u00f2\u00d5\u220f\u003c\u2122\u2190\u22a5\u0391\u2309\u00f5\u00e9\u00dd\u03bc\u00dd\u2122\u00cb\u00ba\u2220\u00ef\u2205\u00f2\u00e6\u0397\u00a9\u03a5\u00fe\u2665\u00ee\u0395\u2190\u00dc\u2229\u00ca\u2217\u2205\u222a\u21d4\u00f1\u00f0\u0394\u2118\u2191\u00e7\u2026\u00c2\u2032\u203e\u0396\u00ff\u2666\u00f3\u00c9\u00ff\u00ed\u00db\u00e2\u2228\u21d1\u00a4\u00f7\u00b6\u00fe\u03b5\u003c\u2192\u2665\u2297\u0192\u00fc\u2217\u00f0\u2033\u03a1\u00b3\u0395\u00a7\u00fe\u2207\u00ec\u00da\u00b1\u03a4\u00e8\u00a5\u2193\u00fd\u03c4\u00f1\u2248\u00a8\u03a0\u0394\u2208\u2329\u00fd\u00eb\u2191\u00f5\u00ae\u00db\u00cf\u03d6\u00e1\u00d3\u222b\u003e\u00ca\u2207\u00f3\u0399\u03c8\u222b\u00ce\u0396\u00d0\u03c5\u00b7\u2265\u223c\u00f2\u00e0\u221d\u2220\u220b\u2208\u00fb\u22a5\u03d6\u00d0\u2286\u039b\u00f8\u0397\u2660\u2282\u00a8\u00e5\u00e9\u03a8\u2209\u221e\u2282\u00f1\u00a3\u2135\u00c7\u2329\u00be\u00a2\u00cd\u2663\u00f6\u00e4\u039d\u00d8\u0391\u0398\u00cc\u2295\u2283\u00da\u2283\u00f0\u230a\u2282\u21b5\u2308\u00ab\u03c2\u2202\u039e\u0399\u25ca\u00d9\u03bd\u00cd\u00a9\u2193\u00af\u03b2\u2229\u00ec\u2260\u2309\u2191\u220b\u2284\u03bf\u2217\u00bd\u0392\u223c\u222a\u03a1\u03be\u00ca\u03bd\u2665\u00ee\u00e2\u00d6\u039c\u00ae\u03bf\u2261\u211c\u2192\u00c6\u2200\u00c3\u0393\u222b\u03ba\u03be\u03b4\u00ef\u00e3\u2287\u00d7\u00cb\u0192\u03bc\u00bf\u00ad\u2193\u03c1\u22c5\u039f\u2286\u00e6\u00bc\u0394\u221a\u00e0\u00d4\u00c8\u00e5\u2044\u00f8\u003c\u00ec\u00c4\u00be\u00ac\u2194\u0393\u03a9\u2202\u2287\u00a0\u232a\u00d5\u00eb\u00aa\u00c9\u2044\u00f9\u00ed\u0395\u00e1\u03ba\u2022\u00c1\u2264\u00e3\u00a1\u00a6\u03c2\u00e9\u00c1\u00d7\u00b5\u00c5\u00b3\u21d3\u21d2\u03c7\u03bb\u03c5\u0396\u03b9\u00fd\u2265\u03c3\u00b2\u00a0\u03b1\u03bb\u00c2\u00b6\u00e8\u00aa\u00cc\u00d6\u00c4\u21d4\u000a\u2666\u2211\u03c3\u03d2\u03c6\u03c4\u2666\u039a\u03b8\u03d6\u03b2\u2192\u03c4\u21d0\u00c3\u00b1\u03a0\u0398\u00f9\u00e7\u230a\u00bf\u00d5\u223c\u2248\u0026\u03d2\u03d1\u03b3\u21d3\u2111\u03c3\u2122\u039b\u03b7\u03c5\u03a1\u21d1\u03b3\u00d4\u00c2\u00b0\u00c0\u00f8\u00b4\u00e6\u230b\u00f9\u2135\u2026\u2284\u2032\u2260\u03c2\u03b6\u21d2\u230b\u2308\u00e5\u00d3\u00c1\u00d9\u00f7\u221a\u2203\u2261\u2283\u25ca\u2261\u03c1\u00ac\u2297\u00db\u21d3\u2228\u2234\u00be\u00f6\u00e4\u00a7\u00d2\u220f\u00c0\u03d1\u00f3\u0026\u21d0\u2135\u2227\u211c\u2032\u00d1\u2044\u00ff\u03c0\u2286\u21d0\u221d\u2229\u03d2\u00f5\u00e3\u00d1\u03c0\u21d1\u2295\u0192\u2033\u203e\u2287\u00d0\u222a\u00dc\u2212\u00e1\u221e\u2297\u03c1\u00f4\u00e2\u2118\u2211\u03b9\u2190\u21d2\u230a\u2207\u2200\u2660\u00e4\u25ca\u03b8\u2194\u00bf\u00a5\u00a9\u03b8\u00fa\u03a6\u00ea\u003e\u00bd\u039f\u00b1\u0393\u2200\u00a1\u00fc\u21b5\u00da\u2245\u2208\u03a7\u00a8\u03b9\u00fb\u211c\u00d6\u00fb\u00b2\u2202\u00a6\u039e\u03d1\u0392\u00b4\u00d7\u2194\u00b8\u03a0\u00ad\u2022\u00bb\u00af\u00b9\u03a8\u00a3\u00a7\u2111\u00fa\u2118\u2203\u2212\u2663\u03b5\u00c7\u03a9\u039d\u0391\u2227\u00ce\u00bb\u03c9\u00c0\u22a5\u03a7\u0392\u2260\u03c0\u00e0\u03a9\u21d4\u00b0\u00a4\u00de\u00b8\u00a6\u00bc\u03b4\u03c7\u03a8\u039c\u2228\u039c\u2111\u2220\u03b4\u03c6\u2295\u00dd\u00a1\u00a5\u00fa\u00c5\u00b9\u00c9\u00ad\u00b7\u03b1\u03bf\u2245\u03b3\u230b\u00cb\u03a7\u2209\u039b\u00c5\u00f4\u0399\u039d\u039a\u03a3\u03b5\u00b6\u00a4\u00b3\u2022\u00c6\u00d1\u00ba\u00ae\u03c7\u00a2\u00c8\u03a3\u2234\u00ef\u03be\u03b2\u00b5\u03a6\u039a\u0026\u03a4\u0398\u2026\u00c8\u03a4\u21b5\u039e\u00b7\u00c7\u00ab\u00b5\u00a3\u00b2\u03b6\u00c3\u00d9\u03c8\u03c9\u03bd\u03b1\u03a5\u03a3\u232a\u2211\u221d\u220b\u221a\u22c5\u2264\u039f\u00ac\u00d8\u00a0\u00b9\u00c6\u00b4\u00a2\u03c9\u00c4\u03a5\u2245\u00b8\u03b7\u2248\u2234\u2212\u03c8\u03bc" assertEquals( RESP, ALL.decodeHtmlEscape() ) } @Test fun testEncode() { val str = buildString { for (i in 1 until 2048) { append(i.toChar()) } } val escaped = str.encodeHtmlEscape() // println(escaped) assertEquals(escaped.decodeHtmlEscape(), str) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/ImageIdConversionTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.Test import kotlin.test.assertEquals internal class ImageIdConversionTest { @Test fun testConversions() { assertEquals( "{f8f1ab55-bf8e-4236-b55e-955848d7069f}.mirai", generateImageIdFromResourceId("/f8f1ab55-bf8e-4236-b55e-955848d7069f"), ) assertEquals( "{EFF4427C-E3D2-7DB6-B1D9-A8AB72E7A29C}.mirai", generateImageIdFromResourceId("/000000000-3666252994-EFF4427CE3D27DB6B1D9A8AB72E7A29C"), ) assertEquals( "{EF42A82D-8DB6-5D0F-4F11-68961D8DA5CB}.png", generateImageIdFromResourceId("{EF42A82D-8DB6-5D0F-4F11-68961D8DA5CB}.png"), ) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/LateinitMutablePropertyTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertSame internal class LateinitMutablePropertyTest { @Test fun canInitialize() { val value = Symbol("expected") val prop by lateinitMutableProperty { value } assertSame(value, prop) } @Test fun canOverride() { val value = Symbol("expected") val overrode = Symbol("override") var prop by lateinitMutableProperty { value } prop = overrode assertSame(overrode, prop) } @Test fun initializerCalledOnce() { val value = Symbol("expected") var counter = 0 val prop by lateinitMutableProperty { counter++ value } assertSame(value, prop) assertSame(value, prop) assertEquals(1, counter) } @Test fun setValuePrevailsOnCompetitionWithInitializer() = runTest { val verySlowInitializer = CompletableDeferred<Unit>() val override = Symbol("override") val initializer = Symbol("initializer") var prop by lateinitMutableProperty { runBlocking { yield(); verySlowInitializer.await() } initializer } launch { println(prop) } prop = override verySlowInitializer.complete(Unit) assertSame(override, prop) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/ResourceAccessLockTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.* internal class ResourceAccessLockTest { @Test fun testInitializedLockCannotReInit() { val lock = ResourceAccessLock() lock.setInitialized() assertFalse { lock.tryInitialize() } } @Test fun testUseFailedIfLockUninitializedOrLocked() { val lock = ResourceAccessLock() lock.setUninitialized() assertFalse { lock.tryUse() } lock.setLocked() assertFalse { lock.tryUse() } } @Test fun testLockFailedIfUninitialized() { val lock = ResourceAccessLock() lock.setUninitialized() assertFalse { lock.lockIfNotUsing() } } @Test fun testLockFailedIfUsing() { val lock = ResourceAccessLock() lock.setInitialized() assertTrue { lock.tryUse() } assertFalse { lock.lockIfNotUsing() } } @Test fun testLockUsedIfInitialized() { val lock = ResourceAccessLock() lock.setInitialized() assertTrue { lock.tryUse() } } @Test fun testRelease() { val lock = ResourceAccessLock() lock.setInitialized() assertFails { lock.release() } assertEquals(ResourceAccessLock.INITIALIZED, lock.currentStatus()) assertTrue { lock.tryUse() } lock.release() } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/SizedCacheTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.Test import kotlin.test.assertEquals internal class SizedCacheTest { @Test fun validIfCacheNotFilled() { val cache = SizedCache<String>(20) val list = mutableListOf<String>() repeat(10) { cache.emit("-$it") list.add("-$it") } assertEquals(list, cache.toList()) } @Test fun validIfNotUsed() { val cache = SizedCache<String>(20) assertEquals(emptyList(), cache.toList()) } @Test fun validIfFilled() { val cache = SizedCache<String>(20) val list = mutableListOf<String>() repeat(20) { cache.emit("-$it") list.add("-$it") } assertEquals(list, cache.toList()) } @Test fun chaosTest() { repeat(1000) { val size = (5..200).random() val list = mutableListOf<Int>() val cache = SizedCache<Int>(size) repeat((100..5000).random().coerceAtLeast(size)) { cache.emit(it) list.add(it) } assertEquals( list.subList(list.size - size, list.size).toMutableList(), cache.toMutableList(), ) } } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/TlvMapTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertContentEquals import kotlin.test.assertEquals import kotlin.test.assertTrue internal class TlvMapTest { private fun dumpTlvMap(map: TlvMap) = buildString { append("tlvMap {\n") map.forEach { (k, v) -> append(" ").append(k.toUHexString()).append(" = ").append(v.toUHexString()).append("\n") } append("}") } private fun assertTlvMapEquals( expected: TlvMap, actual: TlvMap, ) { assertEquals(expected.size, actual.size, "map size not match") expected.keys.forEach { key -> assertTrue("Missing key[$key] in actual") { actual.containsKey(key) } } actual.keys.forEach { key -> assertTrue("Missing key[$key] in expected") { expected.containsKey(key) } } expected.forEach { (key, value) -> assertContentEquals(value, actual[key]) } } @Test fun testTlvWriterNoLength() { testTlvWriter(true) } @Test fun testTlvWriterWithCount() { testTlvWriter(false) } private fun testTlvWriter(withCount: Boolean) { repeat(500) { val tlvMap = TlvMap() val rand = buildPacket { _writeTlvMap(Short.SIZE_BYTES, includeCount = withCount) { repeat(Random.nextInt().and(0xFF).coerceAtLeast(20)) { val nextKey = Random.nextInt().and(0xFF0) if (!tlvMap.containsKey(nextKey)) { val randData = ByteArray(Random.nextInt().and(0xFFF)) Random.nextBytes(randData) tlvMap[nextKey] = randData tlv(nextKey, randData) } } } }.also { pkg -> if (withCount) pkg.discardExact(2) }._readTLVMap() try { assertTlvMapEquals(tlvMap, rand) } catch (e: Throwable) { println("gen: " + dumpTlvMap(tlvMap)) println("read: " + dumpTlvMap(rand)) throw e } } } @Test fun testTlvWriterWithCounter() { val expected = buildPacket { writeShort(4) // count of TLVs writeShort(0x01) writeHexWithLength("66ccff") writeShort(0x04) writeHexWithLength("114514") writeShort(0x19) writeHexWithLength("198100") writeShort(0x233) writeHexWithLength("666666") }.readBytes() val actual = buildPacket { _writeTlvMap { tlv(0x001) { writeHex("66ccff") } tlv(0x004) { writeHex("114514") } tlv(0x019) { writeHex("198100") } tlv(0x233) { writeHex("666666") } println("counter = $counter") } }.readBytes() println(expected.toUHexString()) println(actual.toUHexString()) assertContentEquals(expected, actual) } private fun Output.writeHex(data: String) { writeFully(data.hexToBytes()) } private fun Output.writeHexWithLength(data: String) { val hxd = data.hexToBytes() writeShort(hxd.size.toShort()) writeFully(hxd) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/TrySafelyTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.errors.* import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFailsWith import kotlin.test.assertIs internal class TrySafelyTest { @Test fun `can run block`() { assertEquals( 1, trySafely( block = { 1 }, finally = { } ) ) } @Test fun `can run finally when no exception in block`() { var x = 0 trySafely( block = { }, finally = { x = 1 } ) assertEquals(1, x) } @Test fun `can run finally when exception in block`() { var x = 0 assertFailsWith<Exception> { trySafely( block = { throw Exception() }, finally = { x = 1 } ) } assertEquals(1, x) } @Test fun `can run finally catching`() { assertFailsWith<NoSuchElementException> { trySafely( block = { throw NoSuchElementException() }, finally = { throw IOException("") } ) }.let { e -> assertIs<IOException>(e.suppressedExceptions.single()) } } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/TypeSafeMapTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import net.mamoe.yamlkt.Yaml import net.mamoe.yamlkt.YamlBuilder import kotlin.test.Test import kotlin.test.assertEquals internal class TypeSafeMapTest { private val myKey = TypeKey<String>("test") private val myNullableKey = TypeKey<String?>("testNullable") private val myNullableKey2 = TypeKey<String?>("testNullable2") private val myKey2 = TypeKey<CharSequence>("test2") @Test fun `can set get`() { val map = createMutableTypeSafeMap() map[myKey] = "str" map[myKey2] = "str2" assertEquals(2, map.size) assertEquals("str", map[myKey]) assertEquals("str2", map[myKey2]) } @Test fun `test nulls`() { val map = createMutableTypeSafeMap() map[myNullableKey] = null map[myNullableKey2] = "str2" assertEquals(2, map.size) assertEquals(null, map[myNullableKey]) assertEquals("str2", map[myNullableKey2]) } @Test fun `key is inlined`() { val map = createMutableTypeSafeMap() map[TypeKey<String>("test")] = "str" map[TypeKey<String>("test")] = "str2" assertEquals(1, map.size) assertEquals("str2", map[TypeKey("test")]) } @Test fun `can toMap`() { val map = createMutableTypeSafeMap() map[myKey] = "str" map[myKey2] = "str2" assertEquals(2, map.size) val map1 = map.toMapBoxed() assertEquals(2, map1.size) assertEquals("str", map1[myKey]) assertEquals("str2", map1[myKey2]) } @Test fun `test serialization`() { val map = createMutableTypeSafeMap() map[myKey] = "str" map[myKey2] = "str2" assertEquals(2, map.size) val map1 = map.toMap() // Json does not support reflective serialization, so we use Yaml in JSON format val yaml = Yaml { classSerialization = YamlBuilder.MapSerialization.FLOW_MAP mapSerialization = YamlBuilder.MapSerialization.FLOW_MAP listSerialization = YamlBuilder.ListSerialization.FLOW_SEQUENCE stringSerialization = YamlBuilder.StringSerialization.DOUBLE_QUOTATION encodeDefaultValues = true } val string = yaml.encodeToString(map1) println(string) // { "test2": "str2" ,"test": "str" } val result = createMutableTypeSafeMap(Yaml.decodeMapFromString(string).cast()) assertEquals(map, result) } } ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/channels/OnDemandChannelTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils.channels import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import net.mamoe.mirai.utils.AtomicBoolean import net.mamoe.mirai.utils.testFramework.assertCoroutineSuspends import net.mamoe.mirai.utils.testFramework.assertNoCoroutineSuspension import kotlin.test.* class OnDemandChannelTest { /////////////////////////////////////////////////////////////////////////// // CoroutineScope lifecycle /////////////////////////////////////////////////////////////////////////// @Test fun attachScopeJob() { val job = SupervisorJob() val channel = OnDemandChannel<Int, Int>(job) { fail() } assertEquals(1, job.children.toList().size) channel.close() } @Test fun finishAfterInstantiation() { val supervisor = SupervisorJob() val channel = OnDemandChannel<Int, Int>(supervisor) { fail("ran") } assertEquals(1, supervisor.children.toList().size) val job = supervisor.children.single() assertEquals(true, job.isActive) channel.close() assertEquals(0, supervisor.children.toList().size) assertEquals(false, job.isActive) } @Test fun `cancel producer job on finish`() = runTest { // Actually, this case won't happen, because producer coroutine will be cancelled on [finish] lateinit var job: Job val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { job = currentCoroutineContext()[Job]!! emit(1) emit(1) emit(1) emit(1) fail() } channel.expectMore(1) channel.receiveOrNull() assertTrue { job.isActive } channel.close() assertFalse { job.isActive } yield() } /////////////////////////////////////////////////////////////////////////// // Producer Coroutine — Tickets /////////////////////////////////////////////////////////////////////////// @Test fun `producer receives initial ticket`() = runTest { val channel = OnDemandChannel(currentCoroutineContext()) { initialTicket -> assertEquals(1, initialTicket) emit(2) } channel.expectMore(1) channel.receiveOrNull() channel.close() } @Test fun `producer receives second ticket`() = runTest { val channel = OnDemandChannel(currentCoroutineContext()) { initialTicket -> assertEquals(1, initialTicket) assertEquals(2, emit(3)) } channel.expectMore(1) channel.receiveOrNull() channel.expectMore(2) channel.close() } @Test fun `producer receives third ticket`() = runTest { val channel = OnDemandChannel(currentCoroutineContext()) { initialTicket -> assertEquals(1, initialTicket) assertEquals(2, emit(4)) assertEquals(3, emit(5)) } channel.expectMore(1) channel.receiveOrNull() channel.expectMore(2) channel.receiveOrNull() channel.expectMore(3) channel.close() } /////////////////////////////////////////////////////////////////////////// // Consumer — Receive Correct Values /////////////////////////////////////////////////////////////////////////// @Test fun `receives correct first value`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { emit(3) } channel.expectMore(1) assertEquals(3, channel.receiveOrNull()) channel.close() } @Test fun `receives correct second value`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { emit(3) emit(4) } channel.expectMore(1) assertEquals(3, channel.receiveOrNull()) channel.expectMore(2) assertEquals(4, channel.receiveOrNull()) channel.close() } /////////////////////////////////////////////////////////////////////////// // expectMore/emit/receiveOrNull /////////////////////////////////////////////////////////////////////////// @Test fun `producer coroutine won't start until expectMore`() { val channel = OnDemandChannel<Int, Int> { fail() } channel.close() } @Test fun `producer coroutine starts iff expectMore`() = runTest { var started = false val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { // (1) assertEquals(false, started) started = true yield() // goto (2) fail() } assertFalse { started } assertTrue { channel.expectMore(1) } // launches the job, but it won't execute due to single parallelism yield() // goto (1) // (2) assertTrue { started } channel.close() } @Test fun `receiveOrNull does not suspend if value is ready`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { emit(1) } assertTrue { channel.expectMore(1) } yield() // run `emit` // now value is ready assertNoCoroutineSuspension { channel.receiveOrNull() } channel.close() } @Test fun `receiveOrNull does suspend if value is not ready`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { yield() emit(1) } assertTrue { channel.expectMore(1) } assertCoroutineSuspends { channel.receiveOrNull() } channel.close() } @Test fun `emit won't resume unless another expectMore`() = runTest { val canResume = AtomicBoolean(false) val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { emit(1) if (!canResume.value) fail("Emit should not resume") canResume.value = false } channel.expectMore(1) channel.receiveOrNull() canResume.value = true channel.expectMore(2) yield() // run producer assertEquals(false, canResume.value) channel.close() } /////////////////////////////////////////////////////////////////////////// // Operation while already finished /////////////////////////////////////////////////////////////////////////// @Test fun `expectMore and receiveOrNull while already finished just after instantiation`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { fail("Producer should not run") } channel.close() assertFalse { channel.expectMore(1) } assertNull(channel.receiveOrNull()) assertFalse { channel.expectMore(1) } assertFalse { channel.expectMore(1) } assertNull(channel.receiveOrNull()) assertNull(channel.receiveOrNull()) } @Test fun `expectMore and receiveOrNull while already finished`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { emit(1) } assertTrue { channel.expectMore(1) } assertNotNull(channel.receiveOrNull()) assertFalse { channel.isClosed } assertTrue { channel.expectMore(1) } // `expectMore` don't know if more values are available yield() // go to producer // now we must know producer has no more value assertTrue { channel.isClosed } assertNull(channel.receiveOrNull()) assertFalse { channel.expectMore(1) } assertNull(channel.receiveOrNull()) assertFalse { (channel as CoroutineOnDemandReceiveChannel).getScope().isActive } } @Test fun `emit while already finished`() { // Actually, this case won't happen, because producer coroutine will be cancelled on [finish] `cancel producer job on finish`() } @Test fun `producer exception closes channel then receiveOrNull throws`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { throw NoSuchElementException("Oops") } assertTrue { channel.expectMore(1) } assertFalse { channel.isClosed } assertIs<ChannelState.Producing<*, *>>(channel.state) assertFailsWith<ProducerFailureException> { println(channel.receiveOrNull()) }.also { assertIs<NoSuchElementException>(it.cause) } assertTrue { channel.isClosed } // The exception looks like this. // The first cause is stacktrace-recovered by coroutines, and the second is the original one. //net.mamoe.mirai.utils.channels.ProducerFailureException: Producer failed to produce a value, see cause // at net.mamoe.mirai.utils.channels.CoroutineOnDemandReceiveChannel.receiveOrNull(OnDemandChannelImpl.kt:164) // at net.mamoe.mirai.utils.channels.CoroutineOnDemandReceiveChannel$receiveOrNull$1.invokeSuspend(OnDemandChannelImpl.kt) // at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) // at kotlinx.coroutines.test.TestBuildersKt.runTest$default(Unknown Source) // at net.mamoe.mirai.utils.channels.OnDemandChannelTest.producer exception(OnDemandChannelTest.kt:273) // at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74) //Caused by: java.util.NoSuchElementException: Oops // at net.mamoe.mirai.utils.channels.OnDemandChannelTest$producer exception$1$channel$1.invokeSuspend(OnDemandChannelTest.kt:275) // at net.mamoe.mirai.utils.channels.OnDemandChannelTest$producer exception$1$channel$1.invoke(OnDemandChannelTest.kt) // at net.mamoe.mirai.utils.channels.OnDemandChannelTest$producer exception$1$channel$1.invoke(OnDemandChannelTest.kt) // at net.mamoe.mirai.utils.channels.CoroutineOnDemandReceiveChannel$Producer$1.invokeSuspend(OnDemandChannelImpl.kt:46) // (Coroutine boundary) // at net.mamoe.mirai.utils.channels.CoroutineOnDemandReceiveChannel.receiveOrNull(OnDemandChannelImpl.kt:162) // at net.mamoe.mirai.utils.channels.OnDemandChannelTest$producer exception$1.invokeSuspend(OnDemandChannelTest.kt:280) // at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTestCoroutine$2.invokeSuspend(TestBuilders.kt:212) //Caused by: java.util.NoSuchElementException: Oops // ... } @Test fun `producer exception closes channel then receiveOrNull throws in Producing state`() = runTest { val channel = OnDemandChannel<Int, Int>(currentCoroutineContext()) { throw NoSuchElementException("Oops") } assertTrue { channel.expectMore(1) } yield() // fail the channel first assertIs<ChannelState.Consuming<*, *>>(channel.state) assertFalse { channel.isClosed } // channel won't close until receiveOrNull assertFailsWith<ProducerFailureException> { println(channel.receiveOrNull()) }.also { assertIs<NoSuchElementException>(it.cause) } assertTrue { channel.isClosed } } } private val <T, V> OnDemandReceiveChannel<T, V>.state get() = (this as CoroutineOnDemandReceiveChannel<T, V>).getState() ================================================ FILE: mirai-core-utils/src/commonTest/kotlin/net/mamoe/mirai/utils/testFramework/AssertNoCoroutineSuspension.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils.testFramework import kotlinx.coroutines.* import kotlinx.coroutines.test.runTest import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.resume import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFails import kotlin.test.fail suspend inline fun <R> assertNoCoroutineSuspension( crossinline block: suspend () -> R, ): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } return withContext(Dispatchers.Default.limitedParallelism(1)) { val job = launch(start = CoroutineStart.UNDISPATCHED) { yield() fail("Expected no coroutine suspension") } val ret = block() job.cancel() ret } } /** * Executes [block], and asserts there happens at least one coroutine suspension in [block]. * * When the first coroutine suspension happens, [onSuspend] will be called. */ @OptIn(ExperimentalStdlibApi::class) suspend inline fun <R> assertCoroutineSuspends( noinline onSuspend: (suspend () -> Unit)? = null, crossinline block: suspend () -> R, ): R { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } val dispatcher = currentCoroutineContext()[CoroutineDispatcher] ?: Dispatchers.Main.limitedParallelism(1) return withContext(dispatcher.limitedParallelism(1)) { val job = launch(start = CoroutineStart.UNDISPATCHED) { yield() // goto block onSuspend?.invoke() } val ret = block() kotlin.test.assertTrue("Expected coroutine suspension") { job.isCompleted } job.cancel() ret } } class AssertCoroutineSuspensionTest { @Test fun `assertNoCoroutineSuspension no suspension`() = runTest { assertNoCoroutineSuspension {} } @Test fun `assertNoCoroutineSuspension suspend cancellable`() = runTest { assertFails { assertNoCoroutineSuspension { suspendCancellableCoroutine<Unit> { } } }.run { assertEquals("Expected no coroutine suspension", message) } } @Test fun `assertCoroutineSuspends suspend`() = runTest { assertCoroutineSuspends { suspendCancellableCoroutine { // resume after suspendCancellableCoroutine returns to create a suspension launch(start = CoroutineStart.UNDISPATCHED) { yield() it.resume(Unit) } } } } @Test fun `assertCoroutineSuspends no suspension`() = runTest { assertFails { assertCoroutineSuspends {} }.run { assertEquals("Expected coroutine suspension", message) } } } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/ByteArrayOp.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import io.ktor.utils.io.streams.* import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.InputStream import java.io.OutputStream import java.security.MessageDigest import java.util.zip.* public actual val DEFAULT_BUFFER_SIZE: Int get() = kotlin.io.DEFAULT_BUFFER_SIZE public fun InputStream.md5(): ByteArray { return digest("md5") } public fun InputStream.digest(algorithm: String): ByteArray { val digest = MessageDigest.getInstance(algorithm) digest.reset() use { input -> object : OutputStream() { override fun write(b: Int) { digest.update(b.toByte()) } override fun write(b: ByteArray, off: Int, len: Int) { digest.update(b, off, len) } }.use { output -> input.copyTo(output) } } return digest.digest() } public fun InputStream.sha1(): ByteArray { return digest("SHA-1") } public fun InputStream.sha256(): ByteArray { return digest("SHA-256") } public actual fun ByteArray.md5(offset: Int, length: Int): ByteArray { checkOffsetAndLength(offset, length) return MessageDigest.getInstance("MD5").apply { update(this@md5, offset, length) }.digest() } @JvmOverloads public actual fun ByteArray.sha1(offset: Int, length: Int): ByteArray { checkOffsetAndLength(offset, length) return MessageDigest.getInstance("SHA-1").apply { update(this@sha1, offset, length) }.digest() } @JvmOverloads public actual fun ByteArray.sha256(offset: Int, length: Int): ByteArray { checkOffsetAndLength(offset, length) return MessageDigest.getInstance("SHA-256").apply { update(this@sha256, offset, length) }.digest() } @JvmOverloads public actual fun ByteArray.gzip(offset: Int, length: Int): ByteArray { ByteArrayOutputStream().use { buf -> GZIPOutputStream(buf).use { gzip -> inputStream(offset, length).use { t -> t.copyTo(gzip) } } buf.flush() return buf.toByteArray() } } @JvmOverloads public actual fun ByteArray.ungzip(offset: Int, length: Int): ByteArray { return GZIPInputStream(inputStream(offset, length)).use { it.readBytes() } } public actual fun ByteArray.inflate(offset: Int, length: Int): ByteArray { checkOffsetAndLength(offset, length) if (length == 0) return ByteArray(0) val inflater = Inflater() inflater.reset() return InflaterInputStream(ByteArrayInputStream(this, offset, length), inflater).readBytes() // ByteArrayOutputStream().use { output -> // inflater.setInput(this, offset, length) // ByteArray(DEFAULT_BUFFER_SIZE).let { // while (!inflater.finished()) { // output.write(it, 0, inflater.inflate(it)) // } // } // // inflater.end() // return output.toByteArray() // } } @JvmOverloads public actual fun ByteArray.deflate(offset: Int, length: Int): ByteArray { checkOffsetAndLength(offset, length) if (length == 0) return ByteArray(0) val deflater = Deflater() deflater.setInput(this, offset, length) deflater.finish() ByteArray(DEFAULT_BUFFER_SIZE).let { return it.take(deflater.deflate(it)).toByteArray().also { deflater.end() } } } /** * Input will be closed. */ public actual fun Input.gzipAllAvailable(): ByteArray { return this.readBytes().gzip() // The following doesn't work, input's release won't becalled. Possibly Ktor bug. // return this.use { // ByteArrayOutputStream().use { buf -> // GZIPOutputStream(buf).use { gzip -> // copyTo(gzip.asOutput()) // } // buf.flush() // buf.toByteArray() // } // } } /** * Input will be closed. */ public actual fun Input.ungzipAllAvailable(): ByteArray { return GZIPInputStream(this.asStream()).use { it.readBytes() } } /** * Input will be closed. */ public actual fun Input.inflateAllAvailable(): ByteArray { return this.inflateInput().use { it.readBytes() } } /** * Input will be closed. */ public actual fun Input.deflateAllAvailable(): ByteArray { return this.deflateInput().use { it.readBytes() } } private fun Input.asStream(): InputStream = object : InputStream() { override fun read(): Int { if (endOfInput) return -1 return readByte().toIntUnsigned() } override fun read(buffer: ByteArray, offset: Int, length: Int): Int { if (this@asStream.endOfInput) return -1 return readAvailable(buffer, offset, length) } override fun skip(count: Long): Long = discard(count) override fun close() { this@asStream.close() } } /** * [source] will be closed on returned [Input.close] */ @Suppress("FunctionName") public actual fun GzipDecompressionInput(source: Input): Input { return GZIPInputStream(source.asStream()).asInput() } /** * [source] will be closed on returned [Input.close] */ @Suppress("FunctionName") public actual fun InflateInput(source: Input): Input { val inflater = Inflater() inflater.reset() return InflaterInputStream(source.asStream(), inflater).asInput() } /** * [source] will be closed on returned [Input.close] */ @Suppress("FunctionName") public actual fun DeflateInput(source: Input): Input { val deflater = Deflater() deflater.reset() return DeflaterInputStream(source.asStream(), deflater).asInput() } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Clock.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils public actual inline fun measureTimeMillis(block: () -> Unit): Long = kotlin.system.measureTimeMillis(block) ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Closeable.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.use as ktorUse public actual typealias Closeable = java.io.Closeable @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") @kotlin.internal.LowPriorityInOverloadResolution public actual inline fun <C : Closeable, R> C.use(block: (C) -> R): R = ktorUse(block) public actual fun Closeable.asKtorCloseable(): io.ktor.utils.io.core.Closeable = this public actual fun io.ktor.utils.io.core.Closeable.asMiraiCloseable(): Closeable = this ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Collections.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import java.util.* import java.util.concurrent.CopyOnWriteArraySet import kotlin.reflect.KClass public actual fun <T> Collection<T>.asImmutable(): Collection<T> { return Collections.unmodifiableCollection(this) } @Suppress("NOTHING_TO_INLINE") public actual inline fun <T> List<T>.asImmutable(): List<T> { return Collections.unmodifiableList(this) } @Suppress("NOTHING_TO_INLINE") public actual inline fun <T> Set<T>.asImmutable(): Set<T> { return Collections.unmodifiableSet(this) } @Suppress("NOTHING_TO_INLINE") public inline fun <K, V> Map<K, V>.asImmutable(): Map<K, V> { return Collections.unmodifiableMap(this) } @Suppress("FunctionName") public actual fun <K : Any, V> ConcurrentHashMap(): MutableMap<K, V> { return java.util.concurrent.ConcurrentHashMap() } public actual typealias LinkedList<E> = java.util.LinkedList<E> public actual typealias MutableDeque<E> = java.util.Deque<E> public actual typealias MutableQueue<E> = java.util.Queue<E> @Suppress("FunctionName") public actual fun <K : Enum<K>, V> EnumMap(clazz: KClass<K>): MutableMap<K, V> { return EnumMap(clazz.java) } @Suppress("FunctionName") public actual fun <E> ConcurrentSet(): MutableSet<E> { return CopyOnWriteArraySet() } /** * Same as [MutableCollection.addAll]. * * Adds all the elements of the specified enumeration to this collection. * @return true if any of the specified elements was added to the collection, false if the collection was not modified. */ public fun <T> MutableCollection<T>.addAll(enumeration: Enumeration<T>): Boolean { var addResult = false while (enumeration.hasMoreElements()) { addResult = this.add(enumeration.nextElement()) } return addResult } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/ConcurrentLinkedQueue.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils @Suppress("FunctionName") public actual fun <E> ConcurrentLinkedDeque(): MutableDeque<E> { return java.util.concurrent.ConcurrentLinkedDeque() } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/CoroutineUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.runInterruptible public actual suspend inline fun <R> runBIO( noinline block: () -> R, ): R = runInterruptible(context = Dispatchers.IO, block = block) public actual suspend inline fun <T, R> T.runBIO( crossinline block: T.() -> R, ): R = runInterruptible(context = Dispatchers.IO, block = { block() }) ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/ExceptionCollector.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils internal actual fun hash(e: Throwable): Long { return e.stackTrace.fold(0L) { acc, stackTraceElement -> acc * 31 + hash(stackTraceElement).toLongUnsigned() } } private fun hash(element: StackTraceElement): Int { return element.lineNumber.hashCode() xor element.className.hashCode() xor element.methodName.hashCode() } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Files.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import java.io.File 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() } public fun File.touch(): File = apply { parentFile?.mkdirs() createNewFile() } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/IO.jvm.shared.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.utils import java.io.InputStream private fun dropContent0(stream: InputStream, buffer: ByteArray) { while (true) { val len = stream.read(buffer) if (len == -1) break } } public fun InputStream.dropContent( buffer: Int = 2048, close: Boolean = false, ) { if (close) { dropContent0(this, ByteArray(buffer)) } else { this.use { dropContent0(it, ByteArray(buffer)) } } } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/JvmNioBuffer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") @file:Suppress("NOTHING_TO_INLINE", "UsePropertyAccessSyntax") package net.mamoe.mirai.utils import java.nio.Buffer import java.nio.ByteBuffer import java.nio.CharBuffer import java.nio.charset.Charset public inline var Buffer.pos: Int get() = position() set(value) { position(value) } public inline val Buffer.remaining: Int get() = remaining() public inline var Buffer.limit: Int get() = limit() set(value) { limit(value) } public inline fun Buffer.hasRemaining(size: Int): Boolean = remaining >= size public inline fun ByteBuffer.read(): Byte = get() public inline fun ByteBuffer.readInt(): Int = getInt() public inline fun ByteBuffer.readDouble(): Double = getDouble() public inline fun ByteBuffer.readShort(): Short = getShort() public inline fun ByteBuffer.readLong(): Long = getLong() public inline fun ByteBuffer.readChar(): Char = getChar() public inline fun ByteBuffer.readFloat(): Float = getFloat() public inline fun ByteBuffer.readBytes(dst: ByteArray) { get(dst) } public fun ByteBuffer.readBytes(): ByteArray { val rsp = ByteArray(remaining) readBytes(rsp) return rsp } public fun ByteBuffer.readToChars(charset: Charset = Charsets.UTF_8): CharBuffer { return charset.decode(this) } public fun ByteBuffer.readString(charset: Charset = Charsets.UTF_8): String { return readToChars(charset).toString() } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/MiraiFile.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import io.ktor.utils.io.core.* import io.ktor.utils.io.streams.* import java.io.File public actual interface MiraiFile { /** * Name of this file or directory. Can be '.' and '..' if created by */ public actual val name: String /** * Parent of this file or directory. */ public actual val parent: MiraiFile? /** * Input path from [create]. */ public actual val path: String /** * Normalized absolute [path]. */ public actual val absolutePath: String public actual val length: Long public actual val isFile: Boolean public actual val isDirectory: Boolean public actual fun exists(): Boolean /** * Resolves a [MiraiFile] representing the [path] based on this [MiraiFile]. Result path is not guaranteed to be normalized. */ public actual fun resolve(path: String): MiraiFile public actual fun resolve(file: MiraiFile): MiraiFile public actual fun createNewFile(): Boolean public actual fun delete(): Boolean public actual fun mkdir(): Boolean public actual fun mkdirs(): Boolean public actual fun input(): Input public actual fun output(): Output public actual companion object { public actual fun create(path: String): MiraiFile { return File(path).asMiraiFile() } public actual fun getWorkingDir(): MiraiFile { return create( System.getProperty("user.dir") ?: throw IllegalStateException("System property 'user.dir' is not available") ) } } } public actual fun MiraiFile.deleteRecursively(): Boolean { return this.toJvmFile().deleteRecursively() } public fun File.asMiraiFile(): MiraiFile { return JvmFileAsMiraiFile(this) } public fun MiraiFile.toJvmFile(): File { if (this is JvmFileAsMiraiFile) { return jvmFile } return File(absolutePath) } internal class JvmFileAsMiraiFile( internal val jvmFile: File ) : MiraiFile { override val name: String get() = jvmFile.name override val parent: MiraiFile? get() = jvmFile.parentFile?.asMiraiFile() override val path: String get() = jvmFile.path override val absolutePath: String get() = jvmFile.absolutePath override val length: Long get() = jvmFile.length() override val isFile: Boolean get() = jvmFile.isFile override val isDirectory: Boolean get() = jvmFile.isDirectory override fun exists(): Boolean = jvmFile.exists() override fun resolve(path: String): MiraiFile = jvmFile.resolve(path).asMiraiFile() override fun resolve(file: MiraiFile): MiraiFile = jvmFile.resolve(file.absolutePath).asMiraiFile() override fun createNewFile(): Boolean = jvmFile.createNewFile() override fun delete(): Boolean = jvmFile.delete() override fun mkdir(): Boolean = jvmFile.mkdir() override fun mkdirs(): Boolean = jvmFile.mkdirs() override fun input(): Input = jvmFile.inputStream().asInput() override fun output(): Output = jvmFile.outputStream().asOutput() } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Reflections.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.reflect.KClass import kotlin.reflect.KParameter public fun <T : Any> KClass<T>.createInstanceOrNull(): T? { objectInstance?.let { return it } val noArgsConstructor = constructors.singleOrNull { it.parameters.all(KParameter::isOptional) } ?: return null return noArgsConstructor.callBy(emptyMap()) } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Resources.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils @TestOnly public fun readResource(url: String): String = Thread.currentThread().contextClassLoader?.getResourceAsStream(url)?.readBytes()?.decodeToString() ?: error("Could not find resource '$url'") ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/SecretsProtection.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.builtins.serializer import java.nio.ByteBuffer import java.util.concurrent.atomic.AtomicIntegerFieldUpdater import java.util.concurrent.locks.Lock import java.util.concurrent.locks.ReentrantLock internal actual object SecretsProtectionPlatform { private class NativeBufferWithLock( @JvmField val buffer: ByteBuffer, val lock: Lock = ReentrantLock(), @JvmField @field:Volatile var lowRemainingHit: Int = 0, @JvmField @field:Volatile var unusedHit: Int = 0, ) { companion object { internal val lowRemainingHitUpdater = AtomicIntegerFieldUpdater.newUpdater( NativeBufferWithLock::class.java, "lowRemainingHit" ) internal val unusedHitUpdater = AtomicIntegerFieldUpdater.newUpdater( NativeBufferWithLock::class.java, "unusedHit" ) } } private val bufferSize = systemProp( "mirai.secrets.protection.buffer.size", 0 ).toInt().coerceAtLeast(2048) private val lowRemainingThreshold = bufferSize / 128 private val lowRemainingHitThreshold = systemProp( "mirai.secrets.protection.threshold.low.remaining.hit", 10 ).toInt().coerceAtLeast(1) private val pool = ConcurrentLinkedDeque<NativeBufferWithLock>() init { val tmbuffer = ByteBuffer.allocateDirect(bufferSize) if (!systemProp("mirai.secrets.protection.ignore.warning", false)) { if (tmbuffer.javaClass === ByteBuffer.allocate(1).javaClass) { val ps = System.err synchronized(ps) { ps.println("========================================================================================") ps.println("Mirai SecretsProtection WARNING:") ps.println() ps.println("当前 JRE 实现没有为 `ByteBuffer.allocateDirect` 直接分配本地内存, 请更换其他 JRE.") ps.println("这很有可能导致您的密码等敏感信息被带入内存报告中") ps.println("可添加 JVM 参数 -Dmirai.secrets.protection.ignore.warning=true 来忽略此警告") ps.println() ps.println("Current JRE Implementation not using native memory for `ByteBuffer.allocateDirect`.") ps.println("Please use another JRE.") ps.println("It may cause your passwords to be dumped by other processes.") ps.println("Suppress this warning by adding jvm option -Dmirai.secrets.protection.ignore.warning=true") ps.println() ps.println("========================================================================================") } } } pool.add(NativeBufferWithLock(tmbuffer)) } /* Implementation note: 1. 如果数据超过单个缓冲区大小,直接分配系统内存并返回 2. 查找首个可以放下数据的缓冲区,放入缓冲区并返回对应镜像 3. 如果没有可用缓冲区,分配新缓冲区并加入缓冲区池内 */ @JvmStatic fun allocate(size: Int): ByteBuffer { if (size >= bufferSize) { return ByteBuffer.allocateDirect(size) } fun putInto(buffer: NativeBufferWithLock): ByteBuffer { // @context: buffer.locked = true // @context: buffer.remaining >= buffer.size // 返回存储 data 的数据的 DirectByteBuffer // ByteBuffer.slice(): DirectByteBuffer[start=pos, end=limit] val mirror = buffer.buffer.let { buffer0 -> buffer0.limit = buffer0.pos + size buffer0.slice() } // 原始缓冲区复位 buffer.buffer.let { buf -> buf.limit = buf.capacity() } buffer.buffer.pos += size // 此缓冲区已无可用空间 if (!buffer.buffer.hasRemaining()) { pool.remove(buffer) } return mirror } pool.forEach bufferLoop@{ buffer -> val bufferRemaining = buffer.buffer.remaining if (bufferRemaining >= size) { if (buffer.lock.tryLock()) { try { if (buffer.buffer.hasRemaining(size)) { NativeBufferWithLock.unusedHitUpdater.getAndIncrement(buffer) return putInto(buffer) } } finally { buffer.lock.unlock() } } } NativeBufferWithLock.unusedHitUpdater.getAndDecrement(buffer) // OOM Avoid if (bufferRemaining <= lowRemainingThreshold) { NativeBufferWithLock.lowRemainingHitUpdater.getAndDecrement(buffer) if (buffer.lowRemainingHit >= lowRemainingHitThreshold) { pool.remove(buffer) } } if (buffer.unusedHit >= 20) { pool.remove(buffer) } } val newBuffer = NativeBufferWithLock(ByteBuffer.allocateDirect(bufferSize)) val rsp = putInto(newBuffer) if (newBuffer.buffer.hasRemaining()) { pool.add(newBuffer) } return rsp } @JvmStatic actual fun escape(data: ByteArray): Any { return allocate(data.size).also { it.put(data) it.pos = 0 } } actual fun impl_asString(data: Any): String { data as ByteBuffer return data.duplicate().readString() } actual fun impl_asByteArray(data: Any): ByteArray { data as ByteBuffer return data.duplicate().readBytes() } actual fun impl_getSize(data: Any): Int { return (data as ByteBuffer).remaining } actual object EscapedStringSerializer : KSerializer<SecretsProtection.EscapedString> by String.serializer().map( String.serializer().descriptor.copy("EscapedString"), deserialize = { SecretsProtection.EscapedString(escape(it.toByteArray())) }, serialize = { it.data.cast<ByteBuffer>().duplicate().readString() } ) actual object EscapedByteBufferSerializer : KSerializer<SecretsProtection.EscapedByteBuffer> by ByteArraySerializer().map( ByteArraySerializer().descriptor.copy("EscapedByteBuffer"), deserialize = { SecretsProtection.EscapedByteBuffer(escape(it)) }, serialize = { it.data.cast<ByteBuffer>().duplicate().readBytes() } ) } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Serialization.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.BinaryFormat import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.StringFormat import java.io.File ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Services.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import java.util.* import kotlin.reflect.KClass import kotlin.reflect.full.createInstance private enum class LoaderType { JDK, BOTH, FALLBACK, } private val loaderType = when (systemProp("mirai.service.loader", "both")) { "jdk" -> LoaderType.JDK "both" -> LoaderType.BOTH "fallback" -> LoaderType.FALLBACK else -> throw IllegalStateException("cannot find a service loader, mirai.service.loader must be both, jdk or fallback (default by both)") } @Suppress("UNCHECKED_CAST") public actual fun <T : Any> loadService(clazz: KClass<out T>, fallbackImplementation: String?): T { val fallbackService by lazy { Services.firstImplementationOrNull(Services.qualifiedNameOrFail(clazz)) as T? } val jdkService by lazy { ServiceLoader.load(clazz.java).firstOrNull()?.let { return@lazy it } ServiceLoader.load(clazz.java, clazz.java.classLoader).firstOrNull() } var suppressed: Throwable? = null val services by lazy { when (loaderType) { LoaderType.JDK -> jdkService LoaderType.BOTH -> jdkService ?: fallbackService LoaderType.FALLBACK -> fallbackService }?.let { return@lazy it } if (fallbackImplementation != null) { runCatching { findCreateInstance<T>(fallbackImplementation) }.onFailure { suppressed = it }.getOrNull() } else null } return Services.getOverrideOrNull(clazz) ?: services ?: throw NoSuchElementException("Could not find an implementation for service class ${clazz.qualifiedName}").apply { if (suppressed != null) addSuppressed(suppressed!!) } } private fun <T : Any> findCreateInstance(fallbackImplementation: String): T { return Class.forName(fallbackImplementation).cast<Class<out T>>().kotlin.run { objectInstance ?: createInstance() } } public actual fun <T : Any> loadServiceOrNull(clazz: KClass<out T>, fallbackImplementation: String?): T? { return runCatching { loadService(clazz, fallbackImplementation) }.getOrNull() } @Suppress("UNCHECKED_CAST") public actual fun <T : Any> loadServices(clazz: KClass<out T>): Sequence<T> { fun fallBackServicesSeq(): Sequence<T> { return Services.implementations(Services.qualifiedNameOrFail(clazz)).orEmpty() .map { it.value as T } } fun jdkServices(): Sequence<T> = sequence { val current = ServiceLoader.load(clazz.java).iterator() if (current.hasNext()) { yieldAll(current) } else { yieldAll(ServiceLoader.load(clazz.java, clazz.java.classLoader)) } } fun bothServices(): Sequence<T> = sequence { Services.getOverrideOrNull(clazz)?.let { yield(it) } var jdkServices = ServiceLoader.load(clazz.java).toList() if (jdkServices.isEmpty()) { jdkServices = ServiceLoader.load(clazz.java, clazz.java.classLoader).toList() } yieldAll(jdkServices) Services.implementationsDirectly(Services.qualifiedNameOrFail(clazz)).asSequence() .filter { impl -> // Drop duplicated jdkServices.none { it.javaClass.name == impl.implementationClass } } .forEach { yield(it.instance.value as T) } } return when (loaderType) { LoaderType.JDK -> jdkServices() LoaderType.BOTH -> bothServices() LoaderType.FALLBACK -> fallBackServicesSeq() } } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/StandardUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import java.net.Inet4Address internal actual fun isSameClassPlatform(object1: Any, object2: Any): Boolean { return object1.javaClass == object2.javaClass } public actual fun localIpAddress(): String = runCatching { Inet4Address.getLocalHost().hostAddress }.getOrElse { "192.168.1.123" } public actual fun availableProcessors(): Int = Runtime.getRuntime().availableProcessors() ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/Streams.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.utils import kotlinx.coroutines.CancellableContinuation import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.suspendCancellableCoroutine import java.util.* import java.util.Spliterators.AbstractSpliterator import java.util.concurrent.ArrayBlockingQueue import java.util.function.Consumer import java.util.stream.Stream import java.util.stream.StreamSupport import kotlin.coroutines.* import kotlin.streams.asStream @JvmSynthetic public inline fun <T> stream(@BuilderInference noinline block: suspend SequenceScope<T>.() -> Unit): Stream<T> = sequence(block).asStream() @Suppress("RemoveExplicitTypeArguments") public object JdkStreamSupport { private class CompleteToken(val error: Throwable?) { override fun toString(): String { return "CompleteToken[$error]" } } private val NULL_PLACEHOLDER = Symbol("null")!! /* Implementation: Spliterator.tryAdvance(): - Resume coroutine (*Wait for collector.emit*) - Re-Suspend flow - Put response to queue - Fire response to jdk consumer Completion & Exception caught: (* Spliterator.tryAdvance(): Resume coroutine *) (* No more values or exception thrown. *) (* completion called *) - Put the exception or the completion token to queue - Throw exception in Spliterator.tryAdvance() if possible - Return false in Spliterator.tryAdvance() */ public fun <T> Flow<T>.toStream( context: CoroutineContext = EmptyCoroutineContext, ): Stream<T> { val spliterator = FlowSpliterator( flow = this, coroutineContext = context, ) return StreamSupport.stream(spliterator, false).onClose { spliterator.cancelled = true spliterator.nextStep?.let { nextStep -> if (nextStep is CancellableContinuation<*>) { nextStep.cancel() } else { nextStep.resumeWithException(CancellationException()) } } spliterator.nextStep = null } } private class FlowSpliterator<T>( private val flow: Flow<T>, private val coroutineContext: CoroutineContext, ) : AbstractSpliterator<T>( Long.MAX_VALUE, Spliterator.ORDERED or Spliterator.IMMUTABLE ) { private val queue = ArrayBlockingQueue<Any?>(1) private var completed = false @JvmField var cancelled = false @JvmField var nextStep: Continuation<Unit>? = run { val completion = object : Continuation<Unit> { override val context: CoroutineContext get() = coroutineContext override fun resumeWith(result: Result<Unit>) { nextStep = null completed = true queue.put(CompleteToken(result.exceptionOrNull())) } } return@run (suspend { flow.collect { item -> suspendCancellableCoroutine<Unit> { cont -> nextStep = cont queue.put(boxValue(item)) } } }).createCoroutine(completion) } private inline fun boxValue(value: Any?): Any { return value ?: NULL_PLACEHOLDER } private fun unboxResponse(value: Any?, action: Consumer<in T>): Boolean { if (value is CompleteToken) { // completion & exception caught value.error?.let { throw boxError(it) } completed = true return false // no more value available } if (value === NULL_PLACEHOLDER) { // null @Suppress("UNCHECKED_CAST") action.accept(null as T) } else { @Suppress("UNCHECKED_CAST") action.accept(value as T) } return true } override fun tryAdvance(action: Consumer<in T>): Boolean { if (completed) return false if (queue.isNotEmpty()) { return unboxResponse(queue.take(), action) } if (cancelled) return false val step = nextStep!! nextStep = null step.resume(Unit) return unboxResponse(queue.take(), action) } } private fun boxError(error: Throwable): Throwable { return ExceptionInFlowException(error) } // @PublishedApi public open class ExceptionInFlowException : RuntimeException { public constructor() : super() public constructor(msg: String?) : super(msg) public constructor(cause: Throwable?) : super(cause) public constructor(msg: String?, cause: Throwable?) : super(msg, cause) } } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/StructureToStringTransformer.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils internal actual fun getPlatformDefaultStructureToStringTransformer(): StructureToStringTransformer? { return StructureToStringTransformerLegacy() } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/StructureToStringTransformerLegacy.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.serialization.Transient import java.lang.reflect.Modifier import kotlin.reflect.KClass import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 import kotlin.reflect.full.hasAnnotation import kotlin.reflect.jvm.javaField public class StructureToStringTransformerLegacy : StructureToStringTransformer { override fun transform(any: Any?): String = any._miraiContentToString() override fun transformAndDesensitize(any: Any?): String = transform(any) // desensitization not supported private val indent: String = " ".repeat(4) /** * 将所有元素加入转换为多行的字符串表示. */ private fun <T> Sequence<T>.joinToStringPrefixed(prefix: String, transform: (T) -> CharSequence): String { return this.joinToString(prefix = "$prefix$indent", separator = "\n$prefix$indent", transform = transform) } /** * 将内容格式化为较可读的字符串输出. * * 各数字类型及其无符号类型: 十六进制表示 + 十进制表示. e.g. `0x1000(4096)` * [ByteArray]: 十六进制表示, 通过 [ByteArray.toUHexString] * [Iterable], [Iterator], [Sequence]: 调用各自的 joinToString. * [Map]: 多行输出. 每行显示一个值. 递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 * `data class`: 调用其 [toString] * 其他类型: 反射获取它和它的所有来自 Mirai 的 super 类型的所有自有属性并递归调用 [_miraiContentToString]. 嵌套结构将会以缩进表示 */ @Suppress("FunctionName") // 这样就不容易被 IDE 提示 private fun Any?._miraiContentToString(prefix: String = ""): String = when (this) { is Unit -> "Unit" is UInt -> "0x" + this.toUHexString("") + "($this)" is UByte -> "0x" + this.toUHexString() + "($this)" is UShort -> "0x" + this.toUHexString("") + "($this)" is ULong -> "0x" + this.toUHexString("") + "($this)" is Int -> "0x" + this.toUHexString("") + "($this)" is Byte -> "0x" + this.toUHexString() + "($this)" is Short -> "0x" + this.toUHexString("") + "($this)" is Long -> "0x" + this.toUHexString("") + "($this)" is Boolean -> if (this) "true" else "false" is ByteArray -> { if (this.size == 0) "<Empty ByteArray>" else this.toUHexString() } is ShortArray -> { if (this.size == 0) "<Empty ShortArray>" else this.iterator()._miraiContentToString() } is IntArray -> { if (this.size == 0) "<Empty IntArray>" else this.iterator()._miraiContentToString() } is LongArray -> { if (this.size == 0) "<Empty LongArray>" else this.iterator()._miraiContentToString() } is FloatArray -> { if (this.size == 0) "<Empty FloatArray>" else this.iterator()._miraiContentToString() } is DoubleArray -> { if (this.size == 0) "<Empty DoubleArray>" else this.iterator()._miraiContentToString() } is Array<*> -> { if (this.size == 0) "<Empty Array>" else this.iterator()._miraiContentToString() } is BooleanArray -> { if (this.size == 0) "<Empty BooleanArray>" else this.iterator()._miraiContentToString() } is Iterable<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } is Iterator<*> -> this.asSequence() .joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } is Sequence<*> -> this.joinToString(prefix = "[", postfix = "]") { it._miraiContentToString(prefix) } is Map<*, *> -> this.entries.joinToString( prefix = "{", postfix = "}" ) { it.key._miraiContentToString(prefix) + "=" + it.value._miraiContentToString(prefix) } else -> { if (this == null) "null" else if (this::class.isData) this.toString() else { if (this::class.qualifiedName?.startsWith("net.mamoe.mirai.") == true) { this.contentToStringReflectively(prefix + indent) } else this.toString() /* (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + "{\n" + this::class.members.asSequence().filterIsInstance<KProperty<*>>().filter { !it.isSuspend && it.visibility == KVisibility.PUBLIC } .joinToStringPrefixed( prefix = indent ) { it.name + "=" + kotlin.runCatching { it.call(it).contentToString(indent) }.getOrElse { "<!>" } } */ } } } private fun KProperty1<*, *>.getValueAgainstPermission(receiver: Any): Any? { return this.javaField?.apply { isAccessible = true }?.get(receiver) } private fun Any.canBeIgnored(): Boolean { return when (this) { is String -> this.isEmpty() is ByteArray -> this.isEmpty() is Array<*> -> this.isEmpty() is Int -> this == 0 is Float -> this == 0f is Double -> this == 0.0 is Byte -> this == 0.toByte() is Short -> this == 0.toShort() is Long -> this == 0.toLong() else -> false } } private fun Any.contentToStringReflectively( prefix: String, filter: ((name: String, value: Any?) -> Boolean)? = null, ): String { val newPrefix = "$prefix " return (this::class.simpleName ?: "<UnnamedClass>") + "#" + this::class.hashCode() + " {\n" + this.allMembersFromSuperClassesMatching { it.qualifiedName?.startsWith("net.mamoe.mirai") == true } .distinctBy { it.name } .filterNot { it.name.contains("$") || it.name == "Companion" || it.isConst || it.name == "serialVersionUID" } .mapNotNull { val value = it.getValueAgainstPermission(this) ?: return@mapNotNull null if (filter != null) { if (!filter(it.name, value)) return@mapNotNull it.name to value else { return@mapNotNull null } } it.name to value } .joinToStringPrefixed( prefix = newPrefix ) { (name: String, value: Any?) -> if (value.canBeIgnored()) "" else { "$name=" + kotlin.runCatching { if (value == this) "<this>" else value._miraiContentToString(newPrefix) }.getOrElse { "<!>" } } }.lines().filterNot { it.isBlank() }.joinToString("\n") + "\n$prefix}" } private fun KClass<out Any>.thisClassAndSuperclassSequence(): Sequence<KClass<out Any>> { return sequenceOf(this) + this.supertypes.asSequence() .mapNotNull { type -> type.classifier?.takeIf { it is KClass<*> }?.takeIf { it != Any::class } as? KClass<out Any> }.flatMap { it.thisClassAndSuperclassSequence() } } @Suppress("UNCHECKED_CAST") private fun Any.allMembersFromSuperClassesMatching(classFilter: (KClass<out Any>) -> Boolean): Sequence<KProperty1<Any, *>> { return this::class.thisClassAndSuperclassSequence() .filter { classFilter(it) } .map { it.members } .flatMap { it.asSequence() } .filterIsInstance<KProperty1<*, *>>() .filterNot { it.hasAnnotation<Transient>() } .filterNot { it.isTransient() } .map { it as KProperty1<Any, *> } } private fun KProperty<*>.isTransient(): Boolean = javaField?.modifiers?.and(Modifier.TRANSIENT) != 0 } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/ThreadLocal.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.reflect.KProperty public fun <T : Any?> threadLocal(newInstance: () -> T): ThreadLocal<T> { return object : ThreadLocal<T>() { override fun initialValue(): T = newInstance() } } public operator fun <T> ThreadLocal<T>.getValue(t: Any?, property: KProperty<Any?>): T = this.get() as T // `get()` is from Java and has type of `T!` ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/TimeUtils.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import java.text.SimpleDateFormat import java.util.* public actual fun currentTimeMillis(): Long = System.currentTimeMillis() private val timeFormat: SimpleDateFormat by threadLocal { SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()) } public actual fun formatTime(epochTimeMillis: Long, format: String?): String { return if (format == null) { timeFormat.format(Date(epochTimeMillis)) } else { SimpleDateFormat(format, Locale.getDefault()).format(Date(epochTimeMillis)) } } ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/WeakRef.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") @file:Suppress("unused", "NOTHING_TO_INLINE") package net.mamoe.mirai.utils import kotlin.reflect.KProperty /** * WeakRef that `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only */ public class UnsafeWeakRef<T>(private val weakRef: WeakRef<T>) { public fun get(): T = weakRef.get() ?: error("WeakRef is released") public fun clear(): Unit = weakRef.clear() } /** * Provides delegate value. * * ```kotlin * val bot: Bot by param.unsafeWeakRef() * ``` */ @JvmSynthetic public inline operator fun <T> UnsafeWeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T = get() /** * Weak Reference. * On JVM, it is implemented as a typealias referring to `WeakReference` from JDK. * * Details: * On JVM, instances of objects are stored in the JVM Heap and are accessed via references. * GC(garbage collection) can automatically collect and release the memory used by objects that are not directly referred by any other. * [WeakRef] is not a direct reference, therefore it doesn't hinder GC. * * @see weakRef provides a WeakRef * @see unsafeWeakRef provides a UnsafeWeakRef */ public typealias WeakRef<T> = java.lang.ref.WeakReference<T> /** * Indicates that the property is delegated by a [WeakRef] * * @see weakRef */ @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.SOURCE) public annotation class WeakRefProperty /** * Provides a weak reference to [this] * The `getValue` for delegation returns [this] when [this] is not released by GC */ @JvmSynthetic public inline fun <T> T.weakRef(): WeakRef<T> = WeakRef(this) /** * Constructs an unsafe inline delegate for [this] */ @JvmSynthetic public inline fun <T> WeakRef<T>.unsafe(): UnsafeWeakRef<T> = UnsafeWeakRef(this) /** * Provides a weak reference to [this]. * The `getValue` for delegation throws an [IllegalStateException] if the referent is released by GC. Therefore it returns notnull value only * * **UNSTABLE API**: It is strongly suggested not to use this api */ @JvmSynthetic public inline fun <T> T.unsafeWeakRef(): UnsafeWeakRef<T> = UnsafeWeakRef(this.weakRef()) /** * Provides delegate value. * * ```kotlin * val bot: Bot? by param.weakRef() * ``` */ @JvmSynthetic public inline operator fun <T> WeakRef<T>.getValue(thisRef: Any?, property: KProperty<*>): T? = this.get() /** * Call the block if the referent is absent */ @JvmSynthetic public inline fun <T, R> WeakRef<T>.ifAbsent(block: (T) -> R): R? = this.get()?.let(block) ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/annotations/Range.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils.annotations public actual typealias Range = org.jetbrains.annotations.Range ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils ================================================ FILE: mirai-core-utils/src/jvmBaseMain/kotlin/systemProp.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils internal actual fun getProperty(name: String, default: String): String? = System.getProperty(name, default) internal actual fun setProperty(name: String, value: String) { System.setProperty(name, value) } ================================================ FILE: mirai-core-utils/src/jvmBaseTest/kotlin/ByteArrayOpTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils internal actual class ByteArrayOpTest : CommonByteArrayOpTest() ================================================ FILE: mirai-core-utils/src/jvmBaseTest/kotlin/KotlinFlowToJdkStreamTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.* import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runTest import net.mamoe.mirai.utils.JdkStreamSupport.toStream import org.junit.jupiter.api.Test import java.util.* import java.util.stream.Collectors import java.util.stream.Stream import kotlin.coroutines.EmptyCoroutineContext import kotlin.test.* internal class KotlinFlowToJdkStreamTest { private fun <T> Stream<T>.collectList(): List<T> = use { collect(Collectors.toList()) } @Test internal fun testFlowFinally() = runTest { var finallyCalled = false flow<Any?> { try { while (true) { emit("") } } finally { finallyCalled = true } }.toStream().use { it.findFirst() } assertTrue { finallyCalled } } @Test internal fun testNormally() = runTest { flow<Any?> { emit("1") emit("5") emit("2") emit("3") }.toStream().collectList().let { assertEquals(listOf("1", "5", "2", "3"), it) } } @Test internal fun testSuspendInFlow() = runTest { flow<Any?> { emit("1") yield() // Suspended emit("2") }.toStream(context = Dispatchers.IO).collectList().let { assertEquals(listOf("1", "2"), it) } } @Test internal fun testCounter() = runTest { var counter = 0 flow<Any?> { while (true) { counter++ emit(counter) } }.toStream().use { stream -> stream.limit(5).forEach { } } assertEquals(5, counter) } @Test internal fun testChannelFlow() = runTest { channelFlow<Any?> { send(514) launch { send(94481) } launch { send(94481) } launch { send(94481) } launch { send(94481) } }.toStream().collectList().let { assertEquals(listOf(514, 94481, 94481, 94481, 94481), it) } } @Test internal fun testExceptionCaught() = runTest { val msg = UUID.randomUUID().toString() flow<Any> { error(msg) }.toStream().use { s -> assertFails(msg) { s.findFirst() }.printStackTrace(System.out) } } @Test internal fun testErrorInLaunchedContext() = runTest { lateinit var myError: Throwable val msg = UUID.randomUUID().toString() flow<Any> { myError = Throwable(msg) throw myError }.toStream( context = Dispatchers.IO, ).use { stream -> assertFailsWith<RuntimeException>(msg) { stream.findFirst() }.let { err -> assertSame(myError, err.cause) assertTrue { err.stackTrace.any { it.className == "net.mamoe.mirai.utils.JdkStreamSupport\$FlowSpliterator" && it.methodName == "tryAdvance" } } err.printStackTrace(System.out) } } } @Test internal fun errorWillNotCancelJob() = runTest { val scope = CoroutineScope(EmptyCoroutineContext) val errmsg = UUID.randomUUID().toString() flow<Any> { error(errmsg) }.toStream( context = scope.coroutineContext ).use { assertFails(errmsg) { it.findFirst() } } val job = scope.coroutineContext.job assertTrue { job.isActive } assertFalse { job.isCancelled } assertFalse { job.isCompleted } } } ================================================ FILE: mirai-core-utils/src/jvmBaseTest/kotlin/LateinitMutablePropertyTestJvm.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import kotlinx.coroutines.yield import org.junit.jupiter.api.Test import java.util.concurrent.CompletableFuture import kotlin.concurrent.thread import kotlin.test.assertEquals import kotlin.test.assertSame internal class LateinitMutablePropertyTestJvm { @Test fun `initializer called once if requested by multiple threads`() = runTest { val value = Symbol("expected") var counter = 0 val verySlowInitializer = CompletableDeferred<Unit>() val prop by lateinitMutableProperty { counter++ runBlocking { yield(); verySlowInitializer.await() } value } // requested by 10 threads val lock = CompletableFuture<Unit>() repeat(10) { thread { lock.join() @Suppress("UNUSED_EXPRESSION") prop } } lock.complete(Unit) // resume callers verySlowInitializer.complete(Unit) assertSame(value, prop) assertEquals(1, counter) } } ================================================ FILE: mirai-core-utils/src/jvmBaseTest/kotlin/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils ================================================ FILE: mirai-core-utils/src/jvmMain/kotlin/Actuals.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ @file:JvmMultifileClass @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.utils import java.util.* public actual fun ByteArray.encodeBase64(): String { return Base64.getEncoder().encodeToString(this) } public actual fun String.decodeBase64(): ByteArray { return Base64.getDecoder().decode(this) } public actual inline fun <reified E> Throwable.unwrap(addSuppressed: Boolean): Throwable { if (this !is E) return this return this.findCause { it !is E } ?.also { if (addSuppressed) it.addSuppressed(this) } ?: this } ================================================ FILE: mirai-core-utils/src/jvmMain/kotlin/IO.jvm.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:JvmMultifileClass @file:JvmName("MiraiUtils") @file:Suppress("NOTHING_TO_INLINE") package net.mamoe.mirai.utils import java.nio.file.Files import java.nio.file.Path import kotlin.io.path.deleteIfExists import kotlin.io.path.exists import kotlin.io.path.isDirectory import kotlin.io.path.listDirectoryEntries public val Path.isFile: Boolean get() = Files.exists(this) && !Files.isDirectory(this) public inline fun Path.mkdir() { Files.createDirectory(this) } public inline fun Path.mkdirs() { Files.createDirectories(this) } public fun Path.mkParentDirs() { val current = parent ?: return if (current == this) return if (current.exists()) return current.mkParentDirs() current.mkdir() } public fun Path.deleteRecursivelyMirai(): Boolean { // Kotlin added `Path.deleteRecursively()` in 1.8.0 but was experimental if (isFile) return deleteIfExists() if (isDirectory()) { listDirectoryEntries().forEach { it.deleteRecursivelyMirai() } return deleteIfExists() } return false } ================================================ FILE: mirai-core-utils/src/jvmTest/kotlin/AndroidUnwrapTest.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlin.test.Test import kotlin.test.assertIs class JvmUnwrapTest { @Test fun test() { val e = IllegalStateException( OutOfMemoryError().initCause(VerifyError()).apply { addSuppressed(UnsatisfiedLinkError()) } ) val unwrapped = e.unwrap<IllegalStateException>() unwrapped.printStackTrace() assertIs<OutOfMemoryError>(unwrapped) assertIs<VerifyError>(unwrapped.cause) assertIs<UnsatisfiedLinkError>(unwrapped.suppressed.first()) assertIs<IllegalStateException>(unwrapped.suppressed[1]) } } ================================================ FILE: mirai-core-utils/src/jvmTest/kotlin/SecretsProtectionTest.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.utils import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import java.nio.ByteBuffer import kotlin.test.Test import kotlin.test.assertContentEquals internal class SecretsProtectionTest { @Test fun chaosTest() = runBlocking<Unit> { repeat(500) { launch { val data = ByteArray((1..255).random()) { (0..255).random().toByte() } val buffer = SecretsProtection.escape(data) as ByteBuffer assertContentEquals( data, buffer.duplicate().readBytes() ) delay(100) assertContentEquals( data, buffer.duplicate().readBytes() ) } } } } ================================================ FILE: mirai-deps-test/.gitignore ================================================ test/BuildConfig.kt ================================================ FILE: mirai-deps-test/README.md ================================================ # native-deps-test 测试 shadow relocation ================================================ FILE: mirai-deps-test/build.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ @file:Suppress("UnusedImport") plugins { kotlin("jvm") id("java-gradle-plugin") } dependencies { implementation(gradleApi()) implementation(gradleKotlinDsl()) implementation(`kotlin-reflect`) implementation(kotlin("gradle-plugin-api")) implementation(kotlin("gradle-plugin")) implementation(kotlin("stdlib")) api("com.github.jengelman.gradle.plugins:shadow:6.0.0") api(`jetbrains-annotations`) testImplementation(kotlin("test-junit5")) testImplementation(`junit-jupiter-api`) testImplementation(`junit-jupiter-params`) testRuntimeOnly(`junit-jupiter-engine`) } tasks.getByName("test", Test::class) { environment("mirai.root.project.dir", rootProject.projectDir.absolutePath) systemProperty("mirai.deps.test.must.run", System.getProperty("mirai.deps.test.must.run")) } val publishMiraiArtifactsToMavenLocal by tasks.registering { group = "mirai" description = "Publish all mirai artifacts to MavenLocal" val publishTasks = rootProject.allprojects.mapNotNull { proj -> proj.tasks.findByName("publishToMavenLocal") } dependsOn(publishTasks) doFirst { // Always print this very important message logger.warn("[publishMiraiArtifactsToMavenLocal] Project version is '${project.version}'.") } } tasks.getByName("test") { mustRunAfter(publishMiraiArtifactsToMavenLocal) } tasks.register("updateProjectVersionForLocalDepsTest") { doLast { setProjectVersionForFutureBuilds(DEPS_TEST_VERSION) } } tasks.register("generateBuildConfig") { group = "mirai" doLast { generateBuildConfig() } tasks.getByName("testClasses").dependsOn(this) tasks.getByName("compileTestKotlin").dependsOn(this) } generateBuildConfig() // somehow "generateBuildConfig" won't execute fun generateBuildConfig() { val text = """ package net.mamoe.mirai.deps.test /** * This file was generated by Gradle task `generateBuildConfig`. */ object BuildConfig { /** * Kotlin version used to compile mirai-core */ const val kotlinVersion = "${Versions.kotlinCompiler}" } """.trimIndent() + "\n" val file = project.projectDir.resolve("test/BuildConfig.kt") if (!file.exists() || file.readText() != text) { file.writeText(text) } } /** * Kind note: To run this task you probably need a lot of host memory and luck. * * **If you see errors, don't panic, that's most probably not your fault.** * * Try: * * ```shell * ./gradlew :mirai-deps-test:updateProjectVersionForLocalDepsTest * ./gradlew clean :mirai-deps-test:publishMiraiArtifactsToMavenLocal "-Porg.gradle.parallel=false" * ``` * Note this will change your project version in `buildSrc/src/main/kotlin/Versions.kt`. Be careful to change it back before committing! * Note also this is **extremely slow**. If your computer isn't good enough it may take hours. */ val publishMiraiLocalArtifacts = tasks.register("publishMiraiLocalArtifacts", Exec::class) { group = "mirai" description = "Starts a child process to publish v$DEPS_TEST_VERSION artifacts to MavenLocal" workingDir(rootProject.projectDir) environment("mirai.build.project.version", DEPS_TEST_VERSION) commandLine( "./gradlew", "clean", publishMiraiArtifactsToMavenLocal.name, "--no-daemon", "--stacktrace", "--scan", "-Pkotlin.compiler.execution.strategy=in-process" ) standardOutput = System.out errorOutput = System.err } version = Versions.core ================================================ FILE: mirai-deps-test/gradle.properties ================================================ # # Copyright 2019-2022 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/dev/LICENSE # ================================================ FILE: mirai-deps-test/test/AbstractTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.deps.test import org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenFileLocations import org.gradle.testkit.runner.GradleRunner import org.gradle.testkit.runner.internal.DefaultGradleRunner import org.junit.jupiter.api.BeforeEach import org.junit.jupiter.api.extension.AfterEachCallback import org.junit.jupiter.api.extension.RegisterExtension import org.junit.jupiter.api.io.TempDir import java.io.File // Copied from mirai-console-gradle abstract class AbstractTest { companion object { const val miraiLocalVersion = "2.99.0-deps-test" // do Search Everywhere before changing this const val REASON_LOCAL_ARTIFACT_NOT_AVAILABLE = "local artifacts not available" private const val MIRAI_DEPS_TEST_MUST_RUN = "mirai.deps.test.must.run" // used by GitHub Actions scripts val mavenLocalDir: File by lazy { org.gradle.api.internal.artifacts.mvnsettings.DefaultLocalMavenRepositoryLocator( org.gradle.api.internal.artifacts.mvnsettings.DefaultMavenSettingsProvider(DefaultMavenFileLocations()) ).localMavenRepository } @JvmStatic fun isMiraiLocalAvailable(): Boolean { val commandLine = """./gradlew publishMiraiArtifactsToMavenLocal -Dmirai.build.project.version=$miraiLocalVersion""" return if (mavenLocalDir.resolve("net/mamoe/mirai-core/$miraiLocalVersion").exists()) { println( """ [mirai-deps-test] Found local artifacts `$miraiLocalVersion`! Please note that you may need to manually update local artifacts if you have: - added/removed a dependency for mirai-core series modules - changed version of any of the dependencies for mirai-core series modules You can update by running the following command: $commandLine """.trimIndent() ) true } else { val message = """ [mirai-deps-test] ERROR: Test is not run, because there are no local artifacts available for dependencies testing. Please build and publish local artifacts with version `$miraiLocalVersion` before running this test(:mirai-deps-test:test). This could have be automated but it will take a huge amount of time for your routine testing. You can run this test manually if you have: - added/removed a dependency for mirai-core series modules - changed version of any of the dependencies for mirai-core series modules Note that you can ignore this test if you did not change project (dependency) structure. And you don't need to worry if you does not run this test — this test is always executed on the CI when you make a PR. You can run the following command to publish local artifacts: $commandLine Then you can run this test again. (By your original way or ./gradlew :mirai-deps-test:test) """.trimIndent() System.err.println( message ) if (System.getProperty(MIRAI_DEPS_TEST_MUST_RUN, "false").toBoolean()) { throw AssertionError("System property `mirai.deps.test.must.run` is `true`, which requires the deps test to be run. \n\n$message") } else { false } } } } @JvmField @TempDir var tempDirField: File? = null val tempDir: File get() = tempDirField!! val kotlinVersion = BuildConfig.kotlinVersion lateinit var mainSrcDir: File lateinit var commonMainSrcDir: File lateinit var testDir: File lateinit var buildFile: File lateinit var settingsFile: File lateinit var propertiesFile: File private inline fun <reified T> Any?.cast(): T = this as T @OptIn(ExperimentalStdlibApi::class) fun runGradle(vararg arguments: String) { System.gc() GradleRunner.create() .withProjectDir(tempDir) .withPluginClasspath() .withGradleVersion("7.2") .forwardOutput() .withEnvironment(System.getenv()) .cast<DefaultGradleRunner>().withJvmArguments(buildList { add("-Xmx512m") // Kotlin MPP may need memory to build add("-Dfile.encoding=UTF-8") }) .withArguments(buildList { addAll(arguments) add("-P") add("kotlin.compiler.execution.strategy=in-process") add("-D") add("org.gradle.jvmargs=-Xmx512m") add("-D") add("file.encoding=UTF-8") // add("--stacktrace") add("--info") }) .build() } @BeforeEach fun setup() { println("Temp path is " + tempDir.absolutePath) settingsFile = File(tempDir, "settings.gradle") settingsFile.delete() settingsFile.writeText( """ pluginManagement { repositories { gradlePluginPortal() mavenCentral() mavenLocal() } } """ ) File(tempDir, "gradle.properties").apply { delete() writeText( """ org.gradle.daemon=false org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 """.trimIndent() ) } mainSrcDir = tempDir.resolve("src/main/kotlin").apply { mkdirs() } commonMainSrcDir = tempDir.resolve("src/commonMain/kotlin").apply { mkdirs() } testDir = tempDir.resolve("src/test/kotlin").apply { mkdirs() } buildFile = tempDir.resolve("build.gradle.kts") buildFile.writeText( """ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { kotlin("jvm") version "1.7.0" } group = "org.example" version = "1.0-SNAPSHOT" repositories { mavenCentral() mavenLocal() } dependencies { testImplementation(kotlin("test")) implementation(kotlin("reflect")) testImplementation(kotlin("test-junit5")) testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.2") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.2") } tasks.test { useJUnitPlatform() } tasks.withType<KotlinCompile> { kotlinOptions.jvmTarget = "1.8" } """.trimIndent() + "\n\n" ) } @JvmField @RegisterExtension internal val after: AfterEachCallback = AfterEachCallback { context -> if (context.executionException.isPresent) { val inst = context.requiredTestInstance as AbstractTest println("====================== build.gradle ===========================") println(inst.tempDir.resolveFirstExisting("build.gradle", "build.gradle.kts").readTextIfFound()) println("==================== settings.gradle ==========================") println(inst.tempDir.resolveFirstExisting("settings.gradle", "settings.gradle.kts").readTextIfFound()) } } private fun File.resolveFirstExisting(vararg files: String): File? { return files.asSequence().map { resolve(it) }.firstOrNull { it.exists() } } private fun File?.readTextIfFound(): String = when { this == null -> "(not found)" exists() -> readText() else -> "($name not found)" } } ================================================ FILE: mirai-deps-test/test/CoreDependencyResolutionTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.deps.test import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledIf class CoreDependencyResolutionTest : AbstractTest() { private val testCode = """ package test @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE", "EXPERIMENTAL_API_USAGE") fun main () { println(net.mamoe.mirai.BotFactory) println(net.mamoe.mirai.Mirai) println(net.mamoe.mirai.internal.testHttpClient()) } """.trimIndent() @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test resolve JVM root from Kotlin JVM`() { mainSrcDir.resolve("main.kt").writeText(testCode) buildFile.writeText( """ plugins { id("org.jetbrains.kotlin.jvm") version "$kotlinVersion" } repositories { mavenCentral() mavenLocal() } dependencies { implementation("net.mamoe:mirai-core:$miraiLocalVersion") } kotlin.sourceSets.all { languageSettings.optIn("net.mamoe.mirai.utils.TestOnly") } """.trimIndent() ) runGradle("build") } @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test resolve JVM from Kotlin JVM`() { mainSrcDir.resolve("main.kt").writeText(testCode) buildFile.writeText( """ plugins { id("org.jetbrains.kotlin.jvm") version "$kotlinVersion" } repositories { mavenCentral() mavenLocal() } dependencies { implementation("net.mamoe:mirai-core-jvm:$miraiLocalVersion") } kotlin.sourceSets.all { languageSettings.optIn("net.mamoe.mirai.utils.TestOnly") } """.trimIndent() ) runGradle("build") } @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test resolve JVM and Native from common`() { commonMainSrcDir.resolve("main.kt").writeText(testCode) buildFile.writeText( """ |import org.apache.tools.ant.taskdefs.condition.Os |import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet | |plugins { | id("org.jetbrains.kotlin.multiplatform") version "$kotlinVersion" |} |repositories { | mavenCentral() | mavenLocal() |} |kotlin { | targets { | jvm() | } | sourceSets { | val commonMain by getting { | dependencies { | api("net.mamoe:mirai-core:$miraiLocalVersion") | } | } | } |} |kotlin.sourceSets.all { | languageSettings.optIn("net.mamoe.mirai.utils.TestOnly") |} """.trimMargin() ) runGradle("build") } } ================================================ FILE: mirai-deps-test/test/CoreShadowRelocationTest.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.deps.test import org.junit.jupiter.api.Test import org.junit.jupiter.api.condition.EnabledIf import kotlin.test.assertTrue /** * 为每个模块测试 relocated 的依赖是否存在于运行时 */ class CoreShadowRelocationTest : AbstractTest() { companion object { private const val ByteBufferChannel = "io.ktor.utils.io.ByteBufferChannel" private const val HttpClient = "io.ktor.client.HttpClient" private const val KtorOkHttp = "io.ktor.client.engine.okhttp.OkHttp" private const val OkHttp = "okhttp3.OkHttp" private const val OkIO = "okio.ByteString" private const val BigInteger = "com.ionspin.kotlin.bignum.integer.BigInteger" fun relocated(string: String): String { return "net.mamoe.mirai.internal.deps.$string" } } @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test mirai-core-utils`() { val fragment = buildTestCases { +relocated(`ktor-io`) -both(`ktor-client-core`) -both(`ktor-client-okhttp`) -both(`okhttp3-okhttp`) -both(okio) -both(`kt-bignum`) } applyCodeFragment(fragment) buildFile.appendText( """ dependencies { implementation("net.mamoe:mirai-core-utils:$miraiLocalVersion") } """.trimIndent() ) runGradle("check") } @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test mirai-core-api with transitive mirai-core-utils`() { val fragment = buildTestCases { +relocated(`ktor-io`) -both(`ktor-client-core`) -both(`ktor-client-okhttp`) -both(`okhttp3-okhttp`) -both(okio) -both(`kt-bignum`) +relocated(`ExternalResource-input`) } applyCodeFragment(fragment) buildFile.appendText( """ dependencies { implementation("net.mamoe:mirai-core-api:$miraiLocalVersion") } """.trimIndent() ) runGradle("check") } @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test mirai-core with transitive mirai-core-api and mirai-core-utils`() { val fragment = buildTestCases { +relocated(`ktor-io`) +relocated(`ktor-client-core`) +relocated(`ktor-client-okhttp`) +relocated(`okhttp3-okhttp`) +relocated(okio) +relocated(`ExternalResource-input`) +relocated(`kt-bignum`) } applyCodeFragment(fragment) buildFile.appendText( """ dependencies { implementation("net.mamoe:mirai-core:$miraiLocalVersion") } """.trimIndent() ) runGradle("check") } // ktor-io is shadowed into runtime in mirai-core-utils. So without mirai-core-utils, // we should expect no relocated ktor-io found, otherwise there will be duplicated classes on Android. // https://github.com/mamoe/mirai/issues/2291 @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test mirai-core without transitive mirai-core-api and mirai-core-utils`() { val fragment = buildTestCases { -both(`ktor-io`) +relocated(`ktor-client-core`) +relocated(`ktor-client-okhttp`) +relocated(`okhttp3-okhttp`) +relocated(okio) +relocated(`kt-bignum`) } applyCodeFragment(fragment) buildFile.appendText( """ dependencies { implementation("net.mamoe:mirai-core:$miraiLocalVersion") { exclude("net.mamoe", "mirai-core-api-jvm") exclude("net.mamoe", "mirai-core-utils-jvm") } } """.trimIndent() ) runGradle("check") } @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test mirai-core-api without transitive mirai-core-utils`() { val fragment = buildTestCases { -`mirai-core-utils` -both(`ktor-io`) -both(`ktor-client-core`) -both(`ktor-client-okhttp`) -both(`okhttp3-okhttp`) -both(okio) -both(`kt-bignum`) // +relocated(`ExternalResource-input`) // Will fail with no class def found error because there is no runtime ktor-io } applyCodeFragment(fragment) buildFile.appendText( """ dependencies { implementation("net.mamoe:mirai-core-api:$miraiLocalVersion") { exclude("net.mamoe", "mirai-core-utils-jvm") } } """.trimIndent() ) runGradle("check") } @Test @EnabledIf("isMiraiLocalAvailable", disabledReason = REASON_LOCAL_ARTIFACT_NOT_AVAILABLE) fun `test mirai-core-all`() { val fragment = buildTestCases { +relocated(`ktor-io`) +relocated(`ktor-client-core`) +relocated(`ktor-client-okhttp`) +relocated(`okhttp3-okhttp`) +relocated(okio) +relocated(`ExternalResource-input`) +relocated(`kt-bignum`) } applyCodeFragment(fragment) // mirai-core-all-2.99.0-deps-test-all.jar val miraiCoreAllJar = mavenLocalDir.resolve("net/mamoe/mirai-core-all/$miraiLocalVersion/mirai-core-all-$miraiLocalVersion-all.jar") val path = miraiCoreAllJar.absolutePath.replace("\\", "/") // overcome string escape in source files. assertTrue("'$path' does not exist") { miraiCoreAllJar.exists() } buildFile.appendText( """ dependencies { implementation(fileTree("$path")) } """.trimIndent() ) runGradle("check") } @Suppress("PropertyName") private class TestBuilder { private val result = StringBuilder( """ package test import org.junit.jupiter.api.* import java.lang.reflect.Method import kotlin.reflect.jvm.kotlinFunction import kotlin.test.assertTrue import kotlin.test.assertFalse private val Method.signature: String get() = buildString { append(kotlinFunction?.toString()) return@buildString append(kotlinFunction?.visibility?.name?.lowercase()) append(kotlinFunction?.visibility?.name?.lowercase()) append(' ') append(returnType.canonicalName) append(' ') append(name) append('(') for (parameter in parameters) { append(parameter.type.canonicalName) append(' ') append(parameter.name) append(", ") } if (parameterCount != 0) { deleteAt(lastIndex) deleteAt(lastIndex) } append(')') } class MyTest { """.trimIndent() ).append("\n").append("\n") class ClassTestCase( val name: String, val qualifiedClassName: String, ) class FunctionTestCase( val name: String, val qualifiedClassName: String, val signature: String, val relocated: (FunctionTestCase.() -> FunctionTestCase)? = null, ) val `mirai-core-utils` = ClassTestCase("mirai-core-utils Symbol", "net.mamoe.mirai.utils.Symbol") val `ktor-io` = ClassTestCase("ktor-io ByteBufferChannel", ByteBufferChannel) val `ktor-client-core` = ClassTestCase("ktor-client-core HttpClient", HttpClient) val `ktor-client-okhttp` = ClassTestCase("ktor-client-core OkHttp", KtorOkHttp) val `okhttp3-okhttp` = ClassTestCase("okhttp3 OkHttp", OkHttp) val okio = ClassTestCase("okio ByteString", OkIO) val `kt-bignum` = ClassTestCase("kt-bignum BigInteger", BigInteger) val `ExternalResource-input` = FunctionTestCase( "ExternalResource_input", "net.mamoe.mirai.utils.ExternalResource", "fun net.mamoe.mirai.utils.ExternalResource.input(): io.ktor.utils.io.core.Input" ) original@{ FunctionTestCase( "relocated ExternalResource_input", "net.mamoe.mirai.utils.ExternalResource", "fun net.mamoe.mirai.utils.ExternalResource.input(): ${relocated("io.ktor.utils.io.core.Input")}" ) { this@original } } class RelocatedClassTestCase( val testCase: ClassTestCase ) class BothClassTestCase( val testCase: ClassTestCase ) private fun appendHasClass(name: String, qualifiedClassName: String) { result.append( """ @Test fun `has ${name}`() { Class.forName("$qualifiedClassName") } """.trimIndent() ).append("\n") } fun appendClassHasMethod(name: String, qualifiedClassName: String, methodSignature: String) { result.append( """ @Test fun `has ${name}`() { val signatures = Class.forName("$qualifiedClassName").declaredMethods.map { it.signature } assertTrue("All signatures: " + signatures.joinToString("\n")) { signatures.any { it == "$methodSignature" } } } """.trimIndent() ).append("\n") } fun appendClassMethodNotFound(name: String, qualifiedClassName: String, methodSignature: String) { result.append( """ @Test fun `has ${name}`() { val signatures = Class.forName("$qualifiedClassName").declaredMethods.map { it.signature } assertFalse("All signatures: " + signatures.joinToString("\n")) { signatures.any { it == "$methodSignature" } } } """.trimIndent() ).append("\n") } private fun appendNotFound(name: String, qualifiedClassName: String) { result.append( """ @Test fun `no ${name}`() { assertThrows<ClassNotFoundException> { Class.forName("$qualifiedClassName") } } """.trimIndent() ).append("\n") } /** * Asserts a class exists. Also asserts its relocated class does not exist. */ operator fun FunctionTestCase.unaryPlus() { appendClassHasMethod(name, qualifiedClassName, signature) relocated?.invoke(this)?.let { inverted -> appendClassMethodNotFound(inverted.name, inverted.qualifiedClassName, inverted.signature) } } /** * Asserts a class exists. Also asserts its relocated class does not exist. */ operator fun ClassTestCase.unaryPlus() { appendHasClass(name, qualifiedClassName) appendNotFound("relocated $name", Companion.relocated(qualifiedClassName)) } /** * Asserts a class does not exist. */ operator fun ClassTestCase.unaryMinus() { appendNotFound(name, qualifiedClassName) } /** * Asserts a relocated class exists. Also asserts the original class does not exist. */ operator fun RelocatedClassTestCase.unaryPlus() { this.testCase.run { appendHasClass("relocated $name", Companion.relocated(qualifiedClassName)) appendNotFound(name, qualifiedClassName) } } /** * Asserts a relocated class does not exist. */ operator fun RelocatedClassTestCase.unaryMinus() { this.testCase.run { appendNotFound("relocated $name", Companion.relocated(qualifiedClassName)) } } /** * Asserts both the class and its relocated one do not exist. */ operator fun BothClassTestCase.unaryMinus() { -this.testCase -relocated(this.testCase) } fun relocated(testCase: ClassTestCase): RelocatedClassTestCase = RelocatedClassTestCase(testCase) fun relocated(testCase: FunctionTestCase): FunctionTestCase = testCase.relocated!!(testCase) fun both(testCase: ClassTestCase) = BothClassTestCase(testCase) fun build(): String = result.append("\n}\n").toString() } private inline fun buildTestCases(action: TestBuilder.() -> Unit): String { return TestBuilder().apply(action).build() } private fun applyCodeFragment(fragment: String) { println("Applying code fragment: \n\n$fragment\n\n\n===========End of Fragment===========") testDir.resolve("test.kt").writeText(fragment) } } ================================================ FILE: mirai-deps-test/test/package.kt ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ package net.mamoe.mirai.deps.test ================================================ FILE: mirai-dokka/.gitignore ================================================ /pages ================================================ FILE: mirai-dokka/README.md ================================================ # mirai-dokka 生成的文档会部署到 [project-mirai/mirai-doc](https://github.com/project-mirai/mirai-doc), 网页部署在 https://kdoc.mirai.mamoe.net/ ================================================ FILE: mirai-dokka/build.gradle.kts ================================================ /* * Copyright 2019-2022 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/dev/LICENSE */ plugins { kotlin("jvm") kotlin("plugin.serialization") id("java") `maven-publish` } dependencies { implementation(`kotlinx-serialization-core`) implementation(`kotlinx-serialization-json`) } fun Project.newExec(name: String, type: String, conf: JavaExec.() -> Unit) { tasks.create(name, JavaExec::class.java) { this.classpath = sourceSets["main"].runtimeClasspath this.mainClass.set("net.mamoe.mirai.dokka.${type}Kt") this.workingDir(rootProject.projectDir) this.environment("mirai_ver", rootProject.version.toString()) conf() } } newExec("prepare", "Prepare") { } newExec("deployPages", "DeployToGitHub") { } newExec("update-vers", "BuildVersionList") { } ================================================ FILE: mirai-dokka/frontend/ext.js ================================================ // noinspection ES6ConvertVarToLetConst,JSUnresolvedVariable /* * 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/dev/LICENSE */ (function () { fetch(window.pathToRoot + "../versions.json").then(function (it) { return it.json() }).then(function (rsp) { console.log(rsp); var dir = document.getElementById("searchBar").parentElement; var select = document.createElement("select"); dir.insertBefore(select, dir.firstElementChild); select.appendChild(document.createElement("option")).textContent = "other version"; var toLatest = select.appendChild(document.createElement("option")); toLatest.textContent = "latest"; toLatest.value = ""; for (var v of rsp) { var c = select.appendChild(document.createElement("option")); c.textContent = v; c.value = v; } select.addEventListener("change", function (event) { location.href = window.pathToRoot + "../" + c.value }) }).catch(function (error) { console.log(error); }) })() ================================================ FILE: mirai-dokka/src/BuildVersionList.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.dokka import kotlinx.serialization.builtins.ListSerializer import kotlinx.serialization.builtins.serializer fun main() { val currentVersion = System.getenv("mirai_ver") ?: error("version not found") val versions = pages.resolve("versions.json") json.decodeFromString(ListSerializer(String.serializer()), versions.readText()).toMutableList().let { list -> if (currentVersion in list) return@let list.add(currentVersion) versions.writeText(json.encodeToString(ListSerializer(String.serializer()), list)) } pages.resolve("snapshot").renameTo(pages.resolve(currentVersion)) } ================================================ FILE: mirai-dokka/src/DeployToGitHub.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.dokka fun main() { val token = System.getenv("gh_token") ?: error("Token not found") val currentVersion = System.getenv("mirai_ver") ?: error("version not found") repoexec("git", "config", "--local", "http.https://github.com/.extraheader", "") runCatching { repoexec("git", "remote", "remove", "token") } repoexec( "git", "remote", "add", "token", "https://x-access-token:$token@github.com/project-mirai/mirai-doc.git" ) repoexec("git", "config", "--local", "user.email", "mamoebot@users.noreply.github.com") repoexec("git", "config", "--local", "user.name", "mamoebot") repoexec("git", "add", "-A") repoexec( "git", "commit", "-m", currentVersion, nooutput = true, ) repoexec("git", "push", "token", "HEAD:master") } ================================================ FILE: mirai-dokka/src/Prepare.kt ================================================ /* * 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/dev/LICENSE */ package net.mamoe.mirai.dokka fun main() { if (pages.resolve(".git").isDirectory) { return } exec("git", "clone", "https://github.com/project-mirai/mirai-doc.git", pages.absolutePath) } ================================================ FILE: mirai-dokka/src/system.kt ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ package net.mamoe.mirai.dokka import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.json.Json import java.io.File val pages = File("mirai-dokka/pages") @OptIn(ExperimentalSerializationApi::class) val json = Json { prettyPrint = true prettyPrintIndent = " " } private val FileDevNull = File( if (System.getProperty("os.name") .startsWith("Windows") ) "NUL" else "/dev/null" ) fun system(cmd: String) { val rsp = ProcessBuilder(cmd).inheritIO().start().waitFor() if (rsp != 0) error("Exec return $rsp, $cmd") } fun exec(vararg cmd: String) { val rsp = ProcessBuilder(*cmd).inheritIO().start().waitFor() if (rsp != 0) error("Exec return $rsp, ${cmd.joinToString(" ")}") } fun repoexec( vararg cmd: String, nooutput: Boolean = false, ) { val rsp = ProcessBuilder(*cmd) .inheritIO() .directory(pages) .also { builder -> if (nooutput) { builder.redirectOutput(ProcessBuilder.Redirect.to(FileDevNull)) } } .start() .waitFor() if (rsp != 0) error("Exec return $rsp, ${cmd.joinToString(" ")}") } ================================================ FILE: settings.gradle.kts ================================================ /* * Copyright 2019-2023 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/dev/LICENSE */ import java.util.* /* * Copyright 2019-2022 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/dev/LICENSE */ pluginManagement { repositories { if (System.getProperty("use.maven.local") == "true") { // you can enable by adding `systemProp.use.maven.local=true` in 'gradle.properties'. mavenLocal() } gradlePluginPortal() mavenCentral() google() } } plugins { id("org.gradle.toolchains.foojay-resolver-convention") version "0.4.0" } rootProject.name = "mirai" runCatching { rootProject.projectDir.resolve("local.properties").let { if (!it.exists()) it.createNewFile() } } val localProperties = Properties().apply { rootProject.projectDir.resolve("local.properties").takeIf { it.exists() }?.bufferedReader()?.use { load(it) } } /** * Projects included so far */ val allProjects = mutableListOf<ProjectDescriptor>() fun includeProject(projectPath: String, dir: String? = null) { if (!getLocalProperty("projects." + projectPath.removePrefix(":") + ".enabled", true)) return include(projectPath) if (dir != null) project(projectPath).projectDir = file(dir) allProjects.add(project(projectPath)) } fun includeConsoleProject(projectPath: String, dir: String? = null) = includeProject(projectPath, "mirai-console/$dir") includeProject(":mirai-core-utils") includeProject(":mirai-core-api") includeProject(":mirai-core") includeProject(":mirai-core-mock") includeProject(":mirai-core-all") includeProject(":mirai-bom") includeProject(":mirai-dokka") includeProject(":mirai-deps-test") if (getLocalProperty("projects.mirai-logging.enabled", true)) { includeProject(":mirai-logging-log4j2", "logging/mirai-logging-log4j2") includeProject(":mirai-logging-slf4j", "logging/mirai-logging-slf4j") includeProject(":mirai-logging-slf4j-simple", "logging/mirai-logging-slf4j-simple") includeProject(":mirai-logging-slf4j-logback", "logging/mirai-logging-slf4j-logback") } // mirai-core-api depends on this includeConsoleProject(":mirai-console-compiler-annotations", "tools/compiler-annotations") if (getLocalProperty("projects.mirai-console.enabled", true)) { includeConsoleProject(":mirai-console", "backend/mirai-console") includeConsoleProject(":mirai-console.codegen", "backend/codegen") includeConsoleProject(":mirai-console-frontend-base", "frontend/mirai-console-frontend-base") includeConsoleProject(":mirai-console-terminal", "frontend/mirai-console-terminal") includeConsoleIntegrationTestProjects() includeConsoleProject(":mirai-console-compiler-common", "tools/compiler-common") includeConsoleProject(":mirai-console-intellij", "tools/intellij-plugin") includeConsoleProject(":mirai-console-gradle", "tools/gradle-plugin") } else { // if mirai-console is disabled, disable all relevant projects } //includeConsoleFrontendGraphical() includeProject(":ci-release-helper") includeBinaryCompatibilityValidatorProjects() /** * Configures a project `:validator:path-to-project:target-name` for binary compatibility validation. * * To enable validation for a project, * create a subdirectory with name of the target under "compatibility-validation", * then sync **twice**. See `:mirai-core-api` for an example. * * **Note**: This function depends on [allProjects], and should be used at the end. */ fun includeBinaryCompatibilityValidatorProjects() { val result = mutableListOf<ProjectDescriptor>() for (project in allProjects) { val validationDir = project.projectDir.resolve("compatibility-validation") if (!validationDir.exists()) continue validationDir.listFiles().orEmpty<File>().forEach { dir -> if (dir.resolve("build.gradle.kts").isFile) { // Also change: BinaryCompatibilityConfigurator.getValidatorDir val path = ":validator" + project.path + "-validator:${dir.name}" include(path) project(path).projectDir = dir // project(path).name = "${project.name}-validator-${dir.name}" result.add(project(path)) } } } } fun includeConsoleLegacyFrontendProjects() { println("JDK version: ${JavaVersion.current()}") if (JavaVersion.current() >= JavaVersion.VERSION_1_9) { includeConsoleProject(":mirai-console-graphical", "frontend/mirai-console-graphical") } else { println("当前使用的 JDK 版本为 ${System.getProperty("java.version")}, 请使用 JDK 9 以上版本引入模块 `:mirai-console-graphical`\n") } } fun includeConsoleIntegrationTestProjects() { includeConsoleProject(":mirai-console.integration-test", "backend/integration-test") val consoleIntegrationTestSubPluginBuildGradleKtsTemplate by lazy { rootProject.projectDir .resolve("mirai-console/backend/integration-test/testers") .resolve("tester.template.gradle.kts") .readText() } @Suppress("SimpleRedundantLet") fun includeConsoleITPlugin(prefix: String, path: File) { path.resolve("build.gradle.kts").takeIf { !it.isFile }?.let { initScript -> initScript.writeText(consoleIntegrationTestSubPluginBuildGradleKtsTemplate) } val projectPath = "$prefix${path.name}" include(projectPath) project(projectPath).projectDir = path path.listFiles()?.asSequence().orEmpty() .filter { it.isDirectory } .filter { it.resolve(".nested-module.txt").exists() } .forEach { includeConsoleITPlugin("${projectPath}:", it) } } rootProject.projectDir .resolve("mirai-console/backend/integration-test/testers") .listFiles()?.asSequence().orEmpty() .filter { it.isDirectory } .forEach { includeConsoleITPlugin(":mirai-console.integration-test:", it) } } fun getLocalProperty(name: String): String? { return localProperties.getProperty(name) } fun getLocalProperty(name: String, default: String): String { return localProperties.getProperty(name) ?: default } fun getLocalProperty(name: String, default: Int): Int { return localProperties.getProperty(name)?.toInt() ?: default } fun getLocalProperty(name: String, default: Boolean): Boolean { return localProperties.getProperty(name)?.toBoolean() ?: default }