Commit b7fa7adf authored by Him188's avatar Him188

Add Java-friendly APIs for Contacts

parent 422d84d1
...@@ -35,22 +35,6 @@ import net.mamoe.mirai.utils.io.toUHexString ...@@ -35,22 +35,6 @@ import net.mamoe.mirai.utils.io.toUHexString
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import net.mamoe.mirai.qqandroid.network.protocol.data.jce.FriendInfo as JceFriendInfo 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( internal inline class FriendInfoImpl(
private val jceFriendInfo: JceFriendInfo private val jceFriendInfo: JceFriendInfo
) : FriendInfo { ) : FriendInfo {
...@@ -63,7 +47,7 @@ internal class QQImpl( ...@@ -63,7 +47,7 @@ internal class QQImpl(
override val coroutineContext: CoroutineContext, override val coroutineContext: CoroutineContext,
override val id: Long, override val id: Long,
private val friendInfo: FriendInfo private val friendInfo: FriendInfo
) : ContactImpl(), QQ { ) : QQ() {
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
override val nick: String override val nick: String
get() = friendInfo.nick get() = friendInfo.nick
...@@ -159,7 +143,21 @@ internal class QQImpl( ...@@ -159,7 +143,21 @@ internal class QQImpl(
} }
} finally { } finally {
(image.input as? Closeable)?.close() (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 @MiraiExperimentalAPI
...@@ -187,10 +185,26 @@ internal class MemberImpl( ...@@ -187,10 +185,26 @@ internal class MemberImpl(
group: GroupImpl, group: GroupImpl,
override val coroutineContext: CoroutineContext, override val coroutineContext: CoroutineContext,
memberInfo: MemberInfo memberInfo: MemberInfo
) : ContactImpl(), Member, QQ by qq { ) : Member() {
override val group: GroupImpl by group.unsafeWeakRef() override val group: GroupImpl by group.unsafeWeakRef()
val qq: QQImpl by qq.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 override var permission: MemberPermission = memberInfo.permission
@Suppress("PropertyName") @Suppress("PropertyName")
internal var _nameCard: String = memberInfo.nameCard internal var _nameCard: String = memberInfo.nameCard
...@@ -307,10 +321,9 @@ internal class MemberImpl( ...@@ -307,10 +321,9 @@ internal class MemberImpl(
return result return result
} }
@Suppress("DuplicatedCode") override fun equals(other: Any?): Boolean {
override fun equals(other: Any?): Boolean { // 不要删除. trust me
if (this === other) return true if (this === other) return true
if (other !is Member) return false if (other !is Contact) return false
if (this::class != other::class) return false if (this::class != other::class) return false
return this.id == other.id && this.bot == other.bot return this.id == other.id && this.bot == other.bot
} }
...@@ -347,7 +360,7 @@ internal class GroupImpl( ...@@ -347,7 +360,7 @@ internal class GroupImpl(
override val id: Long, override val id: Long,
groupInfo: GroupInfo, groupInfo: GroupInfo,
members: Sequence<MemberInfo> members: Sequence<MemberInfo>
) : ContactImpl(), Group { ) : Group() {
override val bot: QQAndroidBot by bot.unsafeWeakRef() override val bot: QQAndroidBot by bot.unsafeWeakRef()
val uin: Long = groupInfo.uin val uin: Long = groupInfo.uin
...@@ -398,11 +411,11 @@ internal class GroupImpl( ...@@ -398,11 +411,11 @@ internal class GroupImpl(
}.toLockFreeLinkedList()) }.toLockFreeLinkedList())
internal var _name: String = groupInfo.name internal var _name: String = groupInfo.name
internal var _announcement: String = groupInfo.memo private var _announcement: String = groupInfo.memo
internal var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite private var _allowMemberInvite: Boolean = groupInfo.allowMemberInvite
internal var _confessTalk: Boolean = groupInfo.confessTalk internal var _confessTalk: Boolean = groupInfo.confessTalk
internal var _muteAll: Boolean = groupInfo.muteAll internal var _muteAll: Boolean = groupInfo.muteAll
internal var _autoApprove: Boolean = groupInfo.autoApprove private var _autoApprove: Boolean = groupInfo.autoApprove
internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat internal var _anonymousChat: Boolean = groupInfo.allowAnonymousChat
override var name: String override var name: String
...@@ -666,10 +679,23 @@ internal class GroupImpl( ...@@ -666,10 +679,23 @@ internal class GroupImpl(
} }
} finally { } finally {
(image.input as? Closeable)?.close() (image.input as? Closeable)?.close()
(image.input as? io.ktor.utils.io.core.Closeable)?.close()
} }
override fun toString(): String { override fun toString(): String {
return "Group($id)" 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
}
} }
\ No newline at end of file
...@@ -169,9 +169,9 @@ actual abstract class BotJavaHappyAPI actual constructor() { ...@@ -169,9 +169,9 @@ actual abstract class BotJavaHappyAPI actual constructor() {
} }
} }
// !!! 不要 crossinline, 会编译失败 // !! 不要 crossinline, 会编译失败
@UseExperimental(ExperimentalCoroutinesApi::class) @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 future = object : Future<R> {
val value: CompletableDeferred<R> = CompletableDeferred() val value: CompletableDeferred<R> = CompletableDeferred()
......
/*
* 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
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
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})"
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("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
}
\ No newline at end of file
@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>
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("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
\ No newline at end of file
...@@ -7,15 +7,16 @@ ...@@ -7,15 +7,16 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.utils package net.mamoe.mirai.utils
import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import io.ktor.utils.io.ByteReadChannel
import io.ktor.utils.io.core.Input import io.ktor.utils.io.core.Input
import io.ktor.utils.io.core.copyTo import io.ktor.utils.io.core.copyTo
import io.ktor.utils.io.errors.IOException import io.ktor.utils.io.errors.IOException
import io.ktor.utils.io.streams.asInput
import io.ktor.utils.io.streams.asOutput import io.ktor.utils.io.streams.asOutput
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
...@@ -27,6 +28,17 @@ import java.net.URL ...@@ -27,6 +28,17 @@ import java.net.URL
* 将各类型图片容器转为 [ExternalImage] * 将各类型图片容器转为 [ExternalImage]
*/ */
/**
* 读取 [BufferedImage] 的属性, 然后构造 [ExternalImage]
*/
@Throws(IOException::class)
fun Bitmap.toExternalImage(formatName: String = "gif"): Nothing {
TODO()
}
// suspend inline fun BufferedImage.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 读取文件头识别图片属性, 然后构造 [ExternalImage] * 读取文件头识别图片属性, 然后构造 [ExternalImage]
*/ */
...@@ -48,8 +60,7 @@ fun File.toExternalImage(): ExternalImage { ...@@ -48,8 +60,7 @@ fun File.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [File.toExternalImage] * 在 [IO] 中进行 [File.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun File.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 下载文件到临时目录然后调用 [File.toExternalImage] * 下载文件到临时目录然后调用 [File.toExternalImage]
...@@ -61,6 +72,7 @@ fun URL.toExternalImage(): ExternalImage { ...@@ -61,6 +72,7 @@ fun URL.toExternalImage(): ExternalImage {
openStream().use { input -> openStream().use { input ->
input.copyTo(output) input.copyTo(output)
} }
output.flush()
} }
return file.toExternalImage() return file.toExternalImage()
} }
...@@ -68,8 +80,7 @@ fun URL.toExternalImage(): ExternalImage { ...@@ -68,8 +80,7 @@ fun URL.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [URL.toExternalImage] * 在 [IO] 中进行 [URL.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 保存为临时文件然后调用 [File.toExternalImage] * 保存为临时文件然后调用 [File.toExternalImage]
...@@ -77,8 +88,9 @@ suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toEx ...@@ -77,8 +88,9 @@ suspend fun URL.suspendToExternalImage(): ExternalImage = withContext(IO) { toEx
@Throws(IOException::class) @Throws(IOException::class)
fun InputStream.toExternalImage(): ExternalImage { fun InputStream.toExternalImage(): ExternalImage {
val file = createTempFile().apply { deleteOnExit() } val file = createTempFile().apply { deleteOnExit() }
file.outputStream().asOutput().use { file.outputStream().use {
this.asInput().copyTo(it) this.copyTo(it)
it.flush()
} }
this.close() this.close()
return file.toExternalImage() return file.toExternalImage()
...@@ -87,17 +99,19 @@ fun InputStream.toExternalImage(): ExternalImage { ...@@ -87,17 +99,19 @@ fun InputStream.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [InputStream.toExternalImage] * 在 [IO] 中进行 [InputStream.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend fun InputStream.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
/** /**
* 保存为临时文件然后调用 [File.toExternalImage] * 保存为临时文件然后调用 [File.toExternalImage].
*
* 需要函数调用者 close [this]
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun Input.toExternalImage(): ExternalImage { fun Input.toExternalImage(): ExternalImage {
val file = createTempFile().apply { deleteOnExit() } val file = createTempFile().apply { deleteOnExit() }
file.outputStream().asOutput().use { file.outputStream().asOutput().use {
this.copyTo(it) this.copyTo(it)
it.flush()
} }
return file.toExternalImage() return file.toExternalImage()
} }
...@@ -105,5 +119,17 @@ fun Input.toExternalImage(): ExternalImage { ...@@ -105,5 +119,17 @@ fun Input.toExternalImage(): ExternalImage {
/** /**
* 在 [IO] 中进行 [Input.toExternalImage] * 在 [IO] 中进行 [Input.toExternalImage]
*/ */
@Suppress("unused") suspend inline fun Input.suspendToExternalImage(): ExternalImage = withContext(IO) { toExternalImage() }
suspend 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()
}
\ No newline at end of file
package net.mamoe.mirai.utils
/**
* 图片文件过大
*/
actual class OverFileSizeMaxException : IllegalStateException()
\ No newline at end of file
...@@ -14,6 +14,7 @@ package net.mamoe.mirai.contact ...@@ -14,6 +14,7 @@ package net.mamoe.mirai.contact
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaHappyAPI
import net.mamoe.mirai.event.events.BeforeImageUploadEvent import net.mamoe.mirai.event.events.BeforeImageUploadEvent
import net.mamoe.mirai.event.events.EventCancelledException import net.mamoe.mirai.event.events.EventCancelledException
import net.mamoe.mirai.event.events.ImageUploadEvent import net.mamoe.mirai.event.events.ImageUploadEvent
...@@ -23,25 +24,26 @@ import net.mamoe.mirai.message.MessageReceipt ...@@ -23,25 +24,26 @@ import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.* import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.recall import net.mamoe.mirai.recall
import net.mamoe.mirai.recallIn import net.mamoe.mirai.recallIn
import net.mamoe.mirai.utils.ExternalImage import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.MiraiExperimentalAPI
import net.mamoe.mirai.utils.OverFileSizeMaxException
import net.mamoe.mirai.utils.WeakRefProperty
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group]. * 联系人. 虽然叫做联系人, 但他的子类有 [QQ] 和 [群][Group].
* *
* @author Him188moe * @author Him188moe
*/ */ // 不要删除多平台结构 !!! kotlin bug
interface Contact : CoroutineScope { @UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
@Suppress("INAPPLICABLE_JVM_NAME")
expect abstract class Contact() : CoroutineScope, ContactJavaHappyAPI {
/** /**
* 这个联系人所属 [Bot]. * 这个联系人所属 [Bot].
*/ */
@WeakRefProperty @WeakRefProperty
val bot: Bot abstract val bot: Bot
/** /**
* 可以是 QQ 号码或者群号码. * 可以是 QQ 号码或者群号码.
...@@ -52,7 +54,7 @@ interface Contact : CoroutineScope { ...@@ -52,7 +54,7 @@ interface Contact : CoroutineScope {
* @see QQ.id * @see QQ.id
* @see Group.id * @see Group.id
*/ */
val id: Long abstract val id: Long
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
...@@ -65,11 +67,12 @@ interface Contact : CoroutineScope { ...@@ -65,11 +67,12 @@ interface Contact : CoroutineScope {
* *
* @return 消息回执. 可 [引用回复][MessageReceipt.quote](仅群聊)或 [撤回][MessageReceipt.recall] 这条消息. * @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 BeforeImageUploadEvent 图片发送前事件, cancellable
* @see ImageUploadEvent 图片发送完成事件 * @see ImageUploadEvent 图片发送完成事件
...@@ -77,7 +80,9 @@ interface Contact : CoroutineScope { ...@@ -77,7 +80,9 @@ interface Contact : CoroutineScope {
* @throws EventCancelledException 当发送消息事件被取消 * @throws EventCancelledException 当发送消息事件被取消
* @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB) * @throws OverFileSizeMaxException 当图片文件过大而被服务器拒绝上传时. (最大大小约为 20 MB)
*/ */
suspend fun uploadImage(image: ExternalImage): Image @JvmName("uploadImageSuspend")
@JvmSynthetic
abstract suspend fun uploadImage(image: ExternalImage): Image
/** /**
* 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同. * 判断 `this` 和 [other] 是否是相同的类型, 并且 [id] 相同.
...@@ -87,17 +92,17 @@ interface Contact : CoroutineScope { ...@@ -87,17 +92,17 @@ interface Contact : CoroutineScope {
* 因为, [Member] 含义为群员, 必属于一个群. * 因为, [Member] 含义为群员, 必属于一个群.
* 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人. * 而 [QQ] 含义为一个独立的人, 可以是好友, 也可以是陌生人.
*/ */
override fun equals(other: Any?): Boolean abstract override fun equals(other: Any?): Boolean
/** /**
* @return `bot.hashCode() * 31 + id.hashCode()` * @return `bot.hashCode() * 31 + id.hashCode()`
*/ */
override fun hashCode(): Int abstract override fun hashCode(): Int
/** /**
* @return "QQ($id)" or "Group($id)" or "Member($id)" * @return "QQ($id)" or "Group($id)" or "Member($id)"
*/ */
override fun toString(): String abstract override fun toString(): String
} }
/** /**
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
...@@ -21,11 +21,13 @@ import net.mamoe.mirai.message.MessageReceipt ...@@ -21,11 +21,13 @@ import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.jvm.JvmName import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* 群. 在 QQ Android 中叫做 "Troop" * 群. 在 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 { ...@@ -35,7 +37,7 @@ interface Group : Contact, CoroutineScope {
* @see MemberPermissionChangeEvent * @see MemberPermissionChangeEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常 * @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var name: String abstract var name: String
/** /**
* 入群公告, 没有时为空字符串. * 入群公告, 没有时为空字符串.
* *
...@@ -44,7 +46,7 @@ interface Group : Contact, CoroutineScope { ...@@ -44,7 +46,7 @@ interface Group : Contact, CoroutineScope {
* @see GroupEntranceAnnouncementChangeEvent * @see GroupEntranceAnnouncementChangeEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常 * @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var entranceAnnouncement: String abstract var entranceAnnouncement: String
/** /**
* 全体禁言状态. `true` 为开启. * 全体禁言状态. `true` 为开启.
* *
...@@ -53,7 +55,7 @@ interface Group : Contact, CoroutineScope { ...@@ -53,7 +55,7 @@ interface Group : Contact, CoroutineScope {
* @see GroupMuteAllEvent * @see GroupMuteAllEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常 * @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var isMuteAll: Boolean abstract var isMuteAll: Boolean
/** /**
* 坦白说状态. `true` 为允许. * 坦白说状态. `true` 为允许.
* *
...@@ -62,7 +64,7 @@ interface Group : Contact, CoroutineScope { ...@@ -62,7 +64,7 @@ interface Group : Contact, CoroutineScope {
* @see GroupAllowConfessTalkEvent * @see GroupAllowConfessTalkEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常 * @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var isConfessTalkEnabled: Boolean abstract var isConfessTalkEnabled: Boolean
/** /**
* 允许群员邀请好友入群的状态. `true` 为允许 * 允许群员邀请好友入群的状态. `true` 为允许
* *
...@@ -71,33 +73,33 @@ interface Group : Contact, CoroutineScope { ...@@ -71,33 +73,33 @@ interface Group : Contact, CoroutineScope {
* @see GroupAllowMemberInviteEvent * @see GroupAllowMemberInviteEvent
* @throws PermissionDeniedException 无权限修改时将会抛出异常 * @throws PermissionDeniedException 无权限修改时将会抛出异常
*/ */
var isAllowMemberInvite: Boolean abstract var isAllowMemberInvite: Boolean
/** /**
* 自动加群审批 * 自动加群审批
*/ */
val isAutoApproveEnabled: Boolean abstract val isAutoApproveEnabled: Boolean
/** /**
* 匿名聊天 * 匿名聊天
*/ */
val isAnonymousChatEnabled: Boolean abstract val isAnonymousChatEnabled: Boolean
/** /**
* 同为 groupCode, 用户看到的群号码. * 同为 groupCode, 用户看到的群号码.
*/ */
override val id: Long abstract override val id: Long
/** /**
* 群主. * 群主.
* *
* @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员 * @return 若机器人是群主, 返回 [botAsMember]. 否则返回相应的成员
*/ */
val owner: Member abstract val owner: Member
/** /**
* [Bot] 在群内的 [Member] 实例 * [Bot] 在群内的 [Member] 实例
*/ */
@MiraiExperimentalAPI @MiraiExperimentalAPI
val botAsMember: Member abstract val botAsMember: Member
/** /**
* 机器人被禁言还剩余多少秒 * 机器人被禁言还剩余多少秒
...@@ -105,7 +107,7 @@ interface Group : Contact, CoroutineScope { ...@@ -105,7 +107,7 @@ interface Group : Contact, CoroutineScope {
* @see BotMuteEvent 机器人被禁言事件 * @see BotMuteEvent 机器人被禁言事件
* @see isBotMuted 判断机器人是否正在被禁言 * @see isBotMuted 判断机器人是否正在被禁言
*/ */
val botMuteRemaining: Int abstract val botMuteRemaining: Int
/** /**
* 机器人在这个群里的权限 * 机器人在这个群里的权限
...@@ -115,39 +117,41 @@ interface Group : Contact, CoroutineScope { ...@@ -115,39 +117,41 @@ interface Group : Contact, CoroutineScope {
* *
* @see BotGroupPermissionChangeEvent 机器人群员修改 * @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] 实例创建的时候查询一次. 并与事件同步事件更新 * 在 [Group] 实例创建的时候查询一次. 并与事件同步事件更新
*/ */
val members: ContactList<Member> abstract val members: ContactList<Member>
/** /**
* 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException] * 获取群成员实例. 不存在时抛出 [kotlin.NoSuchElementException]
*/ */
operator fun get(id: Long): Member abstract operator fun get(id: Long): Member
/** /**
* 获取群成员实例, 不存在则 null * 获取群成员实例, 不存在则 null
*/ */
fun getOrNull(id: Long): Member? abstract fun getOrNull(id: Long): Member?
/** /**
* 检查此 id 的群成员是否存在 * 检查此 id 的群成员是否存在
*/ */
operator fun contains(id: Long): Boolean abstract operator fun contains(id: Long): Boolean
/** /**
* 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败 * 让机器人退出这个群. 机器人必须为非群主才能退出. 否则将会失败
*/ */
@JvmName("quitSuspend")
@JvmSynthetic
@MiraiExperimentalAPI("还未支持") @MiraiExperimentalAPI("还未支持")
suspend fun quit(): Boolean abstract suspend fun quit(): Boolean
/** /**
* 构造一个 [Member]. * 构造一个 [Member].
...@@ -156,7 +160,7 @@ interface Group : Contact, CoroutineScope { ...@@ -156,7 +160,7 @@ interface Group : Contact, CoroutineScope {
@MiraiExperimentalAPI("dangerous") @MiraiExperimentalAPI("dangerous")
@Suppress("INAPPLICABLE_JVM_NAME", "FunctionName") @Suppress("INAPPLICABLE_JVM_NAME", "FunctionName")
@JvmName("newMember") @JvmName("newMember")
fun Member(memberInfo: MemberInfo): Member abstract fun Member(memberInfo: MemberInfo): Member
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
...@@ -169,48 +173,19 @@ interface Group : Contact, CoroutineScope { ...@@ -169,48 +173,19 @@ interface Group : Contact, CoroutineScope {
* *
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) * @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 { companion object {
// don't @JvmStatic: JDK 1.8 required
fun calculateGroupUinByGroupCode(groupCode: Long): Long
/** fun calculateGroupCodeByGroupUin(groupUin: 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
}
} }
@MiraiExperimentalAPI @MiraiExperimentalAPI
fun toFullString(): String = "Group(id=${this.id}, name=$name, owner=${owner.id}, members=${members.idContentString})" fun toFullString(): String
} }
/** /**
......
...@@ -12,27 +12,33 @@ ...@@ -12,27 +12,33 @@
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
import net.mamoe.mirai.Bot import net.mamoe.mirai.Bot
import net.mamoe.mirai.JavaHappyAPI
import net.mamoe.mirai.event.events.* import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.utils.MiraiInternalAPI
import net.mamoe.mirai.utils.WeakRefProperty import net.mamoe.mirai.utils.WeakRefProperty
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.ExperimentalTime import kotlin.time.ExperimentalTime
/** /**
* 群成员. * 群成员.
*/ */ // 不要删除多平台结构, kotlin bug
interface Member : QQ, Contact { @Suppress("INAPPLICABLE_JVM_NAME")
@UseExperimental(MiraiInternalAPI::class, JavaHappyAPI::class)
expect abstract class Member() : MemberJavaHappyAPI {
/** /**
* 所在的群. * 所在的群.
*/ */
@WeakRefProperty @WeakRefProperty
val group: Group abstract val group: Group
/** /**
* 成员的权限, 动态更新. * 成员的权限, 动态更新.
* *
* @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发. * @see MemberPermissionChangeEvent 权限变更事件. 由群主或机器人的操作触发.
*/ */
val permission: MemberPermission abstract val permission: MemberPermission
/** /**
* 群名片. 可能为空. * 群名片. 可能为空.
...@@ -46,7 +52,7 @@ interface Member : QQ, Contact { ...@@ -46,7 +52,7 @@ interface Member : QQ, Contact {
* @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. * @see MemberCardChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时 * @throws PermissionDeniedException 无权限修改时
*/ */
var nameCard: String abstract var nameCard: String
/** /**
* 群头衔. * 群头衔.
...@@ -58,7 +64,7 @@ interface Member : QQ, Contact { ...@@ -58,7 +64,7 @@ interface Member : QQ, Contact {
* @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件. * @see MemberSpecialTitleChangeEvent 群名片被管理员, 自己或 [Bot] 改动事件. 修改时也会触发此事件.
* @throws PermissionDeniedException 无权限修改时 * @throws PermissionDeniedException 无权限修改时
*/ */
var specialTitle: String abstract var specialTitle: String
/** /**
* 被禁言剩余时长. 单位为秒. * 被禁言剩余时长. 单位为秒.
...@@ -67,7 +73,7 @@ interface Member : QQ, Contact { ...@@ -67,7 +73,7 @@ interface Member : QQ, Contact {
* @see mute 设置禁言 * @see mute 设置禁言
* @see unmute 取消禁言 * @see unmute 取消禁言
*/ */
val muteTimeRemaining: Int abstract val muteTimeRemaining: Int
/** /**
* 禁言. * 禁言.
...@@ -87,7 +93,9 @@ interface Member : QQ, Contact { ...@@ -87,7 +93,9 @@ interface Member : QQ, Contact {
* @see MemberMuteEvent 成员被禁言事件 * @see MemberMuteEvent 成员被禁言事件
* @throws PermissionDeniedException 无权限修改时 * @throws PermissionDeniedException 无权限修改时
*/ */
suspend fun mute(durationSeconds: Int) @JvmName("muteSuspend")
@JvmSynthetic
abstract suspend fun mute(durationSeconds: Int)
/** /**
* 解除禁言. * 解除禁言.
...@@ -97,7 +105,9 @@ interface Member : QQ, Contact { ...@@ -97,7 +105,9 @@ interface Member : QQ, Contact {
* @see MemberUnmuteEvent 成员被取消禁言事件. * @see MemberUnmuteEvent 成员被取消禁言事件.
* @throws PermissionDeniedException 无权限修改时 * @throws PermissionDeniedException 无权限修改时
*/ */
suspend fun unmute() @JvmName("unmuteSuspend")
@JvmSynthetic
abstract suspend fun unmute()
/** /**
* 踢出该成员. * 踢出该成员.
...@@ -107,12 +117,19 @@ interface Member : QQ, Contact { ...@@ -107,12 +117,19 @@ interface Member : QQ, Contact {
* @see MemberLeaveEvent.Kick 成员被踢出事件. * @see MemberLeaveEvent.Kick 成员被踢出事件.
* @throws PermissionDeniedException 无权限修改时 * @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 * 当且仅当 `[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 @@ ...@@ -7,7 +7,7 @@
* https://github.com/mamoe/mirai/blob/master/LICENSE * https://github.com/mamoe/mirai/blob/master/LICENSE
*/ */
@file:Suppress("EXPERIMENTAL_API_USAGE") @file:Suppress("EXPERIMENTAL_API_USAGE", "unused")
package net.mamoe.mirai.contact package net.mamoe.mirai.contact
...@@ -22,6 +22,8 @@ import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent ...@@ -22,6 +22,8 @@ import net.mamoe.mirai.event.events.MessageSendEvent.GroupMessageSendEvent
import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.MessageChain import net.mamoe.mirai.message.data.MessageChain
import net.mamoe.mirai.utils.MiraiExperimentalAPI import net.mamoe.mirai.utils.MiraiExperimentalAPI
import kotlin.jvm.JvmName
import kotlin.jvm.JvmSynthetic
/** /**
* QQ 对象. * QQ 对象.
...@@ -35,33 +37,32 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI ...@@ -35,33 +37,32 @@ import net.mamoe.mirai.utils.MiraiExperimentalAPI
* *
* @author Him188moe * @author Him188moe
*/ */
interface QQ : Contact, CoroutineScope { @Suppress("INAPPLICABLE_JVM_NAME")
expect abstract class QQ() : Contact, CoroutineScope {
/** /**
* QQ 号码 * 请求头像下载链接
*/ */
override val id: Long // @MiraiExperimentalAPI
//suspend fun queryAvatar(): AvatarLink
/** /**
* 昵称 * QQ 号码
*/ */
val nick: String abstract override val id: Long
/** /**
* 请求头像下载链接 * 昵称
*/ */
// @MiraiExperimentalAPI abstract val nick: String
//suspend fun queryAvatar(): AvatarLink
/** /**
* 查询用户资料 * 查询用户资料
*/ */
@MiraiExperimentalAPI("还未支持") @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 { ...@@ -71,13 +72,13 @@ interface QQ : Contact, CoroutineScope {
* - 共同群内的群名片 * - 共同群内的群名片
*/ */
@MiraiExperimentalAPI("还未支持") @MiraiExperimentalAPI("还未支持")
suspend fun queryPreviousNameList(): PreviousNameList abstract suspend fun queryPreviousNameList(): PreviousNameList
/** /**
* 查询机器人账号给这个人设置的备注 * 查询机器人账号给这个人设置的备注
*/ */
@MiraiExperimentalAPI("还未支持") @MiraiExperimentalAPI("还未支持")
suspend fun queryRemark(): FriendNameRemark abstract suspend fun queryRemark(): FriendNameRemark
/** /**
* 向这个对象发送消息. * 向这个对象发送消息.
...@@ -90,5 +91,9 @@ interface QQ : Contact, CoroutineScope { ...@@ -90,5 +91,9 @@ interface QQ : Contact, CoroutineScope {
* *
* @return 消息回执. 可进行撤回 ([MessageReceipt.recall]) * @return 消息回执. 可进行撤回 ([MessageReceipt.recall])
*/ */
override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ> @JvmSynthetic
@JvmName("sendMessageSuspend")
abstract override suspend fun sendMessage(message: MessageChain): MessageReceipt<QQ>
} }
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
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
\ No newline at end of file
...@@ -16,7 +16,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI ...@@ -16,7 +16,7 @@ import net.mamoe.mirai.utils.MiraiInternalAPI
*/ */
@MiraiInternalAPI @MiraiInternalAPI
@Experimental(level = Experimental.Level.ERROR) @Experimental(level = Experimental.Level.ERROR)
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE) @Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, AnnotationTarget.TYPE, AnnotationTarget.CLASS)
annotation class JavaHappyAPI annotation class JavaHappyAPI
/** /**
......
...@@ -17,5 +17,5 @@ import kotlin.jvm.JvmName ...@@ -17,5 +17,5 @@ import kotlin.jvm.JvmName
/** /**
* 图片文件过大 * 图片文件过大
*/ */ // 不要删除多平台结构, 这是 kotlin 的 bug
class OverFileSizeMaxException : IllegalStateException() expect class OverFileSizeMaxException() : IllegalStateException
\ No newline at end of file
...@@ -170,7 +170,7 @@ actual abstract class BotJavaHappyAPI actual constructor() { ...@@ -170,7 +170,7 @@ actual abstract class BotJavaHappyAPI actual constructor() {
} }
@UseExperimental(ExperimentalCoroutinesApi::class) @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 future = object : Future<R> {
val value: CompletableDeferred<R> = CompletableDeferred() val value: CompletableDeferred<R> = CompletableDeferred()
......
/*
* 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
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
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})"
}
}
\ No newline at end of file
/*
* Copyright 2020 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/master/LICENSE
*/
@file:Suppress("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
}
\ No newline at end of file
@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>
}
\ No newline at end of file
...@@ -155,35 +155,35 @@ suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this) ...@@ -155,35 +155,35 @@ suspend inline fun Contact.sendImage(file: File) = file.sendAsImageTo(this)
// region Contact.uploadImage(IMAGE) // region Contact.uploadImage(IMAGE)
/** /**
* 在 [Dispatchers.IO] 中将图片发送到指定联系人. 不会保存临时文件 * 在 [Dispatchers.IO] 中将图片上传, 但不发送. 不会保存临时文件
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): Image = bufferedImage.upload(this) suspend inline fun Contact.uploadImage(bufferedImage: BufferedImage): Image = bufferedImage.upload(this)
/** /**
* 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中下载 [URL] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this) suspend inline fun Contact.uploadImage(imageUrl: URL): Image = imageUrl.uploadAsImage(this)
/** /**
* 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [Input] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this) suspend inline fun Contact.uploadImage(imageInput: Input): Image = imageInput.uploadAsImage(this)
/** /**
* 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中读取 [InputStream] 到临时文件并将其作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this) suspend inline fun Contact.uploadImage(imageStream: InputStream): Image = imageStream.uploadAsImage(this)
/** /**
* 在 [Dispatchers.IO] 中将文件作为图片发送到指定联系人 * 在 [Dispatchers.IO] 中将文件作为图片上传, 但不发送
* @throws OverFileSizeMaxException * @throws OverFileSizeMaxException
*/ */
@Throws(OverFileSizeMaxException::class) @Throws(OverFileSizeMaxException::class)
......
package net.mamoe.mirai.utils
/**
* 图片文件过大
*/
actual class OverFileSizeMaxException : IllegalStateException()
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment