mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-06 04:32:21 +08:00
feature: add ShortVideo
message support (#2739)
* initial support for ShortVideo message * dump api * [core] upload protocol * [core] short video upload event * [core] doc * [core] protocol * [core] fix mp4 file check * [core] extract fileName from `OnlineShortVideo` to `ShortVideo` * [core] ShortVideo.Builder * [core] mirai code support for `ShortVideo` * [core] add doc for OnlineShortVideo and OfflineShortVideo * [core] fix text * dump api * update `Contact.uploadShortVideo`·` doc * [core] remove mirai code support for ShortVideo * [core] ensure Mirai service is loaded before load other services * [core] introduce `CombinedExternalResource` to reference multiple external resources for combined calculation. * [core] move refine context key defined in `OnlineShortVideoMsgInternal` to `RefineContext` * [core] remove data class * [core] broadcast `ShortVideoUploadEvent.Failed` event * [core] warn when cannot determine fromId * [core] add `contentToString` and `toString` for `OnlineShortVideoMsgInternal` * [core] optimize imports * [core] import * [core] revert * [core] doc * [core] auto close resource * dump api * keep consistence of param name * update doc * move Builder to OfflineShortVideo * optimize RefineContext * RefineContext.merge * dump api * fix test * show more video info * optimize constructor and builder of offline short video * optimize thumbnail * move thumbnail to main constructor arg * dump api * avoid null cast exception. * combine format transition * cleanup
This commit is contained in:
parent
c4815c9a7f
commit
5b3e508b75
@ -366,6 +366,10 @@ public abstract interface class net/mamoe/mirai/contact/Contact : kotlinx/corout
|
||||
public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
|
||||
public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ShortVideo;
|
||||
public abstract fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ShortVideo;
|
||||
public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/contact/Contact$Companion {
|
||||
@ -1888,6 +1892,13 @@ public final class net/mamoe/mirai/event/events/BeforeImageUploadEvent : net/mam
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/BeforeShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public final fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public final fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/event/events/BotActiveEvent : net/mamoe/mirai/event/events/BotEvent {
|
||||
}
|
||||
|
||||
@ -2948,6 +2959,30 @@ public final class net/mamoe/mirai/event/events/OtherClientOnlineEvent : net/mam
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class net/mamoe/mirai/event/events/ShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public abstract fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public abstract fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Failed : net/mamoe/mirai/event/events/ShortVideoUploadEvent {
|
||||
public final fun getErrno ()I
|
||||
public final fun getMessage ()Ljava/lang/String;
|
||||
public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Succeed : net/mamoe/mirai/event/events/ShortVideoUploadEvent {
|
||||
public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public final fun getVideo ()Lnet/mamoe/mirai/message/data/ShortVideo;
|
||||
public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/SignEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public final fun getRank ()Ljava/lang/Integer;
|
||||
@ -4830,6 +4865,39 @@ public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/ma
|
||||
public final class net/mamoe/mirai/message/data/OfflineMessageSource$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/OfflineShortVideo : net/mamoe/mirai/message/data/ShortVideo {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/OfflineShortVideo$Key;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder {
|
||||
public static final field Companion Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion;
|
||||
public final fun build ()Lnet/mamoe/mirai/message/data/OfflineShortVideo;
|
||||
public final fun getFileFormat ()Ljava/lang/String;
|
||||
public final fun getFileMd5 ()[B
|
||||
public final fun getFileName ()Ljava/lang/String;
|
||||
public final fun getFileSize ()J
|
||||
public final fun getThumbnailMd5 ()[B
|
||||
public final fun getThumbnailSize ()J
|
||||
public final fun getVideoId ()Ljava/lang/String;
|
||||
public static final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder;
|
||||
public final fun setFileFormat (Ljava/lang/String;)V
|
||||
public final fun setFileMd5 ([B)V
|
||||
public final fun setFileName (Ljava/lang/String;)V
|
||||
public final fun setFileSize (J)V
|
||||
public final fun setThumbnailMd5 ([B)V
|
||||
public final fun setThumbnailSize (J)V
|
||||
public final fun setVideoId (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion {
|
||||
public final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OfflineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/OnlineAudio : net/mamoe/mirai/message/data/Audio {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
@ -4978,6 +5046,16 @@ public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$
|
||||
public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/OnlineShortVideo : net/mamoe/mirai/message/data/ShortVideo {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/OnlineShortVideo$Key;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
public abstract fun getUrlForDownload ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OnlineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OrNullDelegate {
|
||||
public static final synthetic fun box-impl (Ljava/lang/Object;)Lnet/mamoe/mirai/message/data/OrNullDelegate;
|
||||
public static fun constructor-impl (Ljava/lang/Object;)Ljava/lang/Object;
|
||||
@ -5223,6 +5301,24 @@ public abstract interface class net/mamoe/mirai/message/data/ServiceMessage : ne
|
||||
public final class net/mamoe/mirai/message/data/ServiceMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/ShortVideo : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/ShortVideo$Key;
|
||||
public abstract fun getFileFormat ()Ljava/lang/String;
|
||||
public abstract fun getFileMd5 ()[B
|
||||
public abstract fun getFileSize ()J
|
||||
public abstract fun getFilename ()Ljava/lang/String;
|
||||
public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
|
||||
public abstract fun getVideoId ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/ShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/ShortVideoKt {
|
||||
public static final synthetic fun OfflineShortVideo (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo;
|
||||
public static synthetic fun OfflineShortVideo$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJILjava/lang/Object;)Lnet/mamoe/mirai/message/data/OfflineShortVideo;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/ShowImageFlag : net/mamoe/mirai/message/data/AbstractMessageKey, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageMetadata {
|
||||
public static final field INSTANCE Lnet/mamoe/mirai/message/data/ShowImageFlag;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
|
@ -366,6 +366,10 @@ public abstract interface class net/mamoe/mirai/contact/Contact : kotlinx/corout
|
||||
public static fun uploadImage (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Image;
|
||||
public abstract fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/ShortVideo;
|
||||
public abstract fun uploadShortVideo (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/ShortVideo;
|
||||
public static synthetic fun uploadShortVideo$default (Lnet/mamoe/mirai/contact/Contact;Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/utils/ExternalResource;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/contact/Contact$Companion {
|
||||
@ -1888,6 +1892,13 @@ public final class net/mamoe/mirai/event/events/BeforeImageUploadEvent : net/mam
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/BeforeShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/CancellableEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public final fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public final fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public final fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/event/events/BotActiveEvent : net/mamoe/mirai/event/events/BotEvent {
|
||||
}
|
||||
|
||||
@ -2948,6 +2959,30 @@ public final class net/mamoe/mirai/event/events/OtherClientOnlineEvent : net/mam
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract class net/mamoe/mirai/event/events/ShortVideoUploadEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotActiveEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/event/VerboseEvent {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public abstract fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public abstract fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public abstract fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Failed : net/mamoe/mirai/event/events/ShortVideoUploadEvent {
|
||||
public final fun getErrno ()I
|
||||
public final fun getMessage ()Ljava/lang/String;
|
||||
public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/ShortVideoUploadEvent$Succeed : net/mamoe/mirai/event/events/ShortVideoUploadEvent {
|
||||
public fun getTarget ()Lnet/mamoe/mirai/contact/Contact;
|
||||
public fun getThumbnailSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public final fun getVideo ()Lnet/mamoe/mirai/message/data/ShortVideo;
|
||||
public fun getVideoSource ()Lnet/mamoe/mirai/utils/ExternalResource;
|
||||
public fun toString ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/event/events/SignEvent : net/mamoe/mirai/event/AbstractEvent, net/mamoe/mirai/event/events/BotEvent, net/mamoe/mirai/internal/network/Packet {
|
||||
public fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||
public final fun getRank ()Ljava/lang/Integer;
|
||||
@ -4830,6 +4865,39 @@ public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/ma
|
||||
public final class net/mamoe/mirai/message/data/OfflineMessageSource$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/OfflineShortVideo : net/mamoe/mirai/message/data/ShortVideo {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/OfflineShortVideo$Key;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder {
|
||||
public static final field Companion Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion;
|
||||
public final fun build ()Lnet/mamoe/mirai/message/data/OfflineShortVideo;
|
||||
public final fun getFileFormat ()Ljava/lang/String;
|
||||
public final fun getFileMd5 ()[B
|
||||
public final fun getFileName ()Ljava/lang/String;
|
||||
public final fun getFileSize ()J
|
||||
public final fun getThumbnailMd5 ()[B
|
||||
public final fun getThumbnailSize ()J
|
||||
public final fun getVideoId ()Ljava/lang/String;
|
||||
public static final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder;
|
||||
public final fun setFileFormat (Ljava/lang/String;)V
|
||||
public final fun setFileMd5 ([B)V
|
||||
public final fun setFileName (Ljava/lang/String;)V
|
||||
public final fun setFileSize (J)V
|
||||
public final fun setThumbnailMd5 ([B)V
|
||||
public final fun setThumbnailSize (J)V
|
||||
public final fun setVideoId (Ljava/lang/String;)V
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OfflineShortVideo$Builder$Companion {
|
||||
public final fun newBuilder (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo$Builder;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OfflineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/OnlineAudio : net/mamoe/mirai/message/data/Audio {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
@ -4978,6 +5046,16 @@ public abstract class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$
|
||||
public final class net/mamoe/mirai/message/data/OnlineMessageSource$Outgoing$ToTemp$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/OnlineShortVideo : net/mamoe/mirai/message/data/ShortVideo {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/OnlineShortVideo$Key;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
public abstract fun getUrlForDownload ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OnlineShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/OrNullDelegate {
|
||||
public static final synthetic fun box-impl (Ljava/lang/Object;)Lnet/mamoe/mirai/message/data/OrNullDelegate;
|
||||
public static fun constructor-impl (Ljava/lang/Object;)Ljava/lang/Object;
|
||||
@ -5223,6 +5301,24 @@ public abstract interface class net/mamoe/mirai/message/data/ServiceMessage : ne
|
||||
public final class net/mamoe/mirai/message/data/ServiceMessage$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public abstract interface class net/mamoe/mirai/message/data/ShortVideo : net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageContent {
|
||||
public static final field Key Lnet/mamoe/mirai/message/data/ShortVideo$Key;
|
||||
public abstract fun getFileFormat ()Ljava/lang/String;
|
||||
public abstract fun getFileMd5 ()[B
|
||||
public abstract fun getFileSize ()J
|
||||
public abstract fun getFilename ()Ljava/lang/String;
|
||||
public fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
|
||||
public abstract fun getVideoId ()Ljava/lang/String;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/ShortVideo$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/ShortVideoKt {
|
||||
public static final synthetic fun OfflineShortVideo (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJ)Lnet/mamoe/mirai/message/data/OfflineShortVideo;
|
||||
public static synthetic fun OfflineShortVideo$default (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[BJ[BJILjava/lang/Object;)Lnet/mamoe/mirai/message/data/OfflineShortVideo;
|
||||
}
|
||||
|
||||
public final class net/mamoe/mirai/message/data/ShowImageFlag : net/mamoe/mirai/message/data/AbstractMessageKey, net/mamoe/mirai/message/data/ConstrainSingle, net/mamoe/mirai/message/data/MessageMetadata {
|
||||
public static final field INSTANCE Lnet/mamoe/mirai/message/data/ShowImageFlag;
|
||||
public static final field SERIAL_NAME Ljava/lang/String;
|
||||
|
@ -72,8 +72,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
|
||||
/**
|
||||
* 上传一个 [资源][ExternalResource] 作为图片以备发送.
|
||||
*
|
||||
* **无论上传是否成功都不会关闭 [resource]. 需要调用方手动关闭资源**
|
||||
*
|
||||
* 也可以使用其他扩展: [ExternalResource.uploadAsImage] 使用 [File], [InputStream] 等上传.
|
||||
*
|
||||
* @see Image 查看有关图片的更多信息, 如上传图片
|
||||
@ -88,6 +86,26 @@ public interface Contact : ContactOrBot, CoroutineScope {
|
||||
*/
|
||||
public suspend fun uploadImage(resource: ExternalResource): Image
|
||||
|
||||
/**
|
||||
* 上传 [资源][ExternalResource] 作为短视频发送.
|
||||
* 同时需要上传缩略图作为视频消息显示的封面.
|
||||
*
|
||||
* @see ShortVideo 查看有关短视频的更多信息
|
||||
*
|
||||
* @see BeforeShortVideoUploadEvent 短视频发送前事件,可通过中断来拦截视频上传.
|
||||
* @see ShortVideoUploadEvent 短视频上传完成事件,不可拦截.
|
||||
*
|
||||
* @param thumbnail 短视频封面图,为图片资源.
|
||||
* @param video 视频资源,目前仅支持上传 mp4 格式的视频.
|
||||
* @param fileName 文件名,若为 `null` 则根据 [video] 自动生成.
|
||||
*/
|
||||
public suspend fun uploadShortVideo(
|
||||
thumbnail: ExternalResource,
|
||||
video: ExternalResource,
|
||||
fileName: String? = null
|
||||
): ShortVideo
|
||||
|
||||
@JvmBlockingBridge
|
||||
public companion object {
|
||||
/**
|
||||
* 读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
|
||||
|
@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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:JvmMultifileClass
|
||||
@file:JvmName("BotEventsKt")
|
||||
|
||||
package net.mamoe.mirai.event.events
|
||||
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.event.AbstractEvent
|
||||
import net.mamoe.mirai.event.CancellableEvent
|
||||
import net.mamoe.mirai.event.events.ShortVideoUploadEvent.Failed
|
||||
import net.mamoe.mirai.event.events.ShortVideoUploadEvent.Succeed
|
||||
import net.mamoe.mirai.internal.event.VerboseEvent
|
||||
import net.mamoe.mirai.message.data.ShortVideo
|
||||
import net.mamoe.mirai.utils.ExternalResource
|
||||
import net.mamoe.mirai.utils.MiraiInternalApi
|
||||
|
||||
|
||||
/**
|
||||
* 短视频上传前. 可以阻止上传.
|
||||
*
|
||||
* 此事件总是在 [ShortVideoUploadEvent] 之前广播.
|
||||
* 若此事件被取消, [ShortVideoUploadEvent] 不会广播.
|
||||
*
|
||||
* @see Contact.uploadShortVideo 上传短视频. 为广播这个事件的唯一途径
|
||||
* @since 2.16
|
||||
*/
|
||||
@OptIn(MiraiInternalApi::class)
|
||||
public class BeforeShortVideoUploadEvent @MiraiInternalApi constructor(
|
||||
public val target: Contact,
|
||||
public val thumbnailSource: ExternalResource,
|
||||
public val videoSource: ExternalResource
|
||||
) : BotEvent, BotActiveEvent, AbstractEvent(), CancellableEvent, VerboseEvent {
|
||||
public override val bot: Bot
|
||||
get() = target.bot
|
||||
}
|
||||
|
||||
/**
|
||||
* 短视频上传完成.
|
||||
*
|
||||
* 此事件总是在 [BeforeImageUploadEvent] 之后广播.
|
||||
* 若 [BeforeImageUploadEvent] 被取消, 此事件不会广播.
|
||||
*
|
||||
* @see Contact.uploadShortVideo 上传短视频. 为广播这个事件的唯一途径
|
||||
* @see Succeed
|
||||
* @see Failed
|
||||
* @since 2.16
|
||||
*/
|
||||
@OptIn(MiraiInternalApi::class)
|
||||
public sealed class ShortVideoUploadEvent : BotEvent, BotActiveEvent, AbstractEvent(), VerboseEvent {
|
||||
public abstract val target: Contact
|
||||
public abstract val thumbnailSource: ExternalResource
|
||||
public abstract val videoSource: ExternalResource
|
||||
public override val bot: Bot
|
||||
get() = target.bot
|
||||
|
||||
public class Succeed @MiraiInternalApi constructor(
|
||||
override val target: Contact,
|
||||
override val thumbnailSource: ExternalResource,
|
||||
override val videoSource: ExternalResource,
|
||||
public val video: ShortVideo
|
||||
) : ShortVideoUploadEvent() {
|
||||
override fun toString(): String {
|
||||
return "ShortVideoUploadEvent.Succeed(target=$target, " +
|
||||
"thumbnailSource=$thumbnailSource, " +
|
||||
"videoSource=$videoSource, " +
|
||||
"video=$video)"
|
||||
}
|
||||
}
|
||||
|
||||
public class Failed @MiraiInternalApi constructor(
|
||||
override val target: Contact,
|
||||
override val thumbnailSource: ExternalResource,
|
||||
override val videoSource: ExternalResource,
|
||||
public val errno: Int,
|
||||
public val message: String
|
||||
) : ShortVideoUploadEvent() {
|
||||
override fun toString(): String {
|
||||
return "ShortVideoUploadEvent.Failed(target=$target, " +
|
||||
"thumbnailSource=$thumbnailSource, " +
|
||||
"videoSource=$videoSource, " +
|
||||
"errno=$errno, message='$message')"
|
||||
}
|
||||
}
|
||||
}
|
@ -572,6 +572,7 @@ public interface InternalImageProtocol { // naming it Internal* to assign it a l
|
||||
@MiraiInternalApi
|
||||
public companion object {
|
||||
public val instance: InternalImageProtocol by lazy {
|
||||
Mirai // initialize MiraiImpl first
|
||||
loadService(
|
||||
InternalImageProtocol::class,
|
||||
"net.mamoe.mirai.internal.message.InternalImageProtocolImpl"
|
||||
|
250
mirai-core-api/src/commonMain/kotlin/message/data/ShortVideo.kt
Normal file
250
mirai-core-api/src/commonMain/kotlin/message/data/ShortVideo.kt
Normal file
@ -0,0 +1,250 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.message.data
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import net.mamoe.mirai.Mirai
|
||||
import net.mamoe.mirai.contact.AudioSupported
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.message.MessageSerializers
|
||||
import net.mamoe.mirai.message.data.MessageChain.Companion.serializeToJsonString
|
||||
import net.mamoe.mirai.message.data.visitor.MessageVisitor
|
||||
import net.mamoe.mirai.utils.*
|
||||
|
||||
/**
|
||||
* 短视频消息, 指的是可在聊天界面在线播放的视频消息, 而非在群文件上传的视频文件.
|
||||
*
|
||||
* 短视频消息分为 [OnlineShortVideo] 与 [OfflineShortVideo]. 在本地上传的短视频为 [OfflineShortVideo]. 从服务器接收的短视频为 [OnlineShortVideo].
|
||||
*
|
||||
* 最推荐存储的方式是下载视频文件, 每次都通过上传该文件获取视频消息.
|
||||
* 在上传视频时服务器会根据缓存情况选择回复已有视频 ID 或要求客户端上传.
|
||||
*
|
||||
* # 获取短视频消息示例
|
||||
*
|
||||
* ## 上传短视频
|
||||
* 使用 [Contact.uploadShortVideo], 将视频缩略图和视频[资源][ExternalResource] 上传以得到 [OfflineShortVideo].
|
||||
*
|
||||
* ## 使用 [OfflineShortVideo.Builder] 构建短视频
|
||||
* [OfflineShortVideo] 提供 [Builder][OfflineShortVideo.Builder] 构建方式, 必须指定 [videoId], [filename], [fileMd5], [fileSize] 和 [fileFormat] 参数.
|
||||
* 可选指定 [thumbnailMd5][OfflineShortVideo.Builder.thumbnailMd5] 和 [thumbnailSize][OfflineShortVideo.Builder.thumbnailSize]. 若不提供, 可能会影响服务器判断缓存.
|
||||
*
|
||||
* ## 从服务器接收
|
||||
* 通过监听消息接收的短视频消息可直接转换为 [OnlineShortVideo].
|
||||
*
|
||||
* kotlin 示例:
|
||||
* ```kotlin
|
||||
* val video: OnlineShortVideo = event.message[OnlineShortVideo]
|
||||
* ```
|
||||
*
|
||||
* # 下载视频
|
||||
* 通过 [OnlineShortVideo.urlForDownload] 获取下载链接.
|
||||
* 该下载链接不包含短视频的文件信息, 可以使用 [videoId] 或 [filename] 作为文件名, [fileFormat] 作为文件拓展名.
|
||||
*
|
||||
* @since 2.16
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface ShortVideo : MessageContent, ConstrainSingle {
|
||||
/**
|
||||
* 视频 ID.
|
||||
*/
|
||||
public val videoId: String
|
||||
|
||||
/**
|
||||
* 视频文件 MD5. 16 bytes.
|
||||
*/
|
||||
public val fileMd5: ByteArray
|
||||
|
||||
/*
|
||||
* 视频大小
|
||||
*/
|
||||
public val fileSize: Long
|
||||
|
||||
/**
|
||||
* 视频文件类型(拓展名)
|
||||
*/
|
||||
public val fileFormat: String
|
||||
|
||||
/*
|
||||
* 视频文件名, 不包括拓展名
|
||||
*/
|
||||
public val filename: String
|
||||
|
||||
|
||||
@MiraiInternalApi
|
||||
override fun <D, R> accept(visitor: MessageVisitor<D, R>, data: D): R {
|
||||
return visitor.visitShortVideo(this, data)
|
||||
}
|
||||
|
||||
override val key: MessageKey<*>
|
||||
get() = Key
|
||||
|
||||
|
||||
public companion object Key :
|
||||
AbstractPolymorphicMessageKey<MessageContent, ShortVideo>(MessageContent, { it.safeCast() }) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线短视频消息, 即从消息事件中接收到的视频消息.
|
||||
*
|
||||
* [OnlineShortVideo] 仅可以从事件中的[消息链][MessageChain]接收, 不可手动构造.
|
||||
*
|
||||
* ### 序列化支持
|
||||
*
|
||||
* [OnlineShortVideo] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString].
|
||||
* 也可以在 [MessageSerializers.serializersModule] 获取到 [OnlineShortVideo] 的 [KSerializer].
|
||||
*
|
||||
* 要获取更多有关序列化的信息, 参阅 [MessageSerializers].
|
||||
*
|
||||
* @since 2.16
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface OnlineShortVideo : ShortVideo {
|
||||
/**
|
||||
* 下载链接
|
||||
*/
|
||||
public val urlForDownload: String
|
||||
|
||||
public companion object Key :
|
||||
AbstractPolymorphicMessageKey<ShortVideo, OnlineShortVideo>(ShortVideo, { it.safeCast() }) {
|
||||
public const val SERIAL_NAME: String = "OnlineShortVideo"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 离线短视频消息.
|
||||
*
|
||||
* [OfflineShortVideo] 拥有协议上必要的五个属性:
|
||||
* - 视频 ID [videoId]
|
||||
* - 视频文件名 [filename]
|
||||
* - 视频 MD5 [fileMd5]
|
||||
* - 视频大小 [fileSize]
|
||||
* - 视频格式 [fileFormat]
|
||||
*
|
||||
* 和非必要属性:
|
||||
* - 缩略图 MD5 `thumbnailMd5`
|
||||
* - 缩略图大小 `thumbnailSize`
|
||||
*
|
||||
* [OfflineShortVideo] 可由本地 [ExternalResource] 经过 [AudioSupported.uploadShortVideo] 上传到服务器得到, 故无[下载链接][OnlineShortVideo.urlForDownload].
|
||||
*
|
||||
* [OfflineShortVideo] 支持使用 [OfflineShortVideo.Builder] 可通过上述七个必要参数和两个非必要参数构造 [OfflineShortVideo] 实例.
|
||||
*
|
||||
* ### 序列化支持
|
||||
*
|
||||
* [OfflineShortVideo] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString].
|
||||
* 也可以在 [MessageSerializers.serializersModule] 获取到 [OfflineShortVideo] 的 [KSerializer].
|
||||
*
|
||||
* 要获取更多有关序列化的信息, 参阅 [MessageSerializers].
|
||||
* @since 2.16
|
||||
*/
|
||||
@NotStableForInheritance
|
||||
public interface OfflineShortVideo : ShortVideo {
|
||||
|
||||
public companion object Key :
|
||||
AbstractPolymorphicMessageKey<ShortVideo, OfflineShortVideo>(ShortVideo, { it.safeCast() }) {
|
||||
public const val SERIAL_NAME: String = "OfflineShortVideo"
|
||||
}
|
||||
|
||||
public class Builder internal constructor(
|
||||
public var videoId: String,
|
||||
public var fileMd5: ByteArray,
|
||||
public var fileSize: Long,
|
||||
public var fileFormat: String,
|
||||
public var fileName: String
|
||||
) {
|
||||
/**
|
||||
* 缩略图文件 MD5
|
||||
*
|
||||
* 传入此处的缩略图 MD5 应该仅有以下来源:
|
||||
* * *已通过 [Contact.uploadShortVideo] 上传完成的*缩略图[资源][ExternalResource], 可由 [ExternalResource.md5] 获得.
|
||||
*/
|
||||
public var thumbnailMd5: ByteArray = EMPTY_BYTE_ARRAY
|
||||
|
||||
/**
|
||||
* 缩略图文件大小
|
||||
*
|
||||
* 传入此处的缩略图文件大小应该仅有以下来源:
|
||||
* * *已通过 [Contact.uploadShortVideo] 上传完成的*缩略图[资源][ExternalResource], 可由 [ExternalResource.size] 获得.
|
||||
*/
|
||||
public var thumbnailSize: Long = 0
|
||||
|
||||
public fun build(): OfflineShortVideo {
|
||||
|
||||
@OptIn(MiraiInternalApi::class)
|
||||
return InternalShortVideoProtocol.instance.createOfflineShortVideo(
|
||||
videoId, fileMd5, fileSize, fileFormat, fileName, thumbnailMd5, thumbnailSize
|
||||
)
|
||||
}
|
||||
|
||||
public companion object {
|
||||
/**
|
||||
* 创建一个 [OfflineShortVideo.Builder]
|
||||
*
|
||||
* 在 Kotlin 可以使用类构造器的函数 [OfflineShortVideo]: `OfflineShortVideo(...)`
|
||||
*/
|
||||
@JvmStatic
|
||||
public fun newBuilder(
|
||||
videoId: String,
|
||||
fileName: String,
|
||||
fileFormat: String,
|
||||
fileMd5: ByteArray,
|
||||
fileSize: Long
|
||||
): Builder = Builder(videoId, fileMd5, fileSize, fileFormat, fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造 [OfflineShortVideo]. 有关参数的含义, 参考 [ShortVideo].
|
||||
* @since 2.16
|
||||
*/
|
||||
@Suppress("NOTHING_TO_INLINE")
|
||||
@JvmSynthetic
|
||||
public inline fun OfflineShortVideo(
|
||||
videoId: String,
|
||||
fileName: String,
|
||||
fileFormat: String,
|
||||
fileMd5: ByteArray,
|
||||
fileSize: Long,
|
||||
thumbnailMd5: ByteArray = byteArrayOf(),
|
||||
thumbnailSize: Long = 0,
|
||||
): OfflineShortVideo = OfflineShortVideo.Builder.newBuilder(videoId, fileName, fileFormat, fileMd5, fileSize).apply {
|
||||
this@apply.thumbnailMd5 = thumbnailMd5
|
||||
this@apply.thumbnailSize = thumbnailSize
|
||||
}.build()
|
||||
|
||||
/**
|
||||
* 内部短视频协议实现, 请不要使用此接口
|
||||
* @since 2.16.0
|
||||
*/
|
||||
@MiraiInternalApi
|
||||
public interface InternalShortVideoProtocol {
|
||||
public fun createOfflineShortVideo(
|
||||
videoId: String,
|
||||
fileMd5: ByteArray,
|
||||
fileSize: Long,
|
||||
fileFormat: String,
|
||||
fileName: String,
|
||||
thumbnailMd5: ByteArray,
|
||||
thumbnailSize: Long
|
||||
): OfflineShortVideo
|
||||
|
||||
@MiraiInternalApi
|
||||
public companion object {
|
||||
public val instance: InternalShortVideoProtocol by lazy {
|
||||
Mirai // initialize MiraiImpl first
|
||||
loadService(
|
||||
InternalShortVideoProtocol::class,
|
||||
"net.mamoe.mirai.internal.message.InternalShortVideoProtocolImpl"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -41,6 +41,8 @@ public interface MessageVisitor<in D, out R> {
|
||||
public fun visitVoice(message: net.mamoe.mirai.message.data.Voice, data: D): R
|
||||
public fun visitAudio(message: Audio, data: D): R
|
||||
|
||||
public fun visitShortVideo(message: ShortVideo, data: D): R
|
||||
|
||||
// region HummerMessage
|
||||
public fun visitHummerMessage(message: HummerMessage, data: D): R
|
||||
public fun visitFlashImage(message: FlashImage, data: D): R
|
||||
@ -164,6 +166,10 @@ public abstract class AbstractMessageVisitor<in D, out R> : MessageVisitor<D, R>
|
||||
return visitMessageContent(message, data)
|
||||
}
|
||||
|
||||
override fun visitShortVideo(message: ShortVideo, data: D): R {
|
||||
return visitMessageContent(message, data)
|
||||
}
|
||||
|
||||
public override fun visitHummerMessage(message: HummerMessage, data: D): R {
|
||||
return visitMessageContent(message, data)
|
||||
}
|
||||
|
@ -161,7 +161,9 @@ public interface ExternalResource : java.io.Closeable {
|
||||
* 文件格式,如 "png", "amr". 当无法自动识别格式时为 [DEFAULT_FORMAT_NAME].
|
||||
*
|
||||
* 默认会从文件头识别, 支持的文件类型:
|
||||
* png, jpg, gif, tif, bmp, amr, silk
|
||||
* * 图片类型: png, jpg, gif, tif, bmp
|
||||
* * 语音类型: amr, silk
|
||||
* * 视频类类型: mp4, mkv
|
||||
*
|
||||
* @see net.mamoe.mirai.utils.getFileType
|
||||
* @see net.mamoe.mirai.utils.FILE_TYPES
|
||||
|
@ -16,10 +16,7 @@ import net.mamoe.mirai.event.events.MessagePreSendEvent
|
||||
import net.mamoe.mirai.internal.contact.broadcastMessagePreSendEvent
|
||||
import net.mamoe.mirai.internal.contact.replaceMagicCodes
|
||||
import net.mamoe.mirai.message.MessageReceipt
|
||||
import net.mamoe.mirai.message.data.Image
|
||||
import net.mamoe.mirai.message.data.Message
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
import net.mamoe.mirai.message.data.OnlineMessageSource
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.mock.MockBot
|
||||
import net.mamoe.mirai.mock.contact.MockContact
|
||||
import net.mamoe.mirai.utils.*
|
||||
@ -61,6 +58,14 @@ internal abstract class AbstractMockContact(
|
||||
return bot.uploadMockImage(resource)
|
||||
}
|
||||
|
||||
override suspend fun uploadShortVideo(
|
||||
thumbnail: ExternalResource,
|
||||
video: ExternalResource,
|
||||
fileName: String?
|
||||
): ShortVideo {
|
||||
TODO("mock upload short video")
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "$id"
|
||||
}
|
||||
|
@ -15,27 +15,35 @@ package net.mamoe.mirai.utils
|
||||
import kotlin.jvm.JvmMultifileClass
|
||||
import kotlin.jvm.JvmName
|
||||
|
||||
private class FileType(
|
||||
signature: String,
|
||||
val requiredHeaderSize: Int,
|
||||
val formatName: String
|
||||
) {
|
||||
val signatureRegex = Regex(signature, RegexOption.IGNORE_CASE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件头和文件类型列表
|
||||
*/
|
||||
public val FILE_TYPES: MutableMap<String, String> = mutableMapOf(
|
||||
"FFD8FF" to "jpg",
|
||||
"89504E47" to "png",
|
||||
"47494638" to "gif",
|
||||
private val FILE_TYPES: List<FileType> = listOf(
|
||||
FileType("^FFD8FF", 3, "jpg"),
|
||||
FileType("^89504E47", 4, "png"),
|
||||
FileType("^47494638", 4, "gif"),
|
||||
FileType("^424D", 3, "bmp"),
|
||||
FileType("^2321414D52", 5, "amr"),
|
||||
FileType("^02232153494C4B5F5633", 10, "silk"),
|
||||
FileType("^([a-zA-Z0-9]{8})66747970", 8, "mp4"),
|
||||
|
||||
//"49492A00" to "tif", // client doesn't support
|
||||
"424D" to "bmp",
|
||||
//"52494646" to "webp", // pc client doesn't support
|
||||
|
||||
// "57415645" to "wav", // server doesn't support
|
||||
|
||||
"2321414D52" to "amr",
|
||||
"02232153494C4B5F5633" to "silk",
|
||||
)
|
||||
|
||||
/**
|
||||
* 在 [getFileType] 需要的 [ByteArray] 长度
|
||||
*/
|
||||
public val COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE: Int get() = FILE_TYPES.maxOf { it.key.length / 2 }
|
||||
public val COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE: Int by lazy { FILE_TYPES.maxOf { it.requiredHeaderSize } }
|
||||
|
||||
/*
|
||||
|
||||
@ -53,9 +61,9 @@ public fun getFileType(fileHeader: ByteArray): String? {
|
||||
"",
|
||||
length = COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE.coerceAtMost(fileHeader.size)
|
||||
)
|
||||
FILE_TYPES.forEach { (k, v) ->
|
||||
if (hex.startsWith(k)) {
|
||||
return v
|
||||
FILE_TYPES.forEach { t ->
|
||||
if (hex.contains(t.signatureRegex)) {
|
||||
return t.formatName
|
||||
}
|
||||
}
|
||||
return null
|
||||
|
@ -723,9 +723,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
||||
it.fileName to it.buffer.loadAs(MsgTransmit.PbMultiMsgNew.serializer())
|
||||
}
|
||||
val main = pbs["MultiMsg"] ?: return this.msg.map { it.toNode(bot, EmptyRefineContext) }
|
||||
val context = SimpleRefineContext(mutableMapOf())
|
||||
context[ForwardMessageInternal.MsgTransmits] = pbs
|
||||
return main.toForwardMessageNodes(bot, context)
|
||||
return main.toForwardMessageNodes(bot, SimpleRefineContext(ForwardMessageInternal.MsgTransmits to pbs))
|
||||
}
|
||||
|
||||
private suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
|
||||
|
@ -9,10 +9,26 @@
|
||||
|
||||
package net.mamoe.mirai.internal.contact
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.*
|
||||
import net.mamoe.mirai.event.broadcast
|
||||
import net.mamoe.mirai.event.events.BeforeShortVideoUploadEvent
|
||||
import net.mamoe.mirai.event.events.EventCancelledException
|
||||
import net.mamoe.mirai.event.events.ShortVideoUploadEvent
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.utils.childScopeContext
|
||||
import net.mamoe.mirai.internal.message.data.OfflineShortVideoImpl
|
||||
import net.mamoe.mirai.internal.message.data.ShortVideoThumbnail
|
||||
import net.mamoe.mirai.internal.message.image.calculateImageInfo
|
||||
import net.mamoe.mirai.internal.network.highway.Highway
|
||||
import net.mamoe.mirai.internal.network.highway.ResourceKind
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.PttShortVideo
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.video.PttCenterSvr
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
|
||||
import net.mamoe.mirai.message.data.ShortVideo
|
||||
import net.mamoe.mirai.internal.utils.CombinedExternalResource
|
||||
import net.mamoe.mirai.utils.*
|
||||
import kotlin.contracts.contract
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
@ -21,6 +37,118 @@ internal abstract class AbstractContact(
|
||||
parentCoroutineContext: CoroutineContext,
|
||||
) : Contact {
|
||||
final override val coroutineContext: CoroutineContext = parentCoroutineContext.childScopeContext()
|
||||
|
||||
override suspend fun uploadShortVideo(
|
||||
thumbnail: ExternalResource,
|
||||
video: ExternalResource,
|
||||
fileName: String?
|
||||
): ShortVideo = thumbnail.withAutoClose {
|
||||
video.withAutoClose {
|
||||
if (this !is Group && this !is Friend) {
|
||||
throw UnsupportedOperationException("short video can only upload to friend or group.")
|
||||
}
|
||||
|
||||
if (video.formatName != "mp4") {
|
||||
throw UnsupportedOperationException("video format ${video.formatName} is not supported.")
|
||||
}
|
||||
|
||||
if (BeforeShortVideoUploadEvent(this, thumbnail, video).broadcast().isCancelled) {
|
||||
throw EventCancelledException("cancelled by BeforeShortVideoUploadEvent")
|
||||
}
|
||||
|
||||
// local uploaded offline short video uses video file md5 as its file name by default
|
||||
val videoName = fileName ?: video.md5.toUHexString("")
|
||||
|
||||
val uploadResp = bot.network.sendAndExpect(
|
||||
PttCenterSvr.GroupShortVideoUpReq(
|
||||
client = bot.client,
|
||||
contact = this,
|
||||
thumbnailFileMd5 = thumbnail.md5,
|
||||
thumbnailFileSize = thumbnail.size,
|
||||
videoFileName = videoName,
|
||||
videoFileMd5 = video.md5,
|
||||
videoFileSize = video.size,
|
||||
videoFileFormat = video.formatName
|
||||
)
|
||||
)
|
||||
|
||||
// get thumbnail image width and height
|
||||
val thumbnailInfo = thumbnail.calculateImageInfo()
|
||||
|
||||
// fast path
|
||||
if (uploadResp is PttCenterSvr.GroupShortVideoUpReq.Response.FileExists) {
|
||||
return OfflineShortVideoImpl(
|
||||
uploadResp.fileId,
|
||||
videoName,
|
||||
video.md5,
|
||||
video.size,
|
||||
video.formatName,
|
||||
ShortVideoThumbnail(
|
||||
thumbnail.md5,
|
||||
thumbnail.size,
|
||||
thumbnailInfo.width,
|
||||
thumbnailInfo.height
|
||||
)
|
||||
).also {
|
||||
ShortVideoUploadEvent.Succeed(this, thumbnail, video, it).broadcast()
|
||||
}
|
||||
}
|
||||
|
||||
val highwayRespExt = CombinedExternalResource(thumbnail, video).use { resource ->
|
||||
Highway.uploadResourceBdh(
|
||||
bot = bot,
|
||||
resource = resource,
|
||||
kind = ResourceKind.SHORT_VIDEO,
|
||||
commandId = 25,
|
||||
extendInfo = buildPacket {
|
||||
writeProtoBuf(
|
||||
PttShortVideo.PttShortVideoUploadReq.serializer(),
|
||||
PttCenterSvr.GroupShortVideoUpReq.buildShortVideoFileInfo(
|
||||
client = bot.client,
|
||||
contact = this@AbstractContact,
|
||||
thumbnailFileMd5 = thumbnail.md5,
|
||||
thumbnailFileSize = thumbnail.size,
|
||||
videoFileName = videoName,
|
||||
videoFileMd5 = video.md5,
|
||||
videoFileSize = video.size,
|
||||
videoFileFormat = video.formatName
|
||||
)
|
||||
)
|
||||
}.readBytes(),
|
||||
encrypt = true
|
||||
).extendInfo
|
||||
}
|
||||
|
||||
if (highwayRespExt == null) {
|
||||
ShortVideoUploadEvent.Failed(
|
||||
this,
|
||||
thumbnail,
|
||||
video,
|
||||
-1,
|
||||
"highway upload short video failed, extendInfo is null."
|
||||
).broadcast()
|
||||
error("highway upload short video failed, extendInfo is null.")
|
||||
}
|
||||
|
||||
val highwayUploadResp = highwayRespExt.loadAs(PttShortVideo.PttShortVideoUploadResp.serializer())
|
||||
|
||||
OfflineShortVideoImpl(
|
||||
highwayUploadResp.fileid,
|
||||
videoName,
|
||||
video.md5,
|
||||
video.size,
|
||||
video.formatName,
|
||||
ShortVideoThumbnail(
|
||||
thumbnail.md5,
|
||||
thumbnail.size,
|
||||
thumbnailInfo.width,
|
||||
thumbnailInfo.height
|
||||
)
|
||||
).also {
|
||||
ShortVideoUploadEvent.Succeed(this, thumbnail, video, it).broadcast()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal val Contact.userIdOrNull: Long? get() = if (this is User) this.id else null
|
||||
|
@ -12,6 +12,8 @@ package net.mamoe.mirai.internal.contact.roaming
|
||||
import kotlinx.coroutines.flow.*
|
||||
import net.mamoe.mirai.contact.roaming.RoamingMessageFilter
|
||||
import net.mamoe.mirai.internal.contact.CommonGroupImpl
|
||||
import net.mamoe.mirai.internal.message.RefineContextKey
|
||||
import net.mamoe.mirai.internal.message.SimpleRefineContext
|
||||
import net.mamoe.mirai.internal.message.toMessageChainOnline
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement
|
||||
@ -65,7 +67,18 @@ internal class RoamingMessagesImplGroup(
|
||||
.sortedByDescending { it.msgHead.msgSeq } // Ensure caller receives newer messages first
|
||||
.filter { filter.apply(it) } // Call filter after sort
|
||||
.asFlow()
|
||||
.map { listOf(it).toMessageChainOnline(bot, contact.id, MessageSourceKind.GROUP) }
|
||||
.map {
|
||||
listOf(it).toMessageChainOnline(
|
||||
bot,
|
||||
contact.id,
|
||||
MessageSourceKind.GROUP,
|
||||
SimpleRefineContext(
|
||||
RefineContextKey.MessageSourceKind to MessageSourceKind.GROUP,
|
||||
RefineContextKey.FromId to it.msgHead.fromUin,
|
||||
RefineContextKey.GroupIdOrZero to contact.uin,
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
currentSeq = resp.msgElem.first().msgHead.msgSeq
|
||||
|
@ -16,6 +16,8 @@ import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import kotlinx.coroutines.isActive
|
||||
import net.mamoe.mirai.contact.roaming.RoamingMessageFilter
|
||||
import net.mamoe.mirai.internal.message.RefineContextKey
|
||||
import net.mamoe.mirai.internal.message.SimpleRefineContext
|
||||
import net.mamoe.mirai.internal.message.toMessageChainOnline
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq
|
||||
import net.mamoe.mirai.message.data.MessageChain
|
||||
@ -32,13 +34,18 @@ internal sealed class TimeBasedRoamingMessagesImpl : AbstractRoamingMessages() {
|
||||
while (currentCoroutineContext().isActive) {
|
||||
val resp = requestRoamMsg(timeStart, lastMessageTime, random)
|
||||
val messages = resp.messages ?: break
|
||||
|
||||
if (filter == null || filter === RoamingMessageFilter.ANY) {
|
||||
// fast path
|
||||
messages.forEach { emit(it.toMessageChainOnline(contact.bot)) }
|
||||
messages.forEach { msg ->
|
||||
val context = SimpleRefineContext(RefineContextKey.FromId to msg.msgHead.fromUin)
|
||||
emit(msg.toMessageChainOnline(contact.bot, context))
|
||||
}
|
||||
} else {
|
||||
for (message in messages) {
|
||||
if (filter.invoke(createRoamingMessage(message, messages))) {
|
||||
emit(message.toMessageChainOnline(contact.bot))
|
||||
val context = SimpleRefineContext(RefineContextKey.FromId to message.msgHead.fromUin)
|
||||
emit(message.toMessageChainOnline(contact.bot, context))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,10 @@ import net.mamoe.mirai.internal.message.source.*
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.castOrNull
|
||||
import net.mamoe.mirai.utils.structureToString
|
||||
import net.mamoe.mirai.utils.toLongUnsigned
|
||||
import net.mamoe.mirai.utils.warning
|
||||
|
||||
/**
|
||||
* 只在手动构造 [OfflineMessageSource] 时调用
|
||||
@ -78,7 +80,17 @@ internal suspend fun MsgComm.Msg.toMessageChainOnline(
|
||||
MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0
|
||||
else -> 0
|
||||
}
|
||||
return listOf(this).toMessageChainOnline(bot, groupId, kind, refineContext, facade)
|
||||
|
||||
return listOf(this).toMessageChainOnline(
|
||||
bot,
|
||||
groupId,
|
||||
kind,
|
||||
refineContext.merge(SimpleRefineContext(
|
||||
RefineContextKey.MessageSourceKind to kind,
|
||||
RefineContextKey.GroupIdOrZero to groupId
|
||||
), false),
|
||||
facade
|
||||
)
|
||||
}
|
||||
|
||||
//internal fun List<MsgComm.Msg>.toMessageChainOffline(
|
||||
@ -129,13 +141,28 @@ private fun List<MsgComm.Msg>.toMessageChainImpl(
|
||||
|
||||
val builder = MessageChainBuilder(messageList.sumOf { it.msgBody.richText.elems.size })
|
||||
|
||||
if (onlineSource != null) {
|
||||
builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList))
|
||||
val source = if (onlineSource != null) {
|
||||
ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList)
|
||||
} else null
|
||||
if (source != null) builder.add(source)
|
||||
|
||||
val fromId = source?.fromId ?: firstOrNull()?.msgHead?.fromUin
|
||||
if (fromId == null) {
|
||||
bot.logger.warning {
|
||||
"Cannot determine fromId from message source and msg elements, " +
|
||||
"source: $source, elements: ${this.joinToString(", ")}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
messageList.forEach { msg ->
|
||||
facade.decode(msg.msgBody.richText.elems, groupIdOrZero, messageSourceKind, bot, builder, msg)
|
||||
facade.decode(
|
||||
msg.msgBody.richText.elems,
|
||||
groupIdOrZero,
|
||||
messageSourceKind,
|
||||
bot,
|
||||
builder,
|
||||
msg
|
||||
)
|
||||
}
|
||||
|
||||
for (msg in messageList) {
|
||||
|
@ -16,6 +16,7 @@ import net.mamoe.mirai.internal.message.LightMessageRefiner.refineMessageSource
|
||||
import net.mamoe.mirai.internal.message.flags.InternalFlagOnlyMessage
|
||||
import net.mamoe.mirai.internal.message.source.IncomingMessageSourceInternal
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.cast
|
||||
import net.mamoe.mirai.utils.safeCast
|
||||
|
||||
/**
|
||||
@ -99,6 +100,12 @@ internal class RefineContextKey<T : Any>(
|
||||
append(')')
|
||||
}
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
val MessageSourceKind = RefineContextKey<MessageSourceKind>("MessageSourceKind")
|
||||
val FromId = RefineContextKey<Long>("FromId")
|
||||
val GroupIdOrZero = RefineContextKey<Long>("GroupIdOrZero")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,6 +115,8 @@ internal interface RefineContext {
|
||||
operator fun contains(key: RefineContextKey<*>): Boolean
|
||||
operator fun <T : Any> get(key: RefineContextKey<T>): T?
|
||||
fun <T : Any> getNotNull(key: RefineContextKey<T>): T = get(key) ?: error("No such value of `$key`")
|
||||
fun merge(other: RefineContext, override: Boolean): RefineContext
|
||||
fun entries(): Set<Pair<RefineContextKey<*>, Any>>
|
||||
}
|
||||
|
||||
internal interface MutableRefineContext : RefineContext {
|
||||
@ -118,9 +127,19 @@ internal interface MutableRefineContext : RefineContext {
|
||||
internal object EmptyRefineContext : RefineContext {
|
||||
override fun contains(key: RefineContextKey<*>): Boolean = false
|
||||
override fun <T : Any> get(key: RefineContextKey<T>): T? = null
|
||||
override fun merge(other: RefineContext, override: Boolean): RefineContext {
|
||||
return other
|
||||
}
|
||||
override fun entries(): Set<Pair<RefineContextKey<*>, Any>> {
|
||||
return emptySet()
|
||||
}
|
||||
override fun toString(): String {
|
||||
return "EmptyRefineContext"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return other === EmptyRefineContext
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@ -140,8 +159,32 @@ internal class SimpleRefineContext(
|
||||
override fun remove(key: RefineContextKey<*>) {
|
||||
delegate.remove(key)
|
||||
}
|
||||
|
||||
override fun entries(): Set<Pair<RefineContextKey<*>, Any>> {
|
||||
return delegate.entries.map { (k, v) -> k to v }.toSet()
|
||||
}
|
||||
|
||||
override fun merge(other: RefineContext, override: Boolean): RefineContext {
|
||||
val new = SimpleRefineContext(*entries().toTypedArray())
|
||||
other.entries().forEach { (key, value) ->
|
||||
if (new[key] == null || override) {
|
||||
new[key as RefineContextKey<Any>] = value
|
||||
}
|
||||
}
|
||||
return new
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (other !is RefineContext) return false
|
||||
if (other === this) return true
|
||||
|
||||
return other.entries() == entries()
|
||||
}
|
||||
}
|
||||
|
||||
internal fun SimpleRefineContext(vararg elements: Pair<RefineContextKey<*>, Any>): SimpleRefineContext =
|
||||
SimpleRefineContext(elements.toMap().toMutableMap())
|
||||
|
||||
/**
|
||||
* 执行不需要 `suspend` 的 refine. 用于 [MessageSource.originalMessage].
|
||||
*/
|
||||
|
243
mirai-core/src/commonMain/kotlin/message/data/shortVideo.kt
Normal file
243
mirai-core/src/commonMain/kotlin/message/data/shortVideo.kt
Normal file
@ -0,0 +1,243 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.message.data
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.getMember
|
||||
import net.mamoe.mirai.internal.asQQAndroidBot
|
||||
import net.mamoe.mirai.internal.message.RefinableMessage
|
||||
import net.mamoe.mirai.internal.message.RefineContext
|
||||
import net.mamoe.mirai.internal.message.RefineContextKey
|
||||
import net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.video.PttCenterSvr
|
||||
import net.mamoe.mirai.message.data.*
|
||||
import net.mamoe.mirai.utils.ExternalResource
|
||||
import net.mamoe.mirai.utils.toUHexString
|
||||
|
||||
/**
|
||||
* receive from pipeline and refine to [OnlineShortVideoImpl]
|
||||
*/
|
||||
internal class OnlineShortVideoMsgInternal(
|
||||
private val videoFile: ImMsgBody.VideoFile
|
||||
) : RefinableMessage {
|
||||
|
||||
override fun tryRefine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun refine(bot: Bot, context: MessageChain, refineContext: RefineContext): Message? {
|
||||
bot.asQQAndroidBot()
|
||||
|
||||
val sourceKind = refineContext[RefineContextKey.MessageSourceKind] ?: return null
|
||||
val fromId = refineContext[RefineContextKey.FromId] ?: return null
|
||||
val groupId = refineContext[RefineContextKey.GroupIdOrZero] ?: return null
|
||||
|
||||
val contact = when (sourceKind) {
|
||||
net.mamoe.mirai.message.data.MessageSourceKind.FRIEND -> bot.getFriend(fromId)
|
||||
net.mamoe.mirai.message.data.MessageSourceKind.GROUP -> bot.getGroup(groupId)
|
||||
else -> return null // TODO: ignore processing stranger's video message
|
||||
} as Contact
|
||||
val sender = when (sourceKind) {
|
||||
net.mamoe.mirai.message.data.MessageSourceKind.FRIEND ->
|
||||
bot.getFriend(fromId) ?: error("Cannot find friend $fromId.")
|
||||
net.mamoe.mirai.message.data.MessageSourceKind.GROUP -> {
|
||||
val group = bot.getGroup(groupId) ?: error("Cannot find group $groupId.")
|
||||
group.getMember(fromId) ?: error("Cannot find member $fromId of group $groupId.")
|
||||
}
|
||||
else -> return null // TODO: ignore processing stranger's video message
|
||||
}
|
||||
|
||||
val shortVideoDownloadReq = bot.network.sendAndExpect(
|
||||
PttCenterSvr.ShortVideoDownReq(
|
||||
bot.client,
|
||||
contact,
|
||||
sender,
|
||||
videoFile.fileUuid.decodeToString(),
|
||||
videoFile.fileMd5
|
||||
)
|
||||
)
|
||||
|
||||
if (shortVideoDownloadReq !is PttCenterSvr.ShortVideoDownReq.Response.Success)
|
||||
throw IllegalStateException("Failed to query short video download attributes.")
|
||||
|
||||
if (!shortVideoDownloadReq.fileMd5.contentEquals(videoFile.fileMd5))
|
||||
throw IllegalStateException(
|
||||
"Queried short video download attributes doesn't match the requests. " +
|
||||
"message provides: ${videoFile.fileMd5.toUHexString("")}, " +
|
||||
"queried result: ${shortVideoDownloadReq.fileMd5.toUHexString("")}"
|
||||
)
|
||||
|
||||
val format = ShortVideoProtocol.FORMAT
|
||||
.firstOrNull { it.second == videoFile.fileFormat }?.first
|
||||
?: ExternalResource.DEFAULT_FORMAT_NAME
|
||||
|
||||
return OnlineShortVideoImpl(
|
||||
videoFile.fileUuid.decodeToString(),
|
||||
shortVideoDownloadReq.fileMd5,
|
||||
videoFile.fileName.decodeToString(),
|
||||
videoFile.fileSize.toLong(),
|
||||
format,
|
||||
shortVideoDownloadReq.urlV4,
|
||||
ShortVideoThumbnail(
|
||||
videoFile.thumbFileMd5,
|
||||
videoFile.thumbFileSize.toLong(),
|
||||
videoFile.thumbWidth,
|
||||
videoFile.thumbHeight
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
override fun toString(): String {
|
||||
return "OnlineShortVideoMsgInternal(videoElem=$videoFile)"
|
||||
}
|
||||
|
||||
override fun contentToString(): String {
|
||||
return "[视频元数据]"
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
internal data class ShortVideoThumbnail(
|
||||
val md5: ByteArray,
|
||||
val size: Long,
|
||||
val width: Int?,
|
||||
val height: Int?,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as ShortVideoThumbnail
|
||||
|
||||
if (!md5.contentEquals(other.md5)) return false
|
||||
if (size != other.size) return false
|
||||
if (width != other.width) return false
|
||||
if (height != other.height) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = md5.contentHashCode()
|
||||
result = 31 * result + size.hashCode()
|
||||
result = 31 * result + (width ?: 0)
|
||||
result = 31 * result + (height ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class AbstractShortVideoWithThumbnail : ShortVideo {
|
||||
abstract val thumbnail: ShortVideoThumbnail
|
||||
}
|
||||
|
||||
@Suppress("DuplicatedCode")
|
||||
@SerialName(OnlineShortVideo.SERIAL_NAME)
|
||||
@Serializable
|
||||
internal class OnlineShortVideoImpl(
|
||||
override val videoId: String,
|
||||
override val fileMd5: ByteArray,
|
||||
override val filename: String,
|
||||
override val fileSize: Long,
|
||||
override val fileFormat: String,
|
||||
override val urlForDownload: String,
|
||||
override val thumbnail: ShortVideoThumbnail
|
||||
) : OnlineShortVideo, AbstractShortVideoWithThumbnail() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "[mirai:shortvideo:$videoId, videoName=$filename.$fileFormat, videoMd5=${fileMd5.toUHexString("")}, " +
|
||||
"videoSize=${fileSize}, thumbnailMd5=${thumbnail.md5.toUHexString("")}, thumbnailSize=${thumbnail.size}]"
|
||||
}
|
||||
|
||||
override fun contentToString(): String {
|
||||
return "[视频]"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as OnlineShortVideoImpl
|
||||
|
||||
if (videoId != other.videoId) return false
|
||||
if (!fileMd5.contentEquals(other.fileMd5)) return false
|
||||
if (filename != other.filename) return false
|
||||
if (fileSize != other.fileSize) return false
|
||||
if (fileFormat != other.fileFormat) return false
|
||||
if (urlForDownload != other.urlForDownload) return false
|
||||
if (thumbnail != other.thumbnail) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = videoId.hashCode()
|
||||
result = 31 * result + fileMd5.contentHashCode()
|
||||
result = 31 * result + filename.hashCode()
|
||||
result = 31 * result + fileSize.hashCode()
|
||||
result = 31 * result + fileFormat.hashCode()
|
||||
result = 31 * result + urlForDownload.hashCode()
|
||||
result = 31 * result + thumbnail.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
internal class OfflineShortVideoImpl(
|
||||
override val videoId: String,
|
||||
override val filename: String,
|
||||
override val fileMd5: ByteArray,
|
||||
override val fileSize: Long,
|
||||
override val fileFormat: String,
|
||||
override val thumbnail: ShortVideoThumbnail
|
||||
) : OfflineShortVideo, AbstractShortVideoWithThumbnail() {
|
||||
|
||||
/**
|
||||
* offline short video uses
|
||||
*/
|
||||
override fun toString(): String {
|
||||
return "[mirai:shortvideo:$videoId, videoName=$filename.$fileFormat, videoMd5=${fileMd5.toUHexString("")}, " +
|
||||
"videoSize=${fileSize}, thumbnailMd5=${thumbnail.md5.toUHexString("")}, thumbnailSize=${thumbnail.size}]"
|
||||
}
|
||||
|
||||
override fun contentToString(): String {
|
||||
return "[视频]"
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as OfflineShortVideoImpl
|
||||
|
||||
if (videoId != other.videoId) return false
|
||||
if (filename != other.filename) return false
|
||||
if (!fileMd5.contentEquals(other.fileMd5)) return false
|
||||
if (fileSize != other.fileSize) return false
|
||||
if (fileFormat != other.fileFormat) return false
|
||||
if (thumbnail != other.thumbnail) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = videoId.hashCode()
|
||||
result = 31 * result + filename.hashCode()
|
||||
result = 31 * result + fileMd5.contentHashCode()
|
||||
result = 31 * result + fileSize.hashCode()
|
||||
result = 31 * result + fileFormat.hashCode()
|
||||
result = 31 * result + thumbnail.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.message.image
|
||||
|
||||
import net.mamoe.mirai.internal.message.data.OfflineShortVideoImpl
|
||||
import net.mamoe.mirai.internal.message.data.ShortVideoThumbnail
|
||||
import net.mamoe.mirai.message.data.InternalShortVideoProtocol
|
||||
import net.mamoe.mirai.message.data.OfflineShortVideo
|
||||
|
||||
internal class InternalShortVideoProtocolImpl : InternalShortVideoProtocol {
|
||||
override fun createOfflineShortVideo(
|
||||
videoId: String,
|
||||
fileMd5: ByteArray,
|
||||
fileSize: Long,
|
||||
fileFormat: String,
|
||||
fileName: String,
|
||||
thumbnailMd5: ByteArray,
|
||||
thumbnailSize: Long
|
||||
): OfflineShortVideo {
|
||||
return OfflineShortVideoImpl(
|
||||
videoId,
|
||||
fileName,
|
||||
fileMd5,
|
||||
fileSize,
|
||||
fileFormat,
|
||||
ShortVideoThumbnail(
|
||||
thumbnailMd5,
|
||||
thumbnailSize,
|
||||
0,
|
||||
0
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
@ -135,7 +135,9 @@ internal interface MessageProtocolFacade {
|
||||
groupIdOrZero: Long,
|
||||
messageSourceKind: MessageSourceKind,
|
||||
bot: Bot,
|
||||
): MessageChain = buildMessageChain { decode(elements, groupIdOrZero, messageSourceKind, bot, this, null) }
|
||||
): MessageChain = buildMessageChain {
|
||||
decode(elements, groupIdOrZero, messageSourceKind, bot, this, null)
|
||||
}
|
||||
|
||||
|
||||
fun createSerializersModule(): SerializersModule = SerializersModule {
|
||||
@ -336,6 +338,7 @@ internal class MessageProtocolFacadeImpl(
|
||||
|
||||
return getSingleReceipt(result, message)
|
||||
}
|
||||
|
||||
override suspend fun <C : AbstractContact> preprocessAndSendOutgoing(
|
||||
target: C,
|
||||
message: Message,
|
||||
@ -378,6 +381,7 @@ internal class MessageProtocolFacadeImpl(
|
||||
"Internal error: no MessageReceipt was returned from OutgoingMessagePipeline for message",
|
||||
forDebug = message.structureToString()
|
||||
)
|
||||
|
||||
1 -> return result.single().castUp()
|
||||
else -> throw contextualBugReportException(
|
||||
"Internal error: multiple MessageReceipts were returned from OutgoingMessagePipeline: $result",
|
||||
|
@ -29,6 +29,7 @@ internal interface MessageDecoderContext : ProcessorPipelineContext<ImMsgBody.El
|
||||
val MESSAGE_SOURCE_KIND = TypeKey<MessageSourceKind>("messageSourceKind")
|
||||
val GROUP_ID = TypeKey<Long>("groupId") // zero if not group
|
||||
val CONTAINING_MSG = TypeKey<MsgComm.Msg?>("containingMsg")
|
||||
val FROM_ID = TypeKey<Long>("fromId") // group/temp = sender, friend/stranger = this
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.message.protocol.impl
|
||||
|
||||
import net.mamoe.mirai.internal.message.data.AbstractShortVideoWithThumbnail
|
||||
import net.mamoe.mirai.internal.message.data.OfflineShortVideoImpl
|
||||
import net.mamoe.mirai.internal.message.data.OnlineShortVideoImpl
|
||||
import net.mamoe.mirai.internal.message.data.OnlineShortVideoMsgInternal
|
||||
import net.mamoe.mirai.internal.message.protocol.MessageProtocol
|
||||
import net.mamoe.mirai.internal.message.protocol.ProcessorCollector
|
||||
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoder
|
||||
import net.mamoe.mirai.internal.message.protocol.decode.MessageDecoderContext
|
||||
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoder
|
||||
import net.mamoe.mirai.internal.message.protocol.encode.MessageEncoderContext
|
||||
import net.mamoe.mirai.internal.message.protocol.serialization.MessageSerializer
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.message.data.MessageContent
|
||||
import net.mamoe.mirai.message.data.ShortVideo
|
||||
import net.mamoe.mirai.message.data.SingleMessage
|
||||
|
||||
internal class ShortVideoProtocol : MessageProtocol() {
|
||||
override fun ProcessorCollector.collectProcessorsImpl() {
|
||||
add(Decoder())
|
||||
add(Encoder())
|
||||
|
||||
MessageSerializer.superclassesScope(ShortVideo::class, MessageContent::class, SingleMessage::class) {
|
||||
add(MessageSerializer(OfflineShortVideoImpl::class, OfflineShortVideoImpl.serializer()))
|
||||
add(MessageSerializer(OnlineShortVideoImpl::class, OnlineShortVideoImpl.serializer()))
|
||||
}
|
||||
}
|
||||
|
||||
private class Decoder : MessageDecoder {
|
||||
override suspend fun MessageDecoderContext.process(data: ImMsgBody.Elem) {
|
||||
val videoFile = data.videoFile ?: return
|
||||
markAsConsumed()
|
||||
|
||||
collect(OnlineShortVideoMsgInternal(videoFile))
|
||||
}
|
||||
}
|
||||
|
||||
private class Encoder : MessageEncoder<AbstractShortVideoWithThumbnail> {
|
||||
override suspend fun MessageEncoderContext.process(data: AbstractShortVideoWithThumbnail) {
|
||||
markAsConsumed()
|
||||
|
||||
collect(ImMsgBody.Elem(text = ImMsgBody.Text("你的 QQ 暂不支持查看视频短片,请期待后续版本。")))
|
||||
|
||||
val thumbWidth = if (data.thumbnail.width == null || data.thumbnail.width == 0) 1280 else data.thumbnail.width!!
|
||||
val thumbHeight = if (data.thumbnail.height == null || data.thumbnail.height == 0) 720 else data.thumbnail.height!!
|
||||
|
||||
collect(
|
||||
ImMsgBody.Elem(
|
||||
videoFile = ImMsgBody.VideoFile(
|
||||
fileUuid = data.videoId.encodeToByteArray(),
|
||||
fileMd5 = data.fileMd5,
|
||||
fileName = data.filename.encodeToByteArray(),
|
||||
fileFormat = FORMAT.firstOrNull { it.first == data.fileFormat }?.second ?: 3,
|
||||
fileTime = 10,
|
||||
fileSize = data.fileSize.toInt(),
|
||||
thumbWidth = thumbWidth,
|
||||
thumbHeight = thumbHeight,
|
||||
thumbFileMd5 = data.thumbnail.md5,
|
||||
thumbFileSize = data.thumbnail.size.toInt(),
|
||||
busiType = 0,
|
||||
fromChatType = -1,
|
||||
toChatType = -1,
|
||||
boolSupportProgressive = true,
|
||||
fileWidth = thumbWidth,
|
||||
fileHeight = thumbHeight
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal companion object {
|
||||
internal val FORMAT: List<Pair<String, Int>> = listOf(
|
||||
"ts" to 1,
|
||||
"avi" to 2,
|
||||
"mp4" to 3,
|
||||
"wmv" to 4,
|
||||
"mkv" to 5,
|
||||
"rmvb" to 6,
|
||||
"rm" to 7,
|
||||
"afs" to 8,
|
||||
"mov" to 9,
|
||||
"mod" to 10,
|
||||
"mts" to 11
|
||||
)
|
||||
}
|
||||
}
|
@ -16,6 +16,8 @@ import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.Transient
|
||||
import net.mamoe.mirai.Bot
|
||||
import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl
|
||||
import net.mamoe.mirai.internal.message.RefineContextKey
|
||||
import net.mamoe.mirai.internal.message.SimpleRefineContext
|
||||
import net.mamoe.mirai.internal.message.toMessageChainNoSource
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||
@ -183,7 +185,10 @@ internal fun OfflineMessageSourceImplData(
|
||||
internalIds = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer())
|
||||
.origUids?.mapToIntArray { it.toInt() } ?: intArrayOf(),
|
||||
time = delegate.time,
|
||||
originalMessageLazy = lazy { delegate.toMessageChainNoSource(bot, messageSourceKind, groupIdOrZero) },
|
||||
originalMessageLazy = lazy {
|
||||
val context = SimpleRefineContext(RefineContextKey.FromId to delegate.senderUin)
|
||||
delegate.toMessageChainNoSource(bot, messageSourceKind, groupIdOrZero, context)
|
||||
},
|
||||
fromId = delegate.senderUin,
|
||||
targetId = when {
|
||||
groupIdOrZero != 0L -> groupIdOrZero
|
||||
@ -191,6 +196,7 @@ internal fun OfflineMessageSourceImplData(
|
||||
delegate.srcMsg != null -> runCatching {
|
||||
delegate.srcMsg.loadAs(MsgComm.Msg.serializer()).msgHead.toUin
|
||||
}.getOrElse { 0L }
|
||||
|
||||
else -> 0/*error("cannot find targetId. delegate=${delegate._miraiContentToString()}, delegate.srcMsg=${
|
||||
kotlin.runCatching { delegate.srcMsg?.loadAs(MsgComm.Msg.serializer())?._miraiContentToString() }
|
||||
.fold(
|
||||
|
@ -126,6 +126,8 @@ internal enum class ResourceKind(
|
||||
FORWARD_MESSAGE("forward message"),
|
||||
|
||||
ANNOUNCEMENT_IMAGE("announcement image"),
|
||||
|
||||
SHORT_VIDEO("short video")
|
||||
;
|
||||
|
||||
override fun toString(): String = display
|
||||
|
@ -20,6 +20,8 @@ import net.mamoe.mirai.event.events.MemberCardChangeEvent
|
||||
import net.mamoe.mirai.event.events.MemberSpecialTitleChangeEvent
|
||||
import net.mamoe.mirai.internal.contact.*
|
||||
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
|
||||
import net.mamoe.mirai.internal.message.RefineContextKey
|
||||
import net.mamoe.mirai.internal.message.SimpleRefineContext
|
||||
import net.mamoe.mirai.internal.message.toMessageChainOnline
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
|
||||
@ -159,7 +161,18 @@ internal class GroupMessageProcessor(
|
||||
GroupMessageSyncEvent(
|
||||
client = bot.otherClients.find { it.appId == msgHead.fromInstid }
|
||||
?: return, // don't compare with dstAppId. diff.
|
||||
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, MessageSourceKind.GROUP),
|
||||
message = msgs.map { it.msg }.toMessageChainOnline(
|
||||
bot,
|
||||
group.id,
|
||||
MessageSourceKind.GROUP,
|
||||
SimpleRefineContext(
|
||||
mutableMapOf(
|
||||
RefineContextKey.MessageSourceKind to MessageSourceKind.GROUP,
|
||||
RefineContextKey.FromId to sender.uin,
|
||||
RefineContextKey.GroupIdOrZero to group.uin,
|
||||
)
|
||||
)
|
||||
),
|
||||
time = msgHead.msgTime,
|
||||
group = group,
|
||||
sender = sender,
|
||||
@ -174,7 +187,18 @@ internal class GroupMessageProcessor(
|
||||
GroupMessageEvent(
|
||||
senderName = nameCard.nick,
|
||||
sender = sender,
|
||||
message = msgs.map { it.msg }.toMessageChainOnline(bot, group.id, MessageSourceKind.GROUP),
|
||||
message = msgs.map { it.msg }.toMessageChainOnline(
|
||||
bot,
|
||||
group.id,
|
||||
MessageSourceKind.GROUP,
|
||||
SimpleRefineContext(
|
||||
mutableMapOf(
|
||||
RefineContextKey.MessageSourceKind to MessageSourceKind.GROUP,
|
||||
RefineContextKey.FromId to sender.uin,
|
||||
RefineContextKey.GroupIdOrZero to group.uin,
|
||||
)
|
||||
)
|
||||
),
|
||||
permission = sender.permission,
|
||||
time = msgHead.msgTime,
|
||||
),
|
||||
|
@ -15,6 +15,8 @@ import net.mamoe.mirai.event.Event
|
||||
import net.mamoe.mirai.event.events.*
|
||||
import net.mamoe.mirai.internal.contact.*
|
||||
import net.mamoe.mirai.internal.getGroupByUinOrCode
|
||||
import net.mamoe.mirai.internal.message.RefineContextKey
|
||||
import net.mamoe.mirai.internal.message.SimpleRefineContext
|
||||
import net.mamoe.mirai.internal.message.toMessageChainOnline
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
|
||||
@ -25,6 +27,7 @@ import net.mamoe.mirai.internal.network.components.SsoProcessor
|
||||
import net.mamoe.mirai.internal.network.notice.group.GroupMessageProcessor
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
|
||||
import net.mamoe.mirai.message.data.MessageSourceKind
|
||||
import net.mamoe.mirai.utils.assertUnreachable
|
||||
import net.mamoe.mirai.utils.context
|
||||
|
||||
@ -114,6 +117,7 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type
|
||||
val group = bot.getGroupByUinOrCode(tmpHead.groupUin) ?: return
|
||||
handlePrivateMessage(data, group[senderUin] ?: return)
|
||||
}
|
||||
|
||||
else -> markNotConsumed()
|
||||
}
|
||||
|
||||
@ -129,7 +133,16 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type
|
||||
val msgs = user.fragmentedMessageMerger.tryMerge(this)
|
||||
if (msgs.isEmpty()) return
|
||||
|
||||
val chain = msgs.toMessageChainOnline(bot, 0, user.correspondingMessageSourceKind)
|
||||
val chain = msgs.toMessageChainOnline(
|
||||
bot,
|
||||
0,
|
||||
user.correspondingMessageSourceKind,
|
||||
SimpleRefineContext(
|
||||
RefineContextKey.MessageSourceKind to MessageSourceKind.FRIEND,
|
||||
RefineContextKey.FromId to user.uin,
|
||||
RefineContextKey.GroupIdOrZero to 0L,
|
||||
)
|
||||
)
|
||||
val time = msgHead.msgTime
|
||||
|
||||
collected += if (fromSync) {
|
||||
|
@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.protocol.data.proto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.protobuf.ProtoNumber
|
||||
import net.mamoe.mirai.internal.utils.io.ProtoBuf
|
||||
import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideo : ProtoBuf {
|
||||
@Serializable
|
||||
internal class ServerListInfo(
|
||||
@JvmField @ProtoNumber(1) val upIp: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val upPort: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class CodecConfigReq(
|
||||
@JvmField @ProtoNumber(1) val platformChipinfo: String = "",
|
||||
@JvmField @ProtoNumber(2) val osVersion: String = "",
|
||||
@JvmField @ProtoNumber(3) val deviceName: String = ""
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class DataHole(
|
||||
@JvmField @ProtoNumber(1) val begin: Long = 0L,
|
||||
@JvmField @ProtoNumber(2) val end: Long = 0L
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class ExtensionReq(
|
||||
@JvmField @ProtoNumber(1) val subBusiType: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val userCnt: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoAddr(
|
||||
@JvmField @ProtoNumber(1) val hostType: Int = 0,
|
||||
@JvmField @ProtoNumber(10) val strHost: List<String> = emptyList(),
|
||||
@JvmField @ProtoNumber(11) val urlArgs: String = "",
|
||||
@JvmField @ProtoNumber(21) val strHostIpv6: List<String> = emptyList(),
|
||||
@JvmField @ProtoNumber(22) val strDomain: List<String> = emptyList()
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoDeleteReq(
|
||||
@JvmField @ProtoNumber(1) val fromuin: Long = 0L,
|
||||
@JvmField @ProtoNumber(2) val touin: Long = 0L,
|
||||
@JvmField @ProtoNumber(3) val chatType: Int = 0,
|
||||
@JvmField @ProtoNumber(4) val clientType: Int = 0,
|
||||
@JvmField @ProtoNumber(5) val fileid: String = "",
|
||||
@JvmField @ProtoNumber(6) val groupCode: Long = 0L,
|
||||
@JvmField @ProtoNumber(7) val agentType: Int = 0,
|
||||
@JvmField @ProtoNumber(8) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(9) val businessType: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoDeleteResp(
|
||||
@JvmField @ProtoNumber(1) val int32RetCode: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val retMsg: String = ""
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoDownloadReq(
|
||||
@JvmField @ProtoNumber(1) val fromuin: Long = 0L,
|
||||
@JvmField @ProtoNumber(2) val touin: Long = 0L,
|
||||
@JvmField @ProtoNumber(3) val chatType: Int = 0,
|
||||
@JvmField @ProtoNumber(4) val clientType: Int = 0,
|
||||
@JvmField @ProtoNumber(5) val fileid: String = "",
|
||||
@JvmField @ProtoNumber(6) val groupCode: Long = 0L,
|
||||
@JvmField @ProtoNumber(7) val agentType: Int = 0,
|
||||
@JvmField @ProtoNumber(8) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(9) val businessType: Int = 0,
|
||||
@JvmField @ProtoNumber(10) val fileType: Int = 0,
|
||||
@JvmField @ProtoNumber(11) val downType: Int = 0,
|
||||
@JvmField @ProtoNumber(12) val sceneType: Int = 0,
|
||||
@JvmField @ProtoNumber(13) val needInnerAddr: Int = 0,
|
||||
@JvmField @ProtoNumber(14) val reqTransferType: Int = 0,
|
||||
@JvmField @ProtoNumber(15) val reqHostType: Int = 0,
|
||||
@JvmField @ProtoNumber(20) val flagSupportLargeSize: Int = 0,
|
||||
@JvmField @ProtoNumber(30) val flagClientQuicProtoEnable: Int = 0,
|
||||
@JvmField @ProtoNumber(31) val targetCodecFormat: Int = 0,
|
||||
@JvmField @ProtoNumber(32) val msgCodecConfig: CodecConfigReq? = null,
|
||||
@JvmField @ProtoNumber(33) val sourceCodecFormat: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoDownloadResp(
|
||||
@JvmField @ProtoNumber(1) val int32RetCode: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val retMsg: String = "",
|
||||
@JvmField @ProtoNumber(3) val sameAreaOutAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(4) val diffAreaOutAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(5) val downloadkey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(6) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(7) val sameAreaInnerAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(8) val diffAreaInnerAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(9) val msgDownloadAddr: PttShortVideoAddr? = null,
|
||||
@JvmField @ProtoNumber(10) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(30) val flagServerQuicProtoEnable: Int = 0,
|
||||
@JvmField @ProtoNumber(31) val serverQuicPara: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(32) val codecFormat: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoFileInfo(
|
||||
@JvmField @ProtoNumber(1) val fileName: String = "",
|
||||
@JvmField @ProtoNumber(2) val fileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(3) val thumbFileMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(4) val fileSize: Long = 0L,
|
||||
@JvmField @ProtoNumber(5) val fileResLength: Int = 0,
|
||||
@JvmField @ProtoNumber(6) val fileResWidth: Int = 0,
|
||||
@JvmField @ProtoNumber(7) val fileFormat: Int = 0,
|
||||
@JvmField @ProtoNumber(8) val fileTime: Int = 0,
|
||||
@JvmField @ProtoNumber(9) val thumbFileSize: Long = 0L,
|
||||
@JvmField @ProtoNumber(10) val decryptVideoMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(11) val decryptFileSize: Long = 0L,
|
||||
@JvmField @ProtoNumber(12) val decryptThumbMd5: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(13) val decryptThumbSize: Long = 0L,
|
||||
@JvmField @ProtoNumber(14) val extend: ByteArray = EMPTY_BYTE_ARRAY
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoFileInfoExtend(
|
||||
@JvmField @ProtoNumber(1) val bitRate: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoIpList(
|
||||
@JvmField @ProtoNumber(1) val ip: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val port: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoRetweetReq(
|
||||
@JvmField @ProtoNumber(1) val fromUin: Long = 0L,
|
||||
@JvmField @ProtoNumber(2) val toUin: Long = 0L,
|
||||
@JvmField @ProtoNumber(3) val fromChatType: Int = 0,
|
||||
@JvmField @ProtoNumber(4) val toChatType: Int = 0,
|
||||
@JvmField @ProtoNumber(5) val fromBusiType: Int = 0,
|
||||
@JvmField @ProtoNumber(6) val toBusiType: Int = 0,
|
||||
@JvmField @ProtoNumber(7) val clientType: Int = 0,
|
||||
@JvmField @ProtoNumber(8) val msgPttShortVideoFileInfo: PttShortVideoFileInfo? = null,
|
||||
@JvmField @ProtoNumber(9) val agentType: Int = 0,
|
||||
@JvmField @ProtoNumber(10) val fileid: String = "",
|
||||
@JvmField @ProtoNumber(11) val groupCode: Long = 0L,
|
||||
@JvmField @ProtoNumber(20) val flagSupportLargeSize: Int = 0,
|
||||
@JvmField @ProtoNumber(21) val codecFormat: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoRetweetResp(
|
||||
@JvmField @ProtoNumber(1) val int32RetCode: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val retMsg: String = "",
|
||||
@JvmField @ProtoNumber(3) val sameAreaOutAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(4) val diffAreaOutAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(5) val fileid: String = "",
|
||||
@JvmField @ProtoNumber(6) val ukey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(7) val fileExist: Int = 0,
|
||||
@JvmField @ProtoNumber(8) val sameAreaInnerAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(9) val diffAreaInnerAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(10) val dataHole: List<DataHole> = emptyList(),
|
||||
@JvmField @ProtoNumber(11) val isHotFile: Int = 0,
|
||||
@JvmField @ProtoNumber(12) val longVideoCarryWatchPointType: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoUploadReq(
|
||||
@JvmField @ProtoNumber(1) val fromuin: Long = 0L,
|
||||
@JvmField @ProtoNumber(2) val touin: Long = 0L,
|
||||
@JvmField @ProtoNumber(3) val chatType: Int = 0,
|
||||
@JvmField @ProtoNumber(4) val clientType: Int = 0,
|
||||
@JvmField @ProtoNumber(5) val msgPttShortVideoFileInfo: PttShortVideoFileInfo? = null,
|
||||
@JvmField @ProtoNumber(6) val groupCode: Long = 0L,
|
||||
@JvmField @ProtoNumber(7) val agentType: Int = 0,
|
||||
@JvmField @ProtoNumber(8) val businessType: Int = 0,
|
||||
@JvmField @ProtoNumber(9) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(10) val subBusinessType: Int = 0,
|
||||
@JvmField @ProtoNumber(20) val flagSupportLargeSize: Int = 0,
|
||||
@JvmField @ProtoNumber(21) val codecFormat: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class PttShortVideoUploadResp(
|
||||
@JvmField @ProtoNumber(1) val int32RetCode: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val retMsg: String = "",
|
||||
@JvmField @ProtoNumber(3) val sameAreaOutAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(4) val diffAreaOutAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(5) val fileid: String = "",
|
||||
@JvmField @ProtoNumber(6) val ukey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(7) val fileExist: Int = 0,
|
||||
@JvmField @ProtoNumber(8) val sameAreaInnerAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(9) val diffAreaInnerAddr: List<PttShortVideoIpList> = emptyList(),
|
||||
@JvmField @ProtoNumber(10) val dataHole: List<DataHole> = emptyList(),
|
||||
@JvmField @ProtoNumber(11) val encryptKey: ByteArray = EMPTY_BYTE_ARRAY,
|
||||
@JvmField @ProtoNumber(12) val isHotFile: Int = 0,
|
||||
@JvmField @ProtoNumber(13) val longVideoCarryWatchPointType: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class QuicParameter(
|
||||
@JvmField @ProtoNumber(1) val enableQuic: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val encryptionVer: Int = 1,
|
||||
@JvmField @ProtoNumber(3) val fecVer: Int = 0
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class ReqBody(
|
||||
@JvmField @ProtoNumber(1) val cmd: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val seq: Int = 0,
|
||||
@JvmField @ProtoNumber(3) val msgPttShortVideoUploadReq: PttShortVideoUploadReq? = null,
|
||||
@JvmField @ProtoNumber(4) val msgPttShortVideoDownloadReq: PttShortVideoDownloadReq? = null,
|
||||
@JvmField @ProtoNumber(5) val msgShortVideoRetweetReq: List<PttShortVideoRetweetReq> = emptyList(),
|
||||
@JvmField @ProtoNumber(6) val msgShortVideoDeleteReq: List<PttShortVideoDeleteReq> = emptyList(),
|
||||
@JvmField @ProtoNumber(100) val msgExtensionReq: List<ExtensionReq> = emptyList()
|
||||
) : ProtoBuf
|
||||
|
||||
@Serializable
|
||||
internal class RspBody(
|
||||
@JvmField @ProtoNumber(1) val cmd: Int = 0,
|
||||
@JvmField @ProtoNumber(2) val seq: Int = 0,
|
||||
@JvmField @ProtoNumber(3) val msgPttShortVideoUploadResp: PttShortVideoUploadResp? = null,
|
||||
@JvmField @ProtoNumber(4) val msgPttShortVideoDownloadResp: PttShortVideoDownloadResp? = null,
|
||||
@JvmField @ProtoNumber(5) val msgShortVideoRetweetResp: List<PttShortVideoRetweetResp> = emptyList(),
|
||||
@JvmField @ProtoNumber(6) val msgShortVideoDeleteResp: List<PttShortVideoDeleteResp> = emptyList(),
|
||||
@JvmField @ProtoNumber(100) val changeChannel: Int = 0,
|
||||
@JvmField @ProtoNumber(101) val allowRetry: Int = 0
|
||||
) : ProtoBuf
|
||||
}
|
@ -18,6 +18,7 @@ import net.mamoe.mirai.internal.network.protocol.packet.chat.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.*
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.video.PttCenterSvr
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
|
||||
@ -151,6 +152,8 @@ internal object KnownPacketFactories {
|
||||
PttStore.GroupPttUp,
|
||||
PttStore.GroupPttDown,
|
||||
PttStore.C2CPttDown,
|
||||
PttCenterSvr.GroupShortVideoUpReq,
|
||||
PttCenterSvr.ShortVideoDownReq,
|
||||
LongConn.OffPicUp,
|
||||
// LongConn.OffPicDown,
|
||||
TroopManagement.EditSpecialTitle,
|
||||
|
@ -0,0 +1,187 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.network.protocol.packet.chat.video
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.contact.Contact
|
||||
import net.mamoe.mirai.contact.Friend
|
||||
import net.mamoe.mirai.contact.Group
|
||||
import net.mamoe.mirai.contact.User
|
||||
import net.mamoe.mirai.internal.QQAndroidBot
|
||||
import net.mamoe.mirai.internal.contact.uin
|
||||
import net.mamoe.mirai.internal.network.Packet
|
||||
import net.mamoe.mirai.internal.network.QQAndroidClient
|
||||
import net.mamoe.mirai.internal.network.protocol.data.proto.PttShortVideo
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.OutgoingPacketFactory
|
||||
import net.mamoe.mirai.internal.network.protocol.packet.buildOutgoingUniPacket
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
|
||||
import net.mamoe.mirai.internal.utils.io.serialization.writeProtoBuf
|
||||
|
||||
internal class PttCenterSvr {
|
||||
object GroupShortVideoUpReq :
|
||||
OutgoingPacketFactory<GroupShortVideoUpReq.Response>("PttCenterSvr.GroupShortVideoUpReq") {
|
||||
sealed class Response : Packet {
|
||||
class FileExists(val fileId: String) : Response() {
|
||||
override fun toString(): String {
|
||||
return "PttCenterSvr.GroupShortVideoUpReq.Response.FileExists(fileId=${fileId})"
|
||||
}
|
||||
}
|
||||
|
||||
object RequireUpload : Response() {
|
||||
override fun toString(): String {
|
||||
return "PttCenterSvr.GroupShortVideoUpReq.Response.RequireUpload"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
val resp = readProtoBuf(PttShortVideo.RspBody.serializer())
|
||||
val upResp = resp.msgPttShortVideoUploadResp ?: return Response.RequireUpload
|
||||
|
||||
return if (upResp.fileExist == 1) {
|
||||
Response.FileExists(upResp.fileid)
|
||||
} else {
|
||||
Response.RequireUpload
|
||||
}
|
||||
}
|
||||
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
contact: Contact,
|
||||
thumbnailFileMd5: ByteArray,
|
||||
thumbnailFileSize: Long,
|
||||
videoFileName: String,
|
||||
videoFileMd5: ByteArray,
|
||||
videoFileSize: Long,
|
||||
videoFileFormat: String
|
||||
) = buildOutgoingUniPacket(client) { sequenceId ->
|
||||
writeProtoBuf(
|
||||
PttShortVideo.ReqBody.serializer(),
|
||||
PttShortVideo.ReqBody(
|
||||
cmd = 300,
|
||||
seq = sequenceId,
|
||||
msgPttShortVideoUploadReq = buildShortVideoFileInfo(
|
||||
client,
|
||||
contact,
|
||||
thumbnailFileMd5,
|
||||
thumbnailFileSize,
|
||||
videoFileName,
|
||||
videoFileMd5,
|
||||
videoFileSize,
|
||||
videoFileFormat
|
||||
),
|
||||
msgExtensionReq = listOf(
|
||||
PttShortVideo.ExtensionReq(
|
||||
subBusiType = 0,
|
||||
userCnt = 1
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
internal fun buildShortVideoFileInfo(
|
||||
client: QQAndroidClient,
|
||||
contact: Contact,
|
||||
thumbnailFileMd5: ByteArray,
|
||||
thumbnailFileSize: Long,
|
||||
videoFileName: String,
|
||||
videoFileMd5: ByteArray,
|
||||
videoFileSize: Long,
|
||||
videoFileFormat: String
|
||||
) = PttShortVideo.PttShortVideoUploadReq(
|
||||
fromuin = client.uin,
|
||||
touin = contact.uin,
|
||||
chatType = 1, // guild channel = 4, others = 1
|
||||
clientType = 2,
|
||||
msgPttShortVideoFileInfo = PttShortVideo.PttShortVideoFileInfo(
|
||||
fileName = videoFileName + videoFileFormat,
|
||||
fileMd5 = videoFileMd5,
|
||||
fileSize = videoFileSize,
|
||||
fileResLength = 1280,
|
||||
fileResWidth = 720,
|
||||
// Lcom/tencent/mobileqq/transfile/ShortVideoUploadProcessor;getFormat(Ljava/lang/String;)I
|
||||
fileFormat = 3,
|
||||
fileTime = 120,
|
||||
thumbFileMd5 = thumbnailFileMd5,
|
||||
thumbFileSize = thumbnailFileSize
|
||||
),
|
||||
groupCode = if (contact is Group) contact.uin else 0,
|
||||
flagSupportLargeSize = 1
|
||||
)
|
||||
}
|
||||
|
||||
object ShortVideoDownReq : OutgoingPacketFactory<ShortVideoDownReq.Response>("PttCenterSvr.ShortVideoDownReq") {
|
||||
sealed class Response : Packet {
|
||||
class Success(val fileMd5: ByteArray, val urlV4: String, val urlV6: String?) : Response() {
|
||||
override fun toString(): String {
|
||||
return "PttCenterSvr.ShortVideoDownReq.Response.Success(" +
|
||||
"urlV4=$urlV4, urlV6=$urlV6)"
|
||||
}
|
||||
}
|
||||
|
||||
object Failed : Response() {
|
||||
override fun toString(): String {
|
||||
return "PttCenterSvr.ShortVideoDownReq.Response.Failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun ByteReadPacket.decode(bot: QQAndroidBot): Response {
|
||||
val resp = readProtoBuf(PttShortVideo.RspBody.serializer())
|
||||
|
||||
val shortVideoDownloadResp = resp.msgPttShortVideoDownloadResp ?: return Response.Failed
|
||||
val attr = shortVideoDownloadResp.msgDownloadAddr ?: return Response.Failed
|
||||
|
||||
val fileMd5 = shortVideoDownloadResp.fileMd5
|
||||
val urlV4 = attr.strHost.first() + attr.urlArgs
|
||||
val urlV6 = attr.strHostIpv6.firstOrNull()?.plus(attr.urlArgs)
|
||||
|
||||
return Response.Success(fileMd5, urlV4, urlV6)
|
||||
}
|
||||
|
||||
// Lcom/tencent/mobileqq/transfile/protohandler/ShortVideoDownHandler;constructReqBody(Ljava/util/List;)[B
|
||||
operator fun invoke(
|
||||
client: QQAndroidClient,
|
||||
contact: Contact,
|
||||
sender: User,
|
||||
videoFIleId: String,
|
||||
videoFileMd5: ByteArray,
|
||||
) = buildOutgoingUniPacket(client) { sequenceId ->
|
||||
writeProtoBuf(
|
||||
PttShortVideo.ReqBody.serializer(),
|
||||
PttShortVideo.ReqBody(
|
||||
cmd = 400,
|
||||
seq = sequenceId,
|
||||
msgPttShortVideoDownloadReq = PttShortVideo.PttShortVideoDownloadReq(
|
||||
fromuin = sender.uin,
|
||||
touin = client.uin,
|
||||
chatType = if (sender is Friend) 0 else 1,
|
||||
clientType = 7,
|
||||
fileid = videoFIleId,
|
||||
groupCode = if (contact is Group) contact.uin else 0L,
|
||||
fileMd5 = videoFileMd5,
|
||||
businessType = 1,
|
||||
flagSupportLargeSize = 1,
|
||||
flagClientQuicProtoEnable = 1,
|
||||
fileType = 2, // maybe 1 = newly uploaded video, unverified
|
||||
downType = 2,
|
||||
sceneType = 2, // hooked 0 and 1, but unknown
|
||||
reqTransferType = 1,
|
||||
reqHostType = 11,
|
||||
),
|
||||
msgExtensionReq = listOf(
|
||||
PttShortVideo.ExtensionReq(subBusiType = 0)
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import net.mamoe.mirai.utils.ExternalResource
|
||||
|
||||
@Suppress("FunctionName")
|
||||
internal expect fun CombinedExternalResource(vararg resources: ExternalResource): ExternalResource
|
@ -81,6 +81,10 @@ internal object MiraiCoreServices {
|
||||
msgProtocol,
|
||||
"net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol"
|
||||
) { net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol() }
|
||||
Services.register(
|
||||
msgProtocol,
|
||||
"net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol"
|
||||
) { net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol() }
|
||||
Services.register(
|
||||
msgProtocol,
|
||||
"net.mamoe.mirai.internal.message.protocol.impl.TextProtocol"
|
||||
|
@ -20,6 +20,7 @@ net.mamoe.mirai.internal.message.protocol.impl.PokeMessageProtocol
|
||||
net.mamoe.mirai.internal.message.protocol.impl.PttMessageProtocol
|
||||
net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol
|
||||
net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol
|
||||
net.mamoe.mirai.internal.message.protocol.impl.ShortVideoProtocol
|
||||
net.mamoe.mirai.internal.message.protocol.impl.TextProtocol
|
||||
net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol
|
||||
net.mamoe.mirai.internal.message.protocol.impl.ForwardMessageProtocol
|
||||
|
@ -0,0 +1,10 @@
|
||||
#
|
||||
# Copyright 2019-2022 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
|
||||
#
|
||||
|
||||
net.mamoe.mirai.internal.message.image.InternalShortVideoProtocolImpl
|
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.message
|
||||
|
||||
import net.mamoe.mirai.internal.test.AbstractTest
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
internal class RefineContextTest : AbstractTest() {
|
||||
@Test
|
||||
fun `merge test`() {
|
||||
val Key1 = RefineContextKey<Int>("KeyInt")
|
||||
val Key2 = RefineContextKey<Double>("KeyDouble")
|
||||
val Key3 = RefineContextKey<String>("KeyString")
|
||||
val Key4 = RefineContextKey<ByteArray>("KeyBytes")
|
||||
|
||||
val context1 = SimpleRefineContext(
|
||||
Key1 to 114514,
|
||||
Key2 to 1919.810,
|
||||
Key3 to "sodayo"
|
||||
)
|
||||
|
||||
val context2 = SimpleRefineContext(
|
||||
Key2 to 1919.811,
|
||||
Key3 to "yarimasune",
|
||||
Key4 to byteArrayOf(11, 45, 14)
|
||||
)
|
||||
|
||||
val combinedOverride = context1.merge(context2, override = true)
|
||||
val combinedNotOverride = context1.merge(context2, override = false)
|
||||
|
||||
val context3 = SimpleRefineContext(
|
||||
Key2 to 1919.811,
|
||||
Key3 to "yarimasune"
|
||||
)
|
||||
|
||||
assertEquals(context1, context1.merge(context3, false))
|
||||
assertTrue(combinedOverride != combinedNotOverride)
|
||||
|
||||
assertEquals(4, combinedOverride.entries().size)
|
||||
assertEquals(1919.811, combinedOverride[Key2])
|
||||
assertEquals(1919.810, combinedNotOverride[Key2])
|
||||
assertEquals("sodayo", combinedNotOverride[Key3])
|
||||
assertTrue(byteArrayOf(11, 45, 14).contentEquals(combinedNotOverride[Key4]))
|
||||
}
|
||||
}
|
@ -293,7 +293,10 @@ internal class MessageRefineTest : AbstractTestWithMiraiImpl() {
|
||||
1234567890, 1617378549, "群垃圾,时不时来被gc", PlainText("5")
|
||||
),
|
||||
ForwardMessage.Node(
|
||||
1234567890, 1617382639, "群垃圾,时不时来被gc", redefined[2].messageChain[QuoteReply]!! + PlainText("aseff")
|
||||
1234567890,
|
||||
1617382639,
|
||||
"群垃圾,时不时来被gc",
|
||||
redefined[2].messageChain[QuoteReply]!! + PlainText("aseff")
|
||||
),
|
||||
),
|
||||
redefined,
|
||||
@ -370,10 +373,12 @@ private fun assertMessageChainEquals(expected: MessageChain, actual: MessageChai
|
||||
if (a !is QuoteReply) return false
|
||||
if (!compare(e.source.originalMessage, a.source.originalMessage)) return false
|
||||
}
|
||||
|
||||
is MessageSource -> {
|
||||
if (a !is MessageSource) return false
|
||||
if (!compare(e.originalMessage, a.originalMessage)) return false
|
||||
}
|
||||
|
||||
is ForwardMessage -> {
|
||||
if (a !is ForwardMessage) return false
|
||||
if (e.brief != a.brief) return false
|
||||
@ -383,10 +388,12 @@ private fun assertMessageChainEquals(expected: MessageChain, actual: MessageChai
|
||||
if (e.preview != a.preview) return false
|
||||
assertNodesEquals(e.nodeList, a.nodeList)
|
||||
}
|
||||
|
||||
is Image -> {
|
||||
if (a !is Image) return false
|
||||
if (e.imageId != a.imageId) return false
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (e != a) return false
|
||||
}
|
||||
|
@ -32,6 +32,7 @@ internal class MessageProtocolFacadeTest : AbstractTest() {
|
||||
PokeMessageProtocol
|
||||
PttMessageProtocol
|
||||
RichMessageProtocol
|
||||
ShortVideoProtocol
|
||||
TextProtocol
|
||||
VipFaceProtocol
|
||||
ForwardMessageProtocol
|
||||
|
@ -236,7 +236,12 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
|
||||
protected open fun Deferred<ChecksConfiguration>.doDecoderChecks() {
|
||||
val config = this.getCompleted()
|
||||
doDecoderChecks(config.messageChain, protocols) {
|
||||
decodeAndRefineLight(config.elems, config.groupIdOrZero, config.messageSourceKind, bot)
|
||||
decodeAndRefineLight(
|
||||
config.elems,
|
||||
config.groupIdOrZero,
|
||||
config.messageSourceKind,
|
||||
bot
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -280,6 +285,7 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
|
||||
sender = bot,
|
||||
target = defaultTarget
|
||||
)
|
||||
|
||||
is Friend -> OnlineMessageSourceToFriendImpl(
|
||||
sequenceIds = intArrayOf(1),
|
||||
internalIds = intArrayOf(1),
|
||||
@ -288,6 +294,7 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
|
||||
sender = bot,
|
||||
target = defaultTarget
|
||||
)
|
||||
|
||||
else -> error("Unexpected target: $defaultTarget")
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import io.ktor.utils.io.streams.*
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.Deferred
|
||||
import net.mamoe.mirai.utils.ExternalResource
|
||||
import net.mamoe.mirai.utils.MiraiInternalApi
|
||||
import net.mamoe.mirai.utils.md5
|
||||
import net.mamoe.mirai.utils.sha1
|
||||
import java.io.InputStream
|
||||
import java.io.SequenceInputStream
|
||||
import java.util.Collections
|
||||
|
||||
@Suppress("FunctionName")
|
||||
internal actual fun CombinedExternalResource(vararg resources: ExternalResource): ExternalResource {
|
||||
return CombinedExternalResource(resources.toList())
|
||||
}
|
||||
|
||||
/**
|
||||
* it is caller's responsibility to guarantee the immutability of the stream.
|
||||
*/
|
||||
internal class CombinedExternalResource(
|
||||
private val inputs: Collection<ExternalResource>
|
||||
) : ExternalResource {
|
||||
override val isAutoClose: Boolean = true
|
||||
|
||||
override val size: Long = inputs.sumOf { it.size }
|
||||
override val md5: ByteArray by lazy { combine().md5() }
|
||||
override val sha1: ByteArray by lazy { combine().sha1() }
|
||||
|
||||
override val formatName: String = ""
|
||||
|
||||
private val _closed = CompletableDeferred<Unit>()
|
||||
override val closed: Deferred<Unit>
|
||||
get() = _closed
|
||||
|
||||
override fun close() {
|
||||
_closed.complete(Unit)
|
||||
}
|
||||
|
||||
override fun inputStream(): InputStream = combine()
|
||||
|
||||
@MiraiInternalApi
|
||||
override fun input(): Input = inputStream().asInput()
|
||||
|
||||
private fun combine(): InputStream {
|
||||
return SequenceInputStream(Collections.enumeration(inputs.map { it.inputStream() }))
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2019-2023 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
|
||||
*/
|
||||
|
||||
package net.mamoe.mirai.internal.utils
|
||||
|
||||
import io.ktor.utils.io.core.*
|
||||
import net.mamoe.mirai.internal.test.AbstractTest
|
||||
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.text.toByteArray
|
||||
|
||||
class CombinedExternalResourceTest : AbstractTest() {
|
||||
@Test
|
||||
fun `work`() {
|
||||
val res1 = STRING_1.toByteArray().toExternalResource()
|
||||
val res2 = STRING_2.toByteArray().toExternalResource()
|
||||
|
||||
val combined1 = buildPacket {
|
||||
res1.input().use { it.copyTo(this) }
|
||||
res2.input().use { it.copyTo(this) }
|
||||
}.readBytes().toExternalResource()
|
||||
|
||||
val combined2 = CombinedExternalResource(res1, res2)
|
||||
|
||||
assertEquals(combined1.size, combined2.size)
|
||||
assertTrue { combined1.md5.contentEquals(combined2.md5) }
|
||||
assertTrue { combined1.sha1.contentEquals(combined2.sha1) }
|
||||
}
|
||||
|
||||
|
||||
private val STRING_1 = """
|
||||
b4FNDvv49gMInP29t82fPJuWQ4ArG1k1YVeCN3UReWXplm4H2S4Rp7zTpt8WXRQEtTL7VemlTIytPbwUkus7qgPVsyUCFreRR1vB3QhRznXqcT06fDkXJQJKyyBGEdwddNWZAkqZcdrOk679sG14kKK5GexaQUmdfTivT5VPO8w1yoWPcUHPfpjB0shCEzjkHI84LJbWNRCVjoZhy0jZAKZxLrsi1sGhl30QcXCFnHpPhWbED8Er9c8gVbjYsG8ejaUlbeNNdKW3GoOpgjFLbwZoQI4QZZgvP5jhBWUPiMG3MCcPlYRSgTf70JpDVTE0YOLhXdJJxz87S8MR4M7rU0WO7ZRkoFOQpFHdmfMmJxbiATHHkOyHVhu1mvA0L72MNtDQP5GcKlDbDcdJL7om4FmekAVVnh7R
|
||||
""".trimIndent()
|
||||
private val STRING_2 = """
|
||||
FdDoAZt2hJkKAfEWBNWO44R0tJRmApqIwHDD05oW0jyLVVPOdcPaFjY1muYM1qa6jbhZppWYm1oOmgbpFgdPZRYDgzznR0kSapdqXeSSevV4ww4E1U71ELDMsq4f0a1Y8K6UxIOpQl1n20eoe80fHuXKkfN6kbhROBXcwGbiFRpPg5k8G5hCerQQunQyNoeEZrbKacq2OYkOEJV57LuSbBTF4FMZYxCEp1a8omnK1EUHC1Go5pGy0dovz78KpCshPr7MHNMnRu0FiuJ1WYT8ri8iXWsTx3AMxHRjCYfJgrtqc86L3HW0V6Wr8FqFMJLtFl4PgXj5etfRSaaqRJFIZ3nWiRqW48JMRqdGRvLTUWs1Zoa8H11bych18MVypUQJOyxghLLJw0ZP4CvSNUeJOEMitxFxyzjC
|
||||
""".trimIndent()
|
||||
}
|
Loading…
Reference in New Issue
Block a user