├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ └── android.yml ├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── desugar-rules.pro ├── libs │ └── d8.jar ├── proguard-rules.pro └── src │ ├── androidTest │ ├── assets │ │ └── test.jar │ └── java │ │ └── io │ │ └── github │ │ └── mzdluo123 │ │ └── mirai │ │ └── android │ │ ├── BotServiceTest.kt │ │ ├── PageLoadTest.kt │ │ ├── PluginCompileTest.kt │ │ └── SavePwdTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── aidl │ │ └── io │ │ │ └── github │ │ │ └── mzdluo123 │ │ │ └── mirai │ │ │ └── android │ │ │ └── IbotAidlInterface.aidl │ ├── ic_new_launcher-playstore.png │ ├── java │ │ ├── io │ │ │ └── github │ │ │ │ └── mzdluo123 │ │ │ │ └── mirai │ │ │ │ └── android │ │ │ │ ├── AppSettings.kt │ │ │ │ ├── BotApplication.kt │ │ │ │ ├── IdleResources.kt │ │ │ │ ├── NotificationFactory.kt │ │ │ │ ├── activity │ │ │ │ ├── CaptchaActivity.kt │ │ │ │ ├── CrashReportActivity.kt │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── PluginImportActivity.kt │ │ │ │ └── UnsafeLoginActivity.kt │ │ │ │ ├── appcenter │ │ │ │ ├── UpdateListener.kt │ │ │ │ └── traceEvent.kt │ │ │ │ ├── crash │ │ │ │ ├── MiraiAndroidReportSender.kt │ │ │ │ └── MiraiAndroidReportSenderFactory.kt │ │ │ │ ├── miraiconsole │ │ │ │ ├── AndroidConsoleInput.kt │ │ │ │ ├── AndroidLoginSolver.kt │ │ │ │ ├── AndroidMiraiConsole.kt │ │ │ │ ├── AndroidStatusCommand.kt │ │ │ │ ├── ApkPluginLoader.kt │ │ │ │ ├── DexPluginClassloader.kt │ │ │ │ ├── DexPluginLoader.kt │ │ │ │ └── MiraiAndroidLogger.kt │ │ │ │ ├── provider │ │ │ │ └── FileProvider.java │ │ │ │ ├── receiver │ │ │ │ ├── BootReceiver.kt │ │ │ │ └── PushMsgReceiver.kt │ │ │ │ ├── script │ │ │ │ └── ScriptManager.kt │ │ │ │ ├── service │ │ │ │ ├── BotService.kt │ │ │ │ └── ServiceConnector.kt │ │ │ │ ├── ui │ │ │ │ ├── about │ │ │ │ │ └── AboutFragment.kt │ │ │ │ ├── console │ │ │ │ │ └── ConsoleFragment.kt │ │ │ │ ├── plugin │ │ │ │ │ ├── PluginFragment.kt │ │ │ │ │ └── PluginViewModel.kt │ │ │ │ ├── script │ │ │ │ │ ├── ScriptFragment.kt │ │ │ │ │ ├── ScriptListAdapter.kt │ │ │ │ │ └── ScriptViewModel.kt │ │ │ │ ├── setting │ │ │ │ │ └── SettingFragment.kt │ │ │ │ └── tools │ │ │ │ │ ├── ToolsFragment.kt │ │ │ │ │ └── ToolsFragmentViewModel.kt │ │ │ │ └── utils │ │ │ │ ├── DeviceStatus.java │ │ │ │ ├── DexCompiler.java │ │ │ │ ├── LoopQueue.java │ │ │ │ ├── MiraiAndroidStatus.kt │ │ │ │ ├── RequestUtil.kt │ │ │ │ ├── TextSharer.kt │ │ │ │ ├── dnsQuery.kt │ │ │ │ ├── fileUtils.kt │ │ │ │ └── pasteBin.kt │ │ └── java │ │ │ ├── awt │ │ │ └── image │ │ │ │ └── BufferedImage.java │ │ │ └── lang │ │ │ └── management │ │ │ ├── ManagementFactory.java │ │ │ └── MemoryMXBean.java │ └── res │ │ ├── color │ │ └── color_drawer_item.xml │ │ ├── drawable-hdpi │ │ └── icon.png │ │ ├── drawable-ldpi │ │ └── icon.png │ │ ├── drawable-mdpi │ │ └── icon.png │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ └── icon.png │ │ ├── drawable-xxhdpi │ │ └── icon.png │ │ ├── drawable-xxxhdpi │ │ └── icon.png │ │ ├── drawable │ │ ├── avatar.png │ │ ├── ic_add_white_24dp.xml │ │ ├── ic_android_24.xml │ │ ├── ic_baseline_bug_report_24.xml │ │ ├── ic_baseline_build_24.xml │ │ ├── ic_baseline_clear_24.xml │ │ ├── ic_baseline_folder_24.xml │ │ ├── ic_baseline_insert_drive_file_24.xml │ │ ├── ic_baseline_keyboard_arrow_up_24.xml │ │ ├── ic_baseline_new_releases_24.xml │ │ ├── ic_baseline_publish_24.xml │ │ ├── ic_battery_alert_24.xml │ │ ├── ic_chat_bubble_black_24dp.xml │ │ ├── ic_check_white_24dp.xml │ │ ├── ic_delete_black_24dp.xml │ │ ├── ic_desktop_windows_black_24dp.xml │ │ ├── ic_edit_black_24dp.xml │ │ ├── ic_exit_to_app_24dp.xml │ │ ├── ic_extension_black_24dp.xml │ │ ├── ic_info_black_24dp.xml │ │ ├── ic_insert_drive_file_black_24dp.xml │ │ ├── ic_keyboard_arrow_down_black_24dp.xml │ │ ├── ic_keyboard_return_black_24dp.xml │ │ ├── ic_launcher_background.xml │ │ ├── ic_local_printshop_24.xml │ │ ├── ic_new_launcher_foreground.xml │ │ ├── ic_refresh_black_24dp.xml │ │ ├── ic_restore_24.xml │ │ ├── ic_save_black_24dp.xml │ │ ├── ic_settings_black_24dp.xml │ │ ├── ic_share_24.xml │ │ ├── ic_store_white_24.xml │ │ ├── icon.xml │ │ ├── loading_background.xml │ │ ├── mirai_a.png │ │ ├── mirai_b.png │ │ └── side_nav_bar.xml │ │ ├── layout │ │ ├── activity_captcha.xml │ │ ├── activity_crash.xml │ │ ├── activity_main.xml │ │ ├── activity_plugin_import.xml │ │ ├── activity_unsafe_login.xml │ │ ├── app_bar_main.xml │ │ ├── content_main.xml │ │ ├── dialog_ask_filename.xml │ │ ├── dialog_autologin.xml │ │ ├── dialog_script_info.xml │ │ ├── fragment_about.xml │ │ ├── fragment_home.xml │ │ ├── fragment_plugin.xml │ │ ├── fragment_script.xml │ │ ├── fragment_script_center.xml │ │ ├── fragment_script_center_empty.xml │ │ ├── fragment_script_empty.xml │ │ ├── fragment_tools.xml │ │ ├── item_list_menu.xml │ │ ├── item_plugin.xml │ │ ├── item_script.xml │ │ ├── item_script_center_list.xml │ │ └── nav_header_main.xml │ │ ├── menu │ │ ├── menu_activity_main_drawer.xml │ │ ├── menu_console.xml │ │ ├── menu_script.xml │ │ ├── menu_script_center.xml │ │ ├── plugin_add.xml │ │ ├── plugin_manage.xml │ │ └── unsafe_menu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ ├── ic_launcher_round.xml │ │ ├── ic_new_launcher.xml │ │ └── ic_new_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_new_launcher.png │ │ └── ic_new_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_new_launcher.png │ │ └── ic_new_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_new_launcher.png │ │ └── ic_new_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_new_launcher.png │ │ └── ic_new_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ ├── ic_new_launcher.png │ │ └── ic_new_launcher_round.png │ │ ├── navigation │ │ └── mobile_navigation.xml │ │ ├── values-night │ │ └── colors-night.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-v29 │ │ └── styles.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_new_launcher_background.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── path_shared_file.xml │ │ └── setting_screen.xml │ └── test │ └── java │ └── io │ └── github │ └── mzdluo123 │ └── mirai │ └── android │ └── JvmTests.kt ├── build.gradle ├── docs ├── CNAME ├── changelog.txt ├── develop.md └── index.md ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: 报告一个bug 4 | labels: bug 5 | assignees: '' 6 | 7 | --- 8 | 9 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ``` 26 | 27 | 28 | 29 | ``` 30 | 31 | 32 | #### 复现 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android Build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - 'master' 7 | push: 8 | branches: 9 | - 'master' 10 | 11 | jobs: 12 | build: 13 | name: Run Build 14 | runs-on: ubuntu-18.04 15 | 16 | steps: 17 | - uses: actions/checkout@v1 18 | - name: set up JDK 1.8 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 1.8 22 | - name: Unit tests 23 | run: bash ./gradlew build --scan --stacktrace 24 | 25 | #jobs: 26 | # test: 27 | # runs-on: macos-latest 28 | # steps: 29 | # - name: checkout 30 | # uses: actions/checkout@v2 31 | # 32 | # - name: run tests 33 | # uses: reactivecircus/android-emulator-runner@v2 34 | # with: 35 | # api-level: 29 36 | # script: bash ./gradlew connectedCheck 37 | 38 | # jobs: 39 | # # test: 40 | # # name: Run Unit Tests 41 | # # runs-on: ubuntu-18.04 42 | 43 | # # steps: 44 | # # - uses: actions/checkout@v1 45 | # # - name: set up JDK 1.8 46 | # # uses: actions/setup-java@v1 47 | # # with: 48 | # # java-version: 1.8 49 | # # - name: Unit tests 50 | # # run: bash ./gradlew test --stacktrace 51 | 52 | # apk: 53 | # name: Generate APK 54 | # runs-on: ubuntu-18.04 55 | 56 | # steps: 57 | # - uses: actions/checkout@v1 58 | # - name: set up JDK 1.8 59 | # uses: actions/setup-java@v1 60 | # with: 61 | # java-version: 1.8 62 | # - name: Build debug APK 63 | # run: bash ./gradlew assembleDebug --stacktrace 64 | # - name: Upload APK 65 | # uses: actions/upload-artifact@v1 66 | # with: 67 | # name: MiraiAndroid 68 | # path: app/build/outputs/apk/debug/app-debug.apk 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/caches 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | /.idea/navEditor.xml 10 | /.idea/assetWizardSettings.xml 11 | .DS_Store 12 | /build 13 | /captures 14 | .externalNativeBuild 15 | .cxx 16 | app/release 17 | replay_pid* 18 | hs_* -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/desugar-rules.pro: -------------------------------------------------------------------------------- 1 | # 此文件用于L8 Desugar Shrinking 2 | # 包含整个Desugar Library, 以防止加载desugar的插件时已被加载的desugar class缺少方法 3 | # 只在 Release Build 中使用 4 | 5 | -keepclassmembers class * { *; } -------------------------------------------------------------------------------- /app/libs/d8.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/libs/d8.jar -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | # 配置目前还有点问题无法使用 23 | -keepattributes *Annotation*,Signature 24 | 25 | -keepclasseswithmembers class * extends java.lang.Exception { *;} 26 | 27 | #kotlin 28 | -keep class kotlin.** { *; } 29 | -keep class kotlin.Metadata { *; } 30 | -dontwarn kotlin.** 31 | -keepclassmembers class **$WhenMappings { 32 | ; 33 | } 34 | -keepclassmembers class kotlin.Metadata { 35 | public ; 36 | } 37 | -assumenosideeffects class kotlin.jvm.internal.Intrinsics { 38 | static void checkParameterIsNotNull(java.lang.Object, java.lang.String); 39 | } 40 | 41 | -keepclasseswithmembernames class * { 42 | native ; 43 | } 44 | 45 | -keepclassmembers enum * { *;} 46 | -keep class kotlinx.coroutines.** {*;} 47 | 48 | # jvm平台的一些不存在的类 49 | 50 | -dontwarn java.awt.** 51 | -dontwarn javax.swing.** 52 | -dontwarn sun.misc.** 53 | -dontwarn org.jetbrains.kotlin.** 54 | 55 | # mirai 配置 56 | -keep class net.mamoe.mirai.qqandroid.QQAndroid.$Companion { *; } 57 | -keepclasseswithmembers class * extends net.mamoe.mirai.BotFactory{ *;} 58 | 59 | -keep class net.mamoe.mirai.contact.** { *; } 60 | -keep class net.mamoe.mirai.event.** { *; } 61 | -keep class net.mamoe.mirai.message.** { *; } 62 | -keep class net.mamoe.mirai.network.** { *; } 63 | -keep class net.mamoe.mirai.utils.** { *; } 64 | -keep class net.mamoe.mirai.* { *; } 65 | 66 | # ktor 67 | -keep class io.ktor.client.** { *; } 68 | 69 | -keepclassmembers class io.ktor.** { 70 | volatile ; 71 | } 72 | 73 | 74 | # json 75 | 76 | -keep class kotlinx.serialization.json.** {*;} 77 | -keep class kotlinx.serialization.* {*;} 78 | 79 | # yaml 80 | 81 | -keep class org.yaml.snakeyaml.* {*;} 82 | -keep class org.yaml.snakeyaml.util.* {*;} 83 | 84 | # okhttp 85 | -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase 86 | -dontwarn okhttp3.internal.platform.ConscryptPlatform 87 | 88 | -keep class net.mamoe.mirai.console.** {*;} -------------------------------------------------------------------------------- /app/src/androidTest/assets/test.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/androidTest/assets/test.jar -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/mzdluo123/mirai/android/PageLoadTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android 2 | 3 | import androidx.fragment.app.testing.launchFragmentInContainer 4 | import androidx.test.espresso.Espresso.onView 5 | import androidx.test.espresso.assertion.ViewAssertions 6 | import androidx.test.espresso.matcher.ViewMatchers.* 7 | import androidx.test.ext.junit.runners.AndroidJUnit4 8 | import io.github.mzdluo123.mirai.android.ui.about.AboutFragment 9 | import io.github.mzdluo123.mirai.android.ui.console.ConsoleFragment 10 | import io.github.mzdluo123.mirai.android.ui.plugin.PluginFragment 11 | import io.github.mzdluo123.mirai.android.ui.script.ScriptFragment 12 | import io.github.mzdluo123.mirai.android.ui.tools.ToolsFragment 13 | import org.junit.Test 14 | import org.junit.runner.RunWith 15 | 16 | @RunWith(AndroidJUnit4::class) 17 | class PageLoadTest { 18 | // 19 | // @get:Rule 20 | // val activityRule = ActivityTestRule(MainActivity::class.java) 21 | 22 | 23 | @Test 24 | fun consoleTest() { 25 | with(launchFragmentInContainer()) { 26 | onView(withId(R.id.commandSend_btn)).check(ViewAssertions.matches(isDisplayed())) 27 | } 28 | } 29 | 30 | @Test 31 | fun pluginsTest() { 32 | with(launchFragmentInContainer()) { 33 | onView(withId(R.id.plugin_recycler)).check(ViewAssertions.matches(isDisplayed())) 34 | } 35 | } 36 | 37 | @Test 38 | fun scriptTest() { 39 | with(launchFragmentInContainer()) { 40 | onView(withId(R.id.script_embed_text)).check(ViewAssertions.matches(isDisplayed())) 41 | } 42 | } 43 | 44 | @Test 45 | fun aboutTest() { 46 | with(launchFragmentInContainer()) { 47 | onView(withText("关于Mirai")).check(ViewAssertions.matches(isDisplayed())) 48 | } 49 | } 50 | 51 | @Test 52 | fun toolsTest() { 53 | with(launchFragmentInContainer(themeResId = R.style.AppTheme)) { 54 | onView(withId(R.id.btn_export_device)).check(ViewAssertions.matches(isDisplayed())) 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/mzdluo123/mirai/android/PluginCompileTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.test.ext.junit.runners.AndroidJUnit4 6 | import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 7 | import io.github.mzdluo123.mirai.android.service.BotService 8 | import io.github.mzdluo123.mirai.android.service.ServiceConnector 9 | import io.github.mzdluo123.mirai.android.ui.plugin.PluginViewModel 10 | import kotlinx.coroutines.* 11 | import org.junit.Assert 12 | import org.junit.Test 13 | import org.junit.runner.RunWith 14 | import java.io.File 15 | 16 | @RunWith(AndroidJUnit4::class) 17 | class PluginCompileTest { 18 | 19 | val testContext = getInstrumentation().context 20 | 21 | @Test 22 | fun compileTest() { 23 | val origin = testContext.assets.open("test.jar") 24 | val out = File(BotApplication.context.externalCacheDir, "tmp.jar") 25 | val outStream = out.outputStream() 26 | origin.use { 27 | it.copyTo(outStream) 28 | outStream.close() 29 | } 30 | 31 | // val dexCompile = DexCompiler(BotApplication.context.filesDir,BotApplication.context.cacheDir) 32 | // val outFile = dexCompile.compile(out,true) 33 | // dexCompile.copyResourcesAndMove(outFile, File(BotApplication.context.cacheDir,"test-android.jar")) 34 | 35 | runBlocking { 36 | PluginViewModel().compilePlugin(out, true) 37 | BotApplication.context.startBotService() 38 | val feature = CompletableDeferred() 39 | val conn = ServiceConnector(BotApplication.context) 40 | BotApplication.context.bindService( 41 | Intent(BotApplication.context, BotService::class.java), 42 | conn, 43 | Context.BIND_AUTO_CREATE 44 | ) 45 | val console = object : IConsole.Stub() { 46 | override fun newLog(log: String?) { 47 | if (log != null && "/ga-switch" in log) { 48 | feature.complete(true) 49 | } 50 | } 51 | } 52 | conn.registerConsole(console) 53 | launch { 54 | repeat(60) { 55 | conn.botService.runCmd("/help") 56 | delay(100) 57 | } 58 | } 59 | File(BotApplication.context.filesDir, "/plugins/test-android.jar").deleteOnExit() 60 | withTimeout(6000) { 61 | Assert.assertTrue(feature.await()) 62 | } 63 | } 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/mzdluo123/mirai/android/SavePwdTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android 2 | 3 | 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.test.espresso.Espresso.onView 9 | import androidx.test.espresso.action.ViewActions.* 10 | import androidx.test.espresso.matcher.ViewMatchers.* 11 | import androidx.test.filters.LargeTest 12 | import androidx.test.rule.ActivityTestRule 13 | import androidx.test.runner.AndroidJUnit4 14 | import io.github.mzdluo123.mirai.android.activity.MainActivity 15 | import io.github.mzdluo123.mirai.android.service.BotService 16 | import io.github.mzdluo123.mirai.android.service.ServiceConnector 17 | import kotlinx.coroutines.CompletableDeferred 18 | import kotlinx.coroutines.delay 19 | import kotlinx.coroutines.runBlocking 20 | import kotlinx.coroutines.withTimeout 21 | import org.hamcrest.Description 22 | import org.hamcrest.Matcher 23 | import org.hamcrest.Matchers.allOf 24 | import org.hamcrest.TypeSafeMatcher 25 | import org.junit.Assert 26 | import org.junit.Rule 27 | import org.junit.Test 28 | import org.junit.runner.RunWith 29 | 30 | @LargeTest 31 | @RunWith(AndroidJUnit4::class) 32 | class SavePwdTest { 33 | 34 | @Rule 35 | @JvmField 36 | var mActivityTestRule = ActivityTestRule(MainActivity::class.java) 37 | 38 | @Test 39 | fun savePwdTest() { 40 | 41 | val overflowMenuButton = onView( 42 | allOf( 43 | withContentDescription("更多选项"), 44 | childAtPosition( 45 | childAtPosition( 46 | withId(R.id.toolbar), 47 | 2 48 | ), 49 | 2 50 | ), 51 | isDisplayed() 52 | ) 53 | ) 54 | overflowMenuButton.perform(click()) 55 | 56 | val materialTextView = onView( 57 | allOf( 58 | withId(R.id.title), withText("设置自动登录"), 59 | childAtPosition( 60 | childAtPosition( 61 | withId(R.id.content), 62 | 0 63 | ), 64 | 0 65 | ), 66 | isDisplayed() 67 | ) 68 | ) 69 | materialTextView.perform(click()) 70 | 71 | val appCompatEditText = onView( 72 | allOf( 73 | withId(R.id.qq_input), 74 | childAtPosition( 75 | childAtPosition( 76 | withId(android.R.id.custom), 77 | 0 78 | ), 79 | 2 80 | ), 81 | isDisplayed() 82 | ) 83 | ) 84 | appCompatEditText.perform(replaceText("123"), closeSoftKeyboard()) 85 | 86 | val appCompatEditText2 = onView( 87 | allOf( 88 | withId(R.id.password_input), 89 | childAtPosition( 90 | childAtPosition( 91 | withId(android.R.id.custom), 92 | 0 93 | ), 94 | 3 95 | ), 96 | isDisplayed() 97 | ) 98 | ) 99 | appCompatEditText2.perform(replaceText("qwe"), closeSoftKeyboard()) 100 | 101 | val materialButton = onView( 102 | allOf( 103 | withId(android.R.id.button1), withText("设置自动登录"), 104 | ) 105 | ) 106 | materialButton.perform(scrollTo(), click()) 107 | 108 | runBlocking { 109 | BotApplication.context.stopBotService() 110 | delay(2000) 111 | BotApplication.context.startBotService() 112 | val feature = CompletableDeferred() 113 | val conn = ServiceConnector(BotApplication.context) 114 | BotApplication.context.bindService( 115 | Intent(BotApplication.context, BotService::class.java), 116 | conn, 117 | Context.BIND_AUTO_CREATE 118 | ) 119 | val console = object : IConsole.Stub() { 120 | override fun newLog(log: String?) { 121 | if (log != null && "自动登录" in log) { 122 | feature.complete(true) 123 | } 124 | } 125 | } 126 | conn.registerConsole(console) 127 | BotApplication.context.getSharedPreferences("account", Context.MODE_PRIVATE).apply { 128 | edit().remove("qq").remove("pwd").commit() 129 | } 130 | withTimeout(5000) { 131 | Assert.assertTrue(feature.await()) 132 | } 133 | 134 | } 135 | } 136 | 137 | private fun childAtPosition( 138 | parentMatcher: Matcher, position: Int 139 | ): Matcher { 140 | 141 | return object : TypeSafeMatcher() { 142 | override fun describeTo(description: Description) { 143 | description.appendText("Child at position $position in parent ") 144 | parentMatcher.describeTo(description) 145 | } 146 | 147 | public override fun matchesSafely(view: View): Boolean { 148 | val parent = view.parent 149 | return parent is ViewGroup && parentMatcher.matches(parent) 150 | && view == parent.getChildAt(position) 151 | } 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 74 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 92 | 93 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 111 | 114 | 115 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /app/src/main/aidl/io/github/mzdluo123/mirai/android/IbotAidlInterface.aidl: -------------------------------------------------------------------------------- 1 | // IbotAidlInterface.aidl 2 | package io.github.mzdluo123.mirai.android; 3 | 4 | // Declare any non-default types here with import statements 5 | interface IbotAidlInterface { 6 | //Console 7 | List getLog(); 8 | void clearLog(); 9 | void sendLog(String log); 10 | void runCmd(String cmd); 11 | byte[] getCaptcha(); 12 | String getUrl(); 13 | void submitVerificationResult(String result); 14 | long getLogonId(); 15 | 16 | //Script 17 | // String[] getHostList(); 18 | // boolean reloadScript(int index); 19 | // void setScriptConfig(String config); 20 | // void deleteScript(int index); 21 | // int getScriptSize(); 22 | // void openScript(int index); 23 | // boolean createScript(String name,int type); 24 | // void enableScript(int index); 25 | // void disableScript(int index); 26 | String getBotInfo(); 27 | 28 | 29 | void registerConsole(in IConsole instance); 30 | void unregisterConsole(in IConsole instance); 31 | 32 | } 33 | interface IConsole{ 34 | void newLog(String log); 35 | } -------------------------------------------------------------------------------- /app/src/main/ic_new_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/ic_new_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/AppSettings.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android 2 | 3 | import splitties.preferences.Preferences 4 | import kotlin.reflect.KProperty 5 | 6 | object AppSettings : Preferences("setting") { 7 | 8 | class IntPrefSaveAsStr(private val key: String, private val defaultValue: Int) { 9 | operator fun getValue(thisRef: Preferences, prop: KProperty<*>): Int { 10 | return prefs.getString(key, null)?.toInt() ?: defaultValue 11 | } 12 | 13 | operator fun setValue(thisRef: Preferences, prop: KProperty<*>, value: Int) { 14 | prefs.edit().putString(key, value.toString()).apply() 15 | } 16 | 17 | } 18 | 19 | var allowPushMsg by BoolPref("allow_push_msg_preference", false) 20 | var logBuffer by IntPrefSaveAsStr("log_buffer_preference", 300) 21 | var printToLogcat by BoolPref("print_to_logcat_preference", false) 22 | var refreshPerMinute by IntPrefSaveAsStr("status_refresh_count", 15) 23 | var startOnBoot by BoolPref("start_on_boot_preference", false) 24 | var waitingDebugger by BoolPref("waiting_debugger_preference", false) 25 | 26 | var keepLive by BoolPref("keeplive_preference", false) 27 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/BotApplication.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android 2 | 3 | import android.app.ActivityManager 4 | import android.app.Application 5 | import android.content.Context 6 | import android.content.Intent 7 | import android.os.Build 8 | import android.os.Process 9 | import com.fanjun.keeplive.KeepLive 10 | import com.fanjun.keeplive.config.ForegroundNotification 11 | import com.fanjun.keeplive.config.KeepLiveService 12 | import com.microsoft.appcenter.AppCenter 13 | import com.microsoft.appcenter.analytics.Analytics 14 | import com.microsoft.appcenter.crashes.Crashes 15 | import com.microsoft.appcenter.distribute.Distribute 16 | import io.github.mzdluo123.mirai.android.NotificationFactory.initNotification 17 | import io.github.mzdluo123.mirai.android.activity.CrashReportActivity 18 | import io.github.mzdluo123.mirai.android.appcenter.UpdateListener 19 | import io.github.mzdluo123.mirai.android.appcenter.trace 20 | import io.github.mzdluo123.mirai.android.crash.MiraiAndroidReportSenderFactory 21 | import io.github.mzdluo123.mirai.android.service.BotService 22 | import kotlinx.serialization.json.Json 23 | import okhttp3.OkHttpClient 24 | import org.acra.ACRA 25 | import org.acra.config.CoreConfigurationBuilder 26 | import org.acra.config.DialogConfigurationBuilder 27 | import org.acra.data.StringFormat 28 | import splitties.init.injectAsAppCtx 29 | 30 | 31 | class BotApplication : Application() { 32 | companion object { 33 | 34 | lateinit var context: BotApplication 35 | private set 36 | 37 | val httpClient = lazy { OkHttpClient() } 38 | val json = lazy { Json.Default } 39 | } 40 | 41 | 42 | override fun onCreate() { 43 | super.onCreate() 44 | injectAsAppCtx() 45 | context = this 46 | enableDebugNetLog() 47 | if (!BuildConfig.DEBUG) { 48 | initAppCenter() 49 | } 50 | val processName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) 51 | getProcessName() 52 | else 53 | myGetProcessName() 54 | 55 | // 防止服务进程多次初始化 56 | if (processName?.isEmpty() == false && processName == packageName) { 57 | initNotification() 58 | } 59 | } 60 | 61 | private fun initAppCenter() { 62 | Distribute.setEnabledForDebuggableBuild(false) 63 | Distribute.setListener(UpdateListener()) 64 | AppCenter.start( 65 | this, "70a7bed9-65ce-4526-a448-0be273dbb652", 66 | Analytics::class.java, Crashes::class.java, Distribute::class.java 67 | ) 68 | 69 | } 70 | 71 | 72 | //崩溃事件注册 73 | override fun attachBaseContext(base: Context?) { 74 | super.attachBaseContext(base) 75 | ACRA.init(this, CoreConfigurationBuilder(this).apply { 76 | setBuildConfigClass(BuildConfig::class.java) 77 | .setReportFormat(StringFormat.JSON) 78 | setReportSenderFactoryClasses(MiraiAndroidReportSenderFactory::class.java) 79 | .setBuildConfigClass(BuildConfig::class.java) 80 | getPluginConfigurationBuilder(DialogConfigurationBuilder::class.java) 81 | .setReportDialogClass(CrashReportActivity::class.java) 82 | .setEnabled(true) 83 | 84 | 85 | // getPluginConfigurationBuilder(ToastConfigurationBuilder::class.java) 86 | // .setResText(R.string.acra_toast_text) 87 | // .setEnabled(true) 88 | //不知道为什么开启的时候总是显示这个,先暂时禁用 89 | }) 90 | } 91 | 92 | private fun enableDebugNetLog(){ 93 | if (AppSettings.printToLogcat || BuildConfig.DEBUG){ 94 | System.setProperty("mirai.network.handle.selector.logging","true") 95 | // System.setProperty("mirai.network.packet.logger","true") 96 | 97 | } 98 | } 99 | 100 | private fun myGetProcessName(): String? { 101 | val pid = Process.myPid() 102 | for (appProcess in (getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses) { 103 | if (appProcess.pid == pid) { 104 | return appProcess.processName 105 | } 106 | } 107 | return null 108 | } 109 | 110 | internal fun keepLive() { 111 | val notification = ForegroundNotification("MiraiAndroid", "保活服务已启动", R.mipmap.ic_launcher) 112 | trace("start keepLive") 113 | KeepLive.startWork(this, KeepLive.RunMode.ROGUE, notification, object : KeepLiveService { 114 | override fun onWorking() { 115 | startBotService() 116 | } 117 | 118 | override fun onStop() { 119 | stopBotService() 120 | } 121 | 122 | }) 123 | } 124 | 125 | internal fun startBotService() { 126 | val account = getSharedPreferences("account", Context.MODE_PRIVATE) 127 | this.startService(Intent(this, BotService::class.java).apply { 128 | putExtra("action", BotService.START_SERVICE) 129 | putExtra("qq", account.getLong("qq", 0)) 130 | putExtra("pwd", account.getString("pwd", null)) 131 | }) 132 | } 133 | 134 | internal fun stopBotService() { 135 | startService(Intent(this, BotService::class.java).apply { 136 | putExtra("action", BotService.STOP_SERVICE) 137 | }) 138 | } 139 | 140 | 141 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/IdleResources.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android 2 | 3 | import androidx.test.espresso.idling.CountingIdlingResource 4 | 5 | object IdleResources { 6 | 7 | // Android单元测试所需要的东西 8 | 9 | val loadingData by lazy { CountingIdlingResource("logUploadDialogIdleResources") } 10 | 11 | val botServiceLoading by lazy { CountingIdlingResource("botServiceLoading") } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/activity/CaptchaActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.activity 2 | 3 | import android.app.NotificationManager 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.graphics.BitmapFactory 7 | import android.os.Bundle 8 | import androidx.appcompat.app.AppCompatActivity 9 | import androidx.lifecycle.Observer 10 | import androidx.lifecycle.lifecycleScope 11 | import io.github.mzdluo123.mirai.android.R 12 | import io.github.mzdluo123.mirai.android.miraiconsole.AndroidLoginSolver 13 | import io.github.mzdluo123.mirai.android.service.BotService 14 | import io.github.mzdluo123.mirai.android.service.ServiceConnector 15 | import kotlinx.android.synthetic.main.activity_captcha.* 16 | import kotlinx.coroutines.Dispatchers 17 | import kotlinx.coroutines.launch 18 | 19 | class CaptchaActivity : AppCompatActivity() { 20 | private lateinit var conn: ServiceConnector 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_captcha) 25 | 26 | conn = ServiceConnector(this) 27 | lifecycle.addObserver(conn) 28 | conn.connectStatus.observe(this, { 29 | if (it) { 30 | val data = conn.botService.captcha 31 | lifecycleScope.launch(Dispatchers.Main) { 32 | val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size) 33 | captcha_view.setImageBitmap(bitmap) 34 | } 35 | } 36 | }) 37 | } 38 | 39 | 40 | override fun onStart() { 41 | super.onStart() 42 | val intent = Intent(baseContext, BotService::class.java) 43 | bindService(intent, conn, Context.BIND_AUTO_CREATE) 44 | captchaConfirm_btn.setOnClickListener { 45 | conn.botService.submitVerificationResult(captcha_input.text.toString()) 46 | // 删除通知 47 | val notificationManager = 48 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 49 | notificationManager.cancel(AndroidLoginSolver.CAPTCHA_NOTIFICATION_ID) 50 | finish() 51 | } 52 | } 53 | 54 | 55 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/activity/CrashReportActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.activity 2 | 3 | 4 | import androidx.lifecycle.lifecycleScope 5 | import io.github.mzdluo123.mirai.android.R 6 | import io.github.mzdluo123.mirai.android.utils.shareText 7 | import kotlinx.android.synthetic.main.activity_crash.* 8 | import org.acra.dialog.BaseCrashReportDialog 9 | import org.acra.file.CrashReportPersister 10 | import org.acra.interaction.DialogInteraction 11 | import splitties.toast.toast 12 | import java.io.File 13 | 14 | class CrashReportActivity : BaseCrashReportDialog() { 15 | override fun onStart() { 16 | super.onStart() 17 | setContentView(R.layout.activity_crash) 18 | val file = intent.getSerializableExtra(DialogInteraction.EXTRA_REPORT_FILE) as File 19 | try { 20 | val data = CrashReportPersister().load(file) 21 | file.delete() 22 | crach_data_text.text = data["STACK_TRACE"].toString() 23 | crash_share.setOnClickListener { 24 | shareText(data.toJSON(), lifecycleScope) 25 | } 26 | } catch (e: Exception) { 27 | toast("无法读取错误报告,请尝试手动删除crash文件夹") 28 | finish() 29 | } 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/activity/PluginImportActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.activity 2 | 3 | import android.net.Uri 4 | import android.os.Bundle 5 | import android.widget.Toast 6 | import androidx.appcompat.app.AlertDialog 7 | import androidx.appcompat.app.AppCompatActivity 8 | import androidx.databinding.DataBindingUtil 9 | import androidx.lifecycle.ViewModelProvider 10 | import androidx.lifecycle.lifecycleScope 11 | import io.github.mzdluo123.mirai.android.R 12 | import io.github.mzdluo123.mirai.android.appcenter.trace 13 | import io.github.mzdluo123.mirai.android.databinding.ActivityPluginImportBinding 14 | import io.github.mzdluo123.mirai.android.ui.plugin.PluginViewModel 15 | import io.github.mzdluo123.mirai.android.utils.askFileName 16 | import io.github.mzdluo123.mirai.android.utils.copyToFileDir 17 | import kotlinx.android.synthetic.main.activity_plugin_import.* 18 | import kotlinx.coroutines.CoroutineExceptionHandler 19 | import kotlinx.coroutines.Dispatchers 20 | import kotlinx.coroutines.launch 21 | import kotlinx.coroutines.withContext 22 | import java.io.File 23 | 24 | 25 | class PluginImportActivity : AppCompatActivity() { 26 | 27 | private lateinit var uri: Uri 28 | 29 | private lateinit var pluginViewModel: PluginViewModel 30 | private lateinit var dialog: AlertDialog 31 | private lateinit var activityPluginImportBinding: ActivityPluginImportBinding 32 | override fun onCreate(savedInstanceState: Bundle?) { 33 | super.onCreate(savedInstanceState) 34 | setContentView(R.layout.activity_plugin_import) 35 | // uri = Uri.parse(intent.getStringExtra("uri")) 36 | 37 | // val errorHandel = CoroutineExceptionHandler { _, e -> 38 | // Toast.makeText(this, "无法打开这个文件,请检查这是不是一个合法的插件jar文件", Toast.LENGTH_SHORT).show() 39 | // e.printStackTrace() 40 | // finish() 41 | // 42 | // } 43 | uri = intent.data ?: return 44 | pluginViewModel = ViewModelProvider(this).get(PluginViewModel::class.java) 45 | activityPluginImportBinding = 46 | DataBindingUtil.setContentView(this, R.layout.activity_plugin_import) 47 | // lifecycleScope.launch(errorHandel) { loadPluginData() } 48 | activityPluginImportBinding.importBtn.setOnClickListener { 49 | startImport() 50 | } 51 | 52 | } 53 | 54 | private fun createDialog() { 55 | dialog = AlertDialog.Builder(this) 56 | .setTitle("正在编译") 57 | .setMessage("这可能需要一些时间,请不要最小化") 58 | .setCancelable(false) 59 | .create() 60 | } 61 | 62 | private fun startImport() { 63 | createDialog() 64 | val exceptionHandler = CoroutineExceptionHandler { _, throwable -> 65 | lifecycleScope.launch(Dispatchers.Main) { 66 | dialog.dismiss() 67 | Toast.makeText( 68 | this@PluginImportActivity, 69 | "无法编译插件 \n${throwable}", 70 | Toast.LENGTH_LONG 71 | ).show() 72 | throwable.printStackTrace() 73 | trace( 74 | "install plugin error", 75 | "type" to throwable.javaClass.name, 76 | "msg" to throwable.message 77 | ) 78 | } 79 | } 80 | 81 | 82 | 83 | dialog.show() 84 | when (import_radioGroup.checkedRadioButtonId) { 85 | R.id.compile_radioButton -> { 86 | lifecycleScope.launch(exceptionHandler) { 87 | val name = withContext(Dispatchers.Main) { 88 | askFileName() 89 | } ?: return@launch 90 | // 从contentprovider 读取插件数据到缓存 91 | withContext(Dispatchers.IO) { 92 | copyToFileDir( 93 | uri, 94 | name, 95 | this@PluginImportActivity.getExternalFilesDir(null)!!.absolutePath 96 | ) 97 | } 98 | // 编译插件 99 | pluginViewModel.compilePlugin( 100 | File(baseContext.getExternalFilesDir(null), name), 101 | desugaring_checkBox.isChecked 102 | ) 103 | // 删除缓存 104 | withContext(Dispatchers.IO) { 105 | File(this@PluginImportActivity.getExternalFilesDir(null), name).delete() 106 | } 107 | dialog.dismiss() 108 | Toast.makeText(this@PluginImportActivity, "安装成功,重启后即可加载", Toast.LENGTH_SHORT) 109 | .show() 110 | finish() 111 | trace("install plugin success", "name" to name) 112 | } 113 | } 114 | R.id.copy_radioButton -> { 115 | lifecycleScope.launch(exceptionHandler) { 116 | val name = withContext(Dispatchers.Main) { 117 | askFileName() 118 | } ?: return@launch 119 | withContext(Dispatchers.IO) { 120 | copyToFileDir( 121 | uri, 122 | name, 123 | this@PluginImportActivity.getExternalFilesDir("plugins")!!.absolutePath 124 | ) 125 | } 126 | dialog.dismiss() 127 | Toast.makeText(this@PluginImportActivity, "安装成功,重启后即可加载", Toast.LENGTH_SHORT) 128 | .show() 129 | finish() 130 | trace("install plugin success", "name" to name) 131 | } 132 | } 133 | } 134 | 135 | } 136 | 137 | 138 | } 139 | 140 | 141 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/activity/UnsafeLoginActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.NotificationManager 5 | import android.content.Context 6 | import android.os.Bundle 7 | import android.view.KeyEvent 8 | import android.webkit.* 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.lifecycle.Observer 11 | import androidx.lifecycle.lifecycleScope 12 | import com.google.gson.JsonParser 13 | import io.github.mzdluo123.mirai.android.R 14 | import io.github.mzdluo123.mirai.android.appcenter.trace 15 | import io.github.mzdluo123.mirai.android.miraiconsole.AndroidLoginSolver 16 | import io.github.mzdluo123.mirai.android.service.ServiceConnector 17 | import kotlinx.android.synthetic.main.activity_unsafe_login.* 18 | import kotlinx.coroutines.delay 19 | import kotlinx.coroutines.launch 20 | import splitties.toast.toast 21 | 22 | class UnsafeLoginActivity : AppCompatActivity() { 23 | 24 | private lateinit var conn: ServiceConnector 25 | val gson = JsonParser() 26 | 27 | companion object { 28 | const val TAG = "UnsafeLogin" 29 | } 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | conn = ServiceConnector(this) 34 | lifecycle.addObserver(conn) 35 | setContentView(R.layout.activity_unsafe_login) 36 | initWebView() 37 | refresh_unsafe_web.setOnRefreshListener { 38 | unsafe_login_web.reload() 39 | lifecycleScope.launch { 40 | delay(1000) 41 | refresh_unsafe_web.isRefreshing = false 42 | } 43 | } 44 | // Toast.makeText(this, "请在完成验证后点击右上角继续登录", Toast.LENGTH_LONG).show() 45 | } 46 | 47 | 48 | @SuppressLint("SetJavaScriptEnabled") 49 | private fun initWebView() { 50 | unsafe_login_web.webViewClient = object : WebViewClient() { 51 | // override fun shouldInterceptRequest( 52 | // view: WebView?, 53 | // request: WebResourceRequest? 54 | // ): WebResourceResponse? { 55 | // if (request != null) { 56 | // if ("https://report.qqweb.qq.com/report/compass/dc00898" in request.url.toString()) { 57 | // authFinish() 58 | // } 59 | // } 60 | // return super.shouldInterceptRequest(view, request) 61 | // } 62 | 63 | override fun onPageFinished(view: WebView?, url: String?) { 64 | super.onPageFinished(view, url) 65 | unsafe_login_web.evaluateJavascript( 66 | """ 67 | mqq.invoke = function(a,b,c){ return bridge.invoke(a,b,JSON.stringify(c))}""" 68 | .trimIndent() 69 | ) {} 70 | } 71 | } 72 | unsafe_login_web.webChromeClient = object : WebChromeClient() { 73 | 74 | override fun onConsoleMessage(consoleMessage: ConsoleMessage?): Boolean { 75 | val msg = consoleMessage?.message() 76 | // 按下回到qq按钮之后会打印这句话,于是就用这个解决了。。。。 77 | if (msg?.startsWith("手Q扫码验证") == true) { 78 | authFinish("") 79 | } 80 | return super.onConsoleMessage(consoleMessage) 81 | } 82 | } 83 | WebView.setWebContentsDebuggingEnabled(true) 84 | unsafe_login_web.settings.apply { 85 | javaScriptEnabled = true 86 | domStorageEnabled = true 87 | } 88 | unsafe_login_web.addJavascriptInterface(Bridge(), "bridge") 89 | 90 | conn.connectStatus.observe(this, Observer { 91 | if (it) { 92 | if (conn.botService.url == null) { 93 | toast("获取URL失败,请重试") 94 | finish() 95 | return@Observer 96 | 97 | } 98 | unsafe_login_web.loadUrl(conn.botService.url.replace("verify", "qrcode")) 99 | } 100 | }) 101 | } 102 | 103 | private fun authFinish(token: String) { 104 | conn.botService.submitVerificationResult(token) 105 | val notificationManager = 106 | getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager 107 | notificationManager.cancel(AndroidLoginSolver.CAPTCHA_NOTIFICATION_ID) 108 | finish() 109 | trace("finish UnsafeLogin") 110 | } 111 | 112 | 113 | override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { 114 | if (keyCode == KeyEvent.KEYCODE_BACK) { 115 | if (unsafe_login_web.canGoBack()) { 116 | unsafe_login_web.goBack() 117 | return true 118 | } 119 | } 120 | return false 121 | } 122 | 123 | // override fun onCreateOptionsMenu(menu: Menu?): Boolean { 124 | // menuInflater.inflate(R.menu.unsafe_menu, menu) 125 | // return true 126 | // } 127 | // 128 | // override fun onOptionsItemSelected(item: MenuItem): Boolean { 129 | // authFinish() 130 | // return true 131 | // } 132 | 133 | inner class Bridge { 134 | @JavascriptInterface 135 | fun invoke(cls: String?, method: String?, data: String?) { 136 | if (data != null) { 137 | val jsData = gson.parse(data) 138 | if (method == "onVerifyCAPTCHA") { 139 | authFinish(jsData.asJsonObject["ticket"].asString) 140 | } 141 | } 142 | } 143 | } 144 | } 145 | 146 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/appcenter/UpdateListener.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.appcenter 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import com.microsoft.appcenter.distribute.DistributeListener 6 | import com.microsoft.appcenter.distribute.ReleaseDetails 7 | import splitties.alertdialog.appcompat.alertDialog 8 | import splitties.alertdialog.appcompat.message 9 | import splitties.alertdialog.appcompat.title 10 | 11 | class UpdateListener : DistributeListener { 12 | override fun onReleaseAvailable(activity: Activity, releaseDetails: ReleaseDetails): Boolean { 13 | val dialog = activity.alertDialog { 14 | title = "发现新版本 ${releaseDetails.version}" 15 | message = releaseDetails.releaseNotes 16 | setPositiveButton("立即更新") { _, _ -> 17 | activity.startActivity( 18 | Intent( 19 | Intent.ACTION_VIEW, 20 | releaseDetails.downloadUrl 21 | ) 22 | ) 23 | } 24 | setNeutralButton("查看详细信息") { _, _ -> 25 | activity.startActivity( 26 | Intent( 27 | Intent.ACTION_VIEW, 28 | releaseDetails.releaseNotesUrl 29 | ) 30 | ) 31 | } 32 | } 33 | activity.runOnUiThread { 34 | dialog.show() 35 | } 36 | return true 37 | 38 | } 39 | 40 | override fun onNoReleaseAvailable(activity: Activity) { 41 | 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/appcenter/traceEvent.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.appcenter 2 | 3 | import com.microsoft.appcenter.Flags 4 | import com.microsoft.appcenter.analytics.Analytics 5 | 6 | internal fun trace(event: String) { 7 | Analytics.trackEvent(event) 8 | } 9 | 10 | 11 | internal fun traceCritical(event: String) { 12 | Analytics.trackEvent(event, mapOf(), Flags.CRITICAL) 13 | } 14 | 15 | internal fun trace(event: String, vararg prop: Pair) { 16 | val map = hashMapOf() 17 | prop.forEach { map[it.first] = it.second } 18 | Analytics.trackEvent(event, map) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSender.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.crash 2 | 3 | import android.content.Context 4 | import org.acra.data.CrashReportData 5 | import org.acra.sender.ReportSender 6 | import org.acra.sender.ReportSenderException 7 | import java.io.File 8 | import java.io.FileWriter 9 | 10 | //崩溃日志处理 11 | class MiraiAndroidReportSender() : ReportSender { 12 | @Throws(ReportSenderException::class) 13 | override fun send( 14 | context: Context, 15 | report: CrashReportData 16 | ) { 17 | val outFile = File(context.getExternalFilesDir("crash"), "crashdata") 18 | FileWriter(outFile).also { 19 | it.write(report.toJSON()) 20 | }.close() 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/crash/MiraiAndroidReportSenderFactory.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.crash 2 | 3 | import android.content.Context 4 | import org.acra.config.CoreConfiguration 5 | import org.acra.sender.ReportSender 6 | import org.acra.sender.ReportSenderFactory 7 | 8 | class MiraiAndroidReportSenderFactory : ReportSenderFactory { 9 | override fun create( 10 | context: Context, 11 | config: CoreConfiguration 12 | ): ReportSender = MiraiAndroidReportSender() 13 | 14 | override fun enabled(config: CoreConfiguration): Boolean = true 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidConsoleInput.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.miraiconsole 2 | 3 | import kotlinx.coroutines.channels.Channel 4 | import net.mamoe.mirai.console.util.ConsoleInput 5 | 6 | object AndroidConsoleInput : ConsoleInput { 7 | val inputQueue = Channel { } 8 | override suspend fun requestInput(hint: String): String { 9 | return inputQueue.receive() 10 | } 11 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidLoginSolver.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.miraiconsole 2 | 3 | import android.content.Context 4 | import androidx.core.app.NotificationManagerCompat 5 | import io.github.mzdluo123.mirai.android.NotificationFactory 6 | import io.github.mzdluo123.mirai.android.activity.CaptchaActivity 7 | import io.github.mzdluo123.mirai.android.activity.UnsafeLoginActivity 8 | import kotlinx.coroutines.CompletableDeferred 9 | import net.mamoe.mirai.Bot 10 | import net.mamoe.mirai.utils.LoginSolver 11 | 12 | 13 | class AndroidLoginSolver(private val context: Context) : LoginSolver() { 14 | lateinit var verificationResult: CompletableDeferred 15 | lateinit var captchaData: ByteArray 16 | lateinit var url: String 17 | 18 | companion object { 19 | const val CAPTCHA_NOTIFICATION_ID = 2 20 | } 21 | 22 | override suspend fun onSolvePicCaptcha(bot: Bot, data: ByteArray): String { 23 | MiraiAndroidLogger.info("本次登录需要输入验证码,请在通知栏点击通知来输入") 24 | verificationResult = CompletableDeferred() 25 | captchaData = data 26 | NotificationManagerCompat.from(context).apply { 27 | notify( 28 | CAPTCHA_NOTIFICATION_ID, 29 | NotificationFactory.captchaNotification(CaptchaActivity::class.java) 30 | ) 31 | } 32 | return verificationResult.await() 33 | } 34 | 35 | override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? { 36 | verificationResult = CompletableDeferred() 37 | this.url = url 38 | MiraiAndroidLogger.info(url) 39 | sendVerifyNotification() 40 | return verificationResult.await() 41 | } 42 | 43 | override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? { 44 | verificationResult = CompletableDeferred() 45 | this.url = url 46 | sendVerifyNotification() 47 | return verificationResult.await() 48 | } 49 | 50 | override val isSliderCaptchaSupported: Boolean 51 | get() = true 52 | 53 | private fun sendVerifyNotification() { 54 | MiraiAndroidLogger.info("本次登录需要进行验证,请在通知栏点击通知进行验证") 55 | 56 | NotificationManagerCompat.from(context).apply { 57 | notify( 58 | CAPTCHA_NOTIFICATION_ID, 59 | NotificationFactory.captchaNotification(UnsafeLoginActivity::class.java) 60 | ) 61 | } 62 | } 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/AndroidStatusCommand.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.miraiconsole 2 | 3 | import io.github.mzdluo123.mirai.android.utils.MiraiAndroidStatus 4 | import net.mamoe.mirai.console.command.CommandSender 5 | import net.mamoe.mirai.console.command.ConsoleCommandOwner 6 | import net.mamoe.mirai.console.command.SimpleCommand 7 | 8 | object AndroidStatusCommand : SimpleCommand( 9 | ConsoleCommandOwner, 10 | "androidstatus", 11 | "astatus", 12 | description = "查询MiraiAndroid状态信息" 13 | ) { 14 | @Handler 15 | suspend fun CommandSender.handle() { 16 | sendMessage(MiraiAndroidStatus.recentStatus().format()) 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/ApkPluginLoader.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.miraiconsole 2 | 3 | import android.content.pm.PackageInfo 4 | import android.content.pm.PackageManager 5 | import io.github.mzdluo123.mirai.android.BotApplication 6 | import java.io.File 7 | 8 | object ApkPluginLoader { 9 | fun listPlugins(): Sequence { 10 | return BotApplication.context.packageManager.getInstalledPackages(PackageManager.GET_META_DATA) 11 | .asSequence() 12 | .filter { it.applicationInfo.metaData?.containsKey("miraiandroid_plugin") ?: false } 13 | } 14 | 15 | fun apkPluginFile(): Sequence { 16 | return listPlugins().map { File(it.applicationInfo.publicSourceDir) } 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/DexPluginClassloader.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.miraiconsole 2 | 3 | import android.os.Build 4 | import dalvik.system.DexClassLoader 5 | import net.mamoe.mirai.console.plugin.jvm.ExportManager 6 | import java.io.File 7 | import java.net.URL 8 | import java.util.* 9 | import java.util.concurrent.ConcurrentHashMap 10 | 11 | /** 12 | * copy from net.mamoe.mirai.console.internal.plugin.JvmPluginClassLoader 13 | * */ 14 | 15 | class DexPluginClassLoader( 16 | val file: File, 17 | odexPath: String, 18 | parent: ClassLoader?, 19 | val classLoaders: Collection, 20 | ) : DexClassLoader(file.path, odexPath, file.path, parent) { 21 | //// 只允许插件 getResource 时获取插件自身资源, #205 22 | override fun getResources(name: String?): Enumeration = findResources(name) 23 | override fun getResource(name: String?): URL? = findResource(name) 24 | // getResourceAsStream 在 URLClassLoader 中通过 getResource 确定资源 25 | // 因此无需 override getResourceAsStream 26 | 27 | override fun toString(): String { 28 | return "DexPluginClassLoader{source=$file}" 29 | } 30 | 31 | private val cache = ConcurrentHashMap>() 32 | internal var declaredFilter: ExportManager? = null 33 | 34 | companion object { 35 | val loadingLock = ConcurrentHashMap() 36 | 37 | init { 38 | if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { 39 | ClassLoader.registerAsParallelCapable() 40 | } 41 | } 42 | } 43 | 44 | override fun findClass(name: String): Class<*> { 45 | synchronized(kotlin.run { 46 | val lock = Any() 47 | loadingLock.putIfAbsent(name, lock) ?: lock 48 | }) { 49 | return findClass(name, false) ?: throw ClassNotFoundException(name) 50 | } 51 | } 52 | 53 | internal fun findClass(name: String, disableGlobal: Boolean): Class<*>? { 54 | // First. Try direct load in cache. 55 | val cachedClass = cache[name] 56 | if (cachedClass != null) { 57 | if (disableGlobal) { 58 | val filter = declaredFilter 59 | if (filter != null && !filter.isExported(name)) { 60 | throw LoadingDeniedException(name) 61 | } 62 | } 63 | return cachedClass 64 | } 65 | if (disableGlobal) { 66 | // ==== Process Loading Request From JvmPluginClassLoader ==== 67 | // 68 | // If load from other classloader, 69 | // means no other loaders are cached. 70 | // direct load 71 | return kotlin.runCatching { 72 | super.findClass(name).also { cache[name] = it } 73 | }.getOrElse { 74 | if (it is ClassNotFoundException) null 75 | else throw it 76 | }?.also { 77 | // This request is from other classloader, 78 | // so we need to check the class is exported or not. 79 | val filter = declaredFilter 80 | if (filter != null && !filter.isExported(name)) { 81 | throw LoadingDeniedException(name) 82 | } 83 | } 84 | } 85 | 86 | // ==== Process Loading Request From JDK ClassLoading System ==== 87 | 88 | // First. scan other classLoaders's caches 89 | classLoaders.forEach { otherClassloader -> 90 | if (otherClassloader === this) return@forEach 91 | val filter = otherClassloader.declaredFilter 92 | if (otherClassloader.cache.containsKey(name)) { 93 | return if (filter == null || filter.isExported(name)) { 94 | otherClassloader.cache[name] 95 | } else throw LoadingDeniedException("$name was not exported by $otherClassloader") 96 | } 97 | } 98 | classLoaders.forEach { otherClassloader -> 99 | val other = kotlin.runCatching { 100 | if (otherClassloader === this) super.findClass(name).also { cache[name] = it } 101 | else otherClassloader.findClass(name, true) 102 | }.onFailure { err -> 103 | if (err is LoadingDeniedException || err !is ClassNotFoundException) 104 | throw err 105 | }.getOrNull() 106 | if (other != null) return other 107 | } 108 | throw ClassNotFoundException(name) 109 | } 110 | } 111 | 112 | internal class LoadingDeniedException(name: String) : ClassNotFoundException(name) 113 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/miraiconsole/MiraiAndroidLogger.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.miraiconsole 2 | 3 | import android.text.TextUtils 4 | import android.util.Log 5 | import io.github.mzdluo123.mirai.android.AppSettings 6 | import io.github.mzdluo123.mirai.android.BuildConfig 7 | import io.github.mzdluo123.mirai.android.service.BotService 8 | import io.github.mzdluo123.mirai.android.utils.LoopQueue 9 | import net.mamoe.mirai.utils.SimpleLogger 10 | import java.io.PrintWriter 11 | import java.io.StringWriter 12 | import java.util.concurrent.locks.ReentrantLock 13 | import kotlin.concurrent.withLock 14 | 15 | private const val LOGGER_IDENTITY = "MA" 16 | 17 | private val logStorage = LoopQueue(AppSettings.logBuffer) 18 | 19 | private val printToSysLog = AppSettings.printToLogcat 20 | 21 | private enum class LogColor(val color: String) { 22 | INFO("#28BB28"), 23 | VERBOSE("#44cef6"), 24 | DEBUG(" #136E70"), 25 | WARNING("#FEAC48"), 26 | ERROR("#DD1C1A") 27 | } 28 | 29 | fun logException(err: Throwable?) { 30 | if (err == null) { 31 | return 32 | } 33 | val stringWriter = StringWriter() 34 | err.printStackTrace(PrintWriter(stringWriter)) 35 | MiraiAndroidLogger.error(stringWriter.toString()) 36 | } 37 | 38 | private val lock = ReentrantLock() 39 | internal fun pushLog(log: String) { 40 | lock.withLock { 41 | logStorage.add(log) 42 | 43 | for (i in 0 until BotService.consoleUi.beginBroadcast()) { 44 | try { 45 | BotService.consoleUi.getBroadcastItem(i).newLog(log) 46 | } catch (remoteE: Exception) { 47 | Log.e("MA", remoteE.message ?: "发生错误") 48 | remoteE.printStackTrace() 49 | // thread { logException(remoteE) }.start() // 防止死锁 50 | } 51 | } 52 | BotService.consoleUi.finishBroadcast() 53 | } 54 | 55 | } 56 | 57 | 58 | object MiraiAndroidLogger : 59 | SimpleLogger(LOGGER_IDENTITY, { priority: LogPriority, message: String?, e: Throwable? -> 60 | e?.printStackTrace() 61 | logException(e) 62 | message?.split("\n")?.forEach { 63 | val log = "[${priority.name}] $it" 64 | val colorLog = 65 | "[${priority.name}]${ 66 | TextUtils.htmlEncode(it) 67 | }" 68 | pushLog(colorLog) 69 | if (BuildConfig.DEBUG || printToSysLog) { 70 | Log.i("MA", log) 71 | } 72 | } 73 | } 74 | 75 | ) { 76 | val logs: MutableList 77 | get() = logStorage.toMutableList() 78 | 79 | 80 | fun clearLog() { 81 | logStorage.clear() 82 | } 83 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/receiver/BootReceiver.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.receiver 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Build 7 | import io.github.mzdluo123.mirai.android.AppSettings 8 | import io.github.mzdluo123.mirai.android.service.BotService 9 | import splitties.experimental.ExperimentalSplittiesApi 10 | 11 | class BootReceiver : BroadcastReceiver() { 12 | // companion object{ 13 | // const val TAG = "BootReceiver" 14 | // } 15 | private val ACTION = "android.intent.action.BOOT_COMPLETED" 16 | override fun onReceive(context: Context, intent: Intent) { 17 | // Log.e(TAG,"收到广播") 18 | if (AppSettings.startOnBoot) { 19 | return 20 | } 21 | 22 | if (intent.action == ACTION) { 23 | val startIntent = Intent(context, BotService::class.java) 24 | startIntent.putExtra( 25 | "action", 26 | BotService.START_SERVICE 27 | ) 28 | val account = 29 | context.getSharedPreferences("account", Context.MODE_PRIVATE) 30 | val qq = account.getLong("qq", 0) 31 | val pwd = account.getString("pwd", null) 32 | startIntent.putExtra("qq", qq) 33 | startIntent.putExtra("pwd", pwd) 34 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 35 | context.startForegroundService(startIntent) 36 | } else { 37 | context.startService(startIntent) 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/receiver/PushMsgReceiver.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.receiver 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.util.Log 7 | import io.github.mzdluo123.mirai.android.service.BotService 8 | 9 | class PushMsgReceiver(private val botService: BotService) : BroadcastReceiver() { 10 | companion object { 11 | val TAG = PushMsgReceiver::class.java.name 12 | } 13 | 14 | override fun onReceive(context: Context, intent: Intent) { 15 | try { 16 | Log.d(TAG, "收到广播") 17 | val data = intent.data ?: return 18 | 19 | if (data.scheme != "ma") { 20 | return 21 | } 22 | val id = data.getQueryParameter("id")?.toLong() ?: return 23 | val msg = data.getQueryParameter("msg") ?: return 24 | when (data.host) { 25 | "sendGroupMsg" -> { 26 | val at = data.getQueryParameter("at")?.toLong() 27 | if (at != null) { 28 | botService.sendGroupMsgWithAT(id, msg, at) 29 | return 30 | } 31 | botService.sendGroupMsg(id, msg) 32 | } 33 | "sendFriendMsg" -> botService.sendFriendMsg(id, msg) 34 | } 35 | 36 | } catch (e: Exception) { 37 | e.printStackTrace() 38 | Log.e(TAG, e.toString()) 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/script/ScriptManager.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.script 2 | 3 | import io.github.mzdluo123.mirai.android.BotApplication 4 | import net.mamoe.mirai.Bot 5 | import java.io.File 6 | import java.io.InputStream 7 | 8 | object ScriptManager { 9 | val scriptFolder = BotApplication.context.getExternalFilesDir("script")!! 10 | // private val scripts: MutableList = mutableListOf() 11 | 12 | @ExperimentalUnsignedTypes 13 | fun onLoad() { 14 | // val files = scriptFolder.listFiles().filter { 15 | // it.name.endsWith(".groovy") 16 | // } 17 | // files.forEach { file -> 18 | // kotlin.runCatching { 19 | // scripts.add(ScriptRuntime(file.toPath())) 20 | // }.onFailure { 21 | // MiraiAndroidLogger.error("MiraiAndroid加载脚本${file}时出现问题") 22 | // MiraiAndroidLogger.error(it) 23 | // } 24 | // } 25 | } 26 | 27 | fun onEnable(bot: Bot) { 28 | // scripts.forEach { script -> 29 | // kotlin.runCatching { 30 | // script.onEnable(bot) 31 | // }.onFailure { 32 | // MiraiAndroidLogger.error("MiraiAndroid启用脚本${script.fileName}时出现问题") 33 | // MiraiAndroidLogger.error(it) 34 | // } 35 | // } 36 | } 37 | 38 | fun onDisable() { 39 | // scripts.forEach { script -> 40 | // kotlin.runCatching { 41 | // script.onDisable() 42 | // }.onFailure { 43 | // MiraiAndroidLogger.error("MiraiAndroid停用脚本${script.fileName}时出现问题") 44 | // MiraiAndroidLogger.error(it) 45 | // } 46 | // } 47 | } 48 | 49 | fun addNewScript(name: String, input: InputStream) { 50 | val file = File(scriptFolder, name) 51 | if (file.exists()) { 52 | file.delete() 53 | file.createNewFile() 54 | } 55 | file.writeBytes(input.readBytes()) 56 | input.close() 57 | } 58 | 59 | fun deleteScript(name: String) { 60 | val file = File(scriptFolder, name) 61 | file.delete() 62 | } 63 | 64 | fun listScript(): List { 65 | return scriptFolder.list()!!.toList() 66 | } 67 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/service/ServiceConnector.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.service 2 | 3 | import android.content.ComponentName 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.ServiceConnection 7 | import android.os.DeadObjectException 8 | import android.os.IBinder 9 | import androidx.lifecycle.Lifecycle 10 | import androidx.lifecycle.LifecycleObserver 11 | import androidx.lifecycle.MutableLiveData 12 | import androidx.lifecycle.OnLifecycleEvent 13 | import io.github.mzdluo123.mirai.android.IConsole 14 | import io.github.mzdluo123.mirai.android.IbotAidlInterface 15 | import io.github.mzdluo123.mirai.android.IdleResources 16 | 17 | 18 | class ServiceConnector(var context: Context) : ServiceConnection, LifecycleObserver { 19 | 20 | lateinit var botService: IbotAidlInterface 21 | private set 22 | 23 | var connectStatus = MutableLiveData(false) 24 | private set 25 | 26 | private var callback: IConsole? = null 27 | 28 | override fun onServiceDisconnected(name: ComponentName?) { 29 | connectStatus.value = false 30 | } 31 | 32 | override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 33 | botService = IbotAidlInterface.Stub.asInterface(service) 34 | connectStatus.value = true 35 | if (callback != null) { 36 | botService.registerConsole(callback) 37 | } 38 | 39 | if (!IdleResources.botServiceLoading.isIdleNow) { 40 | IdleResources.botServiceLoading.decrement() 41 | } 42 | } 43 | 44 | @OnLifecycleEvent(Lifecycle.Event.ON_CREATE) 45 | fun connect() { 46 | if (!connectStatus.value!!) { 47 | context.bindService( 48 | Intent(context, BotService::class.java), 49 | this, 50 | Context.BIND_ABOVE_CLIENT 51 | ) 52 | if (IdleResources.botServiceLoading.isIdleNow) { 53 | IdleResources.botServiceLoading.increment() 54 | } 55 | } 56 | } 57 | 58 | @OnLifecycleEvent(Lifecycle.Event.ON_START) 59 | fun onStart() { 60 | if (connectStatus.value!!) { 61 | if (callback != null) { 62 | botService.registerConsole(callback) 63 | } 64 | } 65 | } 66 | 67 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP) 68 | fun onStop() { 69 | if (connectStatus.value!!) { 70 | if (callback != null) { 71 | try { 72 | botService.unregisterConsole(callback) 73 | } catch (ignore: DeadObjectException) { 74 | 75 | } 76 | 77 | callback = null 78 | } 79 | } 80 | } 81 | 82 | @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) 83 | fun disconnect() { 84 | if (connectStatus.value!!) { 85 | if (callback != null) { 86 | try { 87 | botService.unregisterConsole(callback) 88 | } catch (ignore: DeadObjectException) { 89 | 90 | } 91 | 92 | } 93 | context.unbindService(this) 94 | connectStatus.value = false 95 | } 96 | } 97 | 98 | fun registerConsole(callback: IConsole) { 99 | this.callback = callback 100 | 101 | } 102 | 103 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/about/AboutFragment.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.about 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.databinding.DataBindingUtil 10 | import androidx.fragment.app.Fragment 11 | import io.github.mzdluo123.mirai.android.BuildConfig 12 | import io.github.mzdluo123.mirai.android.R 13 | import io.github.mzdluo123.mirai.android.databinding.FragmentAboutBinding 14 | import kotlinx.android.synthetic.main.fragment_about.* 15 | import splitties.toast.toast 16 | 17 | class AboutFragment : Fragment() { 18 | private lateinit var aboutBinding: FragmentAboutBinding 19 | private var click = 0 20 | override fun onCreateView( 21 | inflater: LayoutInflater, container: ViewGroup?, 22 | savedInstanceState: Bundle? 23 | ): View? { 24 | // Inflate the layout for this fragment 25 | val aboutBinding = DataBindingUtil.inflate( 26 | layoutInflater, 27 | R.layout.fragment_about, 28 | container, 29 | false 30 | ) 31 | aboutBinding.appVersion = requireContext().packageManager.getPackageInfo( 32 | requireContext().packageName, 33 | 0 34 | ).versionName 35 | aboutBinding.coreVersion = BuildConfig.COREVERSION 36 | aboutBinding.consoleVersion = BuildConfig.CONSOLEVERSION 37 | return aboutBinding.root 38 | } 39 | 40 | override fun onActivityCreated(savedInstanceState: Bundle?) { 41 | super.onActivityCreated(savedInstanceState) 42 | github_btn.setOnClickListener { 43 | openUrl("https://github.com/mamoe/mirai") 44 | } 45 | github2_bth.setOnClickListener { 46 | openUrl("https://github.com/mzdluo123/MiraiAndroid") 47 | } 48 | btn_visit_forum.setOnClickListener { 49 | openUrl("https://mirai.mamoe.net/") 50 | } 51 | imageView2.setOnClickListener { 52 | if (click < 4) { 53 | click++ 54 | return@setOnClickListener 55 | } 56 | imageView2.setImageResource(R.drawable.avatar) 57 | } 58 | btn_join_group.setOnClickListener { 59 | if (!joinQQGroup("df6wSbKtDBo3cMJ9ULtYAZeln5ZZuA9d")) { 60 | toast("拉起QQ失败,请确认你是否安装了QQ") 61 | } 62 | } 63 | } 64 | 65 | 66 | private fun openUrl(url: String) { 67 | val uri = Uri.parse(url) 68 | startActivity(Intent(Intent.ACTION_VIEW, uri)) 69 | } 70 | 71 | /**************** 72 | * 73 | * 发起添加群流程。群号:MiraiAndroid(206073050) 的 key 为: 2aqIV-MkAOvx53dwUl-VVUYZqn8UrFAJ 74 | * 调用 joinQQGroup(2aqIV-MkAOvx53dwUl-VVUYZqn8UrFAJ) 即可发起手Q客户端申请加群 MiraiAndroid(206073050) 75 | * 76 | * @param key 由官网生成的key 77 | * @return 返回true表示呼起手Q成功,返回false表示呼起失败 78 | */ 79 | fun joinQQGroup(key: String): Boolean { 80 | val intent = Intent() 81 | intent.data = 82 | 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") 83 | // 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 84 | return try { 85 | startActivity(intent) 86 | true 87 | } catch (e: Exception) { 88 | // 未安装手Q或安装的版本不支持 89 | false 90 | } 91 | } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginFragment.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.plugin 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.view.* 7 | import android.widget.Toast 8 | import androidx.appcompat.widget.PopupMenu 9 | import androidx.fragment.app.Fragment 10 | import androidx.lifecycle.Observer 11 | import androidx.lifecycle.ViewModelProvider 12 | import androidx.recyclerview.widget.LinearLayoutManager 13 | import com.chad.library.adapter.base.BaseQuickAdapter 14 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 15 | import io.github.mzdluo123.mirai.android.R 16 | import kotlinx.android.synthetic.main.fragment_plugin.* 17 | import java.io.File 18 | 19 | 20 | class PluginFragment : Fragment() { 21 | 22 | private lateinit var pluginViewModel: PluginViewModel 23 | private lateinit var adapter: PluginsAdapter 24 | 25 | companion object { 26 | const val SELECT_RESULT_CODE = 1 27 | } 28 | 29 | override fun onCreateView( 30 | inflater: LayoutInflater, 31 | container: ViewGroup?, 32 | savedInstanceState: Bundle? 33 | ): View? { 34 | pluginViewModel = 35 | ViewModelProvider(this).get(PluginViewModel::class.java) 36 | val root = inflater.inflate(R.layout.fragment_plugin, container, false) 37 | setHasOptionsMenu(true) 38 | adapter = PluginsAdapter() 39 | 40 | adapter.setOnItemClickListener { _, view, position -> 41 | val menu = PopupMenu(requireContext(), view) 42 | menu.gravity = Gravity.END 43 | menu.menuInflater.inflate(R.menu.plugin_manage, menu.menu) 44 | menu.setOnMenuItemClickListener { item -> 45 | when (item.itemId) { 46 | R.id.action_delete -> { 47 | pluginViewModel.deletePlugin(position) 48 | Toast.makeText(activity, "删除成功,重启后生效", Toast.LENGTH_SHORT).show() 49 | return@setOnMenuItemClickListener true 50 | } 51 | 52 | else -> return@setOnMenuItemClickListener true 53 | } 54 | } 55 | menu.show() 56 | } 57 | 58 | pluginViewModel.pluginList.observe(viewLifecycleOwner, Observer { 59 | adapter.data = it.toMutableList() 60 | adapter.notifyDataSetChanged() 61 | }) 62 | return root 63 | } 64 | 65 | override fun onActivityCreated(savedInstanceState: Bundle?) { 66 | super.onActivityCreated(savedInstanceState) 67 | plugin_recycler.adapter = adapter 68 | plugin_recycler.layoutManager = LinearLayoutManager(activity) 69 | } 70 | 71 | override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { 72 | inflater.inflate(R.menu.plugin_add, menu) 73 | } 74 | 75 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 76 | if (item.itemId == android.R.id.home) { 77 | return false 78 | } 79 | 80 | val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { 81 | // Filter to only show results that can be "opened", such as a 82 | // file (as opposed to a list of contacts or timezones) 83 | addCategory(Intent.CATEGORY_OPENABLE) 84 | 85 | // Filter to show only images, using the image MIME data type. 86 | // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". 87 | // To search for all documents available via installed storage providers, 88 | // it would be "*/*". 89 | type = "application/java-archive" 90 | } 91 | 92 | startActivityForResult(intent, SELECT_RESULT_CODE) 93 | return true 94 | } 95 | 96 | override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { 97 | 98 | // The ACTION_OPEN_DOCUMENT intent was sent with the request code 99 | // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the 100 | // response to some other intent, and the code below shouldn't run at all. 101 | 102 | if (requestCode == SELECT_RESULT_CODE && resultCode == Activity.RESULT_OK) { 103 | 104 | // The document selected by the user won't be returned in the intent. 105 | // Instead, a URI to that document will be contained in the return intent 106 | // provided to this method as a parameter. 107 | // Pull that URI using resultData.getData(). 108 | 109 | 110 | resultData?.data?.also { uri -> 111 | 112 | startActivity( 113 | // Intent(activity, PluginImportActivity::class.java).putExtra( 114 | // "uri", 115 | // uri.toString() 116 | // ) 117 | Intent(Intent.ACTION_VIEW, uri) 118 | ) 119 | 120 | } 121 | } 122 | } 123 | 124 | override fun onResume() { 125 | super.onResume() 126 | pluginViewModel.refreshPluginList() 127 | } 128 | 129 | 130 | } 131 | 132 | 133 | class PluginsAdapter() : 134 | BaseQuickAdapter(R.layout.item_plugin) { 135 | override fun convert(holder: BaseViewHolder, item: MAPluginData) { 136 | holder.setText(R.id.pluginName_text, item.name) 137 | holder.setText(R.id.pluginSize_text, "${item.size}kb") 138 | if (item.apkPackageName != null) { 139 | holder.setVisible(R.id.text_apkFlag, true) 140 | } 141 | } 142 | } 143 | 144 | data class MAPluginData( 145 | val name: String, 146 | val size: Long, 147 | val file: File, 148 | val apkPackageName: String? = null 149 | ) { 150 | 151 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/plugin/PluginViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.plugin 2 | 3 | import android.content.Intent 4 | import android.net.Uri 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import io.github.mzdluo123.mirai.android.BotApplication 9 | import io.github.mzdluo123.mirai.android.miraiconsole.ApkPluginLoader 10 | import io.github.mzdluo123.mirai.android.utils.DexCompiler 11 | import kotlinx.coroutines.Dispatchers 12 | import kotlinx.coroutines.launch 13 | import kotlinx.coroutines.withContext 14 | import java.io.File 15 | 16 | 17 | class PluginViewModel : ViewModel() { 18 | val pluginList = MutableLiveData>() 19 | 20 | init { 21 | refreshPluginList() 22 | } 23 | 24 | private fun loadPluginList(): List { 25 | 26 | val jars = 27 | BotApplication.context.getExternalFilesDir("plugins")?.listFiles()?.asSequence()?.map { 28 | MAPluginData(it.name, it.length() / 1024, it) 29 | } 30 | 31 | val apks = ApkPluginLoader.listPlugins().map { 32 | val file = File(it.applicationInfo.publicSourceDir) 33 | MAPluginData(it.applicationInfo.packageName, file.length() / 1024, file, it.packageName) 34 | } 35 | if (jars != null) { 36 | return (jars + apks).toList() 37 | } 38 | return apks.toList() 39 | } 40 | 41 | fun deletePlugin(pos: Int) { 42 | val file = pluginList.value?.get(pos) ?: return 43 | if (file.apkPackageName != null) { 44 | BotApplication.context.startActivity( 45 | Intent(Intent.ACTION_DELETE) 46 | .setData(Uri.parse("package:" + file.apkPackageName)) 47 | .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 48 | ) 49 | } else { 50 | file.file.delete() 51 | } 52 | 53 | refreshPluginList() 54 | } 55 | 56 | fun refreshPluginList() { 57 | viewModelScope.launch(Dispatchers.IO) { 58 | pluginList.postValue(loadPluginList()) 59 | } 60 | } 61 | 62 | suspend fun compilePlugin(file: File, desugaring: Boolean) { 63 | val workDir = BotApplication.context.getExternalFilesDir(null) ?: return 64 | val tempDir = BotApplication.context.cacheDir 65 | val compiler = DexCompiler(workDir, tempDir) 66 | withContext(Dispatchers.IO) { 67 | if (tempDir.exists()) { 68 | deleteDir(tempDir) 69 | } 70 | tempDir.mkdir() 71 | } 72 | withContext(Dispatchers.Default) { 73 | val out = compiler.compile(file, desugaring) 74 | compiler.copyResourcesAndMove(file, out) 75 | } 76 | 77 | } 78 | 79 | private fun deleteDir(path: File) { 80 | path.listFiles()?.forEach { 81 | if (it.isFile) { 82 | it.delete() 83 | } else { 84 | if (it.isDirectory) { 85 | deleteDir(it) 86 | } 87 | } 88 | } 89 | path.delete() 90 | } 91 | 92 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptListAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.script 2 | 3 | import com.chad.library.adapter.base.BaseQuickAdapter 4 | import com.chad.library.adapter.base.viewholder.BaseViewHolder 5 | import io.github.mzdluo123.mirai.android.R 6 | 7 | class ScriptListAdapter(var fragment: ScriptFragment) : 8 | BaseQuickAdapter(R.layout.item_plugin) { 9 | override fun convert(holder: BaseViewHolder, item: String) { 10 | holder.setText(R.id.pluginName_text, item) 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/script/ScriptViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.script 2 | 3 | 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import io.github.mzdluo123.mirai.android.script.ScriptManager 8 | import kotlinx.coroutines.launch 9 | 10 | class ScriptViewModel() : ViewModel() { 11 | val scriptList: MutableLiveData?> = MutableLiveData() 12 | 13 | init { 14 | viewModelScope.launch { refreshScriptList() } 15 | } 16 | 17 | fun refreshScriptList() { 18 | scriptList.value = ScriptManager.listScript() 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/setting/SettingFragment.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.setting 2 | 3 | import android.os.Bundle 4 | import android.text.InputType 5 | import androidx.preference.EditTextPreference 6 | import androidx.preference.PreferenceFragmentCompat 7 | import io.github.mzdluo123.mirai.android.R 8 | 9 | 10 | class SettingFragment : PreferenceFragmentCompat() { 11 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 12 | preferenceManager.sharedPreferencesName = "setting" 13 | setPreferencesFromResource(R.xml.setting_screen, rootKey) 14 | 15 | findPreference("log_buffer_preference")?.apply { 16 | summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance() 17 | setOnBindEditTextListener { 18 | it.inputType = InputType.TYPE_CLASS_NUMBER 19 | } 20 | } 21 | 22 | 23 | findPreference("status_refresh_count")?.apply { 24 | summaryProvider = EditTextPreference.SimpleSummaryProvider.getInstance() 25 | setOnBindEditTextListener { 26 | it.inputType = InputType.TYPE_CLASS_NUMBER 27 | } 28 | } 29 | 30 | // findPreference("ignore_battery_optimization")?.apply { 31 | // /* 32 | // setOnPreferenceClickListener { preference -> 33 | // true 34 | // } 35 | // */ 36 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 37 | // intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS).apply { 38 | // data = Uri.parse("package:" + requireActivity().packageName) 39 | // } 40 | // } 41 | // 42 | // } 43 | 44 | } 45 | 46 | // override fun onResume() { 47 | // super.onResume() 48 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 49 | // val hasIgnored = ContextCompat.getSystemService( 50 | // requireContext(), 51 | // PowerManager::class.java 52 | // )!!.isIgnoringBatteryOptimizations(requireContext().packageName) 53 | // 54 | // PreferenceManager.getDefaultSharedPreferences(requireContext()).apply { 55 | // edit().putBoolean("ignore_battery_optimization", hasIgnored).apply() 56 | // } 57 | // } 58 | // 59 | // } 60 | 61 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/tools/ToolsFragment.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.tools 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Intent 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.widget.ArrayAdapter 10 | import android.widget.AutoCompleteTextView 11 | import androidx.core.content.FileProvider 12 | import androidx.fragment.app.Fragment 13 | import androidx.fragment.app.viewModels 14 | import io.github.mzdluo123.mirai.android.R 15 | import kotlinx.android.synthetic.main.fragment_tools.* 16 | import splitties.toast.toast 17 | import java.io.File 18 | 19 | 20 | class ToolsFragment : Fragment() { 21 | 22 | val viewModel by viewModels() 23 | override fun onCreateView( 24 | inflater: LayoutInflater, container: ViewGroup?, 25 | savedInstanceState: Bundle? 26 | ): View? { 27 | // Inflate the layout for this fragment 28 | return inflater.inflate(R.layout.fragment_tools, container, false) 29 | } 30 | 31 | @SuppressLint("SdCardPath") 32 | override fun onActivityCreated(savedInstanceState: Bundle?) { 33 | super.onActivityCreated(savedInstanceState) 34 | val adapter = 35 | ArrayAdapter(requireContext(), R.layout.item_list_menu, mutableListOf()) 36 | (menu.editText as AutoCompleteTextView).setAdapter(adapter) 37 | viewModel.botList.observe(viewLifecycleOwner, { arrayOfFiles -> 38 | if (arrayOfFiles == null) { 39 | return@observe 40 | } 41 | adapter.clear() 42 | adapter.addAll(arrayOfFiles.map { it.name }) 43 | adapter.notifyDataSetChanged() 44 | }) 45 | 46 | btn_export_device.setOnClickListener { 47 | val intent = Intent(Intent.ACTION_SEND) 48 | val uri = FileProvider.getUriForFile( 49 | requireContext(), 50 | requireContext().getPackageName() + ".provider", 51 | getDeviceFile() ?: return@setOnClickListener 52 | ) 53 | intent.putExtra(Intent.EXTRA_STREAM, uri) 54 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) 55 | intent.setType("application/octet-stream"); 56 | requireContext().startActivity(intent) 57 | } 58 | btn_reset_device.setOnClickListener { 59 | getDeviceFile()?.delete() ?: return@setOnClickListener 60 | toast("成功") 61 | } 62 | // btn_open_data_folder.setOnClickListener { 63 | // val intent = Intent() 64 | // 65 | // intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK; 66 | // intent.component = ComponentName( 67 | // "com.android.documentsui", 68 | // "com.android.documentsui.files.FilesActivity" 69 | // ) 70 | // 71 | // requireContext().startActivity(intent) 72 | // 73 | // } 74 | } 75 | 76 | private fun getDeviceFile(): File? { 77 | val folder = menu.editText?.text 78 | if (folder?.isEmpty() != false) { 79 | toast("请选择bot") 80 | return null 81 | } 82 | return File(requireContext().getExternalFilesDir(""), "bots/${folder}/device.json") 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/ui/tools/ToolsFragmentViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.ui.tools 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import io.github.mzdluo123.mirai.android.BotApplication 6 | import java.io.File 7 | 8 | class ToolsFragmentViewModel : ViewModel() { 9 | val botList: MutableLiveData?> = MutableLiveData() 10 | 11 | init { 12 | 13 | refreshBotList() 14 | } 15 | 16 | fun refreshBotList() { 17 | botList.value = 18 | File( 19 | BotApplication.context.getExternalFilesDir(""), 20 | "bots" 21 | ).listFiles { dir, _ -> 22 | dir.isDirectory 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/DeviceStatus.java: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils; 2 | 3 | import android.app.ActivityManager; 4 | import android.content.Context; 5 | import android.net.ConnectivityManager; 6 | import android.net.NetworkInfo; 7 | import android.telephony.TelephonyManager; 8 | import android.text.format.Formatter; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.FileNotFoundException; 12 | import java.io.FileReader; 13 | import java.io.IOException; 14 | import java.io.InputStream; 15 | 16 | public class DeviceStatus { 17 | public static String getSystemAvaialbeMemorySize(Context context) { 18 | ActivityManager mActivityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); 19 | // 获得MemoryInfo对象 20 | ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); 21 | // 获得系统可用内存,保存在MemoryInfo对象上 22 | mActivityManager.getMemoryInfo(memoryInfo); 23 | long memSize = memoryInfo.availMem; 24 | // 字符类型转换 25 | String availMemStr = Formatter.formatFileSize(context, memSize);// 调用系统函数,字符串转换 long -String KB/MB 26 | return availMemStr; 27 | } 28 | 29 | public static String getTotalMemory(Context context) { 30 | String str1 = "/proc/meminfo";// 系统内存信息文件 31 | String str2; 32 | String[] arrayOfString; 33 | String initial_memory = ""; 34 | try { 35 | FileReader localFileReader = new FileReader(str1); 36 | BufferedReader localBufferedReader = new BufferedReader(localFileReader, 8192); 37 | str2 = localBufferedReader.readLine();// 读取meminfo第一行,系统总内存大小 38 | arrayOfString = str2.split("//s+"); 39 | String mom = arrayOfString[0].split(":")[1].split("kB")[0]; 40 | initial_memory = Formatter.formatFileSize(context, Integer.valueOf(mom.trim()).intValue() * 1024); // 获得系统总内存,单位是MB,转换为GB 41 | localBufferedReader.close(); 42 | } catch (IOException e) { 43 | } 44 | return initial_memory;// Byte转换为KB或者MB,内存大小规格化 45 | } 46 | 47 | 48 | enum NetState { 49 | WIFI("wifi", 1), CDMA("2G", 2), UMTS("3G", 3), LTE("4G", 4), UNKOWN("unkonw", 5); 50 | private int state; 51 | private String type; 52 | 53 | NetState(String type, int state) { 54 | this.state = state; 55 | this.type = type; 56 | } 57 | } 58 | 59 | public static String getCurrentNetType(Context context) { 60 | // String type = "unknown"; 61 | int state = NetState.UNKOWN.state; 62 | String type = NetState.UNKOWN.type; 63 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 64 | NetworkInfo info = cm.getActiveNetworkInfo(); 65 | 66 | if (info == null) { 67 | // type = "unknown"; 68 | state = NetState.UNKOWN.state; 69 | ; 70 | } else if (info.getType() == ConnectivityManager.TYPE_WIFI) { 71 | // type = "wifi"; 72 | state = NetState.WIFI.state; 73 | } else if (info.getType() == ConnectivityManager.TYPE_MOBILE) { 74 | int subType = info.getSubtype(); 75 | if (subType == TelephonyManager.NETWORK_TYPE_CDMA || subType == TelephonyManager.NETWORK_TYPE_GPRS 76 | || subType == TelephonyManager.NETWORK_TYPE_EDGE) { 77 | // type = "2g"; 78 | state = NetState.CDMA.state; 79 | } else if (subType == TelephonyManager.NETWORK_TYPE_UMTS || subType == TelephonyManager.NETWORK_TYPE_HSDPA 80 | || subType == TelephonyManager.NETWORK_TYPE_EVDO_A 81 | || subType == TelephonyManager.NETWORK_TYPE_EVDO_0 82 | || subType == TelephonyManager.NETWORK_TYPE_EVDO_B) { 83 | // type = "3g"; 84 | state = NetState.UMTS.state; 85 | } else {// LTE是3g到4g的过渡,是3.9G的全球标准 if (subType == 86 | // TelephonyManager.NETWORK_TYPE_LTE) 87 | // type = "4g"; 88 | state = NetState.LTE.state; 89 | } 90 | } 91 | switch (state) { 92 | case 1: 93 | type = NetState.WIFI.type; 94 | break; 95 | case 2: 96 | type = NetState.CDMA.type; 97 | break; 98 | case 3: 99 | type = NetState.UMTS.type; 100 | break; 101 | case 4: 102 | type = NetState.LTE.type; 103 | break; 104 | case 5: 105 | default: 106 | type = NetState.UNKOWN.type; 107 | break; 108 | } 109 | return type; 110 | 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/DexCompiler.java: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils; 2 | 3 | import com.android.tools.r8.CompilationFailedException; 4 | import com.android.tools.r8.D8; 5 | import com.android.tools.r8.D8Command; 6 | import com.android.tools.r8.OutputMode; 7 | 8 | import net.lingala.zip4j.ZipFile; 9 | import net.lingala.zip4j.exception.ZipException; 10 | import net.lingala.zip4j.model.FileHeader; 11 | 12 | import org.apache.commons.io.FileUtils; 13 | 14 | import java.io.File; 15 | import java.io.IOException; 16 | import java.nio.file.Paths; 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | 21 | public class DexCompiler { 22 | private final File tempDir; 23 | private final File pluginDir; 24 | 25 | public DexCompiler(File fileDir, File cache) { 26 | tempDir = cache; 27 | if (!tempDir.exists()) { 28 | tempDir.mkdir(); 29 | } 30 | pluginDir = new File(fileDir.getAbsolutePath(), "plugins"); 31 | } 32 | 33 | public File compile(File jarFile, Boolean desugaring) throws CompilationFailedException, IOException { 34 | String outName = jarFile.getName().substring(0, jarFile.getName().length() - 4) + 35 | "-android.jar"; 36 | File outFile = new File(tempDir, outName).getAbsoluteFile(); 37 | // if (!outFile.exists()) { 38 | // outFile.createNewFile(); 39 | // } 40 | D8Command.Builder command = D8Command.builder() 41 | .addProgramFiles(Paths.get(jarFile.getAbsolutePath())) 42 | .setOutput(Paths.get(outFile.getAbsolutePath()), OutputMode.DexIndexed) 43 | .setMinApiLevel(26); 44 | 45 | if (!desugaring) { 46 | command.setDisableDesugaring(true); 47 | } 48 | 49 | // String[] cmd = new String[3]; 50 | // cmd[0] = "--output"; 51 | // cmd[1] = outFile.getAbsolutePath(); 52 | // cmd[2] = jarFile.getAbsolutePath(); 53 | // D8Command command = D8Command.parse(cmd, Origin.root()).build(); 54 | D8.run(command.build()); 55 | return outFile; 56 | } 57 | 58 | public void copyResourcesAndMove(File origin, File newFile) throws IOException { 59 | ZipFile originZip = new ZipFile(origin); 60 | ZipFile newZip = new ZipFile(newFile); 61 | ArrayList resources = new ArrayList<>(); 62 | List fileHeaders = originZip.getFileHeaders(); 63 | for(FileHeader i : fileHeaders) 64 | { 65 | try { 66 | originZip.extractFile(i, tempDir.getAbsolutePath()); 67 | } catch (ZipException e) { 68 | e.printStackTrace(); 69 | } 70 | } 71 | 72 | for (File file : tempDir.listFiles()) { 73 | if (file.isFile() && !file.getName().equals(newFile.getName())) { 74 | resources.add(file); 75 | } 76 | } 77 | if (resources.size() != 0) { 78 | newZip.addFiles(resources); 79 | } 80 | newZip.addFolder(new File(tempDir, "META-INF")); 81 | // 自带的moveto在遇到不同文件系统的时候会失败 82 | File dest = new File(pluginDir, newFile.getName()); 83 | if (dest.exists()) { 84 | dest.delete(); 85 | } 86 | FileUtils.moveFile(newFile, dest); 87 | 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/LoopQueue.java: -------------------------------------------------------------------------------- 1 | //-------------------------------------------------- 2 | // Class LimitedLoopQueue 3 | //-------------------------------------------------- 4 | // Written by Kenvix 5 | //-------------------------------------------------- 6 | 7 | package io.github.mzdluo123.mirai.android.utils; 8 | 9 | import org.jetbrains.annotations.Contract; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.util.Collection; 14 | import java.util.Iterator; 15 | import java.util.NoSuchElementException; 16 | import java.util.Queue; 17 | 18 | @SuppressWarnings("unchecked") 19 | public class LoopQueue implements Queue { 20 | @Nullable 21 | private Object[] container; 22 | 23 | private int insertPos = 0; 24 | private int headElementPos = 0; 25 | 26 | public LoopQueue(int size) { 27 | this.container = new Object[size]; 28 | } 29 | 30 | @Override 31 | public int size() { 32 | if (insertPos >= headElementPos) 33 | return insertPos - headElementPos; 34 | else 35 | return container.length - (headElementPos - insertPos); 36 | } 37 | 38 | @Override 39 | public boolean isEmpty() { 40 | return insertPos == headElementPos; 41 | } 42 | 43 | @Override 44 | public boolean contains(@Nullable Object o) { 45 | if (o == null) 46 | return false; 47 | 48 | for (int i = 0; i < size(); i++) { 49 | int pos = elementIndexOf(i); 50 | if (o.equals(container[pos])) 51 | return true; 52 | } 53 | 54 | return false; 55 | } 56 | 57 | @NotNull 58 | @Override 59 | public Iterator iterator() { 60 | return new Iterator() { 61 | private int iterHeadElementPos = headElementPos; 62 | 63 | @Override 64 | public boolean hasNext() { 65 | return insertPos != iterHeadElementPos; 66 | } 67 | 68 | @Override 69 | public E next() { 70 | E head = (E) container[iterHeadElementPos]; 71 | iterHeadElementPos = (iterHeadElementPos + 1) % container.length; 72 | 73 | return head; 74 | } 75 | }; 76 | } 77 | 78 | @NotNull 79 | @Override 80 | public E[] toArray() { 81 | Object[] result = new Object[size()]; 82 | 83 | for (int i = 0; i < size(); i++) { 84 | int pos = elementIndexOf(i); 85 | result[i] = container[pos]; 86 | } 87 | 88 | return (E[]) result; 89 | } 90 | 91 | @NotNull 92 | @Override 93 | public T[] toArray(@NotNull T[] a) { 94 | Object[] result; 95 | int size = size(); 96 | 97 | if (a.length >= size) 98 | result = a; 99 | else 100 | result = new Object[size()]; 101 | 102 | for (int i = 0; i < size(); i++) { 103 | int pos = elementIndexOf(i); 104 | result[i] = container[pos]; 105 | } 106 | 107 | return (T[]) result; 108 | } 109 | 110 | private void nextElementPos() { 111 | container[headElementPos] = null; 112 | headElementPos = (headElementPos + 1) % container.length; 113 | } 114 | 115 | private void nextInsertPos() { 116 | insertPos = (insertPos + 1) % container.length; 117 | } 118 | 119 | @Contract(value = "null -> fail; !null -> param1", pure = true) 120 | private T assertElementNotNull(T obj) { 121 | if (obj == null) 122 | throw new NoSuchElementException(); 123 | else 124 | return obj; 125 | } 126 | 127 | private int elementIndexOf(int offset) { 128 | return (headElementPos + offset) % container.length; 129 | } 130 | 131 | @Override 132 | public boolean add(E e) { 133 | container[insertPos] = e; 134 | 135 | if (((insertPos + 1) % container.length) == headElementPos) { 136 | nextElementPos(); 137 | } 138 | 139 | nextInsertPos(); 140 | return true; 141 | } 142 | 143 | @Override 144 | public boolean remove(Object o) { 145 | return false; 146 | } 147 | 148 | @Override 149 | public boolean containsAll(@NotNull Collection c) { 150 | return false; //TODO 151 | } 152 | 153 | @Override 154 | public boolean addAll(@NotNull Collection c) { 155 | return false; //TODO 156 | } 157 | 158 | @Override 159 | public boolean removeAll(@NotNull Collection c) { 160 | return false; //TODO 161 | } 162 | 163 | @Override 164 | public boolean retainAll(@NotNull Collection c) { 165 | return false; //TODO 166 | } 167 | 168 | @Override 169 | public void clear() { 170 | for (int i = 0; i < size(); i++) { 171 | container[elementIndexOf(i)] = null; 172 | } 173 | } 174 | 175 | @Override 176 | public boolean offer(E e) { 177 | return add(e); 178 | } 179 | 180 | @Override 181 | public E remove() { 182 | return assertElementNotNull(poll()); 183 | } 184 | 185 | @Override 186 | public E poll() { 187 | E head = peek(); 188 | nextElementPos(); 189 | 190 | return head; 191 | } 192 | 193 | @Override 194 | public E element() { 195 | return assertElementNotNull(peek()); 196 | } 197 | 198 | @Override 199 | public E peek() { 200 | return (E) container[headElementPos]; 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/MiraiAndroidStatus.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils 2 | 3 | import android.content.Context 4 | import android.os.Build 5 | import io.github.mzdluo123.mirai.android.AppSettings 6 | import io.github.mzdluo123.mirai.android.BotApplication 7 | import io.github.mzdluo123.mirai.android.BuildConfig 8 | import splitties.experimental.ExperimentalSplittiesApi 9 | import java.text.SimpleDateFormat 10 | 11 | class MiraiAndroidStatus( 12 | var miraiAndroidVersion: String, 13 | var coreVersion: String, 14 | var miraiConsoleVersion: String, 15 | var releaseVersion: String, 16 | var sdkVersion: Int, 17 | var memorySize: String, 18 | var netType: String, 19 | var startTime: String, 20 | var logBuffer: Int 21 | ) { 22 | @ExperimentalSplittiesApi 23 | @ExperimentalUnsignedTypes 24 | companion object { 25 | var startTime: Long = 0 26 | fun recentStatus(context: Context = BotApplication.context): MiraiAndroidStatus = 27 | MiraiAndroidStatus( 28 | context.packageManager.getPackageInfo(context.packageName, 0).versionName, 29 | BuildConfig.COREVERSION, 30 | BuildConfig.CONSOLEVERSION, 31 | Build.VERSION.RELEASE, 32 | Build.VERSION.SDK_INT, 33 | DeviceStatus.getSystemAvaialbeMemorySize(context.applicationContext), 34 | DeviceStatus.getCurrentNetType(context.applicationContext), 35 | SimpleDateFormat.getDateTimeInstance().format(startTime), 36 | AppSettings.logBuffer 37 | ) 38 | } 39 | 40 | fun format():String = buildString{ 41 | append("MiraiAndroid v") 42 | append(miraiAndroidVersion) 43 | append("\n") 44 | 45 | append("MiraiCore v") 46 | append(coreVersion) 47 | append("\n") 48 | 49 | append("MiraiConsole v") 50 | append(miraiConsoleVersion) 51 | append("\n") 52 | 53 | append("系统版本 ") 54 | append(releaseVersion) 55 | append(" SDK ") 56 | append(sdkVersion) 57 | append("\n") 58 | 59 | append("内存可用 ") 60 | append(memorySize) 61 | append("\n") 62 | 63 | append("网络 ") 64 | append(netType) 65 | append("\n") 66 | 67 | append("启动时间 ") 68 | append(startTime) 69 | append("\n") 70 | 71 | append("日志缓存行数 ") 72 | append(logBuffer) 73 | } 74 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/RequestUtil.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils 2 | 3 | import okhttp3.* 4 | import java.io.IOException 5 | import kotlin.coroutines.resume 6 | import kotlin.coroutines.resumeWithException 7 | import kotlin.coroutines.suspendCoroutine 8 | 9 | object RequestUtil { 10 | suspend fun get(url: String, clientBuilder: OkHttpClient.Builder.() -> Unit = {}): String? { 11 | val client = OkHttpClient.Builder().apply(clientBuilder).build() 12 | val request: Request = Request.Builder() 13 | .url(url) 14 | .get() 15 | .build() 16 | val call: Call = client.newCall(request) 17 | return suspendCoroutine { continuation -> 18 | call.enqueue(object : Callback { 19 | override fun onFailure(call: Call, e: IOException) { 20 | continuation.resumeWithException(e) 21 | } 22 | 23 | override fun onResponse(call: Call, response: Response) { 24 | continuation.resume(response.body?.string()) 25 | } 26 | }) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/TextSharer.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import androidx.lifecycle.LifecycleCoroutineScope 6 | import io.github.mzdluo123.mirai.android.IdleResources 7 | import kotlinx.coroutines.* 8 | import splitties.alertdialog.appcompat.alertDialog 9 | import splitties.alertdialog.appcompat.message 10 | import splitties.alertdialog.appcompat.title 11 | import splitties.toast.toast 12 | 13 | 14 | fun Context.shareText(text: String, scope: LifecycleCoroutineScope) { 15 | scope.launch { 16 | val waitingDialog = alertDialog { 17 | message = "正在上传" 18 | title = "请稍后" 19 | //setCancelable(false) 20 | } 21 | waitingDialog.show() 22 | IdleResources.loadingData.increment() 23 | val errorHandle = CoroutineExceptionHandler { _, _ -> 24 | waitingDialog.dismiss() 25 | toast("上传失败!") 26 | } 27 | val url = async(errorHandle) { paste(text) } 28 | withContext(Dispatchers.Main) { 29 | val urlResult = url.await() 30 | waitingDialog.dismiss() 31 | IdleResources.loadingData.decrement() 32 | startActivity(Intent.createChooser(Intent(Intent.ACTION_SEND).apply { 33 | type = "text/plain" 34 | putExtra(Intent.EXTRA_SUBJECT, "MiraiAndroid日志分享") 35 | putExtra(Intent.EXTRA_TEXT, urlResult) 36 | flags = Intent.FLAG_ACTIVITY_NEW_TASK 37 | }, "分享到")) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/dnsQuery.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils 2 | 3 | import io.github.mzdluo123.mirai.android.BotApplication 4 | import kotlinx.coroutines.runBlocking 5 | import kotlinx.serialization.json.jsonArray 6 | import kotlinx.serialization.json.jsonObject 7 | import kotlinx.serialization.json.jsonPrimitive 8 | import okhttp3.Dns 9 | import okhttp3.Request 10 | import java.net.InetAddress 11 | 12 | class SafeDns : Dns { 13 | override fun lookup(hostname: String): List { 14 | return runBlocking { 15 | val res = 16 | BotApplication.httpClient.value.newCall( 17 | Request.Builder() 18 | .url("https://cloudflare-dns.com/dns-query?name=$hostname&type=A") 19 | .header("accept", "application/dns-json").build() 20 | ).execute() 21 | val json = BotApplication.json.value.parseToJsonElement(res.body!!.string()) 22 | return@runBlocking listOf( 23 | InetAddress.getByName( 24 | json.jsonObject["Answer"]?.jsonArray?.get( 25 | 0 26 | )?.jsonObject?.get("data")?.jsonPrimitive?.content 27 | ) 28 | ) 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/fileUtils.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils 2 | 3 | import android.content.Context 4 | import android.content.DialogInterface 5 | import android.net.Uri 6 | import android.view.View 7 | import android.widget.EditText 8 | import androidx.appcompat.app.AlertDialog 9 | import io.github.mzdluo123.mirai.android.R 10 | import kotlinx.coroutines.CompletableDeferred 11 | import java.io.File 12 | import java.io.IOException 13 | 14 | @Throws(IOException::class) 15 | fun Context.copyToFileDir(uri: Uri, name: String, path: String): File { 16 | val plugin = File(path, name) 17 | plugin.createNewFile() 18 | val output = plugin.outputStream() 19 | this.contentResolver?.openInputStream(uri)?.use { 20 | val buf = ByteArray(1024) 21 | var bytesRead: Int 22 | while (it.read(buf).also { bytesRead = it } > 0) { 23 | output.write(buf, 0, bytesRead) 24 | } 25 | } 26 | output.close() 27 | return plugin 28 | } 29 | 30 | fun formatFileLength(length: Long): String? { 31 | var length = length 32 | val suffixs = arrayOf("B", "K", "M", "G", "T", "P") 33 | var suffixIndex = 0 34 | var remain = 0f 35 | while (length >= 1024) { 36 | remain = (remain + length % 1024) / 1024 37 | length /= 1024 38 | suffixIndex++ 39 | } 40 | return String.format("%.2f%s", remain + length, suffixs[suffixIndex]) 41 | } 42 | 43 | suspend fun Context.askFileName(): String? { 44 | val name = CompletableDeferred() 45 | val view = View.inflate(this@askFileName, R.layout.dialog_ask_filename,null) 46 | val editText = view.findViewById(R.id.filename_input) 47 | val dialog = AlertDialog.Builder(this@askFileName) 48 | .setView(view) 49 | .setPositiveButton("确定", DialogInterface.OnClickListener { _, _ -> 50 | name.complete(editText.text.toString()) 51 | }) 52 | .setNegativeButton( 53 | "取消", DialogInterface.OnClickListener { _, _ -> 54 | name.complete(null) 55 | }) 56 | .setCancelable(false) 57 | .setTitle("请输入文件名称") 58 | .create() 59 | dialog.show() 60 | return name.await()?.trim() 61 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/mzdluo123/mirai/android/utils/pasteBin.kt: -------------------------------------------------------------------------------- 1 | package io.github.mzdluo123.mirai.android.utils 2 | 3 | import android.annotation.SuppressLint 4 | import io.github.mzdluo123.mirai.android.BotApplication 5 | import kotlinx.coroutines.Dispatchers 6 | import kotlinx.coroutines.withContext 7 | import okhttp3.FormBody 8 | import okhttp3.Request 9 | 10 | @SuppressLint("SetJavaScriptEnabled") 11 | @OptIn(ExperimentalUnsignedTypes::class) 12 | // 13 | //suspend fun paste(text: String): String { 14 | // val res = withContext(Dispatchers.IO) { 15 | // BotApplication.httpClient.value.newCall( 16 | // Request.Builder().url("https://paste.ubuntu.com/") 17 | // .post( 18 | // FormBody.Builder().add("poster", "MiraiAndroid") 19 | // .add("syntax", "text") 20 | // .add("expiration", "") 21 | // .add("content", text).build() 22 | // ).build() 23 | // ).execute() 24 | // } 25 | // 26 | // return res.request.url.toString() 27 | //} 28 | 29 | // 30 | suspend fun paste(text: String): String { 31 | val res = withContext(Dispatchers.IO) { 32 | BotApplication.httpClient.value.newCall( 33 | Request.Builder().url("https://paste.rs/web") 34 | .post( 35 | FormBody.Builder() 36 | .add("extension", "txt") 37 | .add("content", text).build() 38 | ).build() 39 | ).execute() 40 | } 41 | return res.request.url.toString() 42 | } 43 | 44 | 45 | //suspend fun paste(text: String): String { 46 | // return withContext(Dispatchers.Main) { 47 | // val webView = WebView(BotApplication.context) 48 | // 49 | // webView.settings.apply { 50 | // javaScriptEnabled = true 51 | // } 52 | // webView.webViewClient = object :WebViewClient(){ 53 | // override fun onPageFinished(view: WebView?, url: String?) { 54 | // super.onPageFinished(view, url) 55 | // 56 | // webView.evaluateJavascript( 57 | // """ 58 | // document.getElementById("id_expires").selectedIndex = 4 59 | // document.getElementById("id_lexer").selectedIndex = 0 60 | // document.getElementById("id_content").value = `$text` 61 | // document.getElementsByClassName("btn")[0].click() 62 | // """.trimIndent() 63 | // ) {} 64 | // 65 | // } 66 | // 67 | // } 68 | // webView.loadUrl("https://pastebin.mozilla.org/") 69 | // while (webView.url == "https://pastebin.mozilla.org/") { 70 | // delay(100) 71 | // } 72 | // val url = webView.url ?: "上传出错" 73 | // webView.destroy() 74 | // url 75 | // } 76 | //} 77 | -------------------------------------------------------------------------------- /app/src/main/java/java/awt/image/BufferedImage.java: -------------------------------------------------------------------------------- 1 | package java.awt.image; 2 | 3 | //防止崩溃 4 | public class BufferedImage { 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/java/lang/management/ManagementFactory.java: -------------------------------------------------------------------------------- 1 | package java.lang.management; 2 | 3 | public class ManagementFactory { 4 | public static MemoryMXBean getMemoryMXBean() 5 | { 6 | return null; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/java/lang/management/MemoryMXBean.java: -------------------------------------------------------------------------------- 1 | package java.lang.management; 2 | 3 | public class MemoryMXBean { 4 | } 5 | -------------------------------------------------------------------------------- /app/src/main/res/color/color_drawer_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable-xxxhdpi/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable/avatar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_android_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_bug_report_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_build_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_clear_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_folder_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_insert_drive_file_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_new_releases_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_publish_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_battery_alert_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_chat_bubble_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_check_white_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_desktop_windows_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_edit_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_exit_to_app_24dp.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_extension_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_insert_drive_file_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_keyboard_arrow_down_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_keyboard_return_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_local_printshop_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_new_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 14 | 17 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_refresh_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_restore_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_save_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_store_white_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 12 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/loading_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mirai_a.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable/mirai_a.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/mirai_b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mzdluo123/MiraiAndroid/3c5d75da8c391a0d7d01c93da7095e95ea9b7260/app/src/main/res/drawable/mirai_b.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/side_nav_bar.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_captcha.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 18 | 19 | 32 | 33 |