mirror of
https://github.com/mamoe/mirai.git
synced 2025-01-07 16:40:43 +08:00
Remove announcements' low-level API and bundle them into AnnouncementProtocol
, improve code style and maintainability
This commit is contained in:
parent
251bf3d9df
commit
4a2b510a70
@ -16,7 +16,10 @@ import net.mamoe.kjbb.JvmBlockingBridge
|
|||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.data.*
|
import net.mamoe.mirai.data.*
|
||||||
import net.mamoe.mirai.message.data.Voice
|
import net.mamoe.mirai.message.data.Voice
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
||||||
|
import net.mamoe.mirai.utils.MiraiInternalApi
|
||||||
|
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||||
|
import net.mamoe.mirai.utils.WeakRef
|
||||||
import kotlin.annotation.AnnotationTarget.*
|
import kotlin.annotation.AnnotationTarget.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -129,64 +132,6 @@ public interface LowLevelApiAccessor {
|
|||||||
ownerId: Long
|
ownerId: Long
|
||||||
): Sequence<MemberInfo>
|
): Sequence<MemberInfo>
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取群公告列表
|
|
||||||
* @param page 页码
|
|
||||||
*/
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
public suspend fun getRawGroupAnnouncements(
|
|
||||||
bot: Bot,
|
|
||||||
groupId: Long,
|
|
||||||
page: Int = 1,
|
|
||||||
amount: Int = 10
|
|
||||||
): GroupAnnouncementList
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 发送群公告
|
|
||||||
*
|
|
||||||
* @return 公告的fid
|
|
||||||
*/
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
public suspend fun sendGroupAnnouncement(
|
|
||||||
bot: Bot,
|
|
||||||
groupId: Long,
|
|
||||||
announcement: GroupAnnouncement
|
|
||||||
): String
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除群公告
|
|
||||||
* @param fid [GroupAnnouncement.fid]
|
|
||||||
*/
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
public suspend fun deleteGroupAnnouncement(
|
|
||||||
bot: Bot,
|
|
||||||
groupId: Long,
|
|
||||||
fid: String
|
|
||||||
): Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取一条群公告
|
|
||||||
* @param fid [GroupAnnouncement.fid]
|
|
||||||
*/
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
public suspend fun getGroupAnnouncement(
|
|
||||||
bot: Bot,
|
|
||||||
groupId: Long,
|
|
||||||
fid: String
|
|
||||||
): GroupAnnouncement?
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取群活跃信息
|
* 获取群活跃信息
|
||||||
|
@ -21,10 +21,12 @@ import net.mamoe.mirai.utils.MiraiInternalApi
|
|||||||
@SerialName(AnnouncementImage.SERIAL_NAME)
|
@SerialName(AnnouncementImage.SERIAL_NAME)
|
||||||
@Serializable
|
@Serializable
|
||||||
public class AnnouncementImage @MiraiInternalApi public constructor(
|
public class AnnouncementImage @MiraiInternalApi public constructor(
|
||||||
@SerialName("h") public val height: String,
|
public val height: String,
|
||||||
@SerialName("w") public val width: String,
|
public val width: String,
|
||||||
@SerialName("id") public val id: String
|
public val id: String
|
||||||
) {
|
) {
|
||||||
|
// For stability, do not make it `data class`.
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
public const val SERIAL_NAME: String = "AnnouncementImage"
|
public const val SERIAL_NAME: String = "AnnouncementImage"
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,6 @@ package net.mamoe.mirai.contact.announcement
|
|||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 群公告的附加参数.
|
* 群公告的附加参数.
|
||||||
@ -25,12 +23,10 @@ import net.mamoe.mirai.utils.MiraiExperimentalApi
|
|||||||
@Serializable
|
@Serializable
|
||||||
public class AnnouncementParameters internal constructor(
|
public class AnnouncementParameters internal constructor(
|
||||||
/**
|
/**
|
||||||
* 群公告的图片,目前仅支持发送图片,不支持获得图片
|
* 群公告的图片,目前仅支持发送图片,不支持获得图片.
|
||||||
* @suppress 此 API 不稳定, 可能在任意时间改动
|
* @see AnnouncementImage
|
||||||
*/
|
*/
|
||||||
@Transient // do not serialize unstable properties
|
public val image: AnnouncementImage? = null,
|
||||||
@MiraiExperimentalApi
|
|
||||||
public val image: ByteArray? = null,
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 是否发送给新成员
|
* 是否发送给新成员
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.contact.announcement
|
package net.mamoe.mirai.contact.announcement
|
||||||
|
|
||||||
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
|
||||||
import kotlin.contracts.InvocationKind
|
import kotlin.contracts.InvocationKind
|
||||||
import kotlin.contracts.contract
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
@ -52,10 +51,8 @@ public class AnnouncementParametersBuilder @JvmOverloads constructor(
|
|||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* @see AnnouncementParameters.image
|
* @see AnnouncementParameters.image
|
||||||
* @suppress 此 API 不稳定, 可能在任意时间改动
|
|
||||||
*/
|
*/
|
||||||
@MiraiExperimentalApi
|
public var image: AnnouncementImage? = prototype.image
|
||||||
public var image: ByteArray? = prototype.image
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see AnnouncementParameters.sendToNewMember
|
* @see AnnouncementParameters.sendToNewMember
|
||||||
@ -83,10 +80,9 @@ public class AnnouncementParametersBuilder @JvmOverloads constructor(
|
|||||||
public var needConfirm: Boolean = prototype.needConfirm
|
public var needConfirm: Boolean = prototype.needConfirm
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @suppress 此 API 不稳定, 可能在任意时间改动
|
* @see AnnouncementParameters.image
|
||||||
*/
|
*/
|
||||||
@MiraiExperimentalApi
|
public fun image(image: AnnouncementImage): AnnouncementParametersBuilder {
|
||||||
public fun image(image: ByteArray?): AnnouncementParametersBuilder {
|
|
||||||
this.image = image
|
this.image = image
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.toList
|
|||||||
import net.mamoe.kjbb.JvmBlockingBridge
|
import net.mamoe.kjbb.JvmBlockingBridge
|
||||||
import net.mamoe.mirai.contact.PermissionDeniedException
|
import net.mamoe.mirai.contact.PermissionDeniedException
|
||||||
import net.mamoe.mirai.utils.ExternalResource
|
import net.mamoe.mirai.utils.ExternalResource
|
||||||
|
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||||
import java.util.stream.Stream
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
|
||||||
@ -25,6 +26,7 @@ import java.util.stream.Stream
|
|||||||
*
|
*
|
||||||
* @since 2.7
|
* @since 2.7
|
||||||
*/
|
*/
|
||||||
|
@NotStableForInheritance
|
||||||
public interface Announcements {
|
public interface Announcements {
|
||||||
/**
|
/**
|
||||||
* 创建一个能获取该群内所有群公告列表的 [Flow]. 在 [Flow] 被使用时才会分页下载 [OnlineAnnouncement].
|
* 创建一个能获取该群内所有群公告列表的 [Flow]. 在 [Flow] 被使用时才会分页下载 [OnlineAnnouncement].
|
||||||
|
@ -17,6 +17,7 @@ import net.mamoe.mirai.Bot
|
|||||||
import net.mamoe.mirai.contact.Group
|
import net.mamoe.mirai.contact.Group
|
||||||
import net.mamoe.mirai.contact.NormalMember
|
import net.mamoe.mirai.contact.NormalMember
|
||||||
import net.mamoe.mirai.contact.PermissionDeniedException
|
import net.mamoe.mirai.contact.PermissionDeniedException
|
||||||
|
import net.mamoe.mirai.utils.NotStableForInheritance
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
|
|
||||||
|
|
||||||
@ -27,6 +28,7 @@ import java.time.Instant
|
|||||||
*
|
*
|
||||||
* @since 2.7
|
* @since 2.7
|
||||||
*/
|
*/
|
||||||
|
@NotStableForInheritance
|
||||||
public interface OnlineAnnouncement : Announcement {
|
public interface OnlineAnnouncement : Announcement {
|
||||||
/**
|
/**
|
||||||
* 公告所属群
|
* 公告所属群
|
||||||
|
@ -18,7 +18,10 @@ import kotlinx.coroutines.SupervisorJob
|
|||||||
import kotlinx.coroutines.currentCoroutineContext
|
import kotlinx.coroutines.currentCoroutineContext
|
||||||
import kotlinx.io.core.discardExact
|
import kotlinx.io.core.discardExact
|
||||||
import kotlinx.io.core.readBytes
|
import kotlinx.io.core.readBytes
|
||||||
import kotlinx.serialization.json.*
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import kotlinx.serialization.json.long
|
||||||
import net.mamoe.mirai.*
|
import net.mamoe.mirai.*
|
||||||
import net.mamoe.mirai.contact.*
|
import net.mamoe.mirai.contact.*
|
||||||
import net.mamoe.mirai.data.*
|
import net.mamoe.mirai.data.*
|
||||||
@ -557,139 +560,11 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
|||||||
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.ids.contentToString()}: $response" }
|
check(response is PbMessageSvc.PbMsgWithDraw.Response.Success) { "Failed to recall message #${source.ids.contentToString()}: $response" }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION", "OverridingDeprecatedMember")
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
override suspend fun getRawGroupAnnouncements(
|
|
||||||
bot: Bot,
|
|
||||||
groupId: Long,
|
|
||||||
page: Int,
|
|
||||||
amount: Int
|
|
||||||
): GroupAnnouncementList = bot.asQQAndroidBot().run {
|
|
||||||
val rep = bot.asQQAndroidBot().network.run {
|
|
||||||
Mirai.Http.post<String> {
|
|
||||||
url("https://web.qun.qq.com/cgi-bin/announce/list_announce")
|
|
||||||
body = MultiPartFormDataContent(formData {
|
|
||||||
append("qid", groupId)
|
|
||||||
append("bkn", bot.bkn)
|
|
||||||
append("ft", 23) //好像是一个用来识别应用的参数
|
|
||||||
append("s", if (page == 1) 0 else -(page * amount + 1)) // 第一页这里的参数应该是-1
|
|
||||||
append("n", amount)
|
|
||||||
append("ni", if (page == 1) 1 else 0)
|
|
||||||
append("format", "json")
|
|
||||||
})
|
|
||||||
headers {
|
|
||||||
append(
|
|
||||||
"cookie",
|
|
||||||
"uin=o${id}; skey=${client.wLoginSigInfo.sKey.data.encodeToString()};"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// bot.network.logger.error(rep)
|
|
||||||
return json.decodeFromString(GroupAnnouncementList.serializer(), rep)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val json = Json {
|
private val json = Json {
|
||||||
ignoreUnknownKeys = true
|
|
||||||
isLenient = true
|
isLenient = true
|
||||||
|
ignoreUnknownKeys = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("DEPRECATION", "OverridingDeprecatedMember")
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
override suspend fun sendGroupAnnouncement(bot: Bot, groupId: Long, announcement: GroupAnnouncement): String =
|
|
||||||
bot.asQQAndroidBot().run {
|
|
||||||
val rep = Mirai.Http.post<String> {
|
|
||||||
url("https://web.qun.qq.com/cgi-bin/announce/add_qun_notice")
|
|
||||||
body = MultiPartFormDataContent(formData {
|
|
||||||
append("qid", groupId)
|
|
||||||
append("bkn", bkn)
|
|
||||||
append("text", announcement.msg.text)
|
|
||||||
append("pinned", announcement.pinned)
|
|
||||||
append(
|
|
||||||
"settings",
|
|
||||||
json.encodeToString(
|
|
||||||
GroupAnnouncementSettings.serializer(),
|
|
||||||
announcement.settings ?: GroupAnnouncementSettings()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
append("format", "json")
|
|
||||||
})
|
|
||||||
headers {
|
|
||||||
append(
|
|
||||||
"cookie",
|
|
||||||
"uin=o${id};" +
|
|
||||||
" skey=${client.wLoginSigInfo.sKey.data.encodeToString()};" +
|
|
||||||
" p_uin=o${id};" +
|
|
||||||
" p_skey=${client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString()}; "
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsonObj = json.parseToJsonElement(rep)
|
|
||||||
return jsonObj.jsonObject["new_fid"]?.jsonPrimitive?.content
|
|
||||||
?: throw throw IllegalStateException("Send Announcement fail group:$groupId msg:${jsonObj.jsonObject["em"]} content:${announcement.msg.text}")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION", "OverridingDeprecatedMember")
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
override suspend fun deleteGroupAnnouncement(bot: Bot, groupId: Long, fid: String): Boolean =
|
|
||||||
bot.asQQAndroidBot().run {
|
|
||||||
val data = Mirai.Http.post<String> {
|
|
||||||
url("https://web.qun.qq.com/cgi-bin/announce/del_feed")
|
|
||||||
body = MultiPartFormDataContent(formData {
|
|
||||||
append("qid", groupId)
|
|
||||||
append("bkn", bkn)
|
|
||||||
append("fid", fid)
|
|
||||||
append("format", "json")
|
|
||||||
})
|
|
||||||
headers {
|
|
||||||
append(
|
|
||||||
"cookie",
|
|
||||||
"uin=o${id};" +
|
|
||||||
" skey=${client.wLoginSigInfo.sKey.data.encodeToString()};" +
|
|
||||||
" p_uin=o${id};" +
|
|
||||||
" p_skey=${client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString()}; "
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsonObj = json.parseToJsonElement(data)
|
|
||||||
if (jsonObj.jsonObject["ec"]?.jsonPrimitive?.int ?: 1 != 0) {
|
|
||||||
throw throw IllegalStateException("delete Announcement fail group:$groupId msg:${jsonObj.jsonObject["em"]} fid:$fid")
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonObj.jsonObject["ec"]?.jsonPrimitive?.int == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION", "OverridingDeprecatedMember")
|
|
||||||
@LowLevelApi
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
override suspend fun getGroupAnnouncement(bot: Bot, groupId: Long, fid: String): GroupAnnouncement? =
|
|
||||||
bot.asQQAndroidBot().run {
|
|
||||||
val rep = network.run {
|
|
||||||
Mirai.Http.post<String> {
|
|
||||||
url("https://web.qun.qq.com/cgi-bin/announce/get_feed")
|
|
||||||
body = MultiPartFormDataContent(formData {
|
|
||||||
append("qid", groupId)
|
|
||||||
append("bkn", bkn)
|
|
||||||
append("fid", fid)
|
|
||||||
append("format", "json")
|
|
||||||
})
|
|
||||||
headers {
|
|
||||||
append(
|
|
||||||
"cookie",
|
|
||||||
"uin=o${id}; skey=${client.wLoginSigInfo.sKey.data.encodeToString()}; p_uin=o${id};"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// bot.network.logger.error(rep)
|
|
||||||
return json.decodeFromString(GroupAnnouncement.serializer(), rep)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@LowLevelApi
|
@LowLevelApi
|
||||||
@MiraiExperimentalApi
|
@MiraiExperimentalApi
|
||||||
override suspend fun getRawGroupActiveData(bot: Bot, groupId: Long, page: Int): GroupActiveData =
|
override suspend fun getRawGroupActiveData(bot: Bot, groupId: Long, page: Int): GroupActiveData =
|
||||||
|
@ -1,257 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2019-2021 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/dev/LICENSE
|
|
||||||
*/
|
|
||||||
|
|
||||||
@file:Suppress("DEPRECATION")
|
|
||||||
|
|
||||||
package net.mamoe.mirai.internal.contact
|
|
||||||
|
|
||||||
import io.ktor.client.request.*
|
|
||||||
import io.ktor.client.request.forms.*
|
|
||||||
import io.ktor.http.*
|
|
||||||
import io.ktor.utils.io.core.*
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.int
|
|
||||||
import kotlinx.serialization.json.jsonObject
|
|
||||||
import kotlinx.serialization.json.jsonPrimitive
|
|
||||||
import net.mamoe.mirai.Bot
|
|
||||||
import net.mamoe.mirai.Mirai
|
|
||||||
import net.mamoe.mirai.contact.Group
|
|
||||||
import net.mamoe.mirai.contact.MemberPermission
|
|
||||||
import net.mamoe.mirai.contact.announcement.*
|
|
||||||
import net.mamoe.mirai.contact.checkBotPermission
|
|
||||||
import net.mamoe.mirai.data.GroupAnnouncement
|
|
||||||
import net.mamoe.mirai.data.GroupAnnouncementList
|
|
||||||
import net.mamoe.mirai.data.GroupAnnouncementMsg
|
|
||||||
import net.mamoe.mirai.data.GroupAnnouncementSettings
|
|
||||||
import net.mamoe.mirai.internal.asQQAndroidBot
|
|
||||||
import net.mamoe.mirai.utils.*
|
|
||||||
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
|
||||||
import java.util.stream.Stream
|
|
||||||
import kotlin.io.use
|
|
||||||
|
|
||||||
internal class AnnouncementsImpl(
|
|
||||||
private val group: GroupImpl,
|
|
||||||
) : Announcements {
|
|
||||||
inline val bot get() = group.bot
|
|
||||||
|
|
||||||
override suspend fun asFlow(): Flow<OnlineAnnouncement> {
|
|
||||||
return flow {
|
|
||||||
var i = 1
|
|
||||||
while (true) {
|
|
||||||
val result = Mirai.getRawGroupAnnouncements(bot, group.id, i++)
|
|
||||||
checkResult(result, i)
|
|
||||||
|
|
||||||
if (result.inst.isNullOrEmpty() && result.feeds.isNullOrEmpty()) break
|
|
||||||
|
|
||||||
result.inst?.let { emitAll(it.asFlow()) }
|
|
||||||
result.feeds?.let { emitAll(it.asFlow()) }
|
|
||||||
}
|
|
||||||
}.map { it.toAnnouncement(group) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun asStream(): Stream<OnlineAnnouncement> {
|
|
||||||
return stream {
|
|
||||||
var i = 1
|
|
||||||
while (true) {
|
|
||||||
val result = runBlocking { Mirai.getRawGroupAnnouncements(bot, group.id, i++) }
|
|
||||||
checkResult(result, i)
|
|
||||||
|
|
||||||
if (result.inst.isNullOrEmpty() && result.feeds.isNullOrEmpty()) break
|
|
||||||
|
|
||||||
result.inst?.let { yieldAll(it) }
|
|
||||||
result.feeds?.let { yieldAll(it) }
|
|
||||||
}
|
|
||||||
}.map { it.toAnnouncement(group) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkResult(result: GroupAnnouncementList, i: Int) {
|
|
||||||
if (result.ec != 0) {
|
|
||||||
bot.logger.warning { "Failed to get announcements for group ${group.id}, at page $i. result=$result" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun delete(fid: String): Boolean {
|
|
||||||
group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to delete group announcement" }
|
|
||||||
return Mirai.deleteGroupAnnouncement(bot, group.id, fid)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun get(fid: String): OnlineAnnouncement? {
|
|
||||||
return Mirai.getGroupAnnouncement(bot, group.id, fid)?.toAnnouncement(group)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun publish(announcement: Announcement): OnlineAnnouncement = announcement.run {
|
|
||||||
val bot = group.bot
|
|
||||||
group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to send group announcement" }
|
|
||||||
val image = parameters.image
|
|
||||||
val fid = image?.toExternalResource()?.use {
|
|
||||||
val imageUp = AnnouncementProtocol.uploadGroupAnnouncementImage(bot, group.id, it)
|
|
||||||
AnnouncementProtocol.sendGroupAnnouncementWithImage(bot, group.id, imageUp, toGroupAnnouncement(bot.id))
|
|
||||||
} ?: Mirai.sendGroupAnnouncement(bot, group.id, toGroupAnnouncement(bot.id))
|
|
||||||
|
|
||||||
return OnlineAnnouncementImpl(
|
|
||||||
group = group,
|
|
||||||
senderId = bot.id,
|
|
||||||
sender = group.botAsMember,
|
|
||||||
title = title,
|
|
||||||
body = body,
|
|
||||||
parameters = parameters,
|
|
||||||
fid = fid,
|
|
||||||
isAllRead = false,
|
|
||||||
readMemberNumber = 0,
|
|
||||||
publishTime = currentTimeSeconds()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun uploadImage(resource: ExternalResource): AnnouncementImage {
|
|
||||||
return AnnouncementProtocol.uploadGroupAnnouncementImage(bot, group.id, resource)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
internal object AnnouncementProtocol {
|
|
||||||
private val json = Json {
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
isLenient = true
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun uploadGroupAnnouncementImage(
|
|
||||||
bot: Bot,
|
|
||||||
groupId: Long,
|
|
||||||
resource: ExternalResource
|
|
||||||
): AnnouncementImage = bot.asQQAndroidBot().run {
|
|
||||||
//https://youtrack.jetbrains.com/issue/KTOR-455
|
|
||||||
val rep = Mirai.Http.post<String> {
|
|
||||||
url("https://web.qun.qq.com/cgi-bin/announce/upload_img")
|
|
||||||
body = MultiPartFormDataContent(formData {
|
|
||||||
append("\"bkn\"", bkn)
|
|
||||||
append("\"source\"", "troopNotice")
|
|
||||||
append("m", "0")
|
|
||||||
append(
|
|
||||||
"\"pic_up\"",
|
|
||||||
headers = Headers.build {
|
|
||||||
append(HttpHeaders.ContentType, ContentType.Image.PNG)
|
|
||||||
append(HttpHeaders.ContentDisposition, "filename=\"temp_uploadFile.png\"")
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
writeFully(resource.inputStream().withUse { readBytes() })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
headers {
|
|
||||||
append(
|
|
||||||
"cookie",
|
|
||||||
" p_uin=o${id};" +
|
|
||||||
" p_skey=${client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString() ?: error("cookie parse p_skey error")}; "
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsonObj = json.parseToJsonElement(rep)
|
|
||||||
if (jsonObj.jsonObject["ec"]?.jsonPrimitive?.int != 0) {
|
|
||||||
throw IllegalStateException("Upload group announcement image fail group:$groupId msg:${jsonObj.jsonObject["em"]}")
|
|
||||||
}
|
|
||||||
val id = jsonObj.jsonObject["id"]?.jsonPrimitive?.content
|
|
||||||
?: throw IllegalStateException("Upload group announcement image fail group:$groupId msg:${jsonObj.jsonObject["em"]}")
|
|
||||||
return json.decodeFromString(AnnouncementImage.serializer(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun sendGroupAnnouncementWithImage(
|
|
||||||
bot: Bot,
|
|
||||||
groupId: Long,
|
|
||||||
image: AnnouncementImage,
|
|
||||||
announcement: GroupAnnouncement
|
|
||||||
): String = bot.asQQAndroidBot().run {
|
|
||||||
val rep = withContext(network.coroutineContext) {
|
|
||||||
Mirai.Http.post<String> {
|
|
||||||
url("https://web.qun.qq.com/cgi-bin/announce/add_qun_notice")
|
|
||||||
body = MultiPartFormDataContent(formData {
|
|
||||||
append("qid", groupId)
|
|
||||||
append("bkn", bkn)
|
|
||||||
append("text", announcement.msg.text)
|
|
||||||
append("pinned", announcement.pinned)
|
|
||||||
append("pic", image.id)
|
|
||||||
append("imgWidth", image.width)
|
|
||||||
append("imgHeight", image.height)
|
|
||||||
append(
|
|
||||||
"settings",
|
|
||||||
json.encodeToString(
|
|
||||||
GroupAnnouncementSettings.serializer(),
|
|
||||||
announcement.settings ?: GroupAnnouncementSettings()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
append("format", "json")
|
|
||||||
})
|
|
||||||
headers {
|
|
||||||
append(
|
|
||||||
"cookie",
|
|
||||||
" p_uin=o${id};" +
|
|
||||||
" p_skey=${
|
|
||||||
client.wLoginSigInfo.psKeyMap["qun.qq.com"]?.data?.encodeToString() ?: error(
|
|
||||||
"parse error"
|
|
||||||
)
|
|
||||||
}; "
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val jsonObj = json.parseToJsonElement(rep)
|
|
||||||
return jsonObj.jsonObject["new_fid"]?.jsonPrimitive?.content
|
|
||||||
?: throw throw IllegalStateException("Send Announcement with image fail group:$groupId msg:${jsonObj.jsonObject["em"]} content:${announcement.msg.text}")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
internal fun Announcement.toGroupAnnouncement(senderId: Long): GroupAnnouncement {
|
|
||||||
return GroupAnnouncement(
|
|
||||||
sender = senderId,
|
|
||||||
msg = GroupAnnouncementMsg(
|
|
||||||
title = title,
|
|
||||||
text = body
|
|
||||||
),
|
|
||||||
type = if (parameters.sendToNewMember) 20 else 6,
|
|
||||||
settings = GroupAnnouncementSettings(
|
|
||||||
isShowEditCard = if (parameters.isShowEditCard) 1 else 0,
|
|
||||||
tipWindowType = if (parameters.isTip) 0 else 1,
|
|
||||||
confirmRequired = if (parameters.needConfirm) 1 else 0,
|
|
||||||
),
|
|
||||||
pinned = if (parameters.isPinned) 1 else 0,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
private fun GroupAnnouncement.toAnnouncement(group: Group): OnlineAnnouncementImpl {
|
|
||||||
val fid = this.fid
|
|
||||||
val settings = this.settings
|
|
||||||
|
|
||||||
check(fid != null) { "GroupAnnouncement don't have id" }
|
|
||||||
check(settings != null) { "GroupAnnouncement don't have setting" }
|
|
||||||
|
|
||||||
return OnlineAnnouncementImpl(
|
|
||||||
group = group,
|
|
||||||
senderId = sender,
|
|
||||||
sender = group[sender],
|
|
||||||
title = msg.title ?: "",
|
|
||||||
body = msg.text,
|
|
||||||
parameters = buildAnnouncementParameters {
|
|
||||||
isPinned = pinned == 1
|
|
||||||
sendToNewMember = type == 20
|
|
||||||
isTip = settings.tipWindowType == 0
|
|
||||||
needConfirm = settings.confirmRequired == 1
|
|
||||||
isShowEditCard = settings.isShowEditCard == 1
|
|
||||||
},
|
|
||||||
fid = fid,
|
|
||||||
isAllRead = isAllConfirm != 0,
|
|
||||||
readMemberNumber = readNum,
|
|
||||||
publishTime = time
|
|
||||||
)
|
|
||||||
}
|
|
@ -21,6 +21,7 @@ import net.mamoe.mirai.data.MemberInfo
|
|||||||
import net.mamoe.mirai.event.broadcast
|
import net.mamoe.mirai.event.broadcast
|
||||||
import net.mamoe.mirai.event.events.*
|
import net.mamoe.mirai.event.events.*
|
||||||
import net.mamoe.mirai.internal.QQAndroidBot
|
import net.mamoe.mirai.internal.QQAndroidBot
|
||||||
|
import net.mamoe.mirai.internal.contact.announcement.AnnouncementsImpl
|
||||||
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
|
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
|
||||||
import net.mamoe.mirai.internal.message.OfflineGroupImage
|
import net.mamoe.mirai.internal.message.OfflineGroupImage
|
||||||
import net.mamoe.mirai.internal.network.context.BdhSession
|
import net.mamoe.mirai.internal.network.context.BdhSession
|
||||||
|
@ -0,0 +1,284 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019-2021 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/dev/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
|
package net.mamoe.mirai.internal.contact.announcement
|
||||||
|
|
||||||
|
import io.ktor.client.request.*
|
||||||
|
import io.ktor.client.request.forms.*
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.coroutines.flow.*
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.mamoe.mirai.Bot
|
||||||
|
import net.mamoe.mirai.Mirai
|
||||||
|
import net.mamoe.mirai.contact.Group
|
||||||
|
import net.mamoe.mirai.contact.MemberPermission
|
||||||
|
import net.mamoe.mirai.contact.announcement.*
|
||||||
|
import net.mamoe.mirai.contact.checkBotPermission
|
||||||
|
import net.mamoe.mirai.internal.QQAndroidBot
|
||||||
|
import net.mamoe.mirai.internal.asQQAndroidBot
|
||||||
|
import net.mamoe.mirai.internal.contact.GroupImpl
|
||||||
|
import net.mamoe.mirai.internal.contact.OnlineAnnouncementImpl
|
||||||
|
import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.deleteGroupAnnouncement
|
||||||
|
import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.getGroupAnnouncement
|
||||||
|
import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toAnnouncement
|
||||||
|
import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toGroupAnnouncement
|
||||||
|
import net.mamoe.mirai.internal.network.psKey
|
||||||
|
import net.mamoe.mirai.internal.network.sKey
|
||||||
|
import net.mamoe.mirai.internal.utils.io.writeResource
|
||||||
|
import net.mamoe.mirai.utils.*
|
||||||
|
import net.mamoe.mirai.utils.Either.Companion.rightOrNull
|
||||||
|
import java.util.stream.Stream
|
||||||
|
|
||||||
|
internal class AnnouncementsImpl(
|
||||||
|
private val group: GroupImpl,
|
||||||
|
) : Announcements {
|
||||||
|
inline val bot get() = group.bot
|
||||||
|
|
||||||
|
override suspend fun asFlow(): Flow<OnlineAnnouncement> {
|
||||||
|
return flow {
|
||||||
|
var i = 1
|
||||||
|
while (true) {
|
||||||
|
val result = AnnouncementProtocol.getRawGroupAnnouncements(bot, group.id, i++).rightOrNull ?: break
|
||||||
|
|
||||||
|
if (result.inst.isNullOrEmpty() && result.feeds.isNullOrEmpty()) break
|
||||||
|
|
||||||
|
result.inst?.let { emitAll(it.asFlow()) }
|
||||||
|
result.feeds?.let { emitAll(it.asFlow()) }
|
||||||
|
}
|
||||||
|
}.map { it.toAnnouncement(group) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun asStream(): Stream<OnlineAnnouncement> {
|
||||||
|
return stream {
|
||||||
|
var i = 1
|
||||||
|
while (true) {
|
||||||
|
val result = runBlocking {
|
||||||
|
AnnouncementProtocol.getRawGroupAnnouncements(bot, group.id, i++)
|
||||||
|
}.rightOrNull ?: break
|
||||||
|
|
||||||
|
if (result.inst.isNullOrEmpty() && result.feeds.isNullOrEmpty()) break
|
||||||
|
|
||||||
|
result.inst?.let { yieldAll(it) }
|
||||||
|
result.feeds?.let { yieldAll(it) }
|
||||||
|
}
|
||||||
|
}.map { it.toAnnouncement(group) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun delete(fid: String): Boolean {
|
||||||
|
group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to delete group announcement" }
|
||||||
|
return bot.deleteGroupAnnouncement(group.id, fid)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun get(fid: String): OnlineAnnouncement {
|
||||||
|
return bot.getGroupAnnouncement(group.id, fid).toAnnouncement(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun publish(announcement: Announcement): OnlineAnnouncement = announcement.run {
|
||||||
|
val bot = group.bot
|
||||||
|
group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to send group announcement" }
|
||||||
|
val image = parameters.image
|
||||||
|
val fid = AnnouncementProtocol.sendGroupAnnouncement(bot, group.id, toGroupAnnouncement(bot.id), image)
|
||||||
|
|
||||||
|
return OnlineAnnouncementImpl(
|
||||||
|
group = group,
|
||||||
|
senderId = bot.id,
|
||||||
|
sender = group.botAsMember,
|
||||||
|
title = title,
|
||||||
|
body = body,
|
||||||
|
parameters = parameters,
|
||||||
|
fid = fid,
|
||||||
|
isAllRead = false,
|
||||||
|
readMemberNumber = 0,
|
||||||
|
publishTime = currentTimeSeconds()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun uploadImage(resource: ExternalResource): AnnouncementImage {
|
||||||
|
return AnnouncementProtocol.uploadGroupAnnouncementImage(bot, resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object AnnouncementProtocol {
|
||||||
|
@Serializable
|
||||||
|
data class UploadImageResp(
|
||||||
|
@SerialName("ec") override val errorCode: Int = 0,
|
||||||
|
@SerialName("em") override val errorMessage: String? = null,
|
||||||
|
@SerialName("id") val id: String,
|
||||||
|
) : CheckableResponseA(), JsonStruct
|
||||||
|
|
||||||
|
suspend fun uploadGroupAnnouncementImage(
|
||||||
|
bot: Bot,
|
||||||
|
resource: ExternalResource
|
||||||
|
): AnnouncementImage = bot.asQQAndroidBot().run {
|
||||||
|
val resp = Mirai.Http.post<String> {
|
||||||
|
url("https://web.qun.qq.com/cgi-bin/announce/upload_img")
|
||||||
|
body = MultiPartFormDataContent(formData {
|
||||||
|
append("\"bkn\"", bkn)
|
||||||
|
append("\"source\"", "troopNotice")
|
||||||
|
append("m", "0")
|
||||||
|
append(
|
||||||
|
"\"pic_up\"",
|
||||||
|
headers = Headers.build {
|
||||||
|
append(HttpHeaders.ContentType, ContentType.Image.PNG)
|
||||||
|
append(HttpHeaders.ContentDisposition, "filename=\"temp_uploadFile.png\"")
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
writeResource(resource)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
cookie("p_uin", "o${bot.id}")
|
||||||
|
cookie("p_skey", psKey("qun.qq.com"))
|
||||||
|
}.loadSafelyAs(UploadImageResp.serializer()).checked()
|
||||||
|
return resp.id.loadSafelyAs(GroupAnnouncementImage.serializer()).checked().toPublic()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class SendGroupAnnouncementResp(
|
||||||
|
@SerialName("ec") override val errorCode: Int = 0,
|
||||||
|
@SerialName("em") override val errorMessage: String? = null,
|
||||||
|
@SerialName("new_fid") val fid: String,
|
||||||
|
) : CheckableResponseA(), JsonStruct
|
||||||
|
|
||||||
|
suspend fun sendGroupAnnouncement(
|
||||||
|
bot: Bot,
|
||||||
|
groupId: Long,
|
||||||
|
announcement: GroupAnnouncement,
|
||||||
|
image: AnnouncementImage?,
|
||||||
|
): String = bot.asQQAndroidBot().run {
|
||||||
|
return Mirai.Http.post<String> {
|
||||||
|
url("https://web.qun.qq.com/cgi-bin/announce/add_qun_notice")
|
||||||
|
body = MultiPartFormDataContent(formData {
|
||||||
|
append("qid", groupId)
|
||||||
|
this.append("bkn", bkn)
|
||||||
|
append("text", announcement.msg.text)
|
||||||
|
append("pinned", announcement.pinned)
|
||||||
|
image?.let {
|
||||||
|
append("pic", image.id)
|
||||||
|
append("imgWidth", image.width)
|
||||||
|
append("imgHeight", image.height)
|
||||||
|
}
|
||||||
|
append(
|
||||||
|
"settings",
|
||||||
|
announcement.settings.toJsonString(GroupAnnouncementSettings.serializer()),
|
||||||
|
)
|
||||||
|
append("format", "json")
|
||||||
|
})
|
||||||
|
cookie("uin", "o${bot.id}")
|
||||||
|
cookie("p_uin", "o${bot.id}")
|
||||||
|
cookie("skey", sKey)
|
||||||
|
cookie("p_skey", psKey("qun.qq.com"))
|
||||||
|
}.loadSafelyAs(SendGroupAnnouncementResp.serializer()).checked().fid
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getRawGroupAnnouncements(
|
||||||
|
bot: Bot,
|
||||||
|
groupId: Long,
|
||||||
|
page: Int,
|
||||||
|
amount: Int = 10
|
||||||
|
): Either<DeserializationFailure, GroupAnnouncementList> = bot.asQQAndroidBot().run {
|
||||||
|
return bot.asQQAndroidBot().network.run {
|
||||||
|
Mirai.Http.post<String> {
|
||||||
|
url("https://web.qun.qq.com/cgi-bin/announce/list_announce")
|
||||||
|
body = MultiPartFormDataContent(formData {
|
||||||
|
append("qid", groupId)
|
||||||
|
append("bkn", bot.bkn)
|
||||||
|
append("ft", 23) //好像是一个用来识别应用的参数
|
||||||
|
append("s", if (page == 1) 0 else -(page * amount + 1)) // 第一页这里的参数应该是-1
|
||||||
|
append("n", amount)
|
||||||
|
append("ni", if (page == 1) 1 else 0)
|
||||||
|
append("format", "json")
|
||||||
|
})
|
||||||
|
cookie("uin", "o${bot.id}")
|
||||||
|
cookie("skey", sKey)
|
||||||
|
}
|
||||||
|
}.loadSafelyAs(GroupAnnouncementList.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class DeleteResp(
|
||||||
|
@SerialName("ec") override val errorCode: Int = 0,
|
||||||
|
@SerialName("em") override val errorMessage: String? = null,
|
||||||
|
) : CheckableResponseA(), JsonStruct
|
||||||
|
|
||||||
|
suspend fun QQAndroidBot.deleteGroupAnnouncement(groupId: Long, fid: String): Boolean {
|
||||||
|
Mirai.Http.post<String> {
|
||||||
|
url("https://web.qun.qq.com/cgi-bin/announce/del_feed")
|
||||||
|
body = feedBody(groupId, fid)
|
||||||
|
cookie("uin", "o$id")
|
||||||
|
cookie("p_uin", "o$id")
|
||||||
|
cookie("skey", sKey)
|
||||||
|
cookie("p_skey", psKey("qun.qq.com"))
|
||||||
|
}.loadSafelyAs(DeleteResp.serializer()).checked()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun QQAndroidBot.getGroupAnnouncement(groupId: Long, fid: String): GroupAnnouncement {
|
||||||
|
return Mirai.Http.post<String> {
|
||||||
|
url("https://web.qun.qq.com/cgi-bin/announce/get_feed")
|
||||||
|
body = feedBody(groupId, fid)
|
||||||
|
cookie("uin", "o$id")
|
||||||
|
cookie("p_uin", "o$id")
|
||||||
|
cookie("skey", sKey)
|
||||||
|
}.loadAs(GroupAnnouncement.serializer())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun QQAndroidBot.feedBody(
|
||||||
|
groupId: Long,
|
||||||
|
fid: String
|
||||||
|
) = MultiPartFormDataContent(formData {
|
||||||
|
append("qid", groupId)
|
||||||
|
append("bkn", bkn)
|
||||||
|
append("fid", fid)
|
||||||
|
append("format", "json")
|
||||||
|
})
|
||||||
|
|
||||||
|
fun Announcement.toGroupAnnouncement(senderId: Long): GroupAnnouncement {
|
||||||
|
return GroupAnnouncement(
|
||||||
|
sender = senderId,
|
||||||
|
msg = GroupAnnouncementMsg(
|
||||||
|
title = title,
|
||||||
|
text = body
|
||||||
|
),
|
||||||
|
type = if (parameters.sendToNewMember) 20 else 6,
|
||||||
|
settings = GroupAnnouncementSettings(
|
||||||
|
isShowEditCard = if (parameters.isShowEditCard) 1 else 0,
|
||||||
|
tipWindowType = if (parameters.isTip) 0 else 1,
|
||||||
|
confirmRequired = if (parameters.needConfirm) 1 else 0,
|
||||||
|
),
|
||||||
|
pinned = if (parameters.isPinned) 1 else 0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun GroupAnnouncement.toAnnouncement(group: Group): OnlineAnnouncementImpl {
|
||||||
|
val fid = this.fid ?: ""
|
||||||
|
|
||||||
|
return OnlineAnnouncementImpl(
|
||||||
|
group = group,
|
||||||
|
senderId = sender,
|
||||||
|
sender = group[sender],
|
||||||
|
title = msg.title ?: "",
|
||||||
|
body = msg.text,
|
||||||
|
parameters = buildAnnouncementParameters {
|
||||||
|
isPinned = pinned == 1
|
||||||
|
sendToNewMember = type == 20
|
||||||
|
isTip = settings.tipWindowType == 0
|
||||||
|
needConfirm = settings.confirmRequired == 1
|
||||||
|
isShowEditCard = settings.isShowEditCard == 1
|
||||||
|
},
|
||||||
|
fid = fid,
|
||||||
|
isAllRead = isAllConfirm != 0,
|
||||||
|
readMemberNumber = readNum,
|
||||||
|
publishTime = time
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -9,27 +9,22 @@
|
|||||||
|
|
||||||
@file:Suppress("DEPRECATION")
|
@file:Suppress("DEPRECATION")
|
||||||
|
|
||||||
package net.mamoe.mirai.data
|
package net.mamoe.mirai.internal.contact.announcement
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import net.mamoe.mirai.contact.announcement.Announcement
|
import net.mamoe.mirai.contact.announcement.AnnouncementImage
|
||||||
import net.mamoe.mirai.utils.MiraiExperimentalApi
|
import net.mamoe.mirai.utils.CheckableResponseA
|
||||||
|
import net.mamoe.mirai.utils.JsonStruct
|
||||||
|
import net.mamoe.mirai.utils.MiraiInternalApi
|
||||||
|
|
||||||
/**
|
|
||||||
* 群公告的协议数据结构. 仅同于内部操作, 用户请使用 [Announcement].
|
|
||||||
*
|
|
||||||
* @suppress 此 API 非常不稳定, 将在未来版本删除
|
|
||||||
*/
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class GroupAnnouncementList(
|
internal data class GroupAnnouncementList(
|
||||||
val ec: Int, //状态码 0 是正常的
|
@SerialName("ec") override val errorCode: Int = 0,
|
||||||
@SerialName("em") val msg: String, //信息
|
@SerialName("em") override val errorMessage: String? = null,
|
||||||
val feeds: List<GroupAnnouncement>? = null, //群公告列表
|
val feeds: List<GroupAnnouncement>? = null, //群公告列表
|
||||||
val inst: List<GroupAnnouncement>? = null //置顶列表? 应该是发送给新成员的
|
val inst: List<GroupAnnouncement>? = null //置顶列表? 应该是发送给新成员的
|
||||||
) {
|
) : CheckableResponseA(), JsonStruct {
|
||||||
/*
|
/*
|
||||||
// notes from original implementor, luo123, on 2020/3/13
|
// notes from original implementor, luo123, on 2020/3/13
|
||||||
|
|
||||||
@ -41,52 +36,45 @@ public data class GroupAnnouncementList(
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 群公告的协议数据结构. 仅同于内部操作, 用户请使用 [AnnouncementImpl].
|
|
||||||
*
|
|
||||||
* @suppress 此 API 非常不稳定, 将在未来版本删除
|
|
||||||
*/
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class GroupAnnouncement(
|
internal data class GroupAnnouncement(
|
||||||
@SerialName("u") val sender: Long = 0, //发送者id
|
@SerialName("u") val sender: Long = 0, //发送者id
|
||||||
val msg: GroupAnnouncementMsg,
|
val msg: GroupAnnouncementMsg,
|
||||||
val type: Int = 0, //20 为inst , 6 为feeds
|
val type: Int = 0, //20 为inst , 6 为feeds
|
||||||
val settings: GroupAnnouncementSettings? = null,
|
val settings: GroupAnnouncementSettings = GroupAnnouncementSettings.DEFAULT,
|
||||||
@SerialName("pubt") val time: Long = 0, //发布时间
|
@SerialName("pubt") val time: Long = 0, //发布时间
|
||||||
@SerialName("read_num") val readNum: Int = 0, //如果需要确认,则为确认收到的人数,反之则为已经阅读的人数
|
@SerialName("read_num") val readNum: Int = 0, //如果需要确认,则为确认收到的人数,反之则为已经阅读的人数
|
||||||
@SerialName("is_read") val isRead: Int = 0, //好像没用
|
@SerialName("is_read") val isRead: Int = 0, //好像没用
|
||||||
@SerialName("is_all_confirm") val isAllConfirm: Int = 0, //为0 则未全部收到
|
@SerialName("is_all_confirm") val isAllConfirm: Int = 0, //为0 则未全部收到
|
||||||
val pinned: Int = 0, //1为置顶, 0为默认
|
val pinned: Int = 0, //1为置顶, 0为默认
|
||||||
val fid: String? = null, //公告的id
|
val fid: String? = null, //公告的id
|
||||||
)
|
) : JsonStruct
|
||||||
|
|
||||||
/**
|
|
||||||
* 群公告的协议数据结构. 仅同于内部操作, 用户请使用 [AnnouncementImpl].
|
|
||||||
*
|
|
||||||
* @suppress 此 API 非常不稳定, 将在未来版本删除
|
|
||||||
*/
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class GroupAnnouncementMsg(
|
internal class GroupAnnouncementImage @MiraiInternalApi constructor(
|
||||||
|
@SerialName("h") val height: String,
|
||||||
|
@SerialName("w") val width: String,
|
||||||
|
@SerialName("id") val id: String
|
||||||
|
) : JsonStruct {
|
||||||
|
fun toPublic(): AnnouncementImage = AnnouncementImage(height, width, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
internal data class GroupAnnouncementMsg(
|
||||||
val text: String,
|
val text: String,
|
||||||
val text_face: String? = null,
|
val text_face: String? = null,
|
||||||
val title: String? = null
|
val title: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* 群公告的协议数据结构. 仅同于内部操作, 用户请使用 [AnnouncementImpl].
|
|
||||||
*
|
|
||||||
* @suppress 此 API 非常不稳定, 将在未来版本删除
|
|
||||||
*/
|
|
||||||
@Deprecated("Will be removed in the future. Use Announcement instead.", level = DeprecationLevel.WARNING)
|
|
||||||
@MiraiExperimentalApi
|
|
||||||
@Serializable
|
@Serializable
|
||||||
public data class GroupAnnouncementSettings(
|
internal data class GroupAnnouncementSettings(
|
||||||
@SerialName("is_show_edit_card") val isShowEditCard: Int = 0, //引导群成员修改该昵称 1 引导
|
@SerialName("is_show_edit_card") val isShowEditCard: Int = 0, //引导群成员修改该昵称 1 引导
|
||||||
@SerialName("remind_ts") val remindTs: Int = 0,
|
@SerialName("remind_ts") val remindTs: Int = 0,
|
||||||
@SerialName("tip_window_type") val tipWindowType: Int = 0, //是否用弹窗展示 1 不使用
|
@SerialName("tip_window_type") val tipWindowType: Int = 0, //是否用弹窗展示 1 不使用
|
||||||
@SerialName("confirm_required") val confirmRequired: Int = 0 // 是否需要确认收到 1 需要
|
@SerialName("confirm_required") val confirmRequired: Int = 0 // 是否需要确认收到 1 需要
|
||||||
)
|
) : JsonStruct {
|
||||||
|
companion object {
|
||||||
|
val DEFAULT = GroupAnnouncementSettings()
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,8 @@ import kotlinx.io.core.ByteReadPacket
|
|||||||
import kotlinx.io.core.writeFully
|
import kotlinx.io.core.writeFully
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.mamoe.mirai.internal.AbstractBot
|
||||||
|
import net.mamoe.mirai.internal.network.components.BotClientHolder
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
|
|
||||||
|
|
||||||
@ -137,6 +139,10 @@ internal data class WLoginSigInfo(
|
|||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
|
return "WLoginSigInfo(uin=$uin, encryptA1=${encryptA1?.toUHexString()}, noPicSig=${noPicSig?.toUHexString()}, simpleInfo=$simpleInfo, appPri=$appPri, a2ExpiryTime=$a2ExpiryTime, loginBitmap=$loginBitmap, tgt=${tgt.toUHexString()}, a2CreationTime=$a2CreationTime, tgtKey=${tgtKey.toUHexString()}, userStSig=$userStSig, userStKey=${userStKey.toUHexString()}, userStWebSig=$userStWebSig, userA5=$userA5, userA8=$userA8, lsKey=$lsKey, sKey=$sKey, userSig64=$userSig64, openId=${openId.toUHexString()}, openKey=$openKey, vKey=$vKey, accessToken=$accessToken, d2=$d2, d2Key=${d2Key.toUHexString()}, sid=$sid, aqSig=$aqSig, psKey=$psKeyMap, superKey=${superKey.toUHexString()}, payToken=${payToken.toUHexString()}, pf=${pf.toUHexString()}, pfKey=${pfKey.toUHexString()}, da2=${da2.toUHexString()}, wtSessionTicket=$wtSessionTicket, wtSessionTicketKey=${wtSessionTicketKey.toUHexString()}, deviceToken=${deviceToken.toUHexString()})"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getPsKey(name: String): String {
|
||||||
|
return psKeyMap[name]?.data?.encodeToString() ?: error("Cannot find PsKey $name")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal typealias PSKeyMap = MutableMap<String, KeyWithExpiry>
|
internal typealias PSKeyMap = MutableMap<String, KeyWithExpiry>
|
||||||
@ -173,6 +179,11 @@ internal open class KeyWithExpiry(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val KeyWithExpiry.str get() = data.encodeToString()
|
||||||
|
internal val AbstractBot.sKey get() = client.wLoginSigInfo.sKey.str
|
||||||
|
internal fun AbstractBot.psKey(name: String) = client.wLoginSigInfo.getPsKey(name)
|
||||||
|
internal val AbstractBot.client get() = components[BotClientHolder].client
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
internal open class KeyWithCreationTime(
|
internal open class KeyWithCreationTime(
|
||||||
open val data: ByteArray,
|
open val data: ByteArray,
|
||||||
|
@ -13,9 +13,13 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.internal.utils.io
|
package net.mamoe.mirai.internal.utils.io
|
||||||
|
|
||||||
|
import io.ktor.utils.io.streams.*
|
||||||
import kotlinx.io.core.*
|
import kotlinx.io.core.*
|
||||||
|
import kotlinx.io.streams.outputStream
|
||||||
import net.mamoe.mirai.internal.utils.coerceAtMostOrFail
|
import net.mamoe.mirai.internal.utils.coerceAtMostOrFail
|
||||||
import net.mamoe.mirai.internal.utils.crypto.TEA
|
import net.mamoe.mirai.internal.utils.crypto.TEA
|
||||||
|
import net.mamoe.mirai.utils.ExternalResource
|
||||||
|
import net.mamoe.mirai.utils.withUse
|
||||||
|
|
||||||
internal fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLength: Int) {
|
internal fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArray, maxLength: Int) {
|
||||||
if (array.size <= maxLength) {
|
if (array.size <= maxLength) {
|
||||||
@ -29,6 +33,17 @@ internal fun BytePacketBuilder.writeShortLVByteArrayLimitedLength(array: ByteArr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun BytePacketBuilder.writeResource(
|
||||||
|
resource: ExternalResource,
|
||||||
|
close: Boolean = false,
|
||||||
|
): Long = resource.inputStream().withUse { copyTo(outputStream()) }.also {
|
||||||
|
if (close) resource.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun io.ktor.utils.io.core.BytePacketBuilder.writeResource(
|
||||||
|
resource: ExternalResource,
|
||||||
|
): Long = resource.inputStream().withUse { copyTo(outputStream()) }
|
||||||
|
|
||||||
internal inline fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int {
|
internal inline fun BytePacketBuilder.writeShortLVByteArray(byteArray: ByteArray): Int {
|
||||||
this.writeShort(byteArray.size.toShort())
|
this.writeShort(byteArray.size.toShort())
|
||||||
this.writeFully(byteArray)
|
this.writeFully(byteArray)
|
||||||
|
Loading…
Reference in New Issue
Block a user