diff --git a/frontend/mirai-android b/frontend/mirai-android deleted file mode 160000 index 71012829c..000000000 --- a/frontend/mirai-android +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 71012829c054ae67dde5aaf2b5f06a1973e15fb2 diff --git a/.editorconfig b/mirai-console/.editorconfig similarity index 100% rename from .editorconfig rename to mirai-console/.editorconfig diff --git a/.github/workflows/DevTagPublishing.yml b/mirai-console/.github/workflows/DevTagPublishing.yml similarity index 100% rename from .github/workflows/DevTagPublishing.yml rename to mirai-console/.github/workflows/DevTagPublishing.yml diff --git a/.github/workflows/Gradle CI.yml b/mirai-console/.github/workflows/Gradle CI.yml similarity index 100% rename from .github/workflows/Gradle CI.yml rename to mirai-console/.github/workflows/Gradle CI.yml diff --git a/.github/workflows/ReleasePublishing.yml b/mirai-console/.github/workflows/ReleasePublishing.yml similarity index 100% rename from .github/workflows/ReleasePublishing.yml rename to mirai-console/.github/workflows/ReleasePublishing.yml diff --git a/.github/workflows/TagRelease.yml b/mirai-console/.github/workflows/TagRelease.yml similarity index 100% rename from .github/workflows/TagRelease.yml rename to mirai-console/.github/workflows/TagRelease.yml diff --git a/.gitignore b/mirai-console/.gitignore similarity index 100% rename from .gitignore rename to mirai-console/.gitignore diff --git a/.gitmodules b/mirai-console/.gitmodules similarity index 100% rename from .gitmodules rename to mirai-console/.gitmodules diff --git a/backend/codegen/README.md b/mirai-console/backend/codegen/README.md similarity index 100% rename from backend/codegen/README.md rename to mirai-console/backend/codegen/README.md diff --git a/backend/codegen/build.gradle.kts b/mirai-console/backend/codegen/build.gradle.kts similarity index 100% rename from backend/codegen/build.gradle.kts rename to mirai-console/backend/codegen/build.gradle.kts diff --git a/backend/codegen/src/Codegen.kt b/mirai-console/backend/codegen/src/Codegen.kt similarity index 100% rename from backend/codegen/src/Codegen.kt rename to mirai-console/backend/codegen/src/Codegen.kt diff --git a/backend/codegen/src/MessageScopeCodegen.kt b/mirai-console/backend/codegen/src/MessageScopeCodegen.kt similarity index 100% rename from backend/codegen/src/MessageScopeCodegen.kt rename to mirai-console/backend/codegen/src/MessageScopeCodegen.kt diff --git a/backend/codegen/src/ValuePluginDataCodegen.kt b/mirai-console/backend/codegen/src/ValuePluginDataCodegen.kt similarity index 100% rename from backend/codegen/src/ValuePluginDataCodegen.kt rename to mirai-console/backend/codegen/src/ValuePluginDataCodegen.kt diff --git a/backend/codegen/src/old/JSettingCodegen.kt b/mirai-console/backend/codegen/src/old/JSettingCodegen.kt similarity index 100% rename from backend/codegen/src/old/JSettingCodegen.kt rename to mirai-console/backend/codegen/src/old/JSettingCodegen.kt diff --git a/backend/codegen/src/old/SettingValueUseSiteCodegen.kt b/mirai-console/backend/codegen/src/old/SettingValueUseSiteCodegen.kt similarity index 100% rename from backend/codegen/src/old/SettingValueUseSiteCodegen.kt rename to mirai-console/backend/codegen/src/old/SettingValueUseSiteCodegen.kt diff --git a/backend/codegen/src/old/ValueImplCodegen.kt b/mirai-console/backend/codegen/src/old/ValueImplCodegen.kt similarity index 100% rename from backend/codegen/src/old/ValueImplCodegen.kt rename to mirai-console/backend/codegen/src/old/ValueImplCodegen.kt diff --git a/backend/codegen/src/old/ValuesCodegen.kt b/mirai-console/backend/codegen/src/old/ValuesCodegen.kt similarity index 100% rename from backend/codegen/src/old/ValuesCodegen.kt rename to mirai-console/backend/codegen/src/old/ValuesCodegen.kt diff --git a/backend/codegen/src/util.kt b/mirai-console/backend/codegen/src/util.kt similarity index 100% rename from backend/codegen/src/util.kt rename to mirai-console/backend/codegen/src/util.kt diff --git a/backend/mirai-console/README.md b/mirai-console/backend/mirai-console/README.md similarity index 100% rename from backend/mirai-console/README.md rename to mirai-console/backend/mirai-console/README.md diff --git a/backend/mirai-console/build.gradle.kts b/mirai-console/backend/mirai-console/build.gradle.kts similarity index 100% rename from backend/mirai-console/build.gradle.kts rename to mirai-console/backend/mirai-console/build.gradle.kts diff --git a/backend/mirai-console/src/MiraiConsole.kt b/mirai-console/backend/mirai-console/src/MiraiConsole.kt similarity index 100% rename from backend/mirai-console/src/MiraiConsole.kt rename to mirai-console/backend/mirai-console/src/MiraiConsole.kt diff --git a/backend/mirai-console/src/MiraiConsoleFrontEndDescription.kt b/mirai-console/backend/mirai-console/src/MiraiConsoleFrontEndDescription.kt similarity index 100% rename from backend/mirai-console/src/MiraiConsoleFrontEndDescription.kt rename to mirai-console/backend/mirai-console/src/MiraiConsoleFrontEndDescription.kt diff --git a/backend/mirai-console/src/MiraiConsoleImplementation.kt b/mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt similarity index 100% rename from backend/mirai-console/src/MiraiConsoleImplementation.kt rename to mirai-console/backend/mirai-console/src/MiraiConsoleImplementation.kt diff --git a/backend/mirai-console/src/command/AbstractCommand.kt b/mirai-console/backend/mirai-console/src/command/AbstractCommand.kt similarity index 100% rename from backend/mirai-console/src/command/AbstractCommand.kt rename to mirai-console/backend/mirai-console/src/command/AbstractCommand.kt diff --git a/backend/mirai-console/src/command/BuiltInCommands.kt b/mirai-console/backend/mirai-console/src/command/BuiltInCommands.kt similarity index 100% rename from backend/mirai-console/src/command/BuiltInCommands.kt rename to mirai-console/backend/mirai-console/src/command/BuiltInCommands.kt diff --git a/backend/mirai-console/src/command/Command.kt b/mirai-console/backend/mirai-console/src/command/Command.kt similarity index 100% rename from backend/mirai-console/src/command/Command.kt rename to mirai-console/backend/mirai-console/src/command/Command.kt diff --git a/backend/mirai-console/src/command/CommandExecuteResult.kt b/mirai-console/backend/mirai-console/src/command/CommandExecuteResult.kt similarity index 100% rename from backend/mirai-console/src/command/CommandExecuteResult.kt rename to mirai-console/backend/mirai-console/src/command/CommandExecuteResult.kt diff --git a/backend/mirai-console/src/command/CommandExecutionException.kt b/mirai-console/backend/mirai-console/src/command/CommandExecutionException.kt similarity index 100% rename from backend/mirai-console/src/command/CommandExecutionException.kt rename to mirai-console/backend/mirai-console/src/command/CommandExecutionException.kt diff --git a/backend/mirai-console/src/command/CommandManager.kt b/mirai-console/backend/mirai-console/src/command/CommandManager.kt similarity index 100% rename from backend/mirai-console/src/command/CommandManager.kt rename to mirai-console/backend/mirai-console/src/command/CommandManager.kt diff --git a/backend/mirai-console/src/command/CommandOwner.kt b/mirai-console/backend/mirai-console/src/command/CommandOwner.kt similarity index 100% rename from backend/mirai-console/src/command/CommandOwner.kt rename to mirai-console/backend/mirai-console/src/command/CommandOwner.kt diff --git a/backend/mirai-console/src/command/CommandPermissionDeniedException.kt b/mirai-console/backend/mirai-console/src/command/CommandPermissionDeniedException.kt similarity index 100% rename from backend/mirai-console/src/command/CommandPermissionDeniedException.kt rename to mirai-console/backend/mirai-console/src/command/CommandPermissionDeniedException.kt diff --git a/backend/mirai-console/src/command/CommandSender.kt b/mirai-console/backend/mirai-console/src/command/CommandSender.kt similarity index 100% rename from backend/mirai-console/src/command/CommandSender.kt rename to mirai-console/backend/mirai-console/src/command/CommandSender.kt diff --git a/backend/mirai-console/src/command/CompositeCommand.kt b/mirai-console/backend/mirai-console/src/command/CompositeCommand.kt similarity index 100% rename from backend/mirai-console/src/command/CompositeCommand.kt rename to mirai-console/backend/mirai-console/src/command/CompositeCommand.kt diff --git a/backend/mirai-console/src/command/IllegalCommandArgumentException.kt b/mirai-console/backend/mirai-console/src/command/IllegalCommandArgumentException.kt similarity index 100% rename from backend/mirai-console/src/command/IllegalCommandArgumentException.kt rename to mirai-console/backend/mirai-console/src/command/IllegalCommandArgumentException.kt diff --git a/backend/mirai-console/src/command/RawCommand.kt b/mirai-console/backend/mirai-console/src/command/RawCommand.kt similarity index 100% rename from backend/mirai-console/src/command/RawCommand.kt rename to mirai-console/backend/mirai-console/src/command/RawCommand.kt diff --git a/backend/mirai-console/src/command/SimpleCommand.kt b/mirai-console/backend/mirai-console/src/command/SimpleCommand.kt similarity index 100% rename from backend/mirai-console/src/command/SimpleCommand.kt rename to mirai-console/backend/mirai-console/src/command/SimpleCommand.kt diff --git a/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentContext.kt diff --git a/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/CommandArgumentParserBuiltins.kt diff --git a/backend/mirai-console/src/command/descriptor/CommandParameter.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandParameter.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/CommandParameter.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/CommandParameter.kt diff --git a/backend/mirai-console/src/command/descriptor/CommandSignature.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandSignature.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/CommandSignature.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/CommandSignature.kt diff --git a/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt b/mirai-console/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/CommandValueArgumentParser.kt diff --git a/backend/mirai-console/src/command/descriptor/Exceptions.kt b/mirai-console/backend/mirai-console/src/command/descriptor/Exceptions.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/Exceptions.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/Exceptions.kt diff --git a/backend/mirai-console/src/command/descriptor/ExperimentalCommandDescriptors.kt b/mirai-console/backend/mirai-console/src/command/descriptor/ExperimentalCommandDescriptors.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/ExperimentalCommandDescriptors.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/ExperimentalCommandDescriptors.kt diff --git a/backend/mirai-console/src/command/descriptor/TypeVariant.kt b/mirai-console/backend/mirai-console/src/command/descriptor/TypeVariant.kt similarity index 100% rename from backend/mirai-console/src/command/descriptor/TypeVariant.kt rename to mirai-console/backend/mirai-console/src/command/descriptor/TypeVariant.kt diff --git a/backend/mirai-console/src/command/java/JCompositeCommand.kt b/mirai-console/backend/mirai-console/src/command/java/JCompositeCommand.kt similarity index 100% rename from backend/mirai-console/src/command/java/JCompositeCommand.kt rename to mirai-console/backend/mirai-console/src/command/java/JCompositeCommand.kt diff --git a/backend/mirai-console/src/command/java/JRawCommand.kt b/mirai-console/backend/mirai-console/src/command/java/JRawCommand.kt similarity index 100% rename from backend/mirai-console/src/command/java/JRawCommand.kt rename to mirai-console/backend/mirai-console/src/command/java/JRawCommand.kt diff --git a/backend/mirai-console/src/command/java/JSimpleCommand.kt b/mirai-console/backend/mirai-console/src/command/java/JSimpleCommand.kt similarity index 100% rename from backend/mirai-console/src/command/java/JSimpleCommand.kt rename to mirai-console/backend/mirai-console/src/command/java/JSimpleCommand.kt diff --git a/backend/mirai-console/src/command/parse/CommandCall.kt b/mirai-console/backend/mirai-console/src/command/parse/CommandCall.kt similarity index 100% rename from backend/mirai-console/src/command/parse/CommandCall.kt rename to mirai-console/backend/mirai-console/src/command/parse/CommandCall.kt diff --git a/backend/mirai-console/src/command/parse/CommandCallParser.kt b/mirai-console/backend/mirai-console/src/command/parse/CommandCallParser.kt similarity index 100% rename from backend/mirai-console/src/command/parse/CommandCallParser.kt rename to mirai-console/backend/mirai-console/src/command/parse/CommandCallParser.kt diff --git a/backend/mirai-console/src/command/parse/CommandValueArgument.kt b/mirai-console/backend/mirai-console/src/command/parse/CommandValueArgument.kt similarity index 100% rename from backend/mirai-console/src/command/parse/CommandValueArgument.kt rename to mirai-console/backend/mirai-console/src/command/parse/CommandValueArgument.kt diff --git a/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt b/mirai-console/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt similarity index 100% rename from backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt rename to mirai-console/backend/mirai-console/src/command/parse/SpaceSeparatedCommandCallParser.kt diff --git a/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt b/mirai-console/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt similarity index 100% rename from backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt rename to mirai-console/backend/mirai-console/src/command/resolve/BuiltInCommandCallResolver.kt diff --git a/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt b/mirai-console/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt similarity index 100% rename from backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt rename to mirai-console/backend/mirai-console/src/command/resolve/CommandCallInterceptor.kt diff --git a/backend/mirai-console/src/command/resolve/CommandCallResolver.kt b/mirai-console/backend/mirai-console/src/command/resolve/CommandCallResolver.kt similarity index 100% rename from backend/mirai-console/src/command/resolve/CommandCallResolver.kt rename to mirai-console/backend/mirai-console/src/command/resolve/CommandCallResolver.kt diff --git a/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt b/mirai-console/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt similarity index 100% rename from backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt rename to mirai-console/backend/mirai-console/src/command/resolve/ResolvedCommandCall.kt diff --git a/backend/mirai-console/src/data/AbstractPluginData.kt b/mirai-console/backend/mirai-console/src/data/AbstractPluginData.kt similarity index 100% rename from backend/mirai-console/src/data/AbstractPluginData.kt rename to mirai-console/backend/mirai-console/src/data/AbstractPluginData.kt diff --git a/backend/mirai-console/src/data/AutoSavePluginConfig.kt b/mirai-console/backend/mirai-console/src/data/AutoSavePluginConfig.kt similarity index 100% rename from backend/mirai-console/src/data/AutoSavePluginConfig.kt rename to mirai-console/backend/mirai-console/src/data/AutoSavePluginConfig.kt diff --git a/backend/mirai-console/src/data/AutoSavePluginData.kt b/mirai-console/backend/mirai-console/src/data/AutoSavePluginData.kt similarity index 100% rename from backend/mirai-console/src/data/AutoSavePluginData.kt rename to mirai-console/backend/mirai-console/src/data/AutoSavePluginData.kt diff --git a/backend/mirai-console/src/data/AutoSavePluginDataHolder.kt b/mirai-console/backend/mirai-console/src/data/AutoSavePluginDataHolder.kt similarity index 100% rename from backend/mirai-console/src/data/AutoSavePluginDataHolder.kt rename to mirai-console/backend/mirai-console/src/data/AutoSavePluginDataHolder.kt diff --git a/backend/mirai-console/src/data/PluginConfig.kt b/mirai-console/backend/mirai-console/src/data/PluginConfig.kt similarity index 100% rename from backend/mirai-console/src/data/PluginConfig.kt rename to mirai-console/backend/mirai-console/src/data/PluginConfig.kt diff --git a/backend/mirai-console/src/data/PluginData.kt b/mirai-console/backend/mirai-console/src/data/PluginData.kt similarity index 100% rename from backend/mirai-console/src/data/PluginData.kt rename to mirai-console/backend/mirai-console/src/data/PluginData.kt diff --git a/backend/mirai-console/src/data/PluginDataExtensions.kt b/mirai-console/backend/mirai-console/src/data/PluginDataExtensions.kt similarity index 100% rename from backend/mirai-console/src/data/PluginDataExtensions.kt rename to mirai-console/backend/mirai-console/src/data/PluginDataExtensions.kt diff --git a/backend/mirai-console/src/data/PluginDataHolder.kt b/mirai-console/backend/mirai-console/src/data/PluginDataHolder.kt similarity index 100% rename from backend/mirai-console/src/data/PluginDataHolder.kt rename to mirai-console/backend/mirai-console/src/data/PluginDataHolder.kt diff --git a/backend/mirai-console/src/data/PluginDataStorage.kt b/mirai-console/backend/mirai-console/src/data/PluginDataStorage.kt similarity index 100% rename from backend/mirai-console/src/data/PluginDataStorage.kt rename to mirai-console/backend/mirai-console/src/data/PluginDataStorage.kt diff --git a/backend/mirai-console/src/data/ReadOnlyPluginConfig.kt b/mirai-console/backend/mirai-console/src/data/ReadOnlyPluginConfig.kt similarity index 100% rename from backend/mirai-console/src/data/ReadOnlyPluginConfig.kt rename to mirai-console/backend/mirai-console/src/data/ReadOnlyPluginConfig.kt diff --git a/backend/mirai-console/src/data/ReadOnlyPluginData.kt b/mirai-console/backend/mirai-console/src/data/ReadOnlyPluginData.kt similarity index 100% rename from backend/mirai-console/src/data/ReadOnlyPluginData.kt rename to mirai-console/backend/mirai-console/src/data/ReadOnlyPluginData.kt diff --git a/backend/mirai-console/src/data/Value.kt b/mirai-console/backend/mirai-console/src/data/Value.kt similarity index 100% rename from backend/mirai-console/src/data/Value.kt rename to mirai-console/backend/mirai-console/src/data/Value.kt diff --git a/backend/mirai-console/src/data/ValueDescription.kt b/mirai-console/backend/mirai-console/src/data/ValueDescription.kt similarity index 100% rename from backend/mirai-console/src/data/ValueDescription.kt rename to mirai-console/backend/mirai-console/src/data/ValueDescription.kt diff --git a/backend/mirai-console/src/data/ValueName.kt b/mirai-console/backend/mirai-console/src/data/ValueName.kt similarity index 100% rename from backend/mirai-console/src/data/ValueName.kt rename to mirai-console/backend/mirai-console/src/data/ValueName.kt diff --git a/backend/mirai-console/src/data/java/JAutoSavePluginConfig.kt b/mirai-console/backend/mirai-console/src/data/java/JAutoSavePluginConfig.kt similarity index 100% rename from backend/mirai-console/src/data/java/JAutoSavePluginConfig.kt rename to mirai-console/backend/mirai-console/src/data/java/JAutoSavePluginConfig.kt diff --git a/backend/mirai-console/src/data/java/JAutoSavePluginData.kt b/mirai-console/backend/mirai-console/src/data/java/JAutoSavePluginData.kt similarity index 100% rename from backend/mirai-console/src/data/java/JAutoSavePluginData.kt rename to mirai-console/backend/mirai-console/src/data/java/JAutoSavePluginData.kt diff --git a/backend/mirai-console/src/events/CommandExecutionEvent.kt b/mirai-console/backend/mirai-console/src/events/CommandExecutionEvent.kt similarity index 100% rename from backend/mirai-console/src/events/CommandExecutionEvent.kt rename to mirai-console/backend/mirai-console/src/events/CommandExecutionEvent.kt diff --git a/backend/mirai-console/src/events/ConsoleEvent.kt b/mirai-console/backend/mirai-console/src/events/ConsoleEvent.kt similarity index 100% rename from backend/mirai-console/src/events/ConsoleEvent.kt rename to mirai-console/backend/mirai-console/src/events/ConsoleEvent.kt diff --git a/backend/mirai-console/src/extension/ComponentStorage.kt b/mirai-console/backend/mirai-console/src/extension/ComponentStorage.kt similarity index 100% rename from backend/mirai-console/src/extension/ComponentStorage.kt rename to mirai-console/backend/mirai-console/src/extension/ComponentStorage.kt diff --git a/backend/mirai-console/src/extension/Extension.kt b/mirai-console/backend/mirai-console/src/extension/Extension.kt similarity index 100% rename from backend/mirai-console/src/extension/Extension.kt rename to mirai-console/backend/mirai-console/src/extension/Extension.kt diff --git a/backend/mirai-console/src/extension/ExtensionException.kt b/mirai-console/backend/mirai-console/src/extension/ExtensionException.kt similarity index 100% rename from backend/mirai-console/src/extension/ExtensionException.kt rename to mirai-console/backend/mirai-console/src/extension/ExtensionException.kt diff --git a/backend/mirai-console/src/extension/ExtensionPoint.kt b/mirai-console/backend/mirai-console/src/extension/ExtensionPoint.kt similarity index 100% rename from backend/mirai-console/src/extension/ExtensionPoint.kt rename to mirai-console/backend/mirai-console/src/extension/ExtensionPoint.kt diff --git a/backend/mirai-console/src/extension/PluginComponentStorage.kt b/mirai-console/backend/mirai-console/src/extension/PluginComponentStorage.kt similarity index 100% rename from backend/mirai-console/src/extension/PluginComponentStorage.kt rename to mirai-console/backend/mirai-console/src/extension/PluginComponentStorage.kt diff --git a/backend/mirai-console/src/extensions/BotConfigurationAlterer.kt b/mirai-console/backend/mirai-console/src/extensions/BotConfigurationAlterer.kt similarity index 100% rename from backend/mirai-console/src/extensions/BotConfigurationAlterer.kt rename to mirai-console/backend/mirai-console/src/extensions/BotConfigurationAlterer.kt diff --git a/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt b/mirai-console/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt similarity index 100% rename from backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt rename to mirai-console/backend/mirai-console/src/extensions/CommandCallInterceptorProvider.kt diff --git a/backend/mirai-console/src/extensions/CommandCallParserProvider.kt b/mirai-console/backend/mirai-console/src/extensions/CommandCallParserProvider.kt similarity index 100% rename from backend/mirai-console/src/extensions/CommandCallParserProvider.kt rename to mirai-console/backend/mirai-console/src/extensions/CommandCallParserProvider.kt diff --git a/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt b/mirai-console/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt similarity index 100% rename from backend/mirai-console/src/extensions/CommandCallResolverProvider.kt rename to mirai-console/backend/mirai-console/src/extensions/CommandCallResolverProvider.kt diff --git a/backend/mirai-console/src/extensions/PermissionServiceProvider.kt b/mirai-console/backend/mirai-console/src/extensions/PermissionServiceProvider.kt similarity index 100% rename from backend/mirai-console/src/extensions/PermissionServiceProvider.kt rename to mirai-console/backend/mirai-console/src/extensions/PermissionServiceProvider.kt diff --git a/backend/mirai-console/src/extensions/PluginLoaderProvider.kt b/mirai-console/backend/mirai-console/src/extensions/PluginLoaderProvider.kt similarity index 100% rename from backend/mirai-console/src/extensions/PluginLoaderProvider.kt rename to mirai-console/backend/mirai-console/src/extensions/PluginLoaderProvider.kt diff --git a/backend/mirai-console/src/extensions/PostStartupExtension.kt b/mirai-console/backend/mirai-console/src/extensions/PostStartupExtension.kt similarity index 100% rename from backend/mirai-console/src/extensions/PostStartupExtension.kt rename to mirai-console/backend/mirai-console/src/extensions/PostStartupExtension.kt diff --git a/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt b/mirai-console/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt similarity index 100% rename from backend/mirai-console/src/extensions/SingletonExtensionSelector.kt rename to mirai-console/backend/mirai-console/src/extensions/SingletonExtensionSelector.kt diff --git a/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt b/mirai-console/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt similarity index 100% rename from backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt rename to mirai-console/backend/mirai-console/src/internal/MiraiConsoleBuildConstants.kt diff --git a/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt b/mirai-console/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt similarity index 100% rename from backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt rename to mirai-console/backend/mirai-console/src/internal/MiraiConsoleImplementationBridge.kt diff --git a/backend/mirai-console/src/internal/command/CommandManagerImpl.kt b/mirai-console/backend/mirai-console/src/internal/command/CommandManagerImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/command/CommandManagerImpl.kt rename to mirai-console/backend/mirai-console/src/internal/command/CommandManagerImpl.kt diff --git a/backend/mirai-console/src/internal/command/CommandReflector.kt b/mirai-console/backend/mirai-console/src/internal/command/CommandReflector.kt similarity index 100% rename from backend/mirai-console/src/internal/command/CommandReflector.kt rename to mirai-console/backend/mirai-console/src/internal/command/CommandReflector.kt diff --git a/backend/mirai-console/src/internal/command/CommnadConfig.kt b/mirai-console/backend/mirai-console/src/internal/command/CommnadConfig.kt similarity index 100% rename from backend/mirai-console/src/internal/command/CommnadConfig.kt rename to mirai-console/backend/mirai-console/src/internal/command/CommnadConfig.kt diff --git a/backend/mirai-console/src/internal/command/internal.kt b/mirai-console/backend/mirai-console/src/internal/command/internal.kt similarity index 100% rename from backend/mirai-console/src/internal/command/internal.kt rename to mirai-console/backend/mirai-console/src/internal/command/internal.kt diff --git a/backend/mirai-console/src/internal/data/CompositeValueImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/CompositeValueImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/data/CompositeValueImpl.kt rename to mirai-console/backend/mirai-console/src/internal/data/CompositeValueImpl.kt diff --git a/backend/mirai-console/src/internal/data/MemoryPluginDataStorageImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/MemoryPluginDataStorageImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/data/MemoryPluginDataStorageImpl.kt rename to mirai-console/backend/mirai-console/src/internal/data/MemoryPluginDataStorageImpl.kt diff --git a/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt rename to mirai-console/backend/mirai-console/src/internal/data/MultiFilePluginDataStorageImpl.kt diff --git a/backend/mirai-console/src/internal/data/PluginDataImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/PluginDataImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/data/PluginDataImpl.kt rename to mirai-console/backend/mirai-console/src/internal/data/PluginDataImpl.kt diff --git a/backend/mirai-console/src/internal/data/_PluginData.value.kt b/mirai-console/backend/mirai-console/src/internal/data/_PluginData.value.kt similarity index 100% rename from backend/mirai-console/src/internal/data/_PluginData.value.kt rename to mirai-console/backend/mirai-console/src/internal/data/_PluginData.value.kt diff --git a/backend/mirai-console/src/internal/data/_PrimitiveValueDeclarations.kt b/mirai-console/backend/mirai-console/src/internal/data/_PrimitiveValueDeclarations.kt similarity index 100% rename from backend/mirai-console/src/internal/data/_PrimitiveValueDeclarations.kt rename to mirai-console/backend/mirai-console/src/internal/data/_PrimitiveValueDeclarations.kt diff --git a/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt b/mirai-console/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt similarity index 100% rename from backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt rename to mirai-console/backend/mirai-console/src/internal/data/builtins/AutoLoginConfig.kt diff --git a/backend/mirai-console/src/internal/data/builtins/ConsoleDataScope.kt b/mirai-console/backend/mirai-console/src/internal/data/builtins/ConsoleDataScope.kt similarity index 100% rename from backend/mirai-console/src/internal/data/builtins/ConsoleDataScope.kt rename to mirai-console/backend/mirai-console/src/internal/data/builtins/ConsoleDataScope.kt diff --git a/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt b/mirai-console/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt similarity index 100% rename from backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt rename to mirai-console/backend/mirai-console/src/internal/data/builtins/LoggerConfig.kt diff --git a/backend/mirai-console/src/internal/data/collectionUtil.kt b/mirai-console/backend/mirai-console/src/internal/data/collectionUtil.kt similarity index 100% rename from backend/mirai-console/src/internal/data/collectionUtil.kt rename to mirai-console/backend/mirai-console/src/internal/data/collectionUtil.kt diff --git a/backend/mirai-console/src/internal/data/reflectionUtils.kt b/mirai-console/backend/mirai-console/src/internal/data/reflectionUtils.kt similarity index 100% rename from backend/mirai-console/src/internal/data/reflectionUtils.kt rename to mirai-console/backend/mirai-console/src/internal/data/reflectionUtils.kt diff --git a/backend/mirai-console/src/internal/data/serializerHelper.kt b/mirai-console/backend/mirai-console/src/internal/data/serializerHelper.kt similarity index 100% rename from backend/mirai-console/src/internal/data/serializerHelper.kt rename to mirai-console/backend/mirai-console/src/internal/data/serializerHelper.kt diff --git a/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt b/mirai-console/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt rename to mirai-console/backend/mirai-console/src/internal/data/valueFromKTypeImpl.kt diff --git a/backend/mirai-console/src/internal/extension/BuiltInSingletonExtensionSelector.kt b/mirai-console/backend/mirai-console/src/internal/extension/BuiltInSingletonExtensionSelector.kt similarity index 100% rename from backend/mirai-console/src/internal/extension/BuiltInSingletonExtensionSelector.kt rename to mirai-console/backend/mirai-console/src/internal/extension/BuiltInSingletonExtensionSelector.kt diff --git a/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt b/mirai-console/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt similarity index 100% rename from backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt rename to mirai-console/backend/mirai-console/src/internal/extension/ComponentStorageInternal.kt diff --git a/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt b/mirai-console/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt rename to mirai-console/backend/mirai-console/src/internal/logging/LoggerControllerImpl.kt diff --git a/backend/mirai-console/src/internal/logging/MiraiConsoleLogger.kt b/mirai-console/backend/mirai-console/src/internal/logging/MiraiConsoleLogger.kt similarity index 100% rename from backend/mirai-console/src/internal/logging/MiraiConsoleLogger.kt rename to mirai-console/backend/mirai-console/src/internal/logging/MiraiConsoleLogger.kt diff --git a/backend/mirai-console/src/internal/permission/AbstractConcurrentPermissionService.kt b/mirai-console/backend/mirai-console/src/internal/permission/AbstractConcurrentPermissionService.kt similarity index 100% rename from backend/mirai-console/src/internal/permission/AbstractConcurrentPermissionService.kt rename to mirai-console/backend/mirai-console/src/internal/permission/AbstractConcurrentPermissionService.kt diff --git a/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt b/mirai-console/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt similarity index 100% rename from backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt rename to mirai-console/backend/mirai-console/src/internal/permission/BuiltInPermissionServices.kt diff --git a/backend/mirai-console/src/internal/permission/parseFromStringImpl.kt b/mirai-console/backend/mirai-console/src/internal/permission/parseFromStringImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/permission/parseFromStringImpl.kt rename to mirai-console/backend/mirai-console/src/internal/permission/parseFromStringImpl.kt diff --git a/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt b/mirai-console/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt rename to mirai-console/backend/mirai-console/src/internal/plugin/BuiltInJvmPluginLoaderImpl.kt diff --git a/backend/mirai-console/src/internal/plugin/Exceptions.kt b/mirai-console/backend/mirai-console/src/internal/plugin/Exceptions.kt similarity index 100% rename from backend/mirai-console/src/internal/plugin/Exceptions.kt rename to mirai-console/backend/mirai-console/src/internal/plugin/Exceptions.kt diff --git a/backend/mirai-console/src/internal/plugin/ExportManagerImpl.kt b/mirai-console/backend/mirai-console/src/internal/plugin/ExportManagerImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/plugin/ExportManagerImpl.kt rename to mirai-console/backend/mirai-console/src/internal/plugin/ExportManagerImpl.kt diff --git a/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt similarity index 100% rename from backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt rename to mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginClassLoader.kt diff --git a/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt b/mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt similarity index 100% rename from backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt rename to mirai-console/backend/mirai-console/src/internal/plugin/JvmPluginInternal.kt diff --git a/backend/mirai-console/src/internal/plugin/PluginDescriptionUtil.kt b/mirai-console/backend/mirai-console/src/internal/plugin/PluginDescriptionUtil.kt similarity index 100% rename from backend/mirai-console/src/internal/plugin/PluginDescriptionUtil.kt rename to mirai-console/backend/mirai-console/src/internal/plugin/PluginDescriptionUtil.kt diff --git a/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt b/mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt rename to mirai-console/backend/mirai-console/src/internal/plugin/PluginManagerImpl.kt diff --git a/backend/mirai-console/src/internal/util/ByteUtils.kt b/mirai-console/backend/mirai-console/src/internal/util/ByteUtils.kt similarity index 100% rename from backend/mirai-console/src/internal/util/ByteUtils.kt rename to mirai-console/backend/mirai-console/src/internal/util/ByteUtils.kt diff --git a/backend/mirai-console/src/internal/util/CommonUtils.kt b/mirai-console/backend/mirai-console/src/internal/util/CommonUtils.kt similarity index 100% rename from backend/mirai-console/src/internal/util/CommonUtils.kt rename to mirai-console/backend/mirai-console/src/internal/util/CommonUtils.kt diff --git a/backend/mirai-console/src/internal/util/ConsoleInputImpl.kt b/mirai-console/backend/mirai-console/src/internal/util/ConsoleInputImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/util/ConsoleInputImpl.kt rename to mirai-console/backend/mirai-console/src/internal/util/ConsoleInputImpl.kt diff --git a/backend/mirai-console/src/internal/util/JavaPluginSchedulerImpl.kt b/mirai-console/backend/mirai-console/src/internal/util/JavaPluginSchedulerImpl.kt similarity index 100% rename from backend/mirai-console/src/internal/util/JavaPluginSchedulerImpl.kt rename to mirai-console/backend/mirai-console/src/internal/util/JavaPluginSchedulerImpl.kt diff --git a/backend/mirai-console/src/internal/util/PluginServiceHelper.kt b/mirai-console/backend/mirai-console/src/internal/util/PluginServiceHelper.kt similarity index 100% rename from backend/mirai-console/src/internal/util/PluginServiceHelper.kt rename to mirai-console/backend/mirai-console/src/internal/util/PluginServiceHelper.kt diff --git a/backend/mirai-console/src/internal/util/semver/RequirementInternal.kt b/mirai-console/backend/mirai-console/src/internal/util/semver/RequirementInternal.kt similarity index 100% rename from backend/mirai-console/src/internal/util/semver/RequirementInternal.kt rename to mirai-console/backend/mirai-console/src/internal/util/semver/RequirementInternal.kt diff --git a/backend/mirai-console/src/internal/util/semver/RequirementParser.kt b/mirai-console/backend/mirai-console/src/internal/util/semver/RequirementParser.kt similarity index 100% rename from backend/mirai-console/src/internal/util/semver/RequirementParser.kt rename to mirai-console/backend/mirai-console/src/internal/util/semver/RequirementParser.kt diff --git a/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt b/mirai-console/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt similarity index 100% rename from backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt rename to mirai-console/backend/mirai-console/src/internal/util/semver/SemVersionInternal.kt diff --git a/backend/mirai-console/src/logging/AbstractLoggerController.kt b/mirai-console/backend/mirai-console/src/logging/AbstractLoggerController.kt similarity index 100% rename from backend/mirai-console/src/logging/AbstractLoggerController.kt rename to mirai-console/backend/mirai-console/src/logging/AbstractLoggerController.kt diff --git a/backend/mirai-console/src/logging/LoggerController.kt b/mirai-console/backend/mirai-console/src/logging/LoggerController.kt similarity index 100% rename from backend/mirai-console/src/logging/LoggerController.kt rename to mirai-console/backend/mirai-console/src/logging/LoggerController.kt diff --git a/backend/mirai-console/src/permission/Permission.kt b/mirai-console/backend/mirai-console/src/permission/Permission.kt similarity index 100% rename from backend/mirai-console/src/permission/Permission.kt rename to mirai-console/backend/mirai-console/src/permission/Permission.kt diff --git a/backend/mirai-console/src/permission/PermissionId.kt b/mirai-console/backend/mirai-console/src/permission/PermissionId.kt similarity index 100% rename from backend/mirai-console/src/permission/PermissionId.kt rename to mirai-console/backend/mirai-console/src/permission/PermissionId.kt diff --git a/backend/mirai-console/src/permission/PermissionIdNamespace.kt b/mirai-console/backend/mirai-console/src/permission/PermissionIdNamespace.kt similarity index 100% rename from backend/mirai-console/src/permission/PermissionIdNamespace.kt rename to mirai-console/backend/mirai-console/src/permission/PermissionIdNamespace.kt diff --git a/backend/mirai-console/src/permission/PermissionImplementation.kt b/mirai-console/backend/mirai-console/src/permission/PermissionImplementation.kt similarity index 100% rename from backend/mirai-console/src/permission/PermissionImplementation.kt rename to mirai-console/backend/mirai-console/src/permission/PermissionImplementation.kt diff --git a/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt b/mirai-console/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt similarity index 100% rename from backend/mirai-console/src/permission/PermissionRegistryConflictException.kt rename to mirai-console/backend/mirai-console/src/permission/PermissionRegistryConflictException.kt diff --git a/backend/mirai-console/src/permission/PermissionService.kt b/mirai-console/backend/mirai-console/src/permission/PermissionService.kt similarity index 100% rename from backend/mirai-console/src/permission/PermissionService.kt rename to mirai-console/backend/mirai-console/src/permission/PermissionService.kt diff --git a/backend/mirai-console/src/permission/Permittee.kt b/mirai-console/backend/mirai-console/src/permission/Permittee.kt similarity index 100% rename from backend/mirai-console/src/permission/Permittee.kt rename to mirai-console/backend/mirai-console/src/permission/Permittee.kt diff --git a/backend/mirai-console/src/permission/PermitteeId.kt b/mirai-console/backend/mirai-console/src/permission/PermitteeId.kt similarity index 100% rename from backend/mirai-console/src/permission/PermitteeId.kt rename to mirai-console/backend/mirai-console/src/permission/PermitteeId.kt diff --git a/backend/mirai-console/src/plugin/Plugin.kt b/mirai-console/backend/mirai-console/src/plugin/Plugin.kt similarity index 100% rename from backend/mirai-console/src/plugin/Plugin.kt rename to mirai-console/backend/mirai-console/src/plugin/Plugin.kt diff --git a/backend/mirai-console/src/plugin/PluginFileExtensions.kt b/mirai-console/backend/mirai-console/src/plugin/PluginFileExtensions.kt similarity index 100% rename from backend/mirai-console/src/plugin/PluginFileExtensions.kt rename to mirai-console/backend/mirai-console/src/plugin/PluginFileExtensions.kt diff --git a/backend/mirai-console/src/plugin/PluginManager.kt b/mirai-console/backend/mirai-console/src/plugin/PluginManager.kt similarity index 100% rename from backend/mirai-console/src/plugin/PluginManager.kt rename to mirai-console/backend/mirai-console/src/plugin/PluginManager.kt diff --git a/backend/mirai-console/src/plugin/ResourceContainer.kt b/mirai-console/backend/mirai-console/src/plugin/ResourceContainer.kt similarity index 100% rename from backend/mirai-console/src/plugin/ResourceContainer.kt rename to mirai-console/backend/mirai-console/src/plugin/ResourceContainer.kt diff --git a/backend/mirai-console/src/plugin/center/PluginCenter.kt b/mirai-console/backend/mirai-console/src/plugin/center/PluginCenter.kt similarity index 100% rename from backend/mirai-console/src/plugin/center/PluginCenter.kt rename to mirai-console/backend/mirai-console/src/plugin/center/PluginCenter.kt diff --git a/backend/mirai-console/src/plugin/description/IllegalPluginDescriptionException.kt b/mirai-console/backend/mirai-console/src/plugin/description/IllegalPluginDescriptionException.kt similarity index 100% rename from backend/mirai-console/src/plugin/description/IllegalPluginDescriptionException.kt rename to mirai-console/backend/mirai-console/src/plugin/description/IllegalPluginDescriptionException.kt diff --git a/backend/mirai-console/src/plugin/description/PluginDependency.kt b/mirai-console/backend/mirai-console/src/plugin/description/PluginDependency.kt similarity index 100% rename from backend/mirai-console/src/plugin/description/PluginDependency.kt rename to mirai-console/backend/mirai-console/src/plugin/description/PluginDependency.kt diff --git a/backend/mirai-console/src/plugin/description/PluginDescription.kt b/mirai-console/backend/mirai-console/src/plugin/description/PluginDescription.kt similarity index 100% rename from backend/mirai-console/src/plugin/description/PluginDescription.kt rename to mirai-console/backend/mirai-console/src/plugin/description/PluginDescription.kt diff --git a/backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/AbstractJvmPlugin.kt diff --git a/backend/mirai-console/src/plugin/jvm/ExportManager.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/ExportManager.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/ExportManager.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/ExportManager.kt diff --git a/backend/mirai-console/src/plugin/jvm/JavaPlugin.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/JavaPlugin.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/JavaPlugin.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/JavaPlugin.kt diff --git a/backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/JavaPluginScheduler.kt diff --git a/backend/mirai-console/src/plugin/jvm/JvmPlugin.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/JvmPlugin.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/JvmPlugin.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/JvmPlugin.kt diff --git a/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginDescription.kt diff --git a/backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/JvmPluginLoader.kt diff --git a/backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt b/mirai-console/backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt similarity index 100% rename from backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt rename to mirai-console/backend/mirai-console/src/plugin/jvm/KotlinPlugin.kt diff --git a/backend/mirai-console/src/plugin/loader/FilePluginLoader.kt b/mirai-console/backend/mirai-console/src/plugin/loader/FilePluginLoader.kt similarity index 100% rename from backend/mirai-console/src/plugin/loader/FilePluginLoader.kt rename to mirai-console/backend/mirai-console/src/plugin/loader/FilePluginLoader.kt diff --git a/backend/mirai-console/src/plugin/loader/PluginLoadException.kt b/mirai-console/backend/mirai-console/src/plugin/loader/PluginLoadException.kt similarity index 100% rename from backend/mirai-console/src/plugin/loader/PluginLoadException.kt rename to mirai-console/backend/mirai-console/src/plugin/loader/PluginLoadException.kt diff --git a/backend/mirai-console/src/plugin/loader/PluginLoader.kt b/mirai-console/backend/mirai-console/src/plugin/loader/PluginLoader.kt similarity index 100% rename from backend/mirai-console/src/plugin/loader/PluginLoader.kt rename to mirai-console/backend/mirai-console/src/plugin/loader/PluginLoader.kt diff --git a/backend/mirai-console/src/util/Annotations.kt b/mirai-console/backend/mirai-console/src/util/Annotations.kt similarity index 100% rename from backend/mirai-console/src/util/Annotations.kt rename to mirai-console/backend/mirai-console/src/util/Annotations.kt diff --git a/backend/mirai-console/src/util/AnsiMessageBuilder.kt b/mirai-console/backend/mirai-console/src/util/AnsiMessageBuilder.kt similarity index 100% rename from backend/mirai-console/src/util/AnsiMessageBuilder.kt rename to mirai-console/backend/mirai-console/src/util/AnsiMessageBuilder.kt diff --git a/backend/mirai-console/src/util/ConsoleInput.kt b/mirai-console/backend/mirai-console/src/util/ConsoleInput.kt similarity index 100% rename from backend/mirai-console/src/util/ConsoleInput.kt rename to mirai-console/backend/mirai-console/src/util/ConsoleInput.kt diff --git a/backend/mirai-console/src/util/ContactUtils.kt b/mirai-console/backend/mirai-console/src/util/ContactUtils.kt similarity index 100% rename from backend/mirai-console/src/util/ContactUtils.kt rename to mirai-console/backend/mirai-console/src/util/ContactUtils.kt diff --git a/backend/mirai-console/src/util/CoroutineScopeUtils.kt b/mirai-console/backend/mirai-console/src/util/CoroutineScopeUtils.kt similarity index 100% rename from backend/mirai-console/src/util/CoroutineScopeUtils.kt rename to mirai-console/backend/mirai-console/src/util/CoroutineScopeUtils.kt diff --git a/backend/mirai-console/src/util/MessageScope.kt b/mirai-console/backend/mirai-console/src/util/MessageScope.kt similarity index 100% rename from backend/mirai-console/src/util/MessageScope.kt rename to mirai-console/backend/mirai-console/src/util/MessageScope.kt diff --git a/backend/mirai-console/src/util/MessageUtils.kt b/mirai-console/backend/mirai-console/src/util/MessageUtils.kt similarity index 100% rename from backend/mirai-console/src/util/MessageUtils.kt rename to mirai-console/backend/mirai-console/src/util/MessageUtils.kt diff --git a/backend/mirai-console/src/util/SemVersion.kt b/mirai-console/backend/mirai-console/src/util/SemVersion.kt similarity index 100% rename from backend/mirai-console/src/util/SemVersion.kt rename to mirai-console/backend/mirai-console/src/util/SemVersion.kt diff --git a/backend/mirai-console/src/util/StandardUtils.kt b/mirai-console/backend/mirai-console/src/util/StandardUtils.kt similarity index 100% rename from backend/mirai-console/src/util/StandardUtils.kt rename to mirai-console/backend/mirai-console/src/util/StandardUtils.kt diff --git a/backend/mirai-console/src/util/retryCatching.kt b/mirai-console/backend/mirai-console/src/util/retryCatching.kt similarity index 100% rename from backend/mirai-console/src/util/retryCatching.kt rename to mirai-console/backend/mirai-console/src/util/retryCatching.kt diff --git a/backend/mirai-console/test/TestMiraiConosle.kt b/mirai-console/backend/mirai-console/test/TestMiraiConosle.kt similarity index 100% rename from backend/mirai-console/test/TestMiraiConosle.kt rename to mirai-console/backend/mirai-console/test/TestMiraiConosle.kt diff --git a/backend/mirai-console/test/command/JSimpleTest.java b/mirai-console/backend/mirai-console/test/command/JSimpleTest.java similarity index 100% rename from backend/mirai-console/test/command/JSimpleTest.java rename to mirai-console/backend/mirai-console/test/command/JSimpleTest.java diff --git a/backend/mirai-console/test/command/TestCommand.kt b/mirai-console/backend/mirai-console/test/command/TestCommand.kt similarity index 100% rename from backend/mirai-console/test/command/TestCommand.kt rename to mirai-console/backend/mirai-console/test/command/TestCommand.kt diff --git a/backend/mirai-console/test/command/commanTestingUtil.kt b/mirai-console/backend/mirai-console/test/command/commanTestingUtil.kt similarity index 100% rename from backend/mirai-console/test/command/commanTestingUtil.kt rename to mirai-console/backend/mirai-console/test/command/commanTestingUtil.kt diff --git a/backend/mirai-console/test/data/JavaPluginDescriptionTests.kt b/mirai-console/backend/mirai-console/test/data/JavaPluginDescriptionTests.kt similarity index 96% rename from backend/mirai-console/test/data/JavaPluginDescriptionTests.kt rename to mirai-console/backend/mirai-console/test/data/JavaPluginDescriptionTests.kt index 1541a9b91..79b623d0b 100644 --- a/backend/mirai-console/test/data/JavaPluginDescriptionTests.kt +++ b/mirai-console/backend/mirai-console/test/data/JavaPluginDescriptionTests.kt @@ -6,7 +6,7 @@ * * https://github.com/mamoe/mirai/blob/master/LICENSE */ -package data +package net.mamoe.mirai.console.data import net.mamoe.mirai.console.framework.AbstractConsoleTest diff --git a/backend/mirai-console/test/data/PluginMovingTests.kt b/mirai-console/backend/mirai-console/test/data/PluginMovingTests.kt similarity index 100% rename from backend/mirai-console/test/data/PluginMovingTests.kt rename to mirai-console/backend/mirai-console/test/data/PluginMovingTests.kt diff --git a/backend/mirai-console/test/data/SettingTest.kt b/mirai-console/backend/mirai-console/test/data/SettingTest.kt similarity index 100% rename from backend/mirai-console/test/data/SettingTest.kt rename to mirai-console/backend/mirai-console/test/data/SettingTest.kt diff --git a/backend/mirai-console/test/framework/AbstractConsoleTest.kt b/mirai-console/backend/mirai-console/test/framework/AbstractConsoleTest.kt similarity index 100% rename from backend/mirai-console/test/framework/AbstractConsoleTest.kt rename to mirai-console/backend/mirai-console/test/framework/AbstractConsoleTest.kt diff --git a/backend/mirai-console/test/framework/test/FrameworkTest.kt b/mirai-console/backend/mirai-console/test/framework/test/FrameworkTest.kt similarity index 100% rename from backend/mirai-console/test/framework/test/FrameworkTest.kt rename to mirai-console/backend/mirai-console/test/framework/test/FrameworkTest.kt diff --git a/backend/mirai-console/test/logging/TestALC_PathBased.kt b/mirai-console/backend/mirai-console/test/logging/TestALC_PathBased.kt similarity index 100% rename from backend/mirai-console/test/logging/TestALC_PathBased.kt rename to mirai-console/backend/mirai-console/test/logging/TestALC_PathBased.kt diff --git a/backend/mirai-console/test/permission/PermissionsBasicsTest.kt b/mirai-console/backend/mirai-console/test/permission/PermissionsBasicsTest.kt similarity index 100% rename from backend/mirai-console/test/permission/PermissionsBasicsTest.kt rename to mirai-console/backend/mirai-console/test/permission/PermissionsBasicsTest.kt diff --git a/backend/mirai-console/test/util/TestCoroutineUtils.kt b/mirai-console/backend/mirai-console/test/util/TestCoroutineUtils.kt similarity index 100% rename from backend/mirai-console/test/util/TestCoroutineUtils.kt rename to mirai-console/backend/mirai-console/test/util/TestCoroutineUtils.kt diff --git a/backend/mirai-console/test/util/TestSemVersion.kt b/mirai-console/backend/mirai-console/test/util/TestSemVersion.kt similarity index 100% rename from backend/mirai-console/test/util/TestSemVersion.kt rename to mirai-console/backend/mirai-console/test/util/TestSemVersion.kt diff --git a/docs/.ConfiguringProjects_images/6d010b1a.png b/mirai-console/docs/.ConfiguringProjects_images/6d010b1a.png similarity index 100% rename from docs/.ConfiguringProjects_images/6d010b1a.png rename to mirai-console/docs/.ConfiguringProjects_images/6d010b1a.png diff --git a/docs/.ConfiguringProjects_images/a6a3b24b.png b/mirai-console/docs/.ConfiguringProjects_images/a6a3b24b.png similarity index 100% rename from docs/.ConfiguringProjects_images/a6a3b24b.png rename to mirai-console/docs/.ConfiguringProjects_images/a6a3b24b.png diff --git a/docs/.images/PluginMainDeclaration.png b/mirai-console/docs/.images/PluginMainDeclaration.png similarity index 100% rename from docs/.images/PluginMainDeclaration.png rename to mirai-console/docs/.images/PluginMainDeclaration.png diff --git a/docs/.images/PluginMainServiceNotConfigured.png b/mirai-console/docs/.images/PluginMainServiceNotConfigured.png similarity index 100% rename from docs/.images/PluginMainServiceNotConfigured.png rename to mirai-console/docs/.images/PluginMainServiceNotConfigured.png diff --git a/docs/Appendix.md b/mirai-console/docs/Appendix.md similarity index 100% rename from docs/Appendix.md rename to mirai-console/docs/Appendix.md diff --git a/docs/BuiltInCommands.md b/mirai-console/docs/BuiltInCommands.md similarity index 100% rename from docs/BuiltInCommands.md rename to mirai-console/docs/BuiltInCommands.md diff --git a/docs/Commands.md b/mirai-console/docs/Commands.md similarity index 100% rename from docs/Commands.md rename to mirai-console/docs/Commands.md diff --git a/docs/ConfiguringProjects.md b/mirai-console/docs/ConfiguringProjects.md similarity index 100% rename from docs/ConfiguringProjects.md rename to mirai-console/docs/ConfiguringProjects.md diff --git a/docs/Contributing.md b/mirai-console/docs/Contributing.md similarity index 100% rename from docs/Contributing.md rename to mirai-console/docs/Contributing.md diff --git a/docs/Extensions.md b/mirai-console/docs/Extensions.md similarity index 100% rename from docs/Extensions.md rename to mirai-console/docs/Extensions.md diff --git a/docs/FrontEnd.md b/mirai-console/docs/FrontEnd.md similarity index 100% rename from docs/FrontEnd.md rename to mirai-console/docs/FrontEnd.md diff --git a/docs/Permissions.md b/mirai-console/docs/Permissions.md similarity index 100% rename from docs/Permissions.md rename to mirai-console/docs/Permissions.md diff --git a/docs/PluginData.md b/mirai-console/docs/PluginData.md similarity index 100% rename from docs/PluginData.md rename to mirai-console/docs/PluginData.md diff --git a/docs/Plugins.md b/mirai-console/docs/Plugins.md similarity index 100% rename from docs/Plugins.md rename to mirai-console/docs/Plugins.md diff --git a/docs/QA.md b/mirai-console/docs/QA.md similarity index 100% rename from docs/QA.md rename to mirai-console/docs/QA.md diff --git a/docs/README.md b/mirai-console/docs/README.md similarity index 100% rename from docs/README.md rename to mirai-console/docs/README.md diff --git a/docs/Run.md b/mirai-console/docs/Run.md similarity index 100% rename from docs/Run.md rename to mirai-console/docs/Run.md diff --git a/mirai-console/frontend/mirai-android/.github/ISSUE_TEMPLATE/bug_report.md b/mirai-console/frontend/mirai-android/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..03be4ae5c --- /dev/null +++ b/mirai-console/frontend/mirai-android/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,40 @@ +--- +name: Bug report +about: 报告一个bug +title: "[BUG]" +labels: bug +assignees: '' + +--- + + + + + + + + + + +``` + + + +``` + + +#### 复现 + + + + + + + diff --git a/mirai-console/frontend/mirai-android/.github/workflows/android.yml b/mirai-console/frontend/mirai-android/.github/workflows/android.yml new file mode 100644 index 000000000..67f5a9ef0 --- /dev/null +++ b/mirai-console/frontend/mirai-android/.github/workflows/android.yml @@ -0,0 +1,68 @@ +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@v2 +# +# - 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 diff --git a/mirai-console/frontend/mirai-android/.gitignore b/mirai-console/frontend/mirai-android/.gitignore new file mode 100644 index 000000000..cc00fc38f --- /dev/null +++ b/mirai-console/frontend/mirai-android/.gitignore @@ -0,0 +1,15 @@ +*.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 \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/LICENSE b/mirai-console/frontend/mirai-android/LICENSE new file mode 100644 index 000000000..0ad25db4b --- /dev/null +++ b/mirai-console/frontend/mirai-android/LICENSE @@ -0,0 +1,661 @@ + 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 +. diff --git a/mirai-console/frontend/mirai-android/README.md b/mirai-console/frontend/mirai-android/README.md new file mode 100644 index 000000000..076b48601 --- /dev/null +++ b/mirai-console/frontend/mirai-android/README.md @@ -0,0 +1,317 @@ + + +
+ logo
+ + title + +
+ + +# MiraiAndroid + +GitHub Workflow Status + +GitHub issues + +GitHub pull requests + +mirai-console的Android前端程序,可作为qq机器人使用,支持多种脚本接口 + +关于mirai项目以及mirai-console的一切请点击[这里](https://github.com/mamoe/mirai) + +相比使用`Termux`或者是`Linux Deploy`等应用运行mirai的方案,该项目提供的方案具有更好的性能以及更少的资源占用,但可能存在兼容性问题 + +最新的构建版本你可以到release或QQ群内找到 + +MiraiAndroid交流群:`1131127734`但是请注意,如果您违反了群内相关规定或是有其他不当行为你可能会被无理由移出本群 + +图标以及形象由画师DazeCake绘制 + +## 声明 + +### 一切开发旨在学习,请勿用于非法用途 +- 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 . + +`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: 后台运行被系统杀死
+A:请手动将应用添加到系统后台白名单 + +Q:应用崩溃或后台报错
+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 ] +``` diff --git a/mirai-console/frontend/mirai-android/app/.gitignore b/mirai-console/frontend/mirai-android/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/mirai-console/frontend/mirai-android/app/build.gradle b/mirai-console/frontend/mirai-android/app/build.gradle new file mode 100644 index 000000000..e4377a1e3 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/build.gradle @@ -0,0 +1,171 @@ +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 + +} diff --git a/mirai-console/frontend/mirai-android/app/libs/d8.jar b/mirai-console/frontend/mirai-android/app/libs/d8.jar new file mode 100644 index 000000000..b47cc7413 Binary files /dev/null and b/mirai-console/frontend/mirai-android/app/libs/d8.jar differ diff --git a/mirai-console/frontend/mirai-android/app/libs/giteeman-0.2.3.jar b/mirai-console/frontend/mirai-android/app/libs/giteeman-0.2.3.jar new file mode 100644 index 000000000..9bab0e05b Binary files /dev/null and b/mirai-console/frontend/mirai-android/app/libs/giteeman-0.2.3.jar differ diff --git a/mirai-console/frontend/mirai-android/app/libs/mirai-console-0.5.2.jar b/mirai-console/frontend/mirai-android/app/libs/mirai-console-0.5.2.jar new file mode 100644 index 000000000..ff3685b07 Binary files /dev/null and b/mirai-console/frontend/mirai-android/app/libs/mirai-console-0.5.2.jar differ diff --git a/mirai-console/frontend/mirai-android/app/libs/mirai-js-1.0.0.jar b/mirai-console/frontend/mirai-android/app/libs/mirai-js-1.0.0.jar new file mode 100644 index 000000000..4bb5d4872 Binary files /dev/null and b/mirai-console/frontend/mirai-android/app/libs/mirai-js-1.0.0.jar differ diff --git a/mirai-console/frontend/mirai-android/app/proguard-rules.pro b/mirai-console/frontend/mirai-android/app/proguard-rules.pro new file mode 100644 index 000000000..0da1a2f23 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/proguard-rules.pro @@ -0,0 +1,87 @@ +# 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 { + ; +} +-keepclassmembers class kotlin.Metadata { + public ; +} +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + static void checkParameterIsNotNull(java.lang.Object, java.lang.String); +} + +-keepclasseswithmembernames class * { + native ; +} + +-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 ; +} + + +# 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 \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/TestWithIdleResources.kt b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/TestWithIdleResources.kt new file mode 100644 index 000000000..6b1634674 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/TestWithIdleResources.kt @@ -0,0 +1,28 @@ +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) + } +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/activity/NavTest.kt b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/activity/NavTest.kt new file mode 100644 index 000000000..51db9f264 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/activity/NavTest.kt @@ -0,0 +1,250 @@ +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()) + + } +} diff --git a/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleIntentTest.kt b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleIntentTest.kt new file mode 100644 index 000000000..4357b5bb8 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleIntentTest.kt @@ -0,0 +1,78 @@ +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() + } + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleTest.kt b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleTest.kt new file mode 100644 index 000000000..c7e614ad3 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/console/ConsoleTest.kt @@ -0,0 +1,180 @@ +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" + ) + ) + ) + ) + } + + +} diff --git a/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/script/ScriptManageTest.kt b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/script/ScriptManageTest.kt new file mode 100644 index 000000000..b2fc19503 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/script/ScriptManageTest.kt @@ -0,0 +1,100 @@ +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())) + } +} diff --git a/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/utils.kt b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/utils.kt new file mode 100644 index 000000000..222dc8901 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/androidTest/java/io/github/mzdluo123/mirai/android/utils.kt @@ -0,0 +1,49 @@ +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, position: Int +): Matcher { + + return object : TypeSafeMatcher() { + 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() { + 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 + } +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/AndroidManifest.xml b/mirai-console/frontend/mirai-android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..d46b4b7db --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/AndroidManifest.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/aidl/io/github/mzdluo123/mirai/android/IbotAidlInterface.aidl b/mirai-console/frontend/mirai-android/app/src/main/aidl/io/github/mzdluo123/mirai/android/IbotAidlInterface.aidl new file mode 100644 index 000000000..fcf1aa198 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/aidl/io/github/mzdluo123/mirai/android/IbotAidlInterface.aidl @@ -0,0 +1,28 @@ +// 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(); + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/ic_new_launcher-playstore.png b/mirai-console/frontend/mirai-android/app/src/main/ic_new_launcher-playstore.png new file mode 100644 index 000000000..55cf082d9 Binary files /dev/null and b/mirai-console/frontend/mirai-android/app/src/main/ic_new_launcher-playstore.png differ diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/AppSettings.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/AppSettings.kt new file mode 100644 index 000000000..768d9251d --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/AppSettings.kt @@ -0,0 +1,26 @@ +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) +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/BotApplication.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/BotApplication.kt new file mode 100644 index 000000000..01f2ca070 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/BotApplication.kt @@ -0,0 +1,87 @@ +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) + }) + } + + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/IdleResources.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/IdleResources.kt new file mode 100644 index 000000000..8a8f4bb79 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/IdleResources.kt @@ -0,0 +1,12 @@ +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") } +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/NotificationFactory.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/NotificationFactory.kt new file mode 100644 index 000000000..77db725e9 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/NotificationFactory.kt @@ -0,0 +1,143 @@ +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() + } + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/CaptchaActivity.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/CaptchaActivity.kt new file mode 100644 index 000000000..6faee17b7 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/CaptchaActivity.kt @@ -0,0 +1,55 @@ +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() + } + } + + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/MainActivity.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/MainActivity.kt new file mode 100644 index 000000000..9e72d917b --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/MainActivity.kt @@ -0,0 +1,165 @@ +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()}" + ) + ) + } + +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/PluginImportActivity.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/PluginImportActivity.kt new file mode 100644 index 000000000..6092ce374 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/PluginImportActivity.kt @@ -0,0 +1,181 @@ +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>(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 + }.getOrElse { + listOf() + } + ) + + } + withContext(Dispatchers.Main) { + activityPluginImportBinding.pluginData = plugInfo + } + + } + + + } +} + diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/UnsafeLoginActivity.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/UnsafeLoginActivity.kt new file mode 100644 index 000000000..f3905b0f2 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/activity/UnsafeLoginActivity.kt @@ -0,0 +1,110 @@ +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 +// } + +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSender.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSender.kt new file mode 100644 index 000000000..c04b88109 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSender.kt @@ -0,0 +1,22 @@ +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() + } +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSenderFactory.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSenderFactory.kt new file mode 100644 index 000000000..8ef6fe622 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSenderFactory.kt @@ -0,0 +1,16 @@ +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 + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidLoginSolver.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidLoginSolver.kt new file mode 100644 index 000000000..3910ae75f --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidLoginSolver.kt @@ -0,0 +1,63 @@ +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 + 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) + ) + } + } + +} + diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidMiraiConsole.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidMiraiConsole.kt new file mode 100644 index 000000000..08e082801 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidMiraiConsole.kt @@ -0,0 +1,169 @@ +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(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) = 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(selfQQ.avatarUrl).let { avatarData -> + BitmapFactory.decodeByteArray(avatarData, 0, avatarData.size) + } + } catch (e: Exception) { + delay(1000) + downloadAvatar() + } + + private fun Bot.subscribeBotLifeEvent() { + subscribeAlways(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(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) } + } +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/BootReceiver.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/BootReceiver.kt new file mode 100644 index 000000000..f2039947d --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/BootReceiver.kt @@ -0,0 +1,42 @@ +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) + } + } + } +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/PushMsgReceiver.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/PushMsgReceiver.kt new file mode 100644 index 000000000..e6480e1bd --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/receiver/PushMsgReceiver.kt @@ -0,0 +1,40 @@ +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()) + } + + } +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/JavaScriptHost.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/JavaScriptHost.kt new file mode 100644 index 000000000..ccc4a24ea --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/JavaScriptHost.kt @@ -0,0 +1,63 @@ +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() + } + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/LuaScriptHost.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/LuaScriptHost.kt new file mode 100644 index 000000000..0f3bf955b --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/LuaScriptHost.kt @@ -0,0 +1,50 @@ +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() { + + } +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHost.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHost.kt new file mode 100644 index 000000000..923124843 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHost.kt @@ -0,0 +1,75 @@ +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() + } +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHostFactory.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHostFactory.kt new file mode 100644 index 000000000..6c0478e05 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptHostFactory.kt @@ -0,0 +1,48 @@ +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() + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptManager.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptManager.kt new file mode 100644 index 000000000..49e09a0e9 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptManager.kt @@ -0,0 +1,167 @@ +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() + private val bots = mutableListOf() + 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): List = + 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 = List(hosts.size) { + hosts[it].getInfoString() + }.toTypedArray() + + private fun MutableList.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() +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/BotService.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/BotService.kt new file mode 100644 index 000000000..37ddfd710 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/BotService.kt @@ -0,0 +1,345 @@ +@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 { + //防止 + // 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 = ScriptManager.instance.getHostInfoStrings() + } + +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/ServiceConnector.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/ServiceConnector.kt new file mode 100644 index 000000000..78389cd48 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/service/ServiceConnector.kt @@ -0,0 +1,59 @@ +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 + } + } + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/about/AboutFragment.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/about/AboutFragment.kt new file mode 100644 index 000000000..1561bf588 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/about/AboutFragment.kt @@ -0,0 +1,89 @@ +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( + 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 + } + } + +} diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/console/ConsoleFragment.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/console/ConsoleFragment.kt new file mode 100644 index 000000000..166ebbe0a --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/console/ConsoleFragment.kt @@ -0,0 +1,280 @@ +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(R.id.password_input) + val qqInput = alertView.findViewById(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() +// } + +} + + + diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginFragment.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginFragment.kt new file mode 100644 index 000000000..565a92bc6 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginFragment.kt @@ -0,0 +1,144 @@ +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(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") + } +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginViewModel.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginViewModel.kt new file mode 100644 index 000000000..c50063b33 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginViewModel.kt @@ -0,0 +1,73 @@ +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>() + + init { + refreshPluginList() + } + + private fun loadPluginList(): List { + val fileList = mutableListOf() + 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() + } + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterFragment.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterFragment.kt new file mode 100644 index 000000000..8b8b9a646 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterFragment.kt @@ -0,0 +1,156 @@ +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 + ) + ) + } + }) + } + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterListAdapter.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterListAdapter.kt new file mode 100644 index 000000000..272babe65 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterListAdapter.kt @@ -0,0 +1,22 @@ +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(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(R.id.cv_item).setOnClickListener { + listener(item) + } + } + } +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterViewModel.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterViewModel.kt new file mode 100644 index 000000000..dbc4d1696 --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptCenterViewModel.kt @@ -0,0 +1,28 @@ +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>() + + fun showFiles(parent: GiteeFile) { + viewModelScope.launch { + withContext( + Dispatchers.IO + ) { + fileList.postValue(parent.listFiles()) + IdleResources.loadingData.decrement() + } + } + } + +} \ No newline at end of file diff --git a/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptFragment.kt b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptFragment.kt new file mode 100644 index 000000000..b6373363e --- /dev/null +++ b/mirai-console/frontend/mirai-android/app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptFragment.kt @@ -0,0 +1,187 @@ +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