mirror of
https://github.com/mamoe/mirai.git
synced 2025-02-06 10:54:43 +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 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 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 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 {
|
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 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 {
|
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 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 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 fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||||
public final fun getRank ()Ljava/lang/Integer;
|
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 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 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 Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key;
|
||||||
public static final field SERIAL_NAME Ljava/lang/String;
|
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 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 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 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;
|
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 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 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 INSTANCE Lnet/mamoe/mirai/message/data/ShowImageFlag;
|
||||||
public static final field SERIAL_NAME Ljava/lang/String;
|
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 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 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 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 {
|
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 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 {
|
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 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 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 fun getBot ()Lnet/mamoe/mirai/Bot;
|
||||||
public final fun getRank ()Ljava/lang/Integer;
|
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 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 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 Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key;
|
||||||
public static final field SERIAL_NAME Ljava/lang/String;
|
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 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 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 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;
|
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 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 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 INSTANCE Lnet/mamoe/mirai/message/data/ShowImageFlag;
|
||||||
public static final field SERIAL_NAME Ljava/lang/String;
|
public static final field SERIAL_NAME Ljava/lang/String;
|
||||||
|
@ -72,8 +72,6 @@ public interface Contact : ContactOrBot, CoroutineScope {
|
|||||||
/**
|
/**
|
||||||
* 上传一个 [资源][ExternalResource] 作为图片以备发送.
|
* 上传一个 [资源][ExternalResource] 作为图片以备发送.
|
||||||
*
|
*
|
||||||
* **无论上传是否成功都不会关闭 [resource]. 需要调用方手动关闭资源**
|
|
||||||
*
|
|
||||||
* 也可以使用其他扩展: [ExternalResource.uploadAsImage] 使用 [File], [InputStream] 等上传.
|
* 也可以使用其他扩展: [ExternalResource.uploadAsImage] 使用 [File], [InputStream] 等上传.
|
||||||
*
|
*
|
||||||
* @see Image 查看有关图片的更多信息, 如上传图片
|
* @see Image 查看有关图片的更多信息, 如上传图片
|
||||||
@ -88,6 +86,26 @@ public interface Contact : ContactOrBot, CoroutineScope {
|
|||||||
*/
|
*/
|
||||||
public suspend fun uploadImage(resource: ExternalResource): Image
|
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 {
|
public companion object {
|
||||||
/**
|
/**
|
||||||
* 读取 [InputStream] 到临时文件并将其作为图片发送到指定联系人
|
* 读取 [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
|
@MiraiInternalApi
|
||||||
public companion object {
|
public companion object {
|
||||||
public val instance: InternalImageProtocol by lazy {
|
public val instance: InternalImageProtocol by lazy {
|
||||||
|
Mirai // initialize MiraiImpl first
|
||||||
loadService(
|
loadService(
|
||||||
InternalImageProtocol::class,
|
InternalImageProtocol::class,
|
||||||
"net.mamoe.mirai.internal.message.InternalImageProtocolImpl"
|
"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 visitVoice(message: net.mamoe.mirai.message.data.Voice, data: D): R
|
||||||
public fun visitAudio(message: Audio, data: D): R
|
public fun visitAudio(message: Audio, data: D): R
|
||||||
|
|
||||||
|
public fun visitShortVideo(message: ShortVideo, data: D): R
|
||||||
|
|
||||||
// region HummerMessage
|
// region HummerMessage
|
||||||
public fun visitHummerMessage(message: HummerMessage, data: D): R
|
public fun visitHummerMessage(message: HummerMessage, data: D): R
|
||||||
public fun visitFlashImage(message: FlashImage, 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)
|
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 {
|
public override fun visitHummerMessage(message: HummerMessage, data: D): R {
|
||||||
return visitMessageContent(message, data)
|
return visitMessageContent(message, data)
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,9 @@ public interface ExternalResource : java.io.Closeable {
|
|||||||
* 文件格式,如 "png", "amr". 当无法自动识别格式时为 [DEFAULT_FORMAT_NAME].
|
* 文件格式,如 "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.getFileType
|
||||||
* @see net.mamoe.mirai.utils.FILE_TYPES
|
* @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.broadcastMessagePreSendEvent
|
||||||
import net.mamoe.mirai.internal.contact.replaceMagicCodes
|
import net.mamoe.mirai.internal.contact.replaceMagicCodes
|
||||||
import net.mamoe.mirai.message.MessageReceipt
|
import net.mamoe.mirai.message.MessageReceipt
|
||||||
import net.mamoe.mirai.message.data.Image
|
import net.mamoe.mirai.message.data.*
|
||||||
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.mock.MockBot
|
import net.mamoe.mirai.mock.MockBot
|
||||||
import net.mamoe.mirai.mock.contact.MockContact
|
import net.mamoe.mirai.mock.contact.MockContact
|
||||||
import net.mamoe.mirai.utils.*
|
import net.mamoe.mirai.utils.*
|
||||||
@ -61,6 +58,14 @@ internal abstract class AbstractMockContact(
|
|||||||
return bot.uploadMockImage(resource)
|
return bot.uploadMockImage(resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun uploadShortVideo(
|
||||||
|
thumbnail: ExternalResource,
|
||||||
|
video: ExternalResource,
|
||||||
|
fileName: String?
|
||||||
|
): ShortVideo {
|
||||||
|
TODO("mock upload short video")
|
||||||
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "$id"
|
return "$id"
|
||||||
}
|
}
|
||||||
|
@ -15,27 +15,35 @@ package net.mamoe.mirai.utils
|
|||||||
import kotlin.jvm.JvmMultifileClass
|
import kotlin.jvm.JvmMultifileClass
|
||||||
import kotlin.jvm.JvmName
|
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(
|
private val FILE_TYPES: List<FileType> = listOf(
|
||||||
"FFD8FF" to "jpg",
|
FileType("^FFD8FF", 3, "jpg"),
|
||||||
"89504E47" to "png",
|
FileType("^89504E47", 4, "png"),
|
||||||
"47494638" to "gif",
|
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
|
//"49492A00" to "tif", // client doesn't support
|
||||||
"424D" to "bmp",
|
|
||||||
//"52494646" to "webp", // pc client doesn't support
|
//"52494646" to "webp", // pc client doesn't support
|
||||||
|
|
||||||
// "57415645" to "wav", // server doesn't support
|
// "57415645" to "wav", // server doesn't support
|
||||||
|
|
||||||
"2321414D52" to "amr",
|
|
||||||
"02232153494C4B5F5633" to "silk",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 在 [getFileType] 需要的 [ByteArray] 长度
|
* 在 [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)
|
length = COUNT_BYTES_USED_FOR_DETECTING_FILE_TYPE.coerceAtMost(fileHeader.size)
|
||||||
)
|
)
|
||||||
FILE_TYPES.forEach { (k, v) ->
|
FILE_TYPES.forEach { t ->
|
||||||
if (hex.startsWith(k)) {
|
if (hex.contains(t.signatureRegex)) {
|
||||||
return v
|
return t.formatName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
|
@ -723,9 +723,7 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
|
|||||||
it.fileName to it.buffer.loadAs(MsgTransmit.PbMultiMsgNew.serializer())
|
it.fileName to it.buffer.loadAs(MsgTransmit.PbMultiMsgNew.serializer())
|
||||||
}
|
}
|
||||||
val main = pbs["MultiMsg"] ?: return this.msg.map { it.toNode(bot, EmptyRefineContext) }
|
val main = pbs["MultiMsg"] ?: return this.msg.map { it.toNode(bot, EmptyRefineContext) }
|
||||||
val context = SimpleRefineContext(mutableMapOf())
|
return main.toForwardMessageNodes(bot, SimpleRefineContext(ForwardMessageInternal.MsgTransmits to pbs))
|
||||||
context[ForwardMessageInternal.MsgTransmits] = pbs
|
|
||||||
return main.toForwardMessageNodes(bot, context)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
|
private suspend fun MsgComm.Msg.toNode(bot: Bot, refineContext: RefineContext): ForwardMessage.Node {
|
||||||
|
@ -9,10 +9,26 @@
|
|||||||
|
|
||||||
package net.mamoe.mirai.internal.contact
|
package net.mamoe.mirai.internal.contact
|
||||||
|
|
||||||
|
import io.ktor.utils.io.core.*
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.contact.*
|
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.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.contracts.contract
|
||||||
import kotlin.coroutines.CoroutineContext
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
|
||||||
@ -21,6 +37,118 @@ internal abstract class AbstractContact(
|
|||||||
parentCoroutineContext: CoroutineContext,
|
parentCoroutineContext: CoroutineContext,
|
||||||
) : Contact {
|
) : Contact {
|
||||||
final override val coroutineContext: CoroutineContext = parentCoroutineContext.childScopeContext()
|
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
|
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 kotlinx.coroutines.flow.*
|
||||||
import net.mamoe.mirai.contact.roaming.RoamingMessageFilter
|
import net.mamoe.mirai.contact.roaming.RoamingMessageFilter
|
||||||
import net.mamoe.mirai.internal.contact.CommonGroupImpl
|
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.message.toMessageChainOnline
|
||||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopManagement
|
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
|
.sortedByDescending { it.msgHead.msgSeq } // Ensure caller receives newer messages first
|
||||||
.filter { filter.apply(it) } // Call filter after sort
|
.filter { filter.apply(it) } // Call filter after sort
|
||||||
.asFlow()
|
.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
|
currentSeq = resp.msgElem.first().msgHead.msgSeq
|
||||||
|
@ -16,6 +16,8 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
import net.mamoe.mirai.contact.roaming.RoamingMessageFilter
|
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.message.toMessageChainOnline
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq
|
import net.mamoe.mirai.internal.network.protocol.packet.chat.receive.MessageSvcPbGetRoamMsgReq
|
||||||
import net.mamoe.mirai.message.data.MessageChain
|
import net.mamoe.mirai.message.data.MessageChain
|
||||||
@ -32,13 +34,18 @@ internal sealed class TimeBasedRoamingMessagesImpl : AbstractRoamingMessages() {
|
|||||||
while (currentCoroutineContext().isActive) {
|
while (currentCoroutineContext().isActive) {
|
||||||
val resp = requestRoamMsg(timeStart, lastMessageTime, random)
|
val resp = requestRoamMsg(timeStart, lastMessageTime, random)
|
||||||
val messages = resp.messages ?: break
|
val messages = resp.messages ?: break
|
||||||
|
|
||||||
if (filter == null || filter === RoamingMessageFilter.ANY) {
|
if (filter == null || filter === RoamingMessageFilter.ANY) {
|
||||||
// fast path
|
// 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 {
|
} else {
|
||||||
for (message in messages) {
|
for (message in messages) {
|
||||||
if (filter.invoke(createRoamingMessage(message, 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.ImMsgBody
|
||||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||||
import net.mamoe.mirai.message.data.*
|
import net.mamoe.mirai.message.data.*
|
||||||
|
import net.mamoe.mirai.utils.castOrNull
|
||||||
import net.mamoe.mirai.utils.structureToString
|
import net.mamoe.mirai.utils.structureToString
|
||||||
import net.mamoe.mirai.utils.toLongUnsigned
|
import net.mamoe.mirai.utils.toLongUnsigned
|
||||||
|
import net.mamoe.mirai.utils.warning
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 只在手动构造 [OfflineMessageSource] 时调用
|
* 只在手动构造 [OfflineMessageSource] 时调用
|
||||||
@ -78,7 +80,17 @@ internal suspend fun MsgComm.Msg.toMessageChainOnline(
|
|||||||
MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0
|
MessageSourceKind.GROUP -> msgHead.groupInfo?.groupCode ?: 0
|
||||||
else -> 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(
|
//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 })
|
val builder = MessageChainBuilder(messageList.sumOf { it.msgBody.richText.elems.size })
|
||||||
|
|
||||||
if (onlineSource != null) {
|
val source = if (onlineSource != null) {
|
||||||
builder.add(ReceiveMessageTransformer.createMessageSource(bot, onlineSource, messageSourceKind, messageList))
|
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 ->
|
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) {
|
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.flags.InternalFlagOnlyMessage
|
||||||
import net.mamoe.mirai.internal.message.source.IncomingMessageSourceInternal
|
import net.mamoe.mirai.internal.message.source.IncomingMessageSourceInternal
|
||||||
import net.mamoe.mirai.message.data.*
|
import net.mamoe.mirai.message.data.*
|
||||||
|
import net.mamoe.mirai.utils.cast
|
||||||
import net.mamoe.mirai.utils.safeCast
|
import net.mamoe.mirai.utils.safeCast
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -99,6 +100,12 @@ internal class RefineContextKey<T : Any>(
|
|||||||
append(')')
|
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 contains(key: RefineContextKey<*>): Boolean
|
||||||
operator fun <T : Any> get(key: RefineContextKey<T>): T?
|
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 <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 {
|
internal interface MutableRefineContext : RefineContext {
|
||||||
@ -118,9 +127,19 @@ internal interface MutableRefineContext : RefineContext {
|
|||||||
internal object EmptyRefineContext : RefineContext {
|
internal object EmptyRefineContext : RefineContext {
|
||||||
override fun contains(key: RefineContextKey<*>): Boolean = false
|
override fun contains(key: RefineContextKey<*>): Boolean = false
|
||||||
override fun <T : Any> get(key: RefineContextKey<T>): T? = null
|
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 {
|
override fun toString(): String {
|
||||||
return "EmptyRefineContext"
|
return "EmptyRefineContext"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
return other === EmptyRefineContext
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
@ -140,8 +159,32 @@ internal class SimpleRefineContext(
|
|||||||
override fun remove(key: RefineContextKey<*>) {
|
override fun remove(key: RefineContextKey<*>) {
|
||||||
delegate.remove(key)
|
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].
|
* 执行不需要 `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,
|
groupIdOrZero: Long,
|
||||||
messageSourceKind: MessageSourceKind,
|
messageSourceKind: MessageSourceKind,
|
||||||
bot: Bot,
|
bot: Bot,
|
||||||
): MessageChain = buildMessageChain { decode(elements, groupIdOrZero, messageSourceKind, bot, this, null) }
|
): MessageChain = buildMessageChain {
|
||||||
|
decode(elements, groupIdOrZero, messageSourceKind, bot, this, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
fun createSerializersModule(): SerializersModule = SerializersModule {
|
fun createSerializersModule(): SerializersModule = SerializersModule {
|
||||||
@ -336,6 +338,7 @@ internal class MessageProtocolFacadeImpl(
|
|||||||
|
|
||||||
return getSingleReceipt(result, message)
|
return getSingleReceipt(result, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun <C : AbstractContact> preprocessAndSendOutgoing(
|
override suspend fun <C : AbstractContact> preprocessAndSendOutgoing(
|
||||||
target: C,
|
target: C,
|
||||||
message: Message,
|
message: Message,
|
||||||
@ -378,6 +381,7 @@ internal class MessageProtocolFacadeImpl(
|
|||||||
"Internal error: no MessageReceipt was returned from OutgoingMessagePipeline for message",
|
"Internal error: no MessageReceipt was returned from OutgoingMessagePipeline for message",
|
||||||
forDebug = message.structureToString()
|
forDebug = message.structureToString()
|
||||||
)
|
)
|
||||||
|
|
||||||
1 -> return result.single().castUp()
|
1 -> return result.single().castUp()
|
||||||
else -> throw contextualBugReportException(
|
else -> throw contextualBugReportException(
|
||||||
"Internal error: multiple MessageReceipts were returned from OutgoingMessagePipeline: $result",
|
"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 MESSAGE_SOURCE_KIND = TypeKey<MessageSourceKind>("messageSourceKind")
|
||||||
val GROUP_ID = TypeKey<Long>("groupId") // zero if not group
|
val GROUP_ID = TypeKey<Long>("groupId") // zero if not group
|
||||||
val CONTAINING_MSG = TypeKey<MsgComm.Msg?>("containingMsg")
|
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 kotlinx.serialization.Transient
|
||||||
import net.mamoe.mirai.Bot
|
import net.mamoe.mirai.Bot
|
||||||
import net.mamoe.mirai.internal.message.MessageSourceSerializerImpl
|
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.message.toMessageChainNoSource
|
||||||
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
|
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
|
||||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
||||||
@ -183,7 +185,10 @@ internal fun OfflineMessageSourceImplData(
|
|||||||
internalIds = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer())
|
internalIds = delegate.pbReserve.loadAs(SourceMsg.ResvAttr.serializer())
|
||||||
.origUids?.mapToIntArray { it.toInt() } ?: intArrayOf(),
|
.origUids?.mapToIntArray { it.toInt() } ?: intArrayOf(),
|
||||||
time = delegate.time,
|
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,
|
fromId = delegate.senderUin,
|
||||||
targetId = when {
|
targetId = when {
|
||||||
groupIdOrZero != 0L -> groupIdOrZero
|
groupIdOrZero != 0L -> groupIdOrZero
|
||||||
@ -191,6 +196,7 @@ internal fun OfflineMessageSourceImplData(
|
|||||||
delegate.srcMsg != null -> runCatching {
|
delegate.srcMsg != null -> runCatching {
|
||||||
delegate.srcMsg.loadAs(MsgComm.Msg.serializer()).msgHead.toUin
|
delegate.srcMsg.loadAs(MsgComm.Msg.serializer()).msgHead.toUin
|
||||||
}.getOrElse { 0L }
|
}.getOrElse { 0L }
|
||||||
|
|
||||||
else -> 0/*error("cannot find targetId. delegate=${delegate._miraiContentToString()}, delegate.srcMsg=${
|
else -> 0/*error("cannot find targetId. delegate=${delegate._miraiContentToString()}, delegate.srcMsg=${
|
||||||
kotlin.runCatching { delegate.srcMsg?.loadAs(MsgComm.Msg.serializer())?._miraiContentToString() }
|
kotlin.runCatching { delegate.srcMsg?.loadAs(MsgComm.Msg.serializer())?._miraiContentToString() }
|
||||||
.fold(
|
.fold(
|
||||||
|
@ -126,6 +126,8 @@ internal enum class ResourceKind(
|
|||||||
FORWARD_MESSAGE("forward message"),
|
FORWARD_MESSAGE("forward message"),
|
||||||
|
|
||||||
ANNOUNCEMENT_IMAGE("announcement image"),
|
ANNOUNCEMENT_IMAGE("announcement image"),
|
||||||
|
|
||||||
|
SHORT_VIDEO("short video")
|
||||||
;
|
;
|
||||||
|
|
||||||
override fun toString(): String = display
|
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.event.events.MemberSpecialTitleChangeEvent
|
||||||
import net.mamoe.mirai.internal.contact.*
|
import net.mamoe.mirai.internal.contact.*
|
||||||
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
|
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.message.toMessageChainOnline
|
||||||
import net.mamoe.mirai.internal.network.Packet
|
import net.mamoe.mirai.internal.network.Packet
|
||||||
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
|
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
|
||||||
@ -159,7 +161,18 @@ internal class GroupMessageProcessor(
|
|||||||
GroupMessageSyncEvent(
|
GroupMessageSyncEvent(
|
||||||
client = bot.otherClients.find { it.appId == msgHead.fromInstid }
|
client = bot.otherClients.find { it.appId == msgHead.fromInstid }
|
||||||
?: return, // don't compare with dstAppId. diff.
|
?: 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,
|
time = msgHead.msgTime,
|
||||||
group = group,
|
group = group,
|
||||||
sender = sender,
|
sender = sender,
|
||||||
@ -174,7 +187,18 @@ internal class GroupMessageProcessor(
|
|||||||
GroupMessageEvent(
|
GroupMessageEvent(
|
||||||
senderName = nameCard.nick,
|
senderName = nameCard.nick,
|
||||||
sender = sender,
|
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,
|
permission = sender.permission,
|
||||||
time = msgHead.msgTime,
|
time = msgHead.msgTime,
|
||||||
),
|
),
|
||||||
|
@ -15,6 +15,8 @@ import net.mamoe.mirai.event.Event
|
|||||||
import net.mamoe.mirai.event.events.*
|
import net.mamoe.mirai.event.events.*
|
||||||
import net.mamoe.mirai.internal.contact.*
|
import net.mamoe.mirai.internal.contact.*
|
||||||
import net.mamoe.mirai.internal.getGroupByUinOrCode
|
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.message.toMessageChainOnline
|
||||||
import net.mamoe.mirai.internal.network.Packet
|
import net.mamoe.mirai.internal.network.Packet
|
||||||
import net.mamoe.mirai.internal.network.components.NoticePipelineContext
|
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.notice.group.GroupMessageProcessor
|
||||||
import net.mamoe.mirai.internal.network.protocol.data.proto.MsgComm
|
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.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.assertUnreachable
|
||||||
import net.mamoe.mirai.utils.context
|
import net.mamoe.mirai.utils.context
|
||||||
|
|
||||||
@ -114,6 +117,7 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type
|
|||||||
val group = bot.getGroupByUinOrCode(tmpHead.groupUin) ?: return
|
val group = bot.getGroupByUinOrCode(tmpHead.groupUin) ?: return
|
||||||
handlePrivateMessage(data, group[senderUin] ?: return)
|
handlePrivateMessage(data, group[senderUin] ?: return)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> markNotConsumed()
|
else -> markNotConsumed()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +133,16 @@ internal class PrivateMessageProcessor : SimpleNoticeProcessor<MsgComm.Msg>(type
|
|||||||
val msgs = user.fragmentedMessageMerger.tryMerge(this)
|
val msgs = user.fragmentedMessageMerger.tryMerge(this)
|
||||||
if (msgs.isEmpty()) return
|
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
|
val time = msgHead.msgTime
|
||||||
|
|
||||||
collected += if (fromSync) {
|
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.ImgStore
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.LongConn
|
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.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.chat.voice.PttStore
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
|
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
|
||||||
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
|
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
|
||||||
@ -151,6 +152,8 @@ internal object KnownPacketFactories {
|
|||||||
PttStore.GroupPttUp,
|
PttStore.GroupPttUp,
|
||||||
PttStore.GroupPttDown,
|
PttStore.GroupPttDown,
|
||||||
PttStore.C2CPttDown,
|
PttStore.C2CPttDown,
|
||||||
|
PttCenterSvr.GroupShortVideoUpReq,
|
||||||
|
PttCenterSvr.ShortVideoDownReq,
|
||||||
LongConn.OffPicUp,
|
LongConn.OffPicUp,
|
||||||
// LongConn.OffPicDown,
|
// LongConn.OffPicDown,
|
||||||
TroopManagement.EditSpecialTitle,
|
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,
|
msgProtocol,
|
||||||
"net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol"
|
"net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol"
|
||||||
) { 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(
|
Services.register(
|
||||||
msgProtocol,
|
msgProtocol,
|
||||||
"net.mamoe.mirai.internal.message.protocol.impl.TextProtocol"
|
"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.PttMessageProtocol
|
||||||
net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol
|
net.mamoe.mirai.internal.message.protocol.impl.QuoteReplyProtocol
|
||||||
net.mamoe.mirai.internal.message.protocol.impl.RichMessageProtocol
|
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.TextProtocol
|
||||||
net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol
|
net.mamoe.mirai.internal.message.protocol.impl.VipFaceProtocol
|
||||||
net.mamoe.mirai.internal.message.protocol.impl.ForwardMessageProtocol
|
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")
|
1234567890, 1617378549, "群垃圾,时不时来被gc", PlainText("5")
|
||||||
),
|
),
|
||||||
ForwardMessage.Node(
|
ForwardMessage.Node(
|
||||||
1234567890, 1617382639, "群垃圾,时不时来被gc", redefined[2].messageChain[QuoteReply]!! + PlainText("aseff")
|
1234567890,
|
||||||
|
1617382639,
|
||||||
|
"群垃圾,时不时来被gc",
|
||||||
|
redefined[2].messageChain[QuoteReply]!! + PlainText("aseff")
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
redefined,
|
redefined,
|
||||||
@ -370,10 +373,12 @@ private fun assertMessageChainEquals(expected: MessageChain, actual: MessageChai
|
|||||||
if (a !is QuoteReply) return false
|
if (a !is QuoteReply) return false
|
||||||
if (!compare(e.source.originalMessage, a.source.originalMessage)) return false
|
if (!compare(e.source.originalMessage, a.source.originalMessage)) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
is MessageSource -> {
|
is MessageSource -> {
|
||||||
if (a !is MessageSource) return false
|
if (a !is MessageSource) return false
|
||||||
if (!compare(e.originalMessage, a.originalMessage)) return false
|
if (!compare(e.originalMessage, a.originalMessage)) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
is ForwardMessage -> {
|
is ForwardMessage -> {
|
||||||
if (a !is ForwardMessage) return false
|
if (a !is ForwardMessage) return false
|
||||||
if (e.brief != a.brief) 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
|
if (e.preview != a.preview) return false
|
||||||
assertNodesEquals(e.nodeList, a.nodeList)
|
assertNodesEquals(e.nodeList, a.nodeList)
|
||||||
}
|
}
|
||||||
|
|
||||||
is Image -> {
|
is Image -> {
|
||||||
if (a !is Image) return false
|
if (a !is Image) return false
|
||||||
if (e.imageId != a.imageId) return false
|
if (e.imageId != a.imageId) return false
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
if (e != a) return false
|
if (e != a) return false
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ internal class MessageProtocolFacadeTest : AbstractTest() {
|
|||||||
PokeMessageProtocol
|
PokeMessageProtocol
|
||||||
PttMessageProtocol
|
PttMessageProtocol
|
||||||
RichMessageProtocol
|
RichMessageProtocol
|
||||||
|
ShortVideoProtocol
|
||||||
TextProtocol
|
TextProtocol
|
||||||
VipFaceProtocol
|
VipFaceProtocol
|
||||||
ForwardMessageProtocol
|
ForwardMessageProtocol
|
||||||
|
@ -236,7 +236,12 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
|
|||||||
protected open fun Deferred<ChecksConfiguration>.doDecoderChecks() {
|
protected open fun Deferred<ChecksConfiguration>.doDecoderChecks() {
|
||||||
val config = this.getCompleted()
|
val config = this.getCompleted()
|
||||||
doDecoderChecks(config.messageChain, protocols) {
|
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,
|
sender = bot,
|
||||||
target = defaultTarget
|
target = defaultTarget
|
||||||
)
|
)
|
||||||
|
|
||||||
is Friend -> OnlineMessageSourceToFriendImpl(
|
is Friend -> OnlineMessageSourceToFriendImpl(
|
||||||
sequenceIds = intArrayOf(1),
|
sequenceIds = intArrayOf(1),
|
||||||
internalIds = intArrayOf(1),
|
internalIds = intArrayOf(1),
|
||||||
@ -288,6 +294,7 @@ internal abstract class AbstractMessageProtocolTest : AbstractMockNetworkHandler
|
|||||||
sender = bot,
|
sender = bot,
|
||||||
target = defaultTarget
|
target = defaultTarget
|
||||||
)
|
)
|
||||||
|
|
||||||
else -> error("Unexpected 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