From 1c79da0f3845b8ea8538cbf8fa9c3748ab6e6b1d Mon Sep 17 00:00:00 2001 From: cssxsh Date: Thu, 3 Nov 2022 18:42:21 +0800 Subject: [PATCH] [core] feat: Announcement confirmed member api (#2255) * feat: Announcement confirmed * add: native * add: todo * fix: dump --- .../android/api/android.api | 8 +++ .../compatibility-validation/jvm/api/jvm.api | 8 +++ .../contact/announcement/Announcements.kt | 26 ++++++++ .../announcement/OnlineAnnouncement.kt | 23 +++++++ .../internal/contact/MockAnnouncementsImpl.kt | 14 +++++ .../contact/announcement/AnnouncementsImpl.kt | 60 +++++++++++++++++++ .../contact/announcement/GroupAnnouncement.kt | 24 ++++++++ 7 files changed, 163 insertions(+) diff --git a/mirai-core-api/compatibility-validation/android/api/android.api b/mirai-core-api/compatibility-validation/android/api/android.api index ecb963c03..55316d25e 100644 --- a/mirai-core-api/compatibility-validation/android/api/android.api +++ b/mirai-core-api/compatibility-validation/android/api/android.api @@ -844,8 +844,10 @@ public abstract interface class net/mamoe/mirai/contact/announcement/Announcemen public abstract fun delete (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun get (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun members (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun remind (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -886,6 +888,12 @@ public abstract interface class net/mamoe/mirai/contact/announcement/OnlineAnnou public abstract fun getPublicationTime ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getSenderId ()J + public fun members (Z)Ljava/util/List; + public fun members (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun members$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun remind ()V + public fun remind (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun remind$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt { diff --git a/mirai-core-api/compatibility-validation/jvm/api/jvm.api b/mirai-core-api/compatibility-validation/jvm/api/jvm.api index 43a9e77f1..1d69ea44e 100644 --- a/mirai-core-api/compatibility-validation/jvm/api/jvm.api +++ b/mirai-core-api/compatibility-validation/jvm/api/jvm.api @@ -844,8 +844,10 @@ public abstract interface class net/mamoe/mirai/contact/announcement/Announcemen public abstract fun delete (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun get (Ljava/lang/String;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun members (Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;)Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement; public abstract fun publish (Lnet/mamoe/mirai/contact/announcement/Announcement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun remind (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/contact/announcement/AnnouncementImage; public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -886,6 +888,12 @@ public abstract interface class net/mamoe/mirai/contact/announcement/OnlineAnnou public abstract fun getPublicationTime ()J public abstract fun getSender ()Lnet/mamoe/mirai/contact/NormalMember; public abstract fun getSenderId ()J + public fun members (Z)Ljava/util/List; + public fun members (ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun members$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun remind ()V + public fun remind (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun remind$suspendImpl (Lnet/mamoe/mirai/contact/announcement/OnlineAnnouncement;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class net/mamoe/mirai/contact/announcement/OnlineAnnouncementKt { diff --git a/mirai-core-api/src/commonMain/kotlin/contact/announcement/Announcements.kt b/mirai-core-api/src/commonMain/kotlin/contact/announcement/Announcements.kt index 3863dce26..91afae201 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/announcement/Announcements.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/announcement/Announcements.kt @@ -12,6 +12,7 @@ package net.mamoe.mirai.contact.announcement import kotlinx.coroutines.flow.Flow import me.him188.kotlin.jvm.blocking.bridge.JvmBlockingBridge import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.PermissionDeniedException import net.mamoe.mirai.utils.DeprecatedSinceMirai import net.mamoe.mirai.utils.ExternalResource @@ -84,6 +85,31 @@ public interface Announcements : Streamable { @JvmBlockingBridge public suspend fun uploadImage(resource: ExternalResource): AnnouncementImage + /** + * 获取 已确认/未确认 的群成员 + * + * @param fid 公告的 [OnlineAnnouncement.fid] + * @param confirmed 是否确认 + * @return 群成员列表 + * + * @throws PermissionDeniedException 当没有权限时抛出 + * @throws IllegalStateException 当协议异常时抛出 + * + * @see OnlineAnnouncement.members + */ + public suspend fun members(fid: String, confirmed: Boolean): List + + /** + * 提醒 未确认 的群成员 + * + * @param fid 公告的 [OnlineAnnouncement.fid] + * + * @throws PermissionDeniedException 当没有权限时抛出 + * @throws IllegalStateException 当协议异常时抛出 + * + * @see OnlineAnnouncement.remind + */ + public suspend fun remind(fid: String) // no blocking bridge for this method @Suppress("INAPPLICABLE_JVM_NAME") diff --git a/mirai-core-api/src/commonMain/kotlin/contact/announcement/OnlineAnnouncement.kt b/mirai-core-api/src/commonMain/kotlin/contact/announcement/OnlineAnnouncement.kt index 66daa849d..eb2471bd3 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/announcement/OnlineAnnouncement.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/announcement/OnlineAnnouncement.kt @@ -77,6 +77,29 @@ public interface OnlineAnnouncement : Announcement { * @see Announcements.delete */ public suspend fun delete(): Boolean = group.announcements.delete(fid) + + /** + * 获取 已确认/未确认 的群成员 + * + * @param confirmed 是否确认 + * @return 群成员列表 + * + * @throws PermissionDeniedException 当没有权限时抛出 + * @throws IllegalStateException 当协议异常时抛出 + * + * @see Announcements.members + */ + public suspend fun members(confirmed: Boolean): List = group.announcements.members(fid, confirmed) + + /** + * 提醒 未确认 的群成员 + * + * @throws PermissionDeniedException 当没有权限时抛出 + * @throws IllegalStateException 当协议异常时抛出 + * + * @see Announcements.remind + */ + public suspend fun remind(): Unit = group.announcements.remind(fid) } /** diff --git a/mirai-core-mock/src/internal/contact/MockAnnouncementsImpl.kt b/mirai-core-mock/src/internal/contact/MockAnnouncementsImpl.kt index 83187ff0e..e9bf35074 100644 --- a/mirai-core-mock/src/internal/contact/MockAnnouncementsImpl.kt +++ b/mirai-core-mock/src/internal/contact/MockAnnouncementsImpl.kt @@ -92,4 +92,18 @@ internal class MockAnnouncementsImpl( override suspend fun uploadImage(resource: ExternalResource): AnnouncementImage = resource.inResource { AnnouncementImage.create(generateImageId(resource.md5), 500, 500) } + + override suspend fun members(fid: String, confirmed: Boolean): List { + if (!group.botPermission.isOperator()) { + throw PermissionDeniedException("Only administrator have permission see announcement confirmed detail") + } + // TODO: 设置用户可读状态,而不返回全部 + return group.members.toList() + } + + override suspend fun remind(fid: String) { + if (!group.botPermission.isOperator()) { + throw PermissionDeniedException("Only administrator have permission send announcement remind") + } + } } diff --git a/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt b/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt index 754359f91..c6f7e7c5a 100644 --- a/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/announcement/AnnouncementsImpl.kt @@ -14,20 +14,25 @@ import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.http.* import kotlinx.coroutines.flow.* +import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import net.mamoe.mirai.contact.Group import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.NormalMember import net.mamoe.mirai.contact.announcement.* import net.mamoe.mirai.contact.checkBotPermission import net.mamoe.mirai.internal.AbstractBot import net.mamoe.mirai.internal.QQAndroidBot import net.mamoe.mirai.internal.contact.GroupImpl import net.mamoe.mirai.internal.contact.OnlineAnnouncementImpl +import net.mamoe.mirai.internal.contact.active.defaultJson 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.getRawGroupAnnouncements +import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.getReadDetail import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.sendGroupAnnouncement +import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.sendRemind import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toAnnouncement import net.mamoe.mirai.internal.contact.announcement.AnnouncementProtocol.toGroupAnnouncement import net.mamoe.mirai.internal.message.contextualBugReportException @@ -140,6 +145,17 @@ internal abstract class CommonAnnouncementsImpl( AnnouncementProtocol.uploadGroupAnnouncementImage(bot, resource) } } + + override suspend fun members(fid: String, confirmed: Boolean): List { + group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission see announcement confirmed detail" } + val detail = bot.getReadDetail(groupId = group.id, fid = fid, read = confirmed) + return detail.users.mapNotNull { user -> group[user.uin] } + } + + override suspend fun remind(fid: String) { + group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission send announcement remind" } + bot.sendRemind(groupId = group.id, fid = fid) + } } private val serversStub = listOf("web.qun.qq.com" to 80) @@ -296,6 +312,50 @@ internal object AnnouncementProtocol { append("format", "json") }) + private fun CgiData.loadData(serializer: KSerializer): T = + defaultJson.decodeFromJsonElement(serializer, this.data) + + suspend fun QQAndroidBot.getReadDetail(groupId: Long, fid: String, read: Boolean): GroupAnnouncementReadDetail { + val cgi = bot.components[HttpClientProvider].getHttpClient().post { + url("https://qun.qq.com/cgi-bin/qunapp/announce_unread") + parameter("gc", groupId) + parameter("start", 0) + parameter("num", 3000) + parameter("feed_id", fid) + parameter("type", if (read) 1 else 0) + parameter("bkn", client.wLoginSigInfo.bkn) + headers { + // ktor bug + append( + "cookie", + "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey("qun.qq.com")};" + ) + } + }.bodyAsText().loadAs(CgiData.serializer()) + check(cgi.cgicode == 0) { cgi.errorMessage } + return cgi.loadData(GroupAnnouncementReadDetail.serializer()) + } + + suspend fun QQAndroidBot.sendRemind(groupId: Long, fid: String) { + val cgi = bot.components[HttpClientProvider].getHttpClient().post { + url("https://qun.qq.com/cgi-bin/qunapp/announce_remindread") + parameter("gc", groupId) + parameter("feed_id", fid) + parameter("bkn", client.wLoginSigInfo.bkn) + headers { + // ktor bug + append( + "cookie", + "uin=o${id}; skey=${sKey}; p_uin=o${id}; p_skey=${psKey("qun.qq.com")};" + ) + } + }.bodyAsText().loadAs(CgiData.serializer()) + // 14 "该公告不存在" + // 54016 "该公告已提醒多次,不可再提醒。" + // 54010 "提醒太频繁,请稍后再试" + check(cgi.cgicode == 0) { cgi.errorMessage } + } + fun Announcement.toGroupAnnouncement(senderId: Long): GroupAnnouncement { return GroupAnnouncement( sender = senderId, diff --git a/mirai-core/src/commonMain/kotlin/contact/announcement/GroupAnnouncement.kt b/mirai-core/src/commonMain/kotlin/contact/announcement/GroupAnnouncement.kt index 6a6dad70f..131410178 100644 --- a/mirai-core/src/commonMain/kotlin/contact/announcement/GroupAnnouncement.kt +++ b/mirai-core/src/commonMain/kotlin/contact/announcement/GroupAnnouncement.kt @@ -13,6 +13,7 @@ package net.mamoe.mirai.internal.contact.announcement import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement import net.mamoe.mirai.contact.announcement.AnnouncementImage import net.mamoe.mirai.utils.CheckableResponseA import net.mamoe.mirai.utils.JsonStruct @@ -78,3 +79,26 @@ internal data class GroupAnnouncementSettings( val DEFAULT = GroupAnnouncementSettings() } } + +@Serializable +internal data class CgiData( + @SerialName("cgicode") val cgicode: Int, + @SerialName("data") val `data`: JsonElement, + @SerialName("msg") override val errorMessage: String, + @SerialName("retcode") override val errorCode: Int +) : CheckableResponseA(), JsonStruct + +@Serializable +internal data class GroupAnnouncementReadDetail( + @SerialName("read_total") val readTotal: Int = 0, + @SerialName("unread_total") val unreadTotal: Int = 0, + @SerialName("users") val users: List = emptyList() +) { + @Serializable + data class User( + @SerialName("avatar") val avatar: String, + @SerialName("display_name") val displayName: String, + @SerialName("face_flag") val faceFlag: Int, + @SerialName("uin") val uin: Long + ) +} \ No newline at end of file