diff --git a/mirai-demos/mirai-demo-android/build.gradle b/mirai-demos/mirai-demo-android/build.gradle index 1462c5d05..2da33aac9 100644 --- a/mirai-demos/mirai-demo-android/build.gradle +++ b/mirai-demos/mirai-demo-android/build.gradle @@ -1,17 +1,17 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.multiplatform' + id 'kotlin-android-extensions' } android { compileSdkVersion 29 defaultConfig { applicationId "net.mamoe.mirai.demo" - minSdkVersion 23 + minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" - testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -53,10 +53,11 @@ dependencies { testImplementation 'junit:junit:4.12' androidTestImplementation 'junit:junit:4.12' - androidTestImplementation 'com.android.support.test:runner:1.0.2' - androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' def anko_version = "0.10.8" implementation "org.jetbrains.anko:anko-commons:$anko_version" + + implementation group: 'io.ktor', name: 'ktor-client-android', version: '1.2.5' + implementation("io.ktor:ktor-client-android:1.2.5") } \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml b/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml index 59f36d365..c0a1c3165 100644 --- a/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml +++ b/mirai-demos/mirai-demo-android/src/main/AndroidManifest.xml @@ -2,9 +2,11 @@ package="net.mamoe.mirai.demo"> + + @@ -18,5 +20,6 @@ + diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt new file mode 100644 index 000000000..7bd0bec4c --- /dev/null +++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/LoginCallback.kt @@ -0,0 +1,11 @@ +package net.mamoe.mirai.demo + +import android.graphics.Bitmap + +interface LoginCallback { + + suspend fun onCaptcha(bitmap: Bitmap) + suspend fun onSuccess() + suspend fun onFailed() + suspend fun onMessage(message:String) +} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt new file mode 100644 index 000000000..fb28fe9d5 --- /dev/null +++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MainActivity.kt @@ -0,0 +1,121 @@ +package net.mamoe.mirai.demo + +import android.app.ProgressDialog +import android.app.Service +import android.content.ComponentName +import android.content.Intent +import android.content.ServiceConnection +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.Bundle +import android.os.IBinder +import android.util.Log +import android.view.View +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import io.ktor.util.cio.writeChannel +import kotlinx.android.synthetic.main.activity_main.* +import kotlinx.coroutines.* +import net.mamoe.mirai.Bot +import net.mamoe.mirai.event.subscribeFriendMessages +import net.mamoe.mirai.login +import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess +import net.mamoe.mirai.utils.DefaultLogger +import net.mamoe.mirai.utils.MiraiLogger +import net.mamoe.mirai.utils.PlatformLogger +import net.mamoe.mirai.utils.SimpleLogger +import java.io.File + +class MainActivity : AppCompatActivity(),LoginCallback { + + + private lateinit var progressDialog : ProgressDialog + + override suspend fun onCaptcha(bitmap: Bitmap) { + withContext(Dispatchers.Main){ + ll_captcha.visibility = View.VISIBLE + iv_captcha.setImageBitmap(bitmap) + needCaptcha = true + if (progressDialog.isShowing){ + progressDialog.dismiss() + } + } + } + + override suspend fun onMessage(message:String) { + withContext(Dispatchers.Main){ + msg.text = "${msg.text}\n$message" + } + } + + override suspend fun onSuccess() { + withContext(Dispatchers.Main){ + Toast.makeText(this@MainActivity,"登录成功",Toast.LENGTH_SHORT).show() + if (progressDialog.isShowing){ + progressDialog.dismiss() + } + ll_captcha.visibility = View.GONE + et_pwd.visibility = View.GONE + et_qq.visibility = View.GONE + bt_login.visibility = View.GONE + } + + } + + override suspend fun onFailed() { + withContext(Dispatchers.Main){ + Toast.makeText(this@MainActivity,"登录失败",Toast.LENGTH_SHORT).show() + if (progressDialog.isShowing){ + progressDialog.dismiss() + } + } + } + + var binder: MiraiService.MiraiBinder? = null + + var needCaptcha = false + + + private val conn = object : ServiceConnection { + + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + binder = service as MiraiService.MiraiBinder? + } + + override fun onServiceDisconnected(name: ComponentName?) { + binder = null + } + + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + val intent = Intent(this, MiraiService::class.java) + startService(intent) + bindService(intent, conn, Service.BIND_AUTO_CREATE) + progressDialog = ProgressDialog(this) + bt_login.setOnClickListener { + if (!progressDialog.isShowing){ + progressDialog.show() + } + binder?.setCallback(this) + if (!needCaptcha){ + val qq = et_qq.text.toString().toUInt() + val pwd = et_pwd.text.toString() + binder?.startLogin(qq, pwd) + }else{ + val captcha = et_captcha.text.toString() + binder?.setCaptcha(captcha) + } + + } + } + + override fun onDestroy() { + super.onDestroy() + unbindService(conn) + } + +} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt new file mode 100644 index 000000000..40755d382 --- /dev/null +++ b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/MiraiService.kt @@ -0,0 +1,159 @@ +package net.mamoe.mirai.demo + +import android.app.Service +import android.content.Intent +import android.graphics.BitmapFactory +import android.os.Binder +import android.os.IBinder +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import net.mamoe.mirai.Bot +import net.mamoe.mirai.contact.QQ +import net.mamoe.mirai.event.subscribeMessages +import net.mamoe.mirai.login +import net.mamoe.mirai.message.Image +import net.mamoe.mirai.network.protocol.tim.packet.event.GroupMessage +import net.mamoe.mirai.network.protocol.tim.packet.login.LoginResult +import java.lang.ref.WeakReference + +class MiraiService : Service() { + + private var mCaptchaDeferred: CompletableDeferred? = null + + private var mBot: Bot? = null + + private var mCaptcha = "" + set(value) { + field = value + mCaptchaDeferred?.complete(value) + } + + private var mBinder: MiraiBinder? = null + + private var mCallback: WeakReference? = null + + override fun onCreate() { + super.onCreate() + mBinder = MiraiBinder() + + } + + private fun login(qq: UInt, pwd: String) { + GlobalScope.launch { + mBot = Bot(qq, pwd).apply { + val loginResult = login { + captchaSolver = { + val byteArray = byteArrayOf() + it.readFully(byteArray, 0, it.writeRemaining) + val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size) + mCallback?.get()?.onCaptcha(bitmap) + mCaptchaDeferred?.await() + } + } + if (loginResult == LoginResult.SUCCESS) { + mCallback?.get()?.onSuccess() + } else { + mCallback?.get()?.onFailed() + } + } + + + mBot!!.subscribeMessages { + content({ true }) { + mCallback?.get()?.onMessage("收到来自${sender.id}的消息") + } + + // 当接收到消息 == "你好" 时就回复 "你好!" + "你好" reply "你好!" + + // 当消息 == "查看 subject" 时, 执行 lambda + case("查看 subject") { + if (subject is QQ) { + reply("消息主体为 QQ, 你在跟发私聊消息") + } else { + reply("消息主体为 Group, 你在群里发消息") + } + + // 在回复的时候, 一般使用 subject 来作为回复对象. + // 因为当群消息时, subject 为这个群. + // 当好友消息时, subject 为这个好友. + // 所有在 MessagePacket(也就是此时的 this 指代的对象) 中实现的扩展方法, 如刚刚的 "reply", 都是以 subject 作为目标 + } + + + // 当消息里面包含这个类型的消息时 + has { + // this: MessagePacket + // message: MessageChain + // sender: QQ + // it: String (MessageChain.toString) + + if (this is GroupMessage) { + //如果是群消息 + // group: Group + this.group.sendMessage("你在一个群里") + // 等同于 reply("你在一个群里") + } + + reply("图片, ID= ${message[Image].id}")//获取第一个 Image 类型的消息 + reply(message) + } + + + "123" containsReply "你的消息里面包含 123" + + + // 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String + "我的qq" reply { sender.id.toString() } + + + // 当消息前缀为 "我是" 时 + startsWith("我是", removePrefix = true) { + // it: 删除了消息前缀 "我是" 后的消息 + // 如一条消息为 "我是张三", 则此时的 it 为 "张三". + + reply("你是$it") + } + + + // 当消息中包含 "复读" 时 + contains("复读") { + reply(message) + } + + + // 自定义的 filter, filter 中 it 为转为 String 的消息. + // 也可以用任何能在处理时使用的变量, 如 subject, sender, message + content({ it.length == 3 }) { + reply("你发送了长度为 3 的消息") + } + + } + } + + } + + + override fun onBind(intent: Intent?): IBinder? { + return mBinder + } + + + inner class MiraiBinder : Binder() { + + fun startLogin(qq: UInt, pwd: String) { + login(qq, pwd) + } + + fun setCaptcha(captcha: String) { + mCaptcha = captcha + } + + fun setCallback(callback: LoginCallback) { + mCallback = WeakReference(callback) + } + } + + +} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/ui.kt b/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/ui.kt deleted file mode 100644 index 4d438758c..000000000 --- a/mirai-demos/mirai-demo-android/src/main/kotlin/net/mamoe/mirai/demo/ui.kt +++ /dev/null @@ -1,57 +0,0 @@ -@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE") - -package net.mamoe.mirai.demo - -import android.app.Application -import android.os.Bundle -import android.widget.LinearLayout -import androidx.appcompat.app.AppCompatActivity -import net.mamoe.mirai.Bot -import net.mamoe.mirai.event.subscribeFriendMessages -import net.mamoe.mirai.login -import net.mamoe.mirai.network.protocol.tim.packet.login.requireSuccess -import net.mamoe.mirai.utils.DefaultLogger -import net.mamoe.mirai.utils.PlatformLogger -import net.mamoe.mirai.utils.SimpleLogger -import java.io.ByteArrayOutputStream -import java.io.PrintStream -import kotlin.properties.Delegates - -@Suppress("unused") -private val Throwable.stacktraceString: String - get() = ByteArrayOutputStream().also { printStackTrace(PrintStream(it)) }.toString() - -class MyApplication : Application() - -class MainActivity : AppCompatActivity() { - private var rootLayout: LinearLayout by Delegates.notNull() - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - rootLayout = findViewById(R.id.main_view) - - - } - - private suspend fun initializeBot(qq: UInt, password: String) { - DefaultLogger = { - PlatformLogger(it) + SimpleLogger { message, e -> - // TODO: 2019/11/6 - } - } - - val bot = Bot(qq, password).apply { - login { - captchaSolver = { - - "ABCD" - } - }.requireSuccess() - } - - bot.subscribeFriendMessages { - "Hello" reply "Hello Mirai!" - } - } -} \ No newline at end of file diff --git a/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml b/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml index 520f84b9e..ce8b8004b 100644 --- a/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml +++ b/mirai-demos/mirai-demo-android/src/main/res/layout/activity_main.xml @@ -1,8 +1,62 @@ - \ No newline at end of file + + + + + + + + + + + + + +