diff --git a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api index 5c7ed5577..accd37aee 100644 --- a/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api +++ b/binary-compatibility-validator/android/api/binary-compatibility-validator-android.api @@ -362,6 +362,7 @@ public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutin public abstract fun getSettings ()Lnet/mamoe/mirai/contact/GroupSettings; public fun quit ()Z public abstract fun quit (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendAnnouncement$default (Lnet/mamoe/mirai/contact/Group$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;ILjava/lang/Object;)V public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt; @@ -376,6 +377,8 @@ public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutin } public final class net/mamoe/mirai/contact/Group$Companion { + public static synthetic fun sendAnnouncement$default (Lnet/mamoe/mirai/contact/Group$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;ILjava/lang/Object;)V + public static synthetic fun sendAnnouncement$default (Lnet/mamoe/mirai/contact/Group$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;)Z public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -569,6 +572,62 @@ public abstract interface class net/mamoe/mirai/contact/UserOrBot : net/mamoe/mi public abstract fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; } +public class net/mamoe/mirai/data/Announcement { + public static final field Companion Lnet/mamoe/mirai/data/Announcement$Companion; + public static final fun create (JLjava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;)Lnet/mamoe/mirai/data/Announcement; + public final fun getBotId ()J + public final fun getMsg ()Ljava/lang/String; + public final fun getParameters ()Lnet/mamoe/mirai/data/AnnouncementParameters; + public final fun getTitle ()Ljava/lang/String; + public synthetic fun publish (Lnet/mamoe/mirai/contact/Group;)Lkotlin/Unit; + public fun publish (Lnet/mamoe/mirai/contact/Group;)V + public final fun publish (Lnet/mamoe/mirai/contact/Group;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class net/mamoe/mirai/data/Announcement$Companion { + public final fun create (JLjava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;)Lnet/mamoe/mirai/data/Announcement; +} + +public final class net/mamoe/mirai/data/AnnouncementKt { + public static final fun buildAnnouncementParameters (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/data/AnnouncementParameters; +} + +public final class net/mamoe/mirai/data/AnnouncementParameters { + public fun <init> ()V + public final fun builder ()Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun getImage ()[B + public final fun getNeedConfirm ()Z + public final fun getSendToNewMember ()Z + public final fun isPinned ()Z + public final fun isShowEditCard ()Z + public final fun isTip ()Z +} + +public final class net/mamoe/mirai/data/AnnouncementParametersBuilder { + public fun <init> ()V + public fun <init> (Lnet/mamoe/mirai/data/AnnouncementParameters;)V + public synthetic fun <init> (Lnet/mamoe/mirai/data/AnnouncementParameters;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lnet/mamoe/mirai/data/AnnouncementParameters; + public final fun getImage ()[B + public final fun getNeedConfirm ()Z + public final fun getSendToNewMember ()Z + public final fun image ([B)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun isPinned ()Z + public final fun isShowEditCard ()Z + public final fun isTip ()Z + public final fun needConfirm (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun pinned (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun sendToNewMember (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun setImage ([B)V + public final fun setNeedConfirm (Z)V + public final fun setPinned (Z)V + public final fun setSendToNewMember (Z)V + public final fun setShowEditCard (Z)V + public final fun setTip (Z)V + public final fun showEditCard (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun tip (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; +} + public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; @@ -870,6 +929,21 @@ public final class net/mamoe/mirai/data/GroupAnnouncement$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class net/mamoe/mirai/data/GroupAnnouncementImage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lnet/mamoe/mirai/data/GroupAnnouncementImage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/data/GroupAnnouncementImage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/data/GroupAnnouncementImage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class net/mamoe/mirai/data/GroupAnnouncementImage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class net/mamoe/mirai/data/GroupAnnouncementList$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/GroupAnnouncementList$$serializer; public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; @@ -1232,6 +1306,14 @@ public final class net/mamoe/mirai/data/OnlineStatus$Companion { public final fun ofIdOrNull (I)Lnet/mamoe/mirai/data/OnlineStatus; } +public final class net/mamoe/mirai/data/ReceiveAnnouncement : net/mamoe/mirai/data/Announcement { + public final fun getFid ()Ljava/lang/String; + public final fun getPublishTime ()J + public final fun getReadMemberNumber ()I + public final fun getSenderId ()J + public final fun isAllRead ()Z +} + public abstract interface class net/mamoe/mirai/data/StrangerInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getFromGroup ()J public abstract fun getNick ()Ljava/lang/String; diff --git a/binary-compatibility-validator/api/binary-compatibility-validator.api b/binary-compatibility-validator/api/binary-compatibility-validator.api index 393e1be87..0c44154bb 100644 --- a/binary-compatibility-validator/api/binary-compatibility-validator.api +++ b/binary-compatibility-validator/api/binary-compatibility-validator.api @@ -362,6 +362,7 @@ public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutin public abstract fun getSettings ()Lnet/mamoe/mirai/contact/GroupSettings; public fun quit ()Z public abstract fun quit (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static synthetic fun sendAnnouncement$default (Lnet/mamoe/mirai/contact/Group$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;ILjava/lang/Object;)V public fun sendMessage (Ljava/lang/String;)Lnet/mamoe/mirai/message/MessageReceipt; public fun sendMessage (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun sendMessage (Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/MessageReceipt; @@ -376,6 +377,8 @@ public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutin } public final class net/mamoe/mirai/contact/Group$Companion { + public static synthetic fun sendAnnouncement$default (Lnet/mamoe/mirai/contact/Group$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;ILjava/lang/Object;)V + public static synthetic fun sendAnnouncement$default (Lnet/mamoe/mirai/contact/Group$Companion;Lnet/mamoe/mirai/contact/Group;Ljava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;)Z public final fun setEssenceMessage (Lnet/mamoe/mirai/contact/Group;Lnet/mamoe/mirai/message/data/MessageChain;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } @@ -569,6 +572,62 @@ public abstract interface class net/mamoe/mirai/contact/UserOrBot : net/mamoe/mi public abstract fun nudge ()Lnet/mamoe/mirai/message/action/Nudge; } +public class net/mamoe/mirai/data/Announcement { + public static final field Companion Lnet/mamoe/mirai/data/Announcement$Companion; + public static final fun create (JLjava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;)Lnet/mamoe/mirai/data/Announcement; + public final fun getBotId ()J + public final fun getMsg ()Ljava/lang/String; + public final fun getParameters ()Lnet/mamoe/mirai/data/AnnouncementParameters; + public final fun getTitle ()Ljava/lang/String; + public synthetic fun publish (Lnet/mamoe/mirai/contact/Group;)Lkotlin/Unit; + public fun publish (Lnet/mamoe/mirai/contact/Group;)V + public final fun publish (Lnet/mamoe/mirai/contact/Group;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class net/mamoe/mirai/data/Announcement$Companion { + public final fun create (JLjava/lang/String;Ljava/lang/String;Lnet/mamoe/mirai/data/AnnouncementParameters;)Lnet/mamoe/mirai/data/Announcement; +} + +public final class net/mamoe/mirai/data/AnnouncementKt { + public static final fun buildAnnouncementParameters (Lkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/data/AnnouncementParameters; +} + +public final class net/mamoe/mirai/data/AnnouncementParameters { + public fun <init> ()V + public final fun builder ()Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun getImage ()[B + public final fun getNeedConfirm ()Z + public final fun getSendToNewMember ()Z + public final fun isPinned ()Z + public final fun isShowEditCard ()Z + public final fun isTip ()Z +} + +public final class net/mamoe/mirai/data/AnnouncementParametersBuilder { + public fun <init> ()V + public fun <init> (Lnet/mamoe/mirai/data/AnnouncementParameters;)V + public synthetic fun <init> (Lnet/mamoe/mirai/data/AnnouncementParameters;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun build ()Lnet/mamoe/mirai/data/AnnouncementParameters; + public final fun getImage ()[B + public final fun getNeedConfirm ()Z + public final fun getSendToNewMember ()Z + public final fun image ([B)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun isPinned ()Z + public final fun isShowEditCard ()Z + public final fun isTip ()Z + public final fun needConfirm (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun pinned (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun sendToNewMember (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun setImage ([B)V + public final fun setNeedConfirm (Z)V + public final fun setPinned (Z)V + public final fun setSendToNewMember (Z)V + public final fun setShowEditCard (Z)V + public final fun setTip (Z)V + public final fun showEditCard (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; + public final fun tip (Z)Lnet/mamoe/mirai/data/AnnouncementParametersBuilder; +} + public abstract interface class net/mamoe/mirai/data/FriendInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getNick ()Ljava/lang/String; public abstract fun getRemark ()Ljava/lang/String; @@ -870,6 +929,21 @@ public final class net/mamoe/mirai/data/GroupAnnouncement$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public final class net/mamoe/mirai/data/GroupAnnouncementImage$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public static final field INSTANCE Lnet/mamoe/mirai/data/GroupAnnouncementImage$$serializer; + public fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/data/GroupAnnouncementImage; + public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/data/GroupAnnouncementImage;)V + public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class net/mamoe/mirai/data/GroupAnnouncementImage$Companion { + public final fun serializer ()Lkotlinx/serialization/KSerializer; +} + public final class net/mamoe/mirai/data/GroupAnnouncementList$$serializer : kotlinx/serialization/internal/GeneratedSerializer { public static final field INSTANCE Lnet/mamoe/mirai/data/GroupAnnouncementList$$serializer; public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor; @@ -1232,6 +1306,14 @@ public final class net/mamoe/mirai/data/OnlineStatus$Companion { public final fun ofIdOrNull (I)Lnet/mamoe/mirai/data/OnlineStatus; } +public final class net/mamoe/mirai/data/ReceiveAnnouncement : net/mamoe/mirai/data/Announcement { + public final fun getFid ()Ljava/lang/String; + public final fun getPublishTime ()J + public final fun getReadMemberNumber ()I + public final fun getSenderId ()J + public final fun isAllRead ()Z +} + public abstract interface class net/mamoe/mirai/data/StrangerInfo : net/mamoe/mirai/data/UserInfo { public abstract fun getFromGroup ()J public abstract fun getNick ()Ljava/lang/String; diff --git a/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt b/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt index 0417208e3..caad5c00a 100644 --- a/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt +++ b/mirai-core-api/src/commonMain/kotlin/LowLevelApiAccessor.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.Job import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.contact.* import net.mamoe.mirai.data.* +import net.mamoe.mirai.utils.ExternalResource import net.mamoe.mirai.utils.MiraiExperimentalApi import net.mamoe.mirai.utils.WeakRef import kotlin.annotation.AnnotationTarget.* @@ -141,6 +142,18 @@ public interface LowLevelApiAccessor { amount: Int = 10 ): GroupAnnouncementList + /** + * 上传群公告的所需要的一个图片,但不发送 + * + */ + @LowLevelApi + @MiraiExperimentalApi + public suspend fun uploadGroupAnnouncementImage( + bot: Bot, + groupId: Long, + resource: ExternalResource + ): GroupAnnouncementImage + /** * 发送群公告 * @@ -154,6 +167,19 @@ public interface LowLevelApiAccessor { announcement: GroupAnnouncement ): String + /** + * 发送包含图片的群公告 + * + * @return 公告的fid + */ + @LowLevelApi + @MiraiExperimentalApi + public suspend fun sendGroupAnnouncementWithImage( + bot: Bot, + groupId: Long, + image: GroupAnnouncementImage, + announcement: GroupAnnouncement + ):String /** * 删除群公告 diff --git a/mirai-core-api/src/commonMain/kotlin/contact/Group.kt b/mirai-core-api/src/commonMain/kotlin/contact/Group.kt index cc00b48af..497fe1421 100644 --- a/mirai-core-api/src/commonMain/kotlin/contact/Group.kt +++ b/mirai-core-api/src/commonMain/kotlin/contact/Group.kt @@ -12,8 +12,14 @@ package net.mamoe.mirai.contact import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow import net.mamoe.kjbb.JvmBlockingBridge import net.mamoe.mirai.Bot +import net.mamoe.mirai.Mirai +import net.mamoe.mirai.data.Announcement +import net.mamoe.mirai.data.AnnouncementParameters +import net.mamoe.mirai.data.ReceiveAnnouncement +import net.mamoe.mirai.data.covertToGroupAnnouncement import net.mamoe.mirai.event.events.* import net.mamoe.mirai.message.MessageReceipt import net.mamoe.mirai.message.data.* @@ -175,6 +181,28 @@ public interface Group : Contact, CoroutineScope, FileSupported { */ public suspend fun setEssenceMessage(source: MessageSource): Boolean + /** + * 获取所有群公告列表 + */ + @MiraiExperimentalApi + public suspend fun getAnnouncements(): Flow<Announcement> + + /** + * 删除一条群公告 + * @param fid 公告的id [ReceiveAnnouncement.fid] + * + * @throws PermissionDeniedException 没有权限时抛出 + */ + @MiraiExperimentalApi + public suspend fun deleteAnnouncement(fid: String) + + /** + * 获取一条群公告 + * @param fid 公告的id [ReceiveAnnouncement.fid] + */ + @MiraiExperimentalApi + public suspend fun getAnnouncement(fid: String): ReceiveAnnouncement + public companion object { /** * 将一条消息设置为群精华消息, 需要管理员或群主权限. @@ -188,6 +216,39 @@ public interface Group : Contact, CoroutineScope, FileSupported { @JvmBlockingBridge @JvmStatic public suspend fun Group.setEssenceMessage(chain: MessageChain): Boolean = setEssenceMessage(chain.source) + + /** + * 发送一个 [Announcement] + * + * @param title 公告标题 + * @param msg 公告内容 + * @param announcementParameters 公告设置 + */ + @MiraiExperimentalApi + @JvmBlockingBridge + @JvmStatic + public suspend fun Group.sendAnnouncement( + title: String, + msg: String, + announcementParameters: AnnouncementParameters = AnnouncementParameters() + ) { + checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to send group announcement" } + Mirai.sendGroupAnnouncement( + bot, + id, + Announcement(bot.id, title, msg, announcementParameters).covertToGroupAnnouncement() + ) + } + + /** + * 删除一条群公告 + * @param receiveAnnouncement 公告 [ReceiveAnnouncement] + */ + @MiraiExperimentalApi + @JvmBlockingBridge + @JvmStatic + public suspend fun Group.deleteAnnouncement(receiveAnnouncement: ReceiveAnnouncement): Unit = + deleteAnnouncement(receiveAnnouncement.fid) } } @@ -258,3 +319,4 @@ public inline fun Group.getMemberOrFail(id: Long): NormalMember = getOrFail(id) * @see Group.botMuteRemaining 剩余禁言时间 */ public inline val Group.isBotMuted: Boolean get() = this.botMuteRemaining != 0 + diff --git a/mirai-core-api/src/commonMain/kotlin/data/Announcement.kt b/mirai-core-api/src/commonMain/kotlin/data/Announcement.kt new file mode 100644 index 000000000..52e24dc69 --- /dev/null +++ b/mirai-core-api/src/commonMain/kotlin/data/Announcement.kt @@ -0,0 +1,312 @@ +/* + * 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/master/LICENSE + */ + +@file:Suppress("unused") +@file:JvmBlockingBridge + +package net.mamoe.mirai.data + +import net.mamoe.kjbb.JvmBlockingBridge +import net.mamoe.mirai.Mirai +import net.mamoe.mirai.contact.Group +import net.mamoe.mirai.contact.MemberPermission +import net.mamoe.mirai.contact.checkBotPermission +import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource +import java.time.Instant + +/** + * 表示一个群公告. [ReceiveAnnouncement] 表示 + * + * 可通过 [Announcement.create] 构造. + * + * @see Announcement + * + * @since 2.7 + */ +public open class Announcement internal constructor( + /** + * bot的Id + */ + public val botId: Long, + + /** + * 公告的标题 + */ + public val title: String, + + /** + * 公告的内容 + */ + public val msg: String, + + /** + * 公告的可变参数. 可以通过 [AnnouncementParametersBuilder] 构建获得. + * @see AnnouncementParameters + * @see AnnouncementParametersBuilder + */ + public val parameters: AnnouncementParameters +) { + /** + * 发送该公告到群 + */ + public suspend fun publish(group: Group) { + val bot = group.bot + group.checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to send group announcement" } + if (parameters.image == null) + Mirai.sendGroupAnnouncement(bot, group.id, covertToGroupAnnouncement()) + else { + parameters.image.toExternalResource().use { + val image = + Mirai.uploadGroupAnnouncementImage(bot, group.id, it) + Mirai.sendGroupAnnouncementWithImage(bot, group.id, image, covertToGroupAnnouncement()) + } + } + } + + public companion object { + /** + * 构造一个 [Announcement]. + * @see Announcement + */ + @JvmStatic + public fun create(botId: Long, title: String, msg: String, parameters: AnnouncementParameters): Announcement { + return Announcement(botId, title, msg, parameters) + } + } +} + +/** + * 群公告的扩展参数. + * + * 可通过 [AnnouncementParametersBuilder] 构建. [AnnouncementParameters] 用于 [创建公告][Announcement.create]. + * + * @since 2.7 + */ +public class AnnouncementParameters internal constructor( + /** + * 群公告的图片,目前仅支持发送图片,不支持获得图片 + */ + public val image: ByteArray? = null, + + /** + * 是否发送给新成员 + */ + public val sendToNewMember: Boolean = false, + + /** + * 是否置顶,可以有多个置顶公告 + */ + public val isPinned: Boolean = false, + + /** + * 是否显示能够引导群成员修改昵称的窗口 + */ + public val isShowEditCard: Boolean = false, + + /** + * 是否使用弹窗 + */ + public val isTip: Boolean = false, + + /** + * 是否需要群成员确认 + */ + public val needConfirm: Boolean = false, +) { + /** + * 以该对象的参数创建一个 [AnnouncementParametersBuilder]. + */ + public fun builder(): AnnouncementParametersBuilder = AnnouncementParametersBuilder().apply { + val outer = this@AnnouncementParameters + this.image = outer.image + this.sendToNewMember = outer.sendToNewMember + this.isPinned = outer.isPinned + this.isShowEditCard = outer.isShowEditCard + this.isTip = outer.isTip + this.needConfirm = outer.needConfirm + } +} + +/** + * 表示一个收到的群公告. 只能由 mirai 构造. + * + * @since 2.7 + */ +public class ReceiveAnnouncement internal constructor( + /** + * bot的 Id + */ + botId: Long, + /** + * 公告的标题 + */ + title: String, + /** + * 公告的内容 + */ + msg: String, + /** + * 公告的可变参数 + */ + parameters: AnnouncementParameters, + /** + * 公告发送者的 QQ 号 + */ + public val senderId: Long, + + /** + * 公告的 `fid,每个公告仅有一条 `fid`,类似于主键 + */ + public val fid: String, + + /** + * 所有人都已阅读, 如果 [AnnouncementParameters.needConfirm] 为 `true` 则为所有人都已确认, + */ + public val isAllRead: Boolean, + + /** + * 已经阅读的成员数量,如果 [AnnouncementParameters.needConfirm] 为 `true` 则为已经确认的成员数量 + */ + public val readMemberNumber: Int, + + /** + * 公告发出的时间,为 EpochSecond (自 1970-01-01T00:00:00Z 的秒数) + * + * @see Instant.ofEpochSecond + */ + public val publishTime: Long, +) : Announcement(botId, title, msg, parameters) + +/** + * [AnnouncementParameters] 的构建器. 可以构建一个 [AnnouncementParameters] 实例. + * + * ## 获得实例 + * + * 直接构造实例: `new AnnouncementParametersBuilder()` 或者从已有的公告中获取 [AnnouncementParameters.builder]. + * + * ## 使用 + * + * ### 在 Kotlin 使用 + * + * ``` + * val parameters = buildAnnouncementParameters { + * sendToNewMember = true + * // ... + * } + * ``` + * + * ### 在 Java 使用 + * + * ```java + * AnnouncementParameters parameters = new AnnouncementParametersBuilder() + * .sendToNewMember(true) + * .pinned(true) + * .build(); + * ``` + * + * @see buildAnnouncementParameters + * + * @since 2.7 + */ +public class AnnouncementParametersBuilder @JvmOverloads constructor( + prototype: AnnouncementParameters = AnnouncementParameters() +) { + public var image: ByteArray? = prototype.image + public var sendToNewMember: Boolean = prototype.sendToNewMember + public var isPinned: Boolean = prototype.isPinned + public var isShowEditCard: Boolean = prototype.isShowEditCard + public var isTip: Boolean = prototype.isTip + public var needConfirm: Boolean = prototype.needConfirm + + public fun image(image: ByteArray?): AnnouncementParametersBuilder { + this.image = image + return this + } + + public fun sendToNewMember(sendToNewMember: Boolean): AnnouncementParametersBuilder { + this.sendToNewMember = sendToNewMember + return this + } + + public fun pinned(isPinned: Boolean): AnnouncementParametersBuilder { + this.isPinned = isPinned + return this + } + + public fun showEditCard(isShowEditCard: Boolean): AnnouncementParametersBuilder { + this.isShowEditCard = isShowEditCard + return this + } + + public fun tip(isTip: Boolean): AnnouncementParametersBuilder { + this.isTip = isTip + return this + } + + public fun needConfirm(needConfirm: Boolean): AnnouncementParametersBuilder { + this.needConfirm = needConfirm + return this + } + + /** + * 使用当前参数构造 [AnnouncementParameters]. + */ + public fun build(): AnnouncementParameters = + AnnouncementParameters(image, sendToNewMember, isPinned, isShowEditCard, isTip, needConfirm) +} + +/** + * 使用 [AnnouncementParametersBuilder] 构建 [AnnouncementParameters]. + * @see AnnouncementParametersBuilder + * + * @since 2.7 + */ +public inline fun buildAnnouncementParameters( + builderAction: AnnouncementParametersBuilder.() -> Unit +): AnnouncementParameters = AnnouncementParametersBuilder().apply(builderAction).build() + +internal fun GroupAnnouncement.covertToAnnouncement(botId: Long): ReceiveAnnouncement { + check(this.fid != null) { "GroupAnnouncement don't have id" } + check(this.settings != null) { "GroupAnnouncement don't have setting" } + + return ReceiveAnnouncement( + botId = botId, + fid = fid, + senderId = sender, + publishTime = time, + title = msg.title ?: "", + msg = msg.text, + readMemberNumber = readNum, + isAllRead = isAllConfirm != 0, + parameters = buildAnnouncementParameters { + isPinned = pinned == 1 + sendToNewMember = type == 20 + isTip = settings.tipWindowType == 0 + needConfirm = settings.confirmRequired == 1 + isShowEditCard = settings.isShowEditCard == 1 + } + ) +} + +internal fun Announcement.covertToGroupAnnouncement(): GroupAnnouncement { + return GroupAnnouncement( + sender = botId, + msg = GroupAnnouncementMsg( + title = title, + text = msg + ), + 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, + ) +} \ No newline at end of file diff --git a/mirai-core-api/src/commonMain/kotlin/data/GroupAnnouncement.kt b/mirai-core-api/src/commonMain/kotlin/data/GroupAnnouncement.kt index ccdc72f4e..c85cfa61b 100644 --- a/mirai-core-api/src/commonMain/kotlin/data/GroupAnnouncement.kt +++ b/mirai-core-api/src/commonMain/kotlin/data/GroupAnnouncement.kt @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 Mamoe Technologies and contributors. + * 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. @@ -26,20 +26,22 @@ public data class GroupAnnouncementList( val ec: Int, //状态码 0 是正常的 @SerialName("em") val msg: String, //信息 val feeds: List<GroupAnnouncement>? = null, //群公告列表 - val inst: List<GroupAnnouncement>? = null //置顶列表? + val inst: List<GroupAnnouncement>? = null //置顶列表? 应该是发送给新成员的 ) @MiraiExperimentalApi @Serializable public data class GroupAnnouncement( - @SerialName("u") val sender: Long = 0, + @SerialName("u") val sender: Long = 0, //发送者id val msg: GroupAnnouncementMsg, + val type: Int = 0, //20 为inst , 6 为feeds val settings: GroupAnnouncementSettings? = null, - @SerialName("pubt") val time: Long = 0, - @SerialName("read_num") val readNum: Int = 0, - @SerialName("is_read") val isRead: Int = 0, - val pinned: Int = 0, - val fid: String? = null //公告的id + @SerialName("pubt") val time: Long = 0, //发布时间 + @SerialName("read_num") val readNum: Int = 0, //如果需要确认,则为确认收到的人数,反之则为已经阅读的人数 + @SerialName("is_read") val isRead: Int = 0, //好像没用 + @SerialName("is_all_confirm") val isAllConfirm: Int = 0, //为0 则未全部收到 + val pinned: Int = 0, //1为置顶, 0为默认 + val fid: String? = null, //公告的id ) @MiraiExperimentalApi @@ -53,8 +55,16 @@ public data class GroupAnnouncementMsg( @MiraiExperimentalApi @Serializable public data class GroupAnnouncementSettings( - @SerialName("is_show_edit_card") val isShowEditCard: Int = 0, + @SerialName("is_show_edit_card") val isShowEditCard: Int = 0, //引导群成员修改该昵称 1 引导 @SerialName("remind_ts") val remindTs: Int = 0, - @SerialName("tip_window_type") val tipWindowType: Int = 0, - @SerialName("confirm_required") val confirmRequired: Int = 0 + @SerialName("tip_window_type") val tipWindowType: Int = 0, //是否用弹窗展示 1 不使用 + @SerialName("confirm_required") val confirmRequired: Int = 0 // 是否需要确认收到 1 需要 +) + +@MiraiExperimentalApi +@Serializable +public data class GroupAnnouncementImage( + @SerialName("h") val height: String, + @SerialName("w") val width: String, + @SerialName("id") val id: String ) \ No newline at end of file diff --git a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt index a0f259eb5..2fbebc958 100644 --- a/mirai-core/src/commonMain/kotlin/MiraiImpl.kt +++ b/mirai-core/src/commonMain/kotlin/MiraiImpl.kt @@ -14,8 +14,11 @@ import io.ktor.client.engine.okhttp.* import io.ktor.client.features.* import io.ktor.client.request.* import io.ktor.client.request.forms.* +import io.ktor.http.* +import io.ktor.utils.io.core.* import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.currentCoroutineContext +import kotlinx.coroutines.withContext import kotlinx.io.core.discardExact import kotlinx.io.core.readBytes import kotlinx.serialization.json.* @@ -56,6 +59,7 @@ import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_1 import net.mamoe.mirai.message.data.Image.Key.IMAGE_RESOURCE_ID_REGEX_2 import net.mamoe.mirai.utils.* import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource +import kotlin.io.use import kotlin.math.absoluteValue import kotlin.random.Random @@ -592,6 +596,47 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { isLenient = true } + @LowLevelApi + @MiraiExperimentalApi + override suspend fun uploadGroupAnnouncementImage( + bot: Bot, + groupId: Long, + resource: ExternalResource + ): GroupAnnouncementImage = 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(GroupAnnouncementImage.serializer(), id) + } + @LowLevelApi @MiraiExperimentalApi override suspend fun sendGroupAnnouncement(bot: Bot, groupId: Long, announcement: GroupAnnouncement): String = @@ -627,6 +672,53 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor { ?: throw throw IllegalStateException("Send Announcement fail group:$groupId msg:${jsonObj.jsonObject["em"]} content:${announcement.msg.text}") } + @LowLevelApi + @MiraiExperimentalApi + override suspend fun sendGroupAnnouncementWithImage( + bot: Bot, + groupId: Long, + image: GroupAnnouncementImage, + 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}") + } + @LowLevelApi @MiraiExperimentalApi override suspend fun deleteGroupAnnouncement(bot: Bot, groupId: Long, fid: String) = bot.asQQAndroidBot().run { diff --git a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt index 6d2b56bc9..d71e311bc 100644 --- a/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt +++ b/mirai-core/src/commonMain/kotlin/contact/GroupImpl.kt @@ -12,11 +12,11 @@ package net.mamoe.mirai.internal.contact +import kotlinx.coroutines.flow.* import net.mamoe.mirai.LowLevelApi import net.mamoe.mirai.Mirai import net.mamoe.mirai.contact.* -import net.mamoe.mirai.data.GroupInfo -import net.mamoe.mirai.data.MemberInfo +import net.mamoe.mirai.data.* import net.mamoe.mirai.event.broadcast import net.mamoe.mirai.event.events.* import net.mamoe.mirai.internal.QQAndroidBot @@ -248,6 +248,29 @@ internal class GroupImpl( return result.success } + override suspend fun getAnnouncements(): Flow<ReceiveAnnouncement> = + flow { + var i = 1 + while (true) { + val result = Mirai.getRawGroupAnnouncements(bot, id, i++) + check(result.ec == 0) { "Get Group Announcement error at page $i" } + + if (result.inst.isNullOrEmpty() && result.feeds.isNullOrEmpty()) + return@flow + + result.inst?.let { emitAll(it.asFlow()) } + result.feeds?.let { emitAll(it.asFlow()) } + } + }.map { it.covertToAnnouncement(bot.id) } + + override suspend fun deleteAnnouncement(fid: String) { + checkBotPermission(MemberPermission.ADMINISTRATOR) { "Only administrator have permission to delete group announcement" } + Mirai.deleteGroupAnnouncement(bot, id, fid) + } + + override suspend fun getAnnouncement(fid: String): ReceiveAnnouncement = + Mirai.getGroupAnnouncement(bot, id, fid).covertToAnnouncement(bot.id) + override fun toString(): String = "Group($id)" } @@ -278,3 +301,4 @@ internal fun GroupImpl.newAnonymous(name: String, id: String): AnonymousMemberIm anonymousId = id, ) ) as AnonymousMemberImpl +