mirror of
https://github.com/mamoe/mirai.git
synced 2025-03-26 07:20:09 +08:00
Add Java-friendly APIs for Contacts
This commit is contained in:
parent
422d84d150
commit
b7fa7adf99
mirai-core-qqandroid/src/commonMain/kotlin/net/mamoe/mirai/qqandroid
mirai-core/src
androidMain/kotlin/net/mamoe/mirai
commonMain/kotlin/net.mamoe.mirai
jvmMain/kotlin/net/mamoe/mirai
@ -35,22 +35,6 @@ import net.mamoe.mirai.utils.io.toUHexString
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo
|
||||
|
||||
internal abstract class ContactImpl : Contact {
|
||||
override fun hashCode(): Int {
|
||||
var result = bot.hashCode()
|
||||
result = 31 * result + id.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@Suppress("DuplicatedCode")
|
||||
if (this === other) return true
|
||||
if (other !is Contact) return false
|
||||
if (this::class != other::class) return false
|
||||
return this.id == other.id && this.bot == other.bot
|
||||
}
|
||||
}
|
||||
|
||||
internal inline class FriendInfoImpl(
|
||||
private val jceFriendInfo: JceFriendInfo
|
||||
) : FriendInfo {
|
||||
@ -63,7 +47,7 @@ internal class QQImpl(
|
||||
override val coroutineContext: CoroutineContext,
|
||||
override val id: Long,
|
||||
private val friendInfo: FriendInfo
|
||||
) : ContactImpl(), QQ {
|
||||
) : QQ() {
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
override val nick: String
|
||||
get() = friendInfo.nick
|
||||
@ -159,7 +143,21 @@ internal class QQImpl(
|
||||
}
|
||||
} finally {
|
||||
(image.input as? Closeable)?.close()
|
||||
(image.input as? io.ktor.utils.io.core.Closeable)?.close()
|
||||
(image.input as? Closeable)?.close()
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = bot.hashCode()
|
||||
result = 31 * result + id.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@Suppress("DuplicatedCode")
|
||||
if (this === other) return true
|
||||
if (other !is Contact) return false
|
||||
if (this::class != other::class) return false
|
||||
return this.id == other.id && this.bot == other.bot
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
@ -187,10 +185,26 @@ internal class MemberImpl(
|
||||
group: GroupImpl,
|
||||
override val coroutineContext: CoroutineContext,
|
||||
memberInfo: MemberInfo
|
||||
) : ContactImpl(), Member, QQ by qq {
|
||||
) : Member() {
|
||||
override val group: GroupImpl by group.unsafeWeakRef()
|
||||
val qq: QQImpl by qq.unsafeWeakRef()
|
||||
|
||||
// region QQ delegate
|
||||
override val id: Long = qq.id
|
||||
override val nick: String = qq.nick
|
||||
@MiraiExperimentalAPI
|
||||
override suspend fun queryProfile(): Profile = qq.queryProfile()
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
override suspend fun queryPreviousNameList(): PreviousNameList = qq.queryPreviousNameList()
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
override suspend fun queryRemark(): FriendNameRemark = qq.queryRemark()
|
||||
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> = qq.sendMessage(message)
|
||||
override suspend fun uploadImage(image: ExternalImage): Image = qq.uploadImage(image)
|
||||
// endregion
|
||||
|
||||
override var permission: MemberPermission = memberInfo.permission
|
||||
@Suppress("PropertyName")
|
||||
internal var _nameCard: String = memberInfo.nameCard
|
||||
@ -307,10 +321,9 @@ internal class MemberImpl(
|
||||
return result
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
override fun equals(other: Any?): Boolean { // 不要删除. trust me
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is Member) return false
|
||||
if (other !is Contact) return false
|
||||
if (this::class != other::class) return false
|
||||
return this.id == other.id && this.bot == other.bot
|
||||
}
|
||||
@ -347,7 +360,7 @@ internal class GroupImpl(
|
||||
override val id: Long,
|
||||
groupInfo: GroupInfo,
|
||||
members: Sequence<MemberInfo>
|
||||
) : ContactImpl(), Group {
|
||||
) : Group() {
|
||||
override val bot: QQAndroidBot by bot.unsafeWeakRef()
|
||||
val uin: Long = groupInfo.uin
|
||||
|
||||
@ -398,11 +411,11 @@ internal class GroupImpl(
|
||||
}.toLockFreeLinkedList())
|
||||
|
||||
internal var _name: String = groupInfo.name
|
||||
internal var _announcement: String = groupInfo.memo
|
||||
internal var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite
|
||||
private var _announcement: String = groupInfo.memo
|
||||
private var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite
|
||||
internal var _confessTalk: Boolean = groupInfo.confessTalk
|
||||
internal var _muteAll: Boolean = groupInfo.muteAll
|
||||
internal var _autoApprove: Boolean = groupInfo.autoApprove
|
||||
private var _autoApprove: Boolean = groupInfo.autoApprove
|
||||
internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat
|
||||
|
||||
override var name: String
|
||||
@ -666,10 +679,23 @@ internal class GroupImpl(
|
||||
}
|
||||
} finally {
|
||||
(image.input as? Closeable)?.close()
|
||||
(image.input as? io.ktor.utils.io.core.Closeable)?.close()
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Group($id)"
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = bot.hashCode()
|
||||
result = 31 * result + id.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@Suppress("DuplicatedCode", "DuplicatedCode")
|
||||
if (this === other) return true
|
||||
if (other !is Contact) return false
|
||||
if (this::class != other::class) return false
|
||||
return this.id == other.id && this.bot == other.bot
|
||||
}
|
||||
}
|
@ -169,9 +169,9 @@ actual abstract class BotJavaHappyAPI actual constructor() {
|
||||
}
|
||||
}
|
||||
|
||||
// !!! 不要 crossinline, 会编译失败
|
||||
// !! 不要 crossinline, 会编译失败
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
private fun <R> Bot.future(block: suspend Bot.() -> R): Future<R> {
|
||||
internal fun <R, C : CoroutineScope> C.future(block: suspend C.() -> R): Future<R> {
|
||||
val future = object : Future<R> {
|
||||
val value: CompletableDeferred<R> = CompletableDeferred()
|
||||
|
||||
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.BeforeImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.id
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
|
||||
|
||||
/**
|
||||
* 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group].
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
|
||||
actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() {
|
||||
/**
|
||||
* 这个联系人所属 [Bot].
|
||||
*/
|
||||
@WeakRefProperty
|
||||
actual abstract val bot: Bot
|
||||
/**
|
||||
* 可以是 QQ 号码或者群号码.
|
||||
*
|
||||
* 对于 [QQ], `uin` 与 `id` 是相同的意思.
|
||||
* 对于 [Group], `groupCode` 与 `id` 是相同的意思.
|
||||
*
|
||||
* @see QQ.id
|
||||
* @see Group.id
|
||||
*/
|
||||
actual abstract val id: Long
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*/
|
||||
actual abstract suspend fun sendMessage(message: MessageChain): MessageReceipt<out Contact>
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
|
||||
*/
|
||||
actual abstract suspend fun uploadImage(image: ExternalImage): Image
|
||||
|
||||
/**
|
||||
* 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同.
|
||||
*
|
||||
* 注:
|
||||
* [id] 相同的 [Member] 和 [QQ], 他们并不 [equals].
|
||||
* 因为, [Member] 含义为群员, 必属于一个群.
|
||||
* 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人.
|
||||
*/
|
||||
actual abstract override fun equals(other: Any?): Boolean
|
||||
|
||||
/**
|
||||
* @return `bot.hashCode() * 31 + id.hashCode()`
|
||||
*/
|
||||
actual abstract override fun hashCode(): Int
|
||||
|
||||
/**
|
||||
* @return "QQ($id)" or "Group($id)" or "Member($id)"
|
||||
*/
|
||||
actual abstract override fun toString(): String
|
||||
}
|
@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import io.ktor.utils.io.core.Input
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.future
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.uploadImage
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.util.concurrent.Future
|
||||
|
||||
@MiraiInternalAPI
|
||||
@JavaHappyAPI
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused")
|
||||
actual abstract class ContactJavaHappyAPI {
|
||||
|
||||
private inline fun <R> runBlocking(crossinline block: suspend Contact.() -> R): R {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return kotlinx.coroutines.runBlocking { block(this@ContactJavaHappyAPI as Contact) }
|
||||
}
|
||||
|
||||
private inline fun <R> future(crossinline block: suspend Contact.() -> R): Future<R> {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return (this as Contact).run { future { block() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*/
|
||||
@Throws(EventCancelledException::class, IllegalStateException::class)
|
||||
@JvmName("sendMessage")
|
||||
open fun __sendMessageBlockingForJava__(message: Message) {
|
||||
runBlocking { sendMessage(message) }
|
||||
}
|
||||
|
||||
@JvmName("sendMessage")
|
||||
open fun __sendMessageBlockingForJava__(message: String) {
|
||||
runBlocking { sendMessage(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: ExternalImage) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: URL) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: InputStream) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: Input) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: File) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: Bitmap) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @see Contact.sendMessage
|
||||
*/
|
||||
@JvmName("sendMessageAsync")
|
||||
open fun __sendMessageAsyncForJava__(message: Message): Future<MessageReceipt<Contact>> {
|
||||
return future { sendMessage(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @see Contact.sendMessage
|
||||
*/
|
||||
@JvmName("sendMessageAsync")
|
||||
open fun __sendMessageAsyncForJava__(message: String): Future<MessageReceipt<Contact>> {
|
||||
return future { sendMessage(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: ExternalImage): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: URL): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: InputStream): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: Input): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: File): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: Bitmap): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused", "unused")
|
||||
@MiraiInternalAPI
|
||||
@JavaHappyAPI
|
||||
actual abstract class MemberJavaHappyAPI : QQ() {
|
||||
private inline fun <R> runBlocking(crossinline block: suspend Member.() -> R): R {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return kotlinx.coroutines.runBlocking { block(this@MemberJavaHappyAPI as Member) }
|
||||
}
|
||||
|
||||
private inline fun <R> future(crossinline block: suspend Member.() -> R): Future<R> {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return (this as Member).run { future { block() } }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 禁言.
|
||||
*
|
||||
* QQ 中最小操作和显示的时间都是一分钟.
|
||||
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
|
||||
*
|
||||
* 管理员可禁言成员, 群主可禁言管理员和群员.
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("mute")
|
||||
open fun __muteBlockingForJava__(durationSeconds: Int) {
|
||||
runBlocking { mute(durationSeconds) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除禁言.
|
||||
*
|
||||
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("unmute")
|
||||
open fun __unmuteBlockingForJava__() {
|
||||
runBlocking { unmute() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kick")
|
||||
open fun __kickBlockingForJava__(message: String) {
|
||||
runBlocking { kick() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kick")
|
||||
open fun __kickBlockingForJava__() = __kickBlockingForJava__("")
|
||||
|
||||
|
||||
/**
|
||||
* 禁言.
|
||||
*
|
||||
* QQ 中最小操作和显示的时间都是一分钟.
|
||||
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
|
||||
*
|
||||
* 管理员可禁言成员, 群主可禁言管理员和群员.
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("muteAsync")
|
||||
open fun __muteAsyncForJava__(durationSeconds: Int): Future<Unit> {
|
||||
return future { mute(durationSeconds) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除禁言.
|
||||
*
|
||||
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("unmuteAsync")
|
||||
open fun __unmuteAsyncForJava__(): Future<Unit> {
|
||||
return future { unmute() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kickAsync")
|
||||
open fun __kickAsyncForJava__(message: String): Future<Unit> {
|
||||
return future { kick() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kickAsync")
|
||||
open fun __kickAsyncForJava__(): Future<Unit> = __kickAsyncForJava__("")
|
||||
}
|
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
* 群. 在 QQ Android 中叫做 "Troop"
|
||||
*/
|
||||
actual abstract class Group : Contact(), CoroutineScope {
|
||||
/**
|
||||
* 群名称.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
* 频繁修改可能会被服务器拒绝.
|
||||
*
|
||||
* @see MemberPermissionChangeEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var name: String
|
||||
/**
|
||||
* 入群公告, 没有时为空字符串.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see GroupEntranceAnnouncementChangeEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var entranceAnnouncement: String
|
||||
/**
|
||||
* 全体禁言状态. `true` 为开启.
|
||||
*
|
||||
* 当前仅能修改状态.
|
||||
*
|
||||
* @see GroupMuteAllEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var isMuteAll: Boolean
|
||||
/**
|
||||
* 坦白说状态. `true` 为允许.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see GroupAllowConfessTalkEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var isConfessTalkEnabled: Boolean
|
||||
/**
|
||||
* 允许群员邀请好友入群的状态. `true` 为允许
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see GroupAllowMemberInviteEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var isAllowMemberInvite: Boolean
|
||||
/**
|
||||
* 自动加群审批
|
||||
*/
|
||||
actual abstract val isAutoApproveEnabled: Boolean
|
||||
/**
|
||||
* 匿名聊天
|
||||
*/
|
||||
actual abstract val isAnonymousChatEnabled: Boolean
|
||||
/**
|
||||
* 同为 groupCode, 用户看到的群号码.
|
||||
*/
|
||||
actual abstract override val id: Long
|
||||
/**
|
||||
* 群主.
|
||||
*
|
||||
* @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员
|
||||
*/
|
||||
actual abstract val owner: Member
|
||||
/**
|
||||
* [Bot] 在群内的 [Member] 实例
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
actual abstract val botAsMember: Member
|
||||
/**
|
||||
* 机器人被禁言还剩余多少秒
|
||||
*
|
||||
* @see BotMuteEvent 机器人被禁言事件
|
||||
* @see isBotMuted 判断机器人是否正在被禁言
|
||||
*/
|
||||
actual abstract val botMuteRemaining: Int
|
||||
/**
|
||||
* 机器人在这个群里的权限
|
||||
*
|
||||
* @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限
|
||||
* @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator]
|
||||
*
|
||||
* @see BotGroupPermissionChangeEvent 机器人群员修改
|
||||
*/
|
||||
actual abstract val botPermission: MemberPermission
|
||||
/**
|
||||
* 群头像下载链接.
|
||||
*/
|
||||
actual val avatarUrl: String
|
||||
get() = "https://p.qlogo.cn/gh/$id/${id}_1/640"
|
||||
|
||||
/**
|
||||
* 群成员列表, 不含机器人自己, 含群主.
|
||||
* 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新
|
||||
*/
|
||||
actual abstract val members: ContactList<Member>
|
||||
|
||||
/**
|
||||
* 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException]
|
||||
*/
|
||||
actual abstract operator fun get(id: Long): Member
|
||||
|
||||
/**
|
||||
* 获取群成员实例, 不存在则 null
|
||||
*/
|
||||
actual abstract fun getOrNull(id: Long): Member?
|
||||
|
||||
/**
|
||||
* 检查此 id 的群成员是否存在
|
||||
*/
|
||||
actual abstract operator fun contains(id: Long): Boolean
|
||||
|
||||
/**
|
||||
* 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun quit(): Boolean
|
||||
|
||||
/**
|
||||
* 构造一个 [Member].
|
||||
* 非特殊情况请不要使用这个函数. 优先使用 [get].
|
||||
*/
|
||||
@JvmName("newMember")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName")
|
||||
@MiraiExperimentalAPI("dangerous")
|
||||
actual abstract fun Member(memberInfo: MemberInfo): Member
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group>
|
||||
|
||||
actual companion object {
|
||||
/**
|
||||
* by @kar98k
|
||||
*/
|
||||
actual fun calculateGroupUinByGroupCode(groupCode: Long): Long {
|
||||
var left: Long = groupCode / 1000000L
|
||||
|
||||
when (left) {
|
||||
in 0..10 -> left += 202
|
||||
in 11..19 -> left += 480 - 11
|
||||
in 20..66 -> left += 2100 - 20
|
||||
in 67..156 -> left += 2010 - 67
|
||||
in 157..209 -> left += 2147 - 157
|
||||
in 210..309 -> left += 4100 - 210
|
||||
in 310..499 -> left += 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupCode % 1000000L
|
||||
}
|
||||
|
||||
actual fun calculateGroupCodeByGroupUin(groupUin: Long): Long {
|
||||
var left: Long = groupUin / 1000000L
|
||||
|
||||
when (left) {
|
||||
in 0 + 202..10 + 202 -> left -= 202
|
||||
in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11
|
||||
in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20
|
||||
in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67
|
||||
in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157
|
||||
in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210
|
||||
in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupUin % 1000000L
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
actual fun toFullString(): String {
|
||||
return "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
|
||||
/**
|
||||
* 群成员.
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
|
||||
actual abstract class Member : MemberJavaHappyAPI() {
|
||||
/**
|
||||
* 所在的群.
|
||||
*/
|
||||
@WeakRefProperty
|
||||
actual abstract val group: Group
|
||||
/**
|
||||
* 成员的权限, 动态更新.
|
||||
*
|
||||
* @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发.
|
||||
*/
|
||||
actual abstract val permission: MemberPermission
|
||||
/**
|
||||
* 群名片. 可能为空.
|
||||
*
|
||||
* 管理员和群主都可修改任何人(包括群主)的群名片.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see [nameCardOrNick] 获取非空群名片或昵称
|
||||
*
|
||||
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
actual abstract var nameCard: String
|
||||
/**
|
||||
* 群头衔.
|
||||
*
|
||||
* 仅群主可以修改群头衔.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
actual abstract var specialTitle: String
|
||||
/**
|
||||
* 被禁言剩余时长. 单位为秒.
|
||||
*
|
||||
* @see isMuted 判断改成员是否处于禁言状态
|
||||
* @see mute 设置禁言
|
||||
* @see unmute 取消禁言
|
||||
*/
|
||||
actual abstract val muteTimeRemaining: Int
|
||||
|
||||
/**
|
||||
* 禁言.
|
||||
*
|
||||
* QQ 中最小操作和显示的时间都是一分钟.
|
||||
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
|
||||
*
|
||||
* 管理员可禁言成员, 群主可禁言管理员和群员.
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("muteSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun mute(durationSeconds: Int)
|
||||
|
||||
/**
|
||||
* 解除禁言.
|
||||
*
|
||||
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("unmuteSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun unmute()
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kickSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun kick(message: String)
|
||||
|
||||
/**
|
||||
* 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true
|
||||
*/
|
||||
actual abstract override fun equals(other: Any?): Boolean
|
||||
|
||||
/**
|
||||
* @return `bot.hashCode() * 31 + id.hashCode()`
|
||||
*/
|
||||
actual abstract override fun hashCode(): Int
|
||||
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.FriendNameRemark
|
||||
import net.mamoe.mirai.data.PreviousNameList
|
||||
import net.mamoe.mirai.data.Profile
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
* QQ 对象.
|
||||
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
|
||||
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
|
||||
*
|
||||
* 对于同一个 [Bot] 任何一个人的 [QQ] 实例都是单一的.
|
||||
*
|
||||
* A QQ instance helps you to receive event from or sendPacket event to.
|
||||
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
actual abstract class QQ : Contact(), CoroutineScope {
|
||||
/**
|
||||
* 请求头像下载链接
|
||||
*/
|
||||
// @MiraiExperimentalAPI
|
||||
//suspend fun queryAvatar(): AvatarLink
|
||||
/**
|
||||
* QQ 号码
|
||||
*/
|
||||
actual abstract override val id: Long
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
actual abstract val nick: String
|
||||
|
||||
/**
|
||||
* 查询用户资料
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun queryProfile(): Profile
|
||||
|
||||
/**
|
||||
* 头像下载链接
|
||||
*/
|
||||
actual val avatarUrl: String
|
||||
get() = "http://q1.qlogo.cn/g?b=qq&nk=$id&s=640"
|
||||
|
||||
/**
|
||||
* 查询曾用名.
|
||||
*
|
||||
* 曾用名可能是:
|
||||
* - 昵称
|
||||
* - 共同群内的群名片
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun queryPreviousNameList(): PreviousNameList
|
||||
|
||||
/**
|
||||
* 查询机器人账号给这个人设置的备注
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun queryRemark(): FriendNameRemark
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
@JvmName("sendMessageSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ>
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.message
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import io.ktor.utils.io.core.Input
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import net.mamoe.mirai.utils.sendTo
|
||||
import net.mamoe.mirai.utils.toExternalImage
|
||||
import net.mamoe.mirai.utils.upload
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
|
||||
/*
|
||||
* 发送图片的一些扩展函数.
|
||||
*/
|
||||
|
||||
// region IMAGE.sendAsImageTo(Contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片发送到指定联系人. 不会创建临时文件
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun Bitmap.sendTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun URL.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun Input.sendAsImageTo(contact: Contact) = withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun InputStream.sendAsImageTo(contact: Contact) =
|
||||
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun File.sendAsImageTo(contact: Contact) {
|
||||
require(this.exists() && this.canRead())
|
||||
withContext(Dispatchers.IO) { toExternalImage() }.sendTo(contact)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region IMAGE.Upload(Contact): Image
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片上传后构造 [Image]. 不会创建临时文件
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun Bitmap.upload(contact: Contact): Image = withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传后构造 [Image]
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun URL.uploadAsImage(contact: Contact): Image =
|
||||
withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传后构造 [Image]
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun Input.uploadAsImage(contact: Contact): Image =
|
||||
withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传后构造 [Image]
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun InputStream.uploadAsImage(contact: Contact): Image =
|
||||
withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片上传后构造 [Image]
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend fun File.uploadAsImage(contact: Contact): Image {
|
||||
require(this.exists() && this.canRead())
|
||||
return withContext(Dispatchers.IO) { toExternalImage() }.upload(contact)
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Contact.sendImage(IMAGE)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片发送到指定联系人. 不会保存临时文件
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.sendImage(bufferedImage: Bitmap) = bufferedImage.sendTo(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.sendImage(imageUrl: URL) = imageUrl.sendAsImageTo(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.sendImage(imageInput: Input) = imageInput.sendAsImageTo(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.sendImage(imageStream: InputStream) = imageStream.sendAsImageTo(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this)
|
||||
|
||||
// endregion
|
||||
|
||||
// region Contact.uploadImage(IMAGE)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(bufferedImage: Bitmap): Image = bufferedImage.upload(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(file: File): Image = file.uploadAsImage(this)
|
||||
|
||||
// endregion
|
@ -7,15 +7,16 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
|
||||
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import io.ktor.utils.io.ByteReadChannel
|
||||
import io.ktor.utils.io.core.Input
|
||||
import io.ktor.utils.io.core.copyTo
|
||||
import io.ktor.utils.io.errors.IOException
|
||||
import io.ktor.utils.io.streams.asInput
|
||||
import io.ktor.utils.io.streams.asOutput
|
||||
import kotlinx.coroutines.Dispatchers.IO
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -27,6 +28,17 @@ import java.net.URL
|
||||
* 将各类型图片容器转为 [ExternalImage]
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 读取 [BufferedImage] 的属性, 然后构造 [ExternalImage]
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun Bitmap.toExternalImage(formatName: String = "gif"): Nothing {
|
||||
TODO()
|
||||
}
|
||||
|
||||
// suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
|
||||
/**
|
||||
* 读取文件头识别图片属性, 然后构造 [ExternalImage]
|
||||
*/
|
||||
@ -48,8 +60,7 @@ fun File.toExternalImage(): ExternalImage {
|
||||
/**
|
||||
* 在 [IO] 中进行 [File.toExternalImage]
|
||||
*/
|
||||
@Suppress("unused")
|
||||
suspend fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
|
||||
/**
|
||||
* 下载文件到临时目录然后调用 [File.toExternalImage]
|
||||
@ -61,6 +72,7 @@ fun URL.toExternalImage(): ExternalImage {
|
||||
openStream().use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
output.flush()
|
||||
}
|
||||
return file.toExternalImage()
|
||||
}
|
||||
@ -68,8 +80,7 @@ fun URL.toExternalImage(): ExternalImage {
|
||||
/**
|
||||
* 在 [IO] 中进行 [URL.toExternalImage]
|
||||
*/
|
||||
@Suppress("unused")
|
||||
suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
|
||||
/**
|
||||
* 保存为临时文件然后调用 [File.toExternalImage]
|
||||
@ -77,8 +88,9 @@ suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toEx
|
||||
@Throws(IOException::class)
|
||||
fun InputStream.toExternalImage(): ExternalImage {
|
||||
val file = createTempFile().apply { deleteOnExit() }
|
||||
file.outputStream().asOutput().use {
|
||||
this.asInput().copyTo(it)
|
||||
file.outputStream().use {
|
||||
this.copyTo(it)
|
||||
it.flush()
|
||||
}
|
||||
this.close()
|
||||
return file.toExternalImage()
|
||||
@ -87,17 +99,19 @@ fun InputStream.toExternalImage(): ExternalImage {
|
||||
/**
|
||||
* 在 [IO] 中进行 [InputStream.toExternalImage]
|
||||
*/
|
||||
@Suppress("unused")
|
||||
suspend fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
|
||||
/**
|
||||
* 保存为临时文件然后调用 [File.toExternalImage]
|
||||
* 保存为临时文件然后调用 [File.toExternalImage].
|
||||
*
|
||||
* 需要函数调用者 close [this]
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun Input.toExternalImage(): ExternalImage {
|
||||
val file = createTempFile().apply { deleteOnExit() }
|
||||
file.outputStream().asOutput().use {
|
||||
this.copyTo(it)
|
||||
it.flush()
|
||||
}
|
||||
return file.toExternalImage()
|
||||
}
|
||||
@ -105,5 +119,17 @@ fun Input.toExternalImage(): ExternalImage {
|
||||
/**
|
||||
* 在 [IO] 中进行 [Input.toExternalImage]
|
||||
*/
|
||||
@Suppress("unused")
|
||||
suspend fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
|
||||
|
||||
/**
|
||||
* 保存为临时文件然后调用 [File.toExternalImage].
|
||||
*/
|
||||
suspend fun ByteReadChannel.toExternalImage(): ExternalImage {
|
||||
val file = createTempFile().apply { deleteOnExit() }
|
||||
file.outputStream().use {
|
||||
withContext(IO) { copyTo(it) }
|
||||
it.flush()
|
||||
}
|
||||
|
||||
return file.suspendToExternalImage()
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
/**
|
||||
* 图片文件过大
|
||||
*/
|
||||
actual class OverFileSizeMaxException : IllegalStateException()
|
@ -14,6 +14,7 @@ package net.mamoe.mirai.contact
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.BeforeImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
@ -23,25 +24,26 @@ import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.recall
|
||||
import net.mamoe.mirai.recallIn
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
|
||||
/**
|
||||
* 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group].
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
interface Contact : CoroutineScope {
|
||||
*/ // 不要删除多平台结构 !!! kotlin bug
|
||||
@UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
expect abstract class Contact() : CoroutineScope, ContactJavaHappyAPI {
|
||||
/**
|
||||
* 这个联系人所属 [Bot].
|
||||
*/
|
||||
@WeakRefProperty
|
||||
val bot: Bot
|
||||
abstract val bot: Bot
|
||||
|
||||
/**
|
||||
* 可以是 QQ 号码或者群号码.
|
||||
@ -52,7 +54,7 @@ interface Contact : CoroutineScope {
|
||||
* @see QQ.id
|
||||
* @see Group.id
|
||||
*/
|
||||
val id: Long
|
||||
abstract val id: Long
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
@ -65,11 +67,12 @@ interface Contact : CoroutineScope {
|
||||
*
|
||||
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*/
|
||||
suspend fun sendMessage(message: MessageChain): MessageReceipt<out Contact>
|
||||
@JvmName("sendMessageSuspend")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun sendMessage(message: MessageChain): MessageReceipt<out Contact>
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
* TODO 群图片与好友图片在服务器上是通用的, 在 mirai 目前不通用.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
@ -77,7 +80,9 @@ interface Contact : CoroutineScope {
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
|
||||
*/
|
||||
suspend fun uploadImage(image: ExternalImage): Image
|
||||
@JvmName("uploadImageSuspend")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun uploadImage(image: ExternalImage): Image
|
||||
|
||||
/**
|
||||
* 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同.
|
||||
@ -87,17 +92,17 @@ interface Contact : CoroutineScope {
|
||||
* 因为, [Member] 含义为群员, 必属于一个群.
|
||||
* 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人.
|
||||
*/
|
||||
override fun equals(other: Any?): Boolean
|
||||
abstract override fun equals(other: Any?): Boolean
|
||||
|
||||
/**
|
||||
* @return `bot.hashCode() * 31 + id.hashCode()`
|
||||
*/
|
||||
override fun hashCode(): Int
|
||||
abstract override fun hashCode(): Int
|
||||
|
||||
/**
|
||||
* @return "QQ($id)" or "Group($id)" or "Member($id)"
|
||||
*/
|
||||
override fun toString(): String
|
||||
abstract override fun toString(): String
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
@ -21,11 +21,13 @@ import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
* 群. 在 QQ Android 中叫做 "Troop"
|
||||
*/
|
||||
interface Group : Contact, CoroutineScope {
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
expect abstract class Group() : Contact, CoroutineScope {
|
||||
/**
|
||||
* 群名称.
|
||||
*
|
||||
@ -35,7 +37,7 @@ interface Group : Contact, CoroutineScope {
|
||||
* @see MemberPermissionChangeEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
var name: String
|
||||
abstract var name: String
|
||||
/**
|
||||
* 入群公告, 没有时为空字符串.
|
||||
*
|
||||
@ -44,7 +46,7 @@ interface Group : Contact, CoroutineScope {
|
||||
* @see GroupEntranceAnnouncementChangeEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
var entranceAnnouncement: String
|
||||
abstract var entranceAnnouncement: String
|
||||
/**
|
||||
* 全体禁言状态. `true` 为开启.
|
||||
*
|
||||
@ -53,7 +55,7 @@ interface Group : Contact, CoroutineScope {
|
||||
* @see GroupMuteAllEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
var isMuteAll: Boolean
|
||||
abstract var isMuteAll: Boolean
|
||||
/**
|
||||
* 坦白说状态. `true` 为允许.
|
||||
*
|
||||
@ -62,7 +64,7 @@ interface Group : Contact, CoroutineScope {
|
||||
* @see GroupAllowConfessTalkEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
var isConfessTalkEnabled: Boolean
|
||||
abstract var isConfessTalkEnabled: Boolean
|
||||
/**
|
||||
* 允许群员邀请好友入群的状态. `true` 为允许
|
||||
*
|
||||
@ -71,33 +73,33 @@ interface Group : Contact, CoroutineScope {
|
||||
* @see GroupAllowMemberInviteEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
var isAllowMemberInvite: Boolean
|
||||
abstract var isAllowMemberInvite: Boolean
|
||||
/**
|
||||
* 自动加群审批
|
||||
*/
|
||||
val isAutoApproveEnabled: Boolean
|
||||
abstract val isAutoApproveEnabled: Boolean
|
||||
/**
|
||||
* 匿名聊天
|
||||
*/
|
||||
val isAnonymousChatEnabled: Boolean
|
||||
abstract val isAnonymousChatEnabled: Boolean
|
||||
|
||||
/**
|
||||
* 同为 groupCode, 用户看到的群号码.
|
||||
*/
|
||||
override val id: Long
|
||||
abstract override val id: Long
|
||||
|
||||
/**
|
||||
* 群主.
|
||||
*
|
||||
* @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员
|
||||
*/
|
||||
val owner: Member
|
||||
abstract val owner: Member
|
||||
|
||||
/**
|
||||
* [Bot] 在群内的 [Member] 实例
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
val botAsMember: Member
|
||||
abstract val botAsMember: Member
|
||||
|
||||
/**
|
||||
* 机器人被禁言还剩余多少秒
|
||||
@ -105,7 +107,7 @@ interface Group : Contact, CoroutineScope {
|
||||
* @see BotMuteEvent 机器人被禁言事件
|
||||
* @see isBotMuted 判断机器人是否正在被禁言
|
||||
*/
|
||||
val botMuteRemaining: Int
|
||||
abstract val botMuteRemaining: Int
|
||||
|
||||
/**
|
||||
* 机器人在这个群里的权限
|
||||
@ -115,39 +117,41 @@ interface Group : Contact, CoroutineScope {
|
||||
*
|
||||
* @see BotGroupPermissionChangeEvent 机器人群员修改
|
||||
*/
|
||||
val botPermission: MemberPermission
|
||||
abstract val botPermission: MemberPermission
|
||||
|
||||
/**
|
||||
* 群头像下载链接.
|
||||
*/
|
||||
val avatarUrl: String get() = "https://p.qlogo.cn/gh/$id/${id}_1/640"
|
||||
val avatarUrl: String
|
||||
|
||||
/**
|
||||
* 群成员列表, 不含机器人自己, 含群主.
|
||||
* 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新
|
||||
*/
|
||||
val members: ContactList<Member>
|
||||
abstract val members: ContactList<Member>
|
||||
|
||||
/**
|
||||
* 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException]
|
||||
*/
|
||||
operator fun get(id: Long): Member
|
||||
abstract operator fun get(id: Long): Member
|
||||
|
||||
/**
|
||||
* 获取群成员实例, 不存在则 null
|
||||
*/
|
||||
fun getOrNull(id: Long): Member?
|
||||
abstract fun getOrNull(id: Long): Member?
|
||||
|
||||
/**
|
||||
* 检查此 id 的群成员是否存在
|
||||
*/
|
||||
operator fun contains(id: Long): Boolean
|
||||
abstract operator fun contains(id: Long): Boolean
|
||||
|
||||
/**
|
||||
* 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败
|
||||
*/
|
||||
@JvmName("quitSuspend")
|
||||
@JvmSynthetic
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun quit(): Boolean
|
||||
abstract suspend fun quit(): Boolean
|
||||
|
||||
/**
|
||||
* 构造一个 [Member].
|
||||
@ -156,7 +160,7 @@ interface Group : Contact, CoroutineScope {
|
||||
@MiraiExperimentalAPI("dangerous")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName")
|
||||
@JvmName("newMember")
|
||||
fun Member(memberInfo: MemberInfo): Member
|
||||
abstract fun Member(memberInfo: MemberInfo): Member
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
@ -169,48 +173,19 @@ interface Group : Contact, CoroutineScope {
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group>
|
||||
@JvmName("sendMessageSuspend")
|
||||
@JvmSynthetic
|
||||
abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group>
|
||||
|
||||
companion object {
|
||||
// don't @JvmStatic: JDK 1.8 required
|
||||
fun calculateGroupUinByGroupCode(groupCode: Long): Long
|
||||
|
||||
/**
|
||||
* by @kar98k
|
||||
*/ // don't @JvmStatic: JDK 1.8 required
|
||||
fun calculateGroupUinByGroupCode(groupCode: Long): Long {
|
||||
var left: Long = groupCode / 1000000L
|
||||
|
||||
when (left) {
|
||||
in 0..10 -> left += 202
|
||||
in 11..19 -> left += 480 - 11
|
||||
in 20..66 -> left += 2100 - 20
|
||||
in 67..156 -> left += 2010 - 67
|
||||
in 157..209 -> left += 2147 - 157
|
||||
in 210..309 -> left += 4100 - 210
|
||||
in 310..499 -> left += 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupCode % 1000000L
|
||||
}
|
||||
|
||||
fun calculateGroupCodeByGroupUin(groupUin: Long): Long {
|
||||
var left: Long = groupUin / 1000000L
|
||||
|
||||
when (left) {
|
||||
in 0 + 202..10 + 202 -> left -= 202
|
||||
in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11
|
||||
in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20
|
||||
in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67
|
||||
in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157
|
||||
in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210
|
||||
in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupUin % 1000000L
|
||||
}
|
||||
fun calculateGroupCodeByGroupUin(groupUin: Long): Long
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
|
||||
fun toFullString(): String
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -12,27 +12,33 @@
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.ExperimentalTime
|
||||
|
||||
/**
|
||||
* 群成员.
|
||||
*/
|
||||
interface Member : QQ, Contact {
|
||||
*/ // 不要删除多平台结构, kotlin bug
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
|
||||
expect abstract class Member() : MemberJavaHappyAPI {
|
||||
/**
|
||||
* 所在的群.
|
||||
*/
|
||||
@WeakRefProperty
|
||||
val group: Group
|
||||
abstract val group: Group
|
||||
|
||||
/**
|
||||
* 成员的权限, 动态更新.
|
||||
*
|
||||
* @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发.
|
||||
*/
|
||||
val permission: MemberPermission
|
||||
abstract val permission: MemberPermission
|
||||
|
||||
/**
|
||||
* 群名片. 可能为空.
|
||||
@ -46,7 +52,7 @@ interface Member : QQ, Contact {
|
||||
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
var nameCard: String
|
||||
abstract var nameCard: String
|
||||
|
||||
/**
|
||||
* 群头衔.
|
||||
@ -58,7 +64,7 @@ interface Member : QQ, Contact {
|
||||
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
var specialTitle: String
|
||||
abstract var specialTitle: String
|
||||
|
||||
/**
|
||||
* 被禁言剩余时长. 单位为秒.
|
||||
@ -67,7 +73,7 @@ interface Member : QQ, Contact {
|
||||
* @see mute 设置禁言
|
||||
* @see unmute 取消禁言
|
||||
*/
|
||||
val muteTimeRemaining: Int
|
||||
abstract val muteTimeRemaining: Int
|
||||
|
||||
/**
|
||||
* 禁言.
|
||||
@ -87,7 +93,9 @@ interface Member : QQ, Contact {
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
suspend fun mute(durationSeconds: Int)
|
||||
@JvmName("muteSuspend")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun mute(durationSeconds: Int)
|
||||
|
||||
/**
|
||||
* 解除禁言.
|
||||
@ -97,7 +105,9 @@ interface Member : QQ, Contact {
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
suspend fun unmute()
|
||||
@JvmName("unmuteSuspend")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun unmute()
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
@ -107,12 +117,19 @@ interface Member : QQ, Contact {
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
suspend fun kick(message: String = "")
|
||||
@JvmName("kickSuspend")
|
||||
@JvmSynthetic
|
||||
abstract suspend fun kick(message: String = "")
|
||||
|
||||
/**
|
||||
* 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true
|
||||
*/
|
||||
override fun equals(other: Any?): Boolean
|
||||
abstract override fun equals(other: Any?): Boolean
|
||||
|
||||
/**
|
||||
* @return `bot.hashCode() * 31 + id.hashCode()`
|
||||
*/
|
||||
abstract override fun hashCode(): Int
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -7,7 +7,7 @@
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE")
|
||||
@file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
@ -22,6 +22,8 @@ import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
import kotlin.jvm.JvmName
|
||||
import kotlin.jvm.JvmSynthetic
|
||||
|
||||
/**
|
||||
* QQ 对象.
|
||||
@ -35,33 +37,32 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
interface QQ : Contact, CoroutineScope {
|
||||
/**
|
||||
* QQ 号码
|
||||
*/
|
||||
override val id: Long
|
||||
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
val nick: String
|
||||
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
expect abstract class QQ() : Contact, CoroutineScope {
|
||||
/**
|
||||
* 请求头像下载链接
|
||||
*/
|
||||
// @MiraiExperimentalAPI
|
||||
//suspend fun queryAvatar(): AvatarLink
|
||||
/**
|
||||
* QQ 号码
|
||||
*/
|
||||
abstract override val id: Long
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
abstract val nick: String
|
||||
|
||||
/**
|
||||
* 查询用户资料
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryProfile(): Profile
|
||||
abstract suspend fun queryProfile(): Profile
|
||||
|
||||
/**
|
||||
* 头像下载链接
|
||||
*/
|
||||
val avatarUrl: String get() = "http://q1.qlogo.cn/g?b=qq&nk=$id&s=640"
|
||||
val avatarUrl: String
|
||||
|
||||
/**
|
||||
* 查询曾用名.
|
||||
@ -71,13 +72,13 @@ interface QQ : Contact, CoroutineScope {
|
||||
* - 共同群内的群名片
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryPreviousNameList(): PreviousNameList
|
||||
abstract suspend fun queryPreviousNameList(): PreviousNameList
|
||||
|
||||
/**
|
||||
* 查询机器人账号给这个人设置的备注
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
suspend fun queryRemark(): FriendNameRemark
|
||||
abstract suspend fun queryRemark(): FriendNameRemark
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
@ -90,5 +91,9 @@ interface QQ : Contact, CoroutineScope {
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ>
|
||||
@JvmSynthetic
|
||||
@JvmName("sendMessageSuspend")
|
||||
abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ>
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
|
||||
/**
|
||||
* [Contact] 中为了让 `Java` 更容易调用的 API
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
@JavaHappyAPI
|
||||
expect abstract class ContactJavaHappyAPI
|
||||
|
||||
/**
|
||||
* [Member] 中为了让 `Java` 更容易调用的 API
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
@JavaHappyAPI
|
||||
expect abstract class MemberJavaHappyAPI : QQ
|
@ -16,7 +16,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
*/
|
||||
@MiraiInternalAPI
|
||||
@Experimental(level = Experimental.Level.ERROR)
|
||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.CLASS)
|
||||
annotation class JavaHappyAPI
|
||||
|
||||
/**
|
||||
|
@ -17,5 +17,5 @@ import kotlin.jvm.JvmName
|
||||
|
||||
/**
|
||||
* 图片文件过大
|
||||
*/
|
||||
class OverFileSizeMaxException : IllegalStateException()
|
||||
*/ // 不要删除多平台结构, 这是 kotlin 的 bug
|
||||
expect class OverFileSizeMaxException() : IllegalStateException
|
@ -170,7 +170,7 @@ actual abstract class BotJavaHappyAPI actual constructor() {
|
||||
}
|
||||
|
||||
@UseExperimental(ExperimentalCoroutinesApi::class)
|
||||
private fun <R> Bot.future(block: suspend Bot.() -> R): Future<R> {
|
||||
internal fun <R, C : CoroutineScope> C.future(block: suspend C.() -> R): Future<R> {
|
||||
val future = object : Future<R> {
|
||||
val value: CompletableDeferred<R> = CompletableDeferred()
|
||||
|
||||
|
100
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt
Normal file
100
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Contact.kt
Normal file
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.BeforeImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.ImageUploadEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.id
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
|
||||
/**
|
||||
* 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group].
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
@UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
|
||||
actual abstract class Contact : CoroutineScope, ContactJavaHappyAPI() {
|
||||
/**
|
||||
* 这个联系人所属 [Bot].
|
||||
*/
|
||||
@WeakRefProperty
|
||||
actual abstract val bot: Bot
|
||||
/**
|
||||
* 可以是 QQ 号码或者群号码.
|
||||
*
|
||||
* 对于 [QQ], `uin` 与 `id` 是相同的意思.
|
||||
* 对于 [Group], `groupCode` 与 `id` 是相同的意思.
|
||||
*
|
||||
* @see QQ.id
|
||||
* @see Group.id
|
||||
*/
|
||||
actual abstract val id: Long
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*/
|
||||
@JvmName("sendMessageSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun sendMessage(message: MessageChain): MessageReceipt<out Contact>
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
|
||||
*/
|
||||
@JvmName("uploadImageSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun uploadImage(image: ExternalImage): Image
|
||||
|
||||
/**
|
||||
* 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同.
|
||||
*
|
||||
* 注:
|
||||
* [id] 相同的 [Member] 和 [QQ], 他们并不 [equals].
|
||||
* 因为, [Member] 含义为群员, 必属于一个群.
|
||||
* 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人.
|
||||
*/
|
||||
actual abstract override fun equals(other: Any?): Boolean
|
||||
|
||||
/**
|
||||
* @return `bot.hashCode() * 31 + id.hashCode()`
|
||||
*/
|
||||
actual abstract override fun hashCode(): Int
|
||||
|
||||
/**
|
||||
* @return "QQ($id)" or "Group($id)" or "Member($id)"
|
||||
*/
|
||||
actual abstract override fun toString(): String
|
||||
}
|
@ -0,0 +1,340 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import io.ktor.utils.io.core.Input
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.future
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.uploadImage
|
||||
import net.mamoe.mirai.utils.ExternalImage
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.OverFileSizeMaxException
|
||||
import java.awt.image.BufferedImage
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.URL
|
||||
import java.util.concurrent.Future
|
||||
|
||||
@MiraiInternalAPI
|
||||
@JavaHappyAPI
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused")
|
||||
actual abstract class ContactJavaHappyAPI {
|
||||
|
||||
private inline fun <R> runBlocking(crossinline block: suspend Contact.() -> R): R {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return kotlinx.coroutines.runBlocking { block(this@ContactJavaHappyAPI as Contact) }
|
||||
}
|
||||
|
||||
private inline fun <R> future(crossinline block: suspend Contact.() -> R): Future<R> {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return (this as Contact).run { future { block() } }
|
||||
}
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息.
|
||||
*/
|
||||
@Throws(EventCancelledException::class, IllegalStateException::class)
|
||||
@JvmName("sendMessage")
|
||||
open fun __sendMessageBlockingForJava__(message: Message) {
|
||||
runBlocking { sendMessage(message) }
|
||||
}
|
||||
|
||||
@JvmName("sendMessage")
|
||||
open fun __sendMessageBlockingForJava__(message: String) {
|
||||
runBlocking { sendMessage(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: ExternalImage) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: URL) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: InputStream) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: Input) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: File) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
@JvmName("uploadImage")
|
||||
open fun __uploadImageBlockingForJava__(image: BufferedImage) {
|
||||
runBlocking { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @see Contact.sendMessage
|
||||
*/
|
||||
@JvmName("sendMessageAsync")
|
||||
open fun __sendMessageAsyncForJava__(message: Message): Future<MessageReceipt<Contact>> {
|
||||
return future { sendMessage(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @see Contact.sendMessage
|
||||
*/
|
||||
@JvmName("sendMessageAsync")
|
||||
open fun __sendMessageAsyncForJava__(message: String): Future<MessageReceipt<Contact>> {
|
||||
return future { sendMessage(message) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传一个图片以备发送.
|
||||
*
|
||||
* @see BeforeImageUploadEvent 图片发送前事件, cancellable
|
||||
* @see ImageUploadEvent 图片发送完成事件
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: ExternalImage): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: URL): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: InputStream): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: Input): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: File): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件
|
||||
*/
|
||||
@JvmName("uploadImageAsync")
|
||||
open fun __uploadImageAsyncForJava__(image: BufferedImage): Future<Image> {
|
||||
return future { uploadImage(image) }
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName", "unused", "unused")
|
||||
@MiraiInternalAPI
|
||||
@JavaHappyAPI
|
||||
actual abstract class MemberJavaHappyAPI : QQ() {
|
||||
private inline fun <R> runBlocking(crossinline block: suspend Member.() -> R): R {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return kotlinx.coroutines.runBlocking { block(this@MemberJavaHappyAPI as Member) }
|
||||
}
|
||||
|
||||
private inline fun <R> future(crossinline block: suspend Member.() -> R): Future<R> {
|
||||
@Suppress("CAST_NEVER_SUCCEEDS")
|
||||
return (this as Member).run { future { block() } }
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 禁言.
|
||||
*
|
||||
* QQ 中最小操作和显示的时间都是一分钟.
|
||||
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
|
||||
*
|
||||
* 管理员可禁言成员, 群主可禁言管理员和群员.
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("mute")
|
||||
open fun __muteBlockingForJava__(seconds: Int) {
|
||||
runBlocking { mute(seconds) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除禁言.
|
||||
*
|
||||
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("unmute")
|
||||
open fun __unmuteBlockingForJava__() {
|
||||
runBlocking { unmute() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kick")
|
||||
open fun __kickBlockingForJava__(message: String) {
|
||||
runBlocking { kick() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kick")
|
||||
open fun __kickBlockingForJava__() = __kickBlockingForJava__("")
|
||||
|
||||
|
||||
/**
|
||||
* 禁言.
|
||||
*
|
||||
* QQ 中最小操作和显示的时间都是一分钟.
|
||||
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
|
||||
*
|
||||
* 管理员可禁言成员, 群主可禁言管理员和群员.
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("muteAsync")
|
||||
open fun __muteAsyncForJava__(seconds: Int): Future<Unit> {
|
||||
return future { mute(seconds) }
|
||||
}
|
||||
|
||||
/**
|
||||
* 解除禁言.
|
||||
*
|
||||
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("unmuteAsync")
|
||||
open fun __unmuteAsyncForJava__(): Future<Unit> {
|
||||
return future { unmute() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kickAsync")
|
||||
open fun __kickAsyncForJava__(message: String): Future<Unit> {
|
||||
return future { kick() }
|
||||
}
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("kickAsync")
|
||||
open fun __kickAsyncForJava__(): Future<Unit> = __kickAsyncForJava__("")
|
||||
}
|
209
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt
Normal file
209
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Group.kt
Normal file
@ -0,0 +1,209 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.MemberInfo
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
* 群. 在 QQ Android 中叫做 "Troop"
|
||||
*/
|
||||
actual abstract class Group : Contact(), CoroutineScope {
|
||||
/**
|
||||
* 群名称.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
* 频繁修改可能会被服务器拒绝.
|
||||
*
|
||||
* @see MemberPermissionChangeEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var name: String
|
||||
/**
|
||||
* 入群公告, 没有时为空字符串.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see GroupEntranceAnnouncementChangeEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var entranceAnnouncement: String
|
||||
/**
|
||||
* 全体禁言状态. `true` 为开启.
|
||||
*
|
||||
* 当前仅能修改状态.
|
||||
*
|
||||
* @see GroupMuteAllEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var isMuteAll: Boolean
|
||||
/**
|
||||
* 坦白说状态. `true` 为允许.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see GroupAllowConfessTalkEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var isConfessTalkEnabled: Boolean
|
||||
/**
|
||||
* 允许群员邀请好友入群的状态. `true` 为允许
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see GroupAllowMemberInviteEvent
|
||||
* @throws PermissionDeniedException 无权限修改时将会抛出异常
|
||||
*/
|
||||
actual abstract var isAllowMemberInvite: Boolean
|
||||
/**
|
||||
* 自动加群审批
|
||||
*/
|
||||
actual abstract val isAutoApproveEnabled: Boolean
|
||||
/**
|
||||
* 匿名聊天
|
||||
*/
|
||||
actual abstract val isAnonymousChatEnabled: Boolean
|
||||
/**
|
||||
* 同为 groupCode, 用户看到的群号码.
|
||||
*/
|
||||
actual abstract override val id: Long
|
||||
/**
|
||||
* 群主.
|
||||
*
|
||||
* @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员
|
||||
*/
|
||||
actual abstract val owner: Member
|
||||
/**
|
||||
* [Bot] 在群内的 [Member] 实例
|
||||
*/
|
||||
@MiraiExperimentalAPI
|
||||
actual abstract val botAsMember: Member
|
||||
/**
|
||||
* 机器人被禁言还剩余多少秒
|
||||
*
|
||||
* @see BotMuteEvent 机器人被禁言事件
|
||||
* @see isBotMuted 判断机器人是否正在被禁言
|
||||
*/
|
||||
actual abstract val botMuteRemaining: Int
|
||||
/**
|
||||
* 机器人在这个群里的权限
|
||||
*
|
||||
* @see Group.checkBotPermission 检查 [Bot] 在这个群里的权限
|
||||
* @see Group.checkBotPermissionOperator 要求 [Bot] 在这个群里的权限为 [管理员或群主][MemberPermission.isOperator]
|
||||
*
|
||||
* @see BotGroupPermissionChangeEvent 机器人群员修改
|
||||
*/
|
||||
actual abstract val botPermission: MemberPermission
|
||||
/**
|
||||
* 群头像下载链接.
|
||||
*/
|
||||
actual val avatarUrl: String
|
||||
get() = "https://p.qlogo.cn/gh/$id/${id}_1/640"
|
||||
|
||||
/**
|
||||
* 群成员列表, 不含机器人自己, 含群主.
|
||||
* 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新
|
||||
*/
|
||||
actual abstract val members: ContactList<Member>
|
||||
|
||||
/**
|
||||
* 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException]
|
||||
*/
|
||||
actual abstract operator fun get(id: Long): Member
|
||||
|
||||
/**
|
||||
* 获取群成员实例, 不存在则 null
|
||||
*/
|
||||
actual abstract fun getOrNull(id: Long): Member?
|
||||
|
||||
/**
|
||||
* 检查此 id 的群成员是否存在
|
||||
*/
|
||||
actual abstract operator fun contains(id: Long): Boolean
|
||||
|
||||
/**
|
||||
* 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun quit(): Boolean
|
||||
|
||||
/**
|
||||
* 构造一个 [Member].
|
||||
* 非特殊情况请不要使用这个函数. 优先使用 [get].
|
||||
*/
|
||||
@JvmName("newMember")
|
||||
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName")
|
||||
@MiraiExperimentalAPI("dangerous")
|
||||
actual abstract fun Member(memberInfo: MemberInfo): Member
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
@JvmSynthetic
|
||||
actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<Group>
|
||||
|
||||
actual companion object {
|
||||
/**
|
||||
* by @kar98k
|
||||
*/
|
||||
actual fun calculateGroupUinByGroupCode(groupCode: Long): Long {
|
||||
var left: Long = groupCode / 1000000L
|
||||
|
||||
when (left) {
|
||||
in 0..10 -> left += 202
|
||||
in 11..19 -> left += 480 - 11
|
||||
in 20..66 -> left += 2100 - 20
|
||||
in 67..156 -> left += 2010 - 67
|
||||
in 157..209 -> left += 2147 - 157
|
||||
in 210..309 -> left += 4100 - 210
|
||||
in 310..499 -> left += 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupCode % 1000000L
|
||||
}
|
||||
|
||||
actual fun calculateGroupCodeByGroupUin(groupUin: Long): Long {
|
||||
var left: Long = groupUin / 1000000L
|
||||
|
||||
when (left) {
|
||||
in 0 + 202..10 + 202 -> left -= 202
|
||||
in 11 + 480 - 11..19 + 480 - 11 -> left -= 480 - 11
|
||||
in 20 + 2100 - 20..66 + 2100 - 20 -> left -= 2100 - 20
|
||||
in 67 + 2010 - 67..156 + 2010 - 67 -> left -= 2010 - 67
|
||||
in 157 + 2147 - 157..209 + 2147 - 157 -> left -= 2147 - 157
|
||||
in 210 + 4100 - 210..309 + 4100 - 210 -> left -= 4100 - 210
|
||||
in 310 + 3800 - 310..499 + 3800 - 310 -> left -= 3800 - 310
|
||||
}
|
||||
|
||||
return left * 1000000L + groupUin % 1000000L
|
||||
}
|
||||
}
|
||||
|
||||
@MiraiExperimentalAPI
|
||||
actual fun toFullString(): String {
|
||||
return "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})"
|
||||
}
|
||||
|
||||
}
|
126
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Member.kt
Normal file
126
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/Member.kt
Normal file
@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2020 Mamoe Technologies and contributors.
|
||||
*
|
||||
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可在以下链接找到该许可证.
|
||||
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
|
||||
*
|
||||
* https://github.com/mamoe/mirai/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.JavaHappyAPI
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.utils.MiraiInternalAPI
|
||||
import net.mamoe.mirai.utils.WeakRefProperty
|
||||
|
||||
/**
|
||||
* 群成员.
|
||||
*/
|
||||
@UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
actual abstract class Member : MemberJavaHappyAPI() {
|
||||
/**
|
||||
* 所在的群.
|
||||
*/
|
||||
@WeakRefProperty
|
||||
actual abstract val group: Group
|
||||
/**
|
||||
* 成员的权限, 动态更新.
|
||||
*
|
||||
* @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发.
|
||||
*/
|
||||
actual abstract val permission: MemberPermission
|
||||
/**
|
||||
* 群名片. 可能为空.
|
||||
*
|
||||
* 管理员和群主都可修改任何人(包括群主)的群名片.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see [nameCardOrNick] 获取非空群名片或昵称
|
||||
*
|
||||
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
actual abstract var nameCard: String
|
||||
/**
|
||||
* 群头衔.
|
||||
*
|
||||
* 仅群主可以修改群头衔.
|
||||
*
|
||||
* 在修改时将会异步上传至服务器.
|
||||
*
|
||||
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
actual abstract var specialTitle: String
|
||||
/**
|
||||
* 被禁言剩余时长. 单位为秒.
|
||||
*
|
||||
* @see isMuted 判断改成员是否处于禁言状态
|
||||
* @see mute 设置禁言
|
||||
* @see unmute 取消禁言
|
||||
*/
|
||||
actual abstract val muteTimeRemaining: Int
|
||||
|
||||
/**
|
||||
* 禁言.
|
||||
*
|
||||
* QQ 中最小操作和显示的时间都是一分钟.
|
||||
* 机器人可以实现精确到秒, 会被客户端显示为 1 分钟但不影响实际禁言时间.
|
||||
*
|
||||
* 管理员可禁言成员, 群主可禁言管理员和群员.
|
||||
*
|
||||
* @param durationSeconds 持续时间. 精确到秒. 范围区间表示为 `(0s, 30days]`. 超过范围则会抛出异常.
|
||||
* @return 机器人无权限时返回 `false`
|
||||
*
|
||||
* @see Int.minutesToSeconds
|
||||
* @see Int.hoursToSeconds
|
||||
* @see Int.daysToSeconds
|
||||
*
|
||||
* @see MemberMuteEvent 成员被禁言事件
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("muteSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun mute(durationSeconds: Int)
|
||||
|
||||
/**
|
||||
* 解除禁言.
|
||||
*
|
||||
* 管理员可解除成员的禁言, 群主可解除管理员和群员的禁言.
|
||||
*
|
||||
* @see MemberUnmuteEvent 成员被取消禁言事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("muteSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun unmute()
|
||||
|
||||
/**
|
||||
* 踢出该成员.
|
||||
*
|
||||
* 管理员可踢出成员, 群主可踢出管理员和群员.
|
||||
*
|
||||
* @see MemberLeaveEvent.Kick 成员被踢出事件.
|
||||
* @throws PermissionDeniedException 无权限修改时
|
||||
*/
|
||||
@JvmName("muteSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract suspend fun kick(message: String)
|
||||
|
||||
/**
|
||||
* 当且仅当 `[other] is [Member] && [other].id == this.id && [other].group == this.group` 时为 true
|
||||
*/
|
||||
actual abstract override fun equals(other: Any?): Boolean
|
||||
|
||||
/**
|
||||
* @return `bot.hashCode() * 31 + id.hashCode()`
|
||||
*/
|
||||
actual abstract override fun hashCode(): Int
|
||||
|
||||
}
|
88
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt
Normal file
88
mirai-core/src/jvmMain/kotlin/net/mamoe/mirai/contact/QQ.kt
Normal file
@ -0,0 +1,88 @@
|
||||
@file:Suppress("unused")
|
||||
|
||||
package net.mamoe.mirai.contact
|
||||
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.data.FriendNameRemark
|
||||
import net.mamoe.mirai.data.PreviousNameList
|
||||
import net.mamoe.mirai.data.Profile
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.FriendMessageSendEvent
|
||||
import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.utils.MiraiExperimentalAPI
|
||||
|
||||
/**
|
||||
* QQ 对象.
|
||||
* 注意: 一个 [QQ] 实例并不是独立的, 它属于一个 [Bot].
|
||||
* 它不能被直接构造. 任何时候都应从 [Bot.getFriend] 或事件中获取.
|
||||
*
|
||||
* 对于同一个 [Bot] 任何一个人的 [QQ] 实例都是单一的.
|
||||
*
|
||||
* A QQ instance helps you to receive event from or sendPacket event to.
|
||||
* Notice that, one QQ instance belong to one [Bot], that is, QQ instances from different [Bot] are NOT the same.
|
||||
*
|
||||
* @author Him188moe
|
||||
*/
|
||||
@Suppress("INAPPLICABLE_JVM_NAME")
|
||||
actual abstract class QQ : Contact(), CoroutineScope {
|
||||
/**
|
||||
* 请求头像下载链接
|
||||
*/
|
||||
// @MiraiExperimentalAPI
|
||||
//suspend fun queryAvatar(): AvatarLink
|
||||
/**
|
||||
* QQ 号码
|
||||
*/
|
||||
actual abstract override val id: Long
|
||||
/**
|
||||
* 昵称
|
||||
*/
|
||||
actual abstract val nick: String
|
||||
|
||||
/**
|
||||
* 查询用户资料
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun queryProfile(): Profile
|
||||
|
||||
/**
|
||||
* 头像下载链接
|
||||
*/
|
||||
actual val avatarUrl: String
|
||||
get() = "http://q1.qlogo.cn/g?b=qq&nk=$id&s=640"
|
||||
|
||||
/**
|
||||
* 查询曾用名.
|
||||
*
|
||||
* 曾用名可能是:
|
||||
* - 昵称
|
||||
* - 共同群内的群名片
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun queryPreviousNameList(): PreviousNameList
|
||||
|
||||
/**
|
||||
* 查询机器人账号给这个人设置的备注
|
||||
*/
|
||||
@MiraiExperimentalAPI("还未支持")
|
||||
actual abstract suspend fun queryRemark(): FriendNameRemark
|
||||
|
||||
/**
|
||||
* 向这个对象发送消息.
|
||||
*
|
||||
* @see FriendMessageSendEvent 发送好友信息事件, cancellable
|
||||
* @see GroupMessageSendEvent 发送群消息事件. cancellable
|
||||
*
|
||||
* @throws EventCancelledException 当发送消息事件被取消
|
||||
* @throws IllegalStateException 发送群消息时若 [Bot] 被禁言抛出
|
||||
*
|
||||
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
|
||||
*/
|
||||
@JvmName("sendMessageSuspend")
|
||||
@JvmSynthetic
|
||||
actual abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ>
|
||||
|
||||
}
|
@ -155,35 +155,35 @@ suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this)
|
||||
// region Contact.uploadImage(IMAGE)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将图片发送到指定联系人. 不会保存临时文件
|
||||
* 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): Image = bufferedImage.upload(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人
|
||||
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人
|
||||
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
|
||||
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this)
|
||||
|
||||
/**
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人
|
||||
* 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
|
||||
* @throws OverFileSizeMaxException
|
||||
*/
|
||||
@Throws(OverFileSizeMaxException::class)
|
||||
|
@ -0,0 +1,6 @@
|
||||
package net.mamoe.mirai.utils
|
||||
|
||||
/**
|
||||
* 图片文件过大
|
||||
*/
|
||||
actual class OverFileSizeMaxException : IllegalStateException()
|
Loading…
Reference in New Issue
Block a user