Commit 12ad7542 authored by Him188's avatar Him188

Separate `mirai-demos` series from main repository

parent c6ae8e32
apply plugin: "kotlin"
apply plugin: "java"
dependencies {
runtimeOnly files("../../mirai-core/build/classes/kotlin/jvm/main") // IDE bug
runtimeOnly files("../../mirai-core-qqandroid/build/classes/kotlin/jvm/main") // IDE bug
implementation project(":mirai-core-qqandroid")
api group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
api group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
}
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package demo.subscribe
import kotlinx.coroutines.CompletableJob
import net.mamoe.mirai.Bot
import net.mamoe.mirai.BotAccount
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.QQ
import net.mamoe.mirai.contact.isOperator
import net.mamoe.mirai.contact.sendMessage
import net.mamoe.mirai.event.*
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.message.nextMessage
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.utils.FileBasedDeviceInfo
import net.mamoe.mirai.utils.MiraiInternalAPI
import java.io.File
@MiraiInternalAPI
private fun readTestAccount(): BotAccount? {
val file = File("testAccount.txt")
if (!file.exists() || !file.canRead()) {
return null
}
println("Reading account from testAccount.text")
val lines = file.readLines()
return try {
BotAccount(lines[0].toLong(), lines[1])
} catch (e: IndexOutOfBoundsException) {
null
}
}
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot( // JVM 下也可以不写 `QQAndroid.` 引用顶层函数
123456789,
"123456"
) {
// 覆盖默认的配置
+FileBasedDeviceInfo // 使用 "device.json" 保存设备信息
// networkLoggerSupplier = { SilentLogger } // 禁用网络层输出
}.alsoLogin()
bot.messageDSL()
directlySubscribe(bot)
bot.join()//等到直到断开连接
}
/**
* 使用 dsl 监听消息事件
*
* @see subscribeFriendMessages
* @see subscribeMessages
* @see subscribeGroupMessages
*
* @see MessageSubscribersBuilder
*/
fun Bot.messageDSL() {
// 监听这个 bot 的来自所有群和好友的消息
this.subscribeMessages {
// 当接收到消息 == "你好" 时就回复 "你好!"
"你好" reply "你好!"
// 当消息 == "查看 subject" 时, 执行 lambda
case("查看 subject") {
if (subject is QQ) {
reply("消息主体为 QQ, 你在发私聊消息")
} else {
reply("消息主体为 Group, 你在群里发消息")
}
// 在回复的时候, 一般使用 subject 来作为回复对象.
// 因为当群消息时, subject 为这个群.
// 当好友消息时, subject 为这个好友.
// 所有在 MessagePacket(也就是此时的 this 指代的对象) 中实现的扩展方法, 如刚刚的 "reply", 都是以 subject 作为目标
}
// 当消息里面包含这个类型的消息时
has<Image> {
// this: MessagePacket
// message: MessageChain
// sender: QQ
// it: String (MessageChain.toString)
// message[Image].download() // 还未支持 download
if (this is GroupMessage) {
//如果是群消息
// group: Group
this.group.sendMessage("你在一个群里")
// 等同于 reply("你在一个群里")
}
reply("图片, ID= ${message[Image]}")//获取第一个 Image 类型的消息
reply(message)
}
"hello.*world".toRegex() matchingReply {
"Hello!"
}
"123" containsReply "你的消息里面包含 123"
// 当收到 "我的qq" 就执行 lambda 并回复 lambda 的返回值 String
"我的qq" reply { sender.id }
"at all" reply AtAll // at 全体成员
// 如果是这个 QQ 号发送的消息(可以是好友消息也可以是群消息)
sentBy(123456789) {
}
// 当消息前缀为 "我是" 时
startsWith("我是", removePrefix = true) {
// it: 删除了消息前缀 "我是" 后的消息
// 如一条消息为 "我是张三", 则此时的 it 为 "张三".
reply("你是$it")
}
// listener 管理
var repeaterListener: CompletableJob? = null
contains("开启复读") {
repeaterListener?.complete()
bot.subscribeGroupMessages {
repeaterListener = contains("复读") {
reply(message)
}
}
}
contains("关闭复读") {
if (repeaterListener?.complete() == null) {
reply("没有开启复读")
} else {
reply("成功关闭复读")
}
}
// 自定义的 filter, filter 中 it 为转为 String 的消息.
// 也可以用任何能在处理时使用的变量, 如 subject, sender, message
content({ it.length == 3 }) {
reply("你发送了长度为 3 的消息")
}
case("上传好友图片") {
val filename = it.substringAfter("上传好友图片")
File("C:\\Users\\Him18\\Desktop\\$filename").sendAsImageTo(subject)
}
case("上传群图片") {
val filename = it.substringAfter("上传好友图片")
File("C:\\Users\\Him18\\Desktop\\$filename").sendAsImageTo(subject)
}
}
subscribeMessages {
case("你好") {
// this: MessagePacket
// message: MessageChain
// sender: QQ
// it: String (来自 MessageChain.toString)
// group: Group (如果是群消息)
reply("你好")
}
}
subscribeFriendMessages {
contains("A") {
// this: FriendMessage
// message: MessageChain
// sender: QQ
// it: String (来自 MessageChain.toString)
reply("B")
}
}
subscribeGroupMessages {
// this: FriendMessage
// message: MessageChain
// sender: QQ
// it: String (来自 MessageChain.toString)
// group: Group
case("recall") {
reply("😎").recallIn(3000) // 3 秒后自动撤回这条消息
}
case("禁言") {
// 挂起当前协程, 等待下一条满足条件的消息.
// 发送 "禁言" 后需要再发送一条消息 at 一个人.
val value: At = nextMessage { message.any(At) }[At]
value.member().mute(10)
}
startsWith("群名=") {
if (!sender.isOperator()) {
sender.mute(5)
return@startsWith
}
group.name = it
}
}
}
/**
* 监听单个事件
*/
@Suppress("UNUSED_VARIABLE")
suspend fun directlySubscribe(bot: Bot) {
// 在当前协程作用域 (CoroutineScope) 下创建一个子 Job, 监听一个事件.
//
// 手动处理消息
//
// subscribeAlways 函数返回 Listener, Listener 是一个 CompletableJob.
//
// 例如:
// ```kotlin
// runBlocking {// this: CoroutineScope
// subscribeAlways<FriendMessage> {
// }
// }
// ```
// 则这个 `runBlocking` 永远不会结束, 因为 `subscribeAlways` 在 `runBlocking` 的 `CoroutineScope` 下创建了一个 Job.
// 正确的用法为:
// 在 Bot 的 CoroutineScope 下创建一个监听事件的 Job, 则这个子 Job 会在 Bot 离线后自动完成 (complete).
bot.subscribeAlways<FriendMessage> {
// this: FriendMessageEvent
// event: FriendMessageEvent
// 获取第一个纯文本消息, 获取不到会抛出 NoSuchElementException
// val firstText = message.first<PlainText>()
val firstText = message.firstOrNull<PlainText>()
// 获取第一个图片
val firstImage = message.firstOrNull<Image>()
when {
message eq "你好" -> reply("你好!")
"复读" in message -> sender.sendMessage(message)
"发群消息" in message -> {
bot.getGroup(580266363).sendMessage(message.toString().substringAfter("发群消息"))
}
}
}
}
\ No newline at end of file
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.multiplatform'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion 29
defaultConfig {
applicationId "net.mamoe.mirai.demo"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
packagingOptions {
exclude 'META-INF/main.kotlin_module'
exclude 'META-INF/ktor-http.kotlin_module'
exclude 'META-INF/kotlinx-io.kotlin_module'
exclude 'META-INF/atomicfu.kotlin_module'
exclude 'META-INF/ktor-utils.kotlin_module'
exclude 'META-INF/kotlinx-coroutines-io.kotlin_module'
exclude 'META-INF/kotlinx-coroutines-core.kotlin_module'
exclude 'META-INF/ktor-http-cio.kotlin_module'
exclude 'META-INF/ktor-http-cio.kotlin_module'
exclude 'META-INF/ktor-client-core.kotlin_module'
exclude "META-INF/kotlinx-serialization-runtime.kotlin_module"
exclude 'META-INF/ktor-io.kotlin_module'
}
}
kotlin {
targets.fromPreset(presets.android, 'android')
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib"
implementation project(':mirai-core-qqandroid')
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-android', version: "1.3.2"
//implementation 'com.android.support:appcompat-v7:29.1.1'// https://mvnrepository.com/artifact/androidx.appcompat/appcompat
implementation group: 'androidx.appcompat', name: 'appcompat', version: '1.1.0'
testImplementation "org.jetbrains.kotlin:kotlin-test"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'junit:junit:4.12'
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
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="net.mamoe.mirai.demo">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:label="@string/app_name"
android:allowBackup="true"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="net.mamoe.mirai.demo.MainActivity"
android:theme="@style/AppTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<service android:name=".MiraiService"/>
</application>
</manifest>
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package net.mamoe.mirai.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
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("DEPRECATION", "EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.demo
import android.annotation.SuppressLint
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.os.Bundle
import android.os.IBinder
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
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()
}
}
}
@SuppressLint("SetTextI18n")
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
private 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().toLong()
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
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_API_USAGE")
package net.mamoe.mirai.demo
import android.app.Service
import android.content.Context
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 kotlinx.io.core.IoBuffer
import kotlinx.io.core.readBytes
import net.mamoe.mirai.Bot
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.qqandroid.QQAndroid
import net.mamoe.mirai.utils.LoginSolver
import java.lang.ref.WeakReference
class MiraiService : Service() {
private lateinit var mCaptchaDeferred: CompletableDeferred<String>
private lateinit var mBot: Bot
private var mCaptcha = ""
set(value) {
field = value
mCaptchaDeferred.complete(value)
}
private var mBinder: MiraiBinder? = null
private var mCallback: WeakReference<LoginCallback>? = null
override fun onCreate() {
super.onCreate()
mBinder = MiraiBinder()
}
private fun login(context: Context, qq: Long, pwd: String) {
GlobalScope.launch {
mBot = QQAndroid.Bot(context, qq, pwd) {
loginSolver = object : LoginSolver() {
override suspend fun onSolvePicCaptcha(bot: Bot, data: IoBuffer): String? {
val bytes = data.readBytes()
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
mCaptchaDeferred = CompletableDeferred()
mCallback?.get()?.onCaptcha(bitmap)
return mCaptchaDeferred.await()
}
override suspend fun onSolveSliderCaptcha(bot: Bot, url: String): String? {
TODO("not implemented")
}
override suspend fun onSolveUnsafeDeviceLoginVerify(bot: Bot, url: String): String? {
TODO("not implemented")
}
}
}.apply {
try {
login()
mCallback?.get()?.onSuccess()
} catch (e: Exception) {
mCallback?.get()?.onFailed()
}
}
mBot.subscribeMessages {
always {
mCallback?.get()?.onMessage("收到来自${sender.id}的消息")
}
// 当接收到消息 == "你好" 时就回复 "你好!"
"你好" reply "你好!"
}
}
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
inner class MiraiBinder : Binder() {
fun startLogin(qq: Long, pwd: String) {
login(applicationContext, qq, pwd)
}
fun setCaptcha(captcha: String) {
mCaptcha = captcha
}
fun setCallback(callback: LoginCallback) {
mCallback = WeakReference(callback)
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:scrollbars="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<EditText
android:layout_width="match_parent"
android:layout_height="46dp"
android:inputType="number"
android:layout_marginTop="10dp"
android:ems="15"
android:hint="请输入QQ号"
android:id="@+id/et_qq" />
<EditText
android:layout_width="match_parent"
android:layout_height="46dp"
android:inputType="textPassword"
android:hint="请输入密码"
android:layout_marginTop="10dp"
android:id="@+id/et_pwd" />
<LinearLayout
android:id="@+id/ll_captcha"
android:layout_marginTop="5dp"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<EditText
android:id="@+id/et_captcha"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="46dp"
android:hint="输入验证码"/>
<ImageView
android:id="@+id/iv_captcha"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginLeft="5dp"
android:scaleType="fitXY"/>
</LinearLayout>
<Button
android:text="登录"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:id="@+id/bt_login"
android:background="#3F51B5"
android:textColor="#FFFFFF"/>
<TextView
android:id="@+id/msg"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Mirai</string>
</resources>
\ No newline at end of file
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
apply plugin: "kotlin"
apply plugin: "java"
apply plugin: "application"
apply plugin: "kotlinx-serialization"
dependencies {
runtimeOnly files("../../mirai-core/build/classes/kotlin/jvm/main") // IDE bug
runtimeOnly files("../../mirai-core-qqandroid/build/classes/kotlin/jvm/main") // IDE bug
api project(":mirai-core")
api project(":mirai-core-qqandroid")
api("org.jetbrains.kotlinx:atomicfu:$atomicFuVersion")
implementation group: 'org.jetbrains.kotlin', name: 'kotlin-stdlib-jdk8', version: kotlinVersion
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: coroutinesVersion
implementation("org.jetbrains.kotlinx:kotlinx-io:$kotlinXIoVersion")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-io:$coroutinesIoVersion")
implementation group: 'com.alibaba', name: 'fastjson', version: '1.2.62'
api 'org.jsoup:jsoup:1.12.1'
}
run{
standardInput = System.in
mainClassName = "demo.gentleman.MainKt"
}
compileKotlin {
kotlinOptions {
freeCompilerArgs = ["-XXLanguage:+InlineClasses"]
}
}
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
package demo.gentleman
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.launch
import net.mamoe.mirai.contact.Contact
/**
* 最少缓存的图片数量
*/
private const val IMAGE_BUFFER_CAPACITY: Int = 5
/**
* 为不同的联系人提供图片
*/
@ExperimentalUnsignedTypes
@ExperimentalCoroutinesApi
object Gentlemen : MutableMap<Long, Gentleman> by mutableMapOf() {
fun provide(key: Contact, keyword: String = ""): Gentleman = this.getOrPut(key.id) { Gentleman(key, keyword) }
}
/**
* 工作是缓存图片
*/
@ExperimentalCoroutinesApi
class Gentleman(private val contact: Contact, private val keyword: String) : Channel<GentleImage> by Channel(IMAGE_BUFFER_CAPACITY) {
init {
GlobalScope.launch {
while (!isClosedForSend) {
send(GentleImage(contact, keyword).apply {
seImage// start downloading
})
}
}
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("EXPERIMENTAL_UNSIGNED_LITERALS", "EXPERIMENTAL_API_USAGE")
package demo.gentleman
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import net.mamoe.mirai.Bot
import net.mamoe.mirai.alsoLogin
import net.mamoe.mirai.contact.Member
import net.mamoe.mirai.contact.MemberPermission
import net.mamoe.mirai.event.Event
import net.mamoe.mirai.event.events.BotEvent
import net.mamoe.mirai.event.subscribeAlways
import net.mamoe.mirai.event.subscribeGroupMessages
import net.mamoe.mirai.event.subscribeMessages
import net.mamoe.mirai.message.FriendMessage
import net.mamoe.mirai.message.GroupMessage
import net.mamoe.mirai.message.data.At
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.buildXMLMessage
import net.mamoe.mirai.message.data.getValue
import net.mamoe.mirai.message.sendAsImageTo
import net.mamoe.mirai.utils.ContextImpl
import java.io.File
import java.util.*
import javax.swing.filechooser.FileSystemView
import kotlin.random.Random
@Suppress("UNUSED_VARIABLE")
suspend fun main() {
val bot = Bot(
ContextImpl(),
913366033,
"a18260132383"
) {
// override config here.
}.alsoLogin()
// 任何可以监听的对象都继承 Event, 因此这个订阅会订阅全部的事件.
GlobalScope.subscribeAlways<Event> {
//bot.logger.verbose("收到了一个事件: $this")
}
// 全局范围订阅事件, 不受 bot 实例影响
GlobalScope.subscribeAlways<BotEvent> {
}
// 订阅来自这个 bot 的群消息事件
bot.subscribeGroupMessages {
startsWith("mute") {
val at: At by message
at.member().mute(30)
}
startsWith("unmute") {
val at: At by message
at.member().unmute()
}
}
// 订阅来自这个 bot 的消息事件, 可以是群消息也可以是好友消息
bot.subscribeMessages {
always {
}
case("at me") { At(sender as Member).reply() }
// 等同于 "at me" reply { At(sender) }
"你好" reply "你好!"
"grouplist" reply {
//"https://ssl.ptlogin2.qq.com/jump?pt_clientver=5509&pt_src=1&keyindex=9&clientuin=" + bot.qqAccount + "&clientkey=" + com.tick_tock.pctim.utils.Util.byte2HexString(
// user.txprotocol.serviceTicketHttp
//).replace(" ", "").toString() + "&u1=http%3A%2F%2Fqun.qq.com%2Fmember.html%23gid%3D168209441"
}
"xml" reply {
val template =
"""
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
<msg templateID='1' serviceID='1' action='plugin' actionData='ACTION_LINK' brief='BRIEF' flag='3' url=''>
<item bg='0' layout='4'>
<picture cover='TITLE_PICTURE_LINK'/>
<title size='30' color='#fc7299'>TITLE</title>
</item>
<item>
<summary color='#fc7299'>CONTENT</summary>
<picture cover='CONTENT_PICTURE_LINK'/>
</item>
<source name='ExHentai' icon='ExHentai'/>
</msg>
""".trimIndent()
buildXMLMessage {
item {
picture("http://img.mamoe.net/2019/12/03/be35ccb489ecb.jpg")
title("This is title")
}
item {
summary("This is a summary colored #66CCFF", color = "#66CCFF")
picture("http://img.mamoe.net/2019/12/03/74c8614c4a161.jpg")
}
source("Mirai", "http://img.mamoe.net/2019/12/03/02eea0f6e826a.png")
}.reply()
}
(contains("1") and has<Image>()){
reply("Your message has a string \"1\" and an image contained")
}
has<Image> {
if (this is FriendMessage || (this is GroupMessage && this.permission == MemberPermission.ADMINISTRATOR)) withContext(IO) {
val image: Image by message
// 等同于 val image = message[Image]
try {
image.downloadTo(newTestTempFile(suffix = ".png").also { reply("Temp file: ${it.absolutePath}") })
reply(image.imageId + " downloaded")
} catch (e: Exception) {
e.printStackTrace()
reply(e.message ?: e::class.java.simpleName)
}
}
}
startsWith("上传图片", removePrefix = true) handler@{
val file = File(FileSystemView.getFileSystemView().homeDirectory, it)
if (!file.exists()) {
reply("图片不存在")
return@handler
}
reply("sent")
file.sendAsImageTo(subject)
}
startsWith("色图", removePrefix = true) {
repeat(it.toIntOrNull() ?: 1) {
GlobalScope.launch {
delay(Random.Default.nextLong(100, 1000))
Gentlemen.provide(subject).receive().image.await().send()
}
}
}
startsWith("不够色", removePrefix = true) {
repeat(it.toIntOrNull() ?: 1) {
GlobalScope.launch {
delay(Random.Default.nextLong(100, 1000))
Gentlemen.provide(subject).receive().seImage.await().send()
}
}
}
startsWith("添加好友", removePrefix = true) {
reply(bot.addFriend(it.toLong()).toString())
}
}
bot.join()//等到直到断开连接
}
private fun newTestTempFile(filename: String = "${UUID.randomUUID()}", suffix: String = ".tmp"): File =
File(System.getProperty("user.dir"), filename + suffix).also { it.createNewFile(); it.deleteOnExit() }
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<p><a href="http://www.kyotoanimation.co.jp/">京都动画</a>作品<a
href="https://www.bilibili.com/bangumi/media/md3365/?from=search&seid=14448313700764690387">《境界的彼方》</a><a
href="https://zh.moegirl.org/zh-hans/%E6%A0%97%E5%B1%B1%E6%9C%AA%E6%9D%A5">栗山未来(Kuriyama <b>Mirai</b>)</a></p>
<p><a href="https://www.crypton.co.jp/miku_eng">初音未来</a>的线下创作文化活动<a href="https://magicalmirai.com/2019/index_en.html">(Magical
<b>Mirai</b>)</a></p>
</body>
</html>
\ No newline at end of file
apply plugin: "java"
apply plugin: "kotlin"
dependencies {
runtimeOnly files("../../mirai-core/build/classes/kotlin/jvm/main") // IDE bug
runtimeOnly files("../../mirai-core-qqandroid/build/classes/kotlin/jvm/main") // IDE bug
implementation project(":mirai-core-qqandroid")
implementation project(":mirai-japt")
}
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
compileJava.options.encoding = 'UTF-8'
compileTestJava.options.encoding = 'UTF-8'
\ No newline at end of file
package demo;
import net.mamoe.mirai.japt.BlockingBot;
import net.mamoe.mirai.japt.BlockingContacts;
import net.mamoe.mirai.japt.BlockingQQ;
import net.mamoe.mirai.japt.Events;
import net.mamoe.mirai.message.GroupMessage;
import net.mamoe.mirai.message.data.At;
import net.mamoe.mirai.message.data.Image;
import net.mamoe.mirai.message.data.MessageUtils;
import net.mamoe.mirai.utils.BotConfiguration;
import net.mamoe.mirai.utils.SystemDeviceInfoKt;
import java.io.File;
class BlockingTest {
public static void main(String[] args) throws InterruptedException {
// 使用自定义的配置
BlockingBot bot = BlockingBot.newInstance(123456, "", new BotConfiguration() {
{
setDeviceInfo(context ->
SystemDeviceInfoKt.loadAsDeviceInfo(new File("deviceInfo.json"), context)
);
setHeartbeatPeriodMillis(50 * 1000);
}
});
// 使用默认的配置
// BlockingBot bot = BlockingBot.newInstance(123456, "");
bot.login();
bot.getFriendList().forEach(friend -> {
System.out.println(friend.getNick());
});
Events.subscribeAlways(GroupMessage.class, (GroupMessage message) -> {
final BlockingQQ sender = BlockingContacts.createBlocking(message.getSender());
sender.sendMessage("Hello World!");
System.out.println("发送完了");
sender.sendMessage(MessageUtils.newChain()
.plus(new At(message.getSender()))
.plus(Image.fromId("{xxxx}.jpg"))
.plus("123465")
);
});
Thread.sleep(999999999);
}
}
......@@ -21,34 +21,14 @@ pluginManagement {
rootProject.name = 'mirai'
include(':mirai-demos')
try {
def keyProps = new Properties()
def keyFile = file("local.properties")
if (keyFile.exists()) keyFile.withInputStream { keyProps.load(it) }
if (!keyProps.getProperty("sdk.dir", "").isEmpty()) {
include(':mirai-demos:mirai-demo-android')
project(':mirai-demos:mirai-demo-android').projectDir = file('mirai-demos/mirai-demo-android')
} else {
println("Android SDK 可能未安装. \n将不会加载模块 `mirai-demo-android`, 但这并不影响其他 demo 的加载 ")
println("Android SDK might not be installed. \nModule `mirai-demo-android` will not be included, but other demos will not be influenced")
}
} catch (Exception e) {
e.printStackTrace()
}
include(':mirai-core')
//include(':mirai-core-timpc')
include(':mirai-core-qqandroid')
//include(':mirai-api')
include(':mirai-api-http')
include(':mirai-demos:mirai-demo-1')
include(':mirai-demos:mirai-demo-gentleman')
include(':mirai-demos:mirai-demo-java')
include(':mirai-plugins')
include(':mirai-plugins:image-sender')
//include(':mirai-plugins')
//include(':mirai-plugins:image-sender')
try{
def javaVersion = System.getProperty("java.version")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment