Introduce Audio for new API, revert changes on Voice

This commit is contained in:
Him188 2021-08-05 12:50:07 +08:00
parent cf1c5d4d81
commit 8d8aca3f1c
24 changed files with 1313 additions and 380 deletions

View File

@ -176,6 +176,11 @@ public abstract interface class net/mamoe/mirai/contact/AnonymousMember : net/ma
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class net/mamoe/mirai/contact/AudioSupported : net/mamoe/mirai/contact/Contact {
public fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/OfflineAudio;
public abstract fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class net/mamoe/mirai/contact/BotIsBeingMutedException : java/lang/RuntimeException {
public fun <init> (Lnet/mamoe/mirai/contact/Group;)V
public final fun getTarget ()Lnet/mamoe/mirai/contact/Group;
@ -326,7 +331,7 @@ public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamo
public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile;
}
public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/User, net/mamoe/mirai/contact/VoiceSupported {
public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User {
public fun delete ()V
public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun nudge ()Lnet/mamoe/mirai/message/action/FriendNudge;
@ -338,7 +343,7 @@ public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/corouti
public abstract fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/FileSupported, net/mamoe/mirai/contact/VoiceSupported {
public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/FileSupported {
public static final field Companion Lnet/mamoe/mirai/contact/Group$Companion;
public abstract fun contains (J)Z
public fun contains (Lnet/mamoe/mirai/contact/NormalMember;)Z
@ -365,6 +370,8 @@ public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutin
public fun setEssenceMessage (Lnet/mamoe/mirai/message/data/MessageSource;)Z
public abstract fun setEssenceMessage (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun setName (Ljava/lang/String;)V
public fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Voice;
public abstract fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class net/mamoe/mirai/contact/Group$Companion {
@ -560,11 +567,6 @@ public abstract interface class net/mamoe/mirai/contact/UserOrBot : net/mamoe/mi
public abstract fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
}
public abstract interface class net/mamoe/mirai/contact/VoiceSupported : net/mamoe/mirai/contact/Contact {
public fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Voice;
public abstract fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class net/mamoe/mirai/contact/announcement/Announcement {
public static final field Companion Lnet/mamoe/mirai/contact/announcement/Announcement$Companion;
public abstract fun getContent ()Ljava/lang/String;
@ -3158,6 +3160,41 @@ public final class net/mamoe/mirai/message/data/AtAll : net/mamoe/mirai/message/
public fun toString ()Ljava/lang/String;
}
public abstract interface class net/mamoe/mirai/message/data/Audio : net/mamoe/mirai/message/data/MessageContent {
public static final field Key Lnet/mamoe/mirai/message/data/Audio$Key;
public fun contentToString ()Ljava/lang/String;
public abstract fun getCodec ()Lnet/mamoe/mirai/message/data/AudioCodec;
public abstract fun getExtraData ()[B
public abstract fun getFileMd5 ()[B
public abstract fun getFileSize ()J
public abstract fun getFilename ()Ljava/lang/String;
public abstract fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/Audio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
}
public final class net/mamoe/mirai/message/data/AudioCodec : java/lang/Enum {
public static final field AMR Lnet/mamoe/mirai/message/data/AudioCodec;
public static final field Companion Lnet/mamoe/mirai/message/data/AudioCodec$Companion;
public static final field SILK Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun getFormatName ()Ljava/lang/String;
public final fun getId ()I
public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public static fun values ()[Lnet/mamoe/mirai/message/data/AudioCodec;
}
public final class net/mamoe/mirai/message/data/AudioCodec$Companion {
public final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec;
}
public abstract interface class net/mamoe/mirai/message/data/ConstrainSingle : net/mamoe/mirai/message/data/SingleMessage {
public abstract fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
}
@ -4499,6 +4536,8 @@ public final class net/mamoe/mirai/message/data/MessageUtils {
public static final synthetic fun At (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/At;
public static final synthetic fun FileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
public static final synthetic fun Image (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
public static final synthetic fun OfflineAudio (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio;
public static final synthetic fun OfflineAudio (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio;
public static final synthetic fun UnsupportedMessage ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage;
public static final synthetic fun at (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/message/data/At;
public static final fun buildMessageChain (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain;
@ -4541,6 +4580,7 @@ public final class net/mamoe/mirai/message/data/MessageUtils {
public static final synthetic fun toMessageChain ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain;
public static final fun toOfflineMessageSource (Lnet/mamoe/mirai/message/data/OnlineMessageSource;)Lnet/mamoe/mirai/message/data/OfflineMessageSource;
public static final synthetic fun toPlainText (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/PlainText;
public static final synthetic fun toVoice (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice;
}
public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum {
@ -4604,6 +4644,26 @@ public final class net/mamoe/mirai/message/data/MusicShare$Key : net/mamoe/mirai
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract interface class net/mamoe/mirai/message/data/OfflineAudio : net/mamoe/mirai/message/data/Audio {
public static final field Key Lnet/mamoe/mirai/message/data/OfflineAudio$Key;
public static final field SERIAL_NAME Ljava/lang/String;
}
public abstract interface class net/mamoe/mirai/message/data/OfflineAudio$Factory {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE;
public abstract fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio;
public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio;
}
public final class net/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE : net/mamoe/mirai/message/data/OfflineAudio$Factory {
public fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio;
public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio;
}
public final class net/mamoe/mirai/message/data/OfflineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public static final field SERIAL_NAME Ljava/lang/String;
}
public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/mamoe/mirai/message/data/MessageSource {
public static final field Key Lnet/mamoe/mirai/message/data/OfflineMessageSource$Key;
public fun <init> ()V
@ -4613,6 +4673,17 @@ public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/ma
public final class net/mamoe/mirai/message/data/OfflineMessageSource$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
}
public abstract interface class net/mamoe/mirai/message/data/OnlineAudio : net/mamoe/mirai/message/data/Audio {
public static final field Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public abstract fun getLength ()J
public abstract fun getUrlForDownload ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/OnlineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public static final field SERIAL_NAME Ljava/lang/String;
}
public abstract class net/mamoe/mirai/message/data/OnlineMessageSource : net/mamoe/mirai/message/data/MessageSource {
public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Key;
public abstract fun getBot ()Lnet/mamoe/mirai/Bot;
@ -5090,32 +5161,39 @@ public final class net/mamoe/mirai/message/data/VipFace$Kind$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage {
public class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage {
public static final field Key Lnet/mamoe/mirai/message/data/Voice$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun <init> (ILjava/lang/String;[BJILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;[BJILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun contentToString ()Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public final fun getCodec ()I
public static final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice;
public fun getFileName ()Ljava/lang/String;
public fun getFileSize ()J
public fun getMd5 ()[B
public final fun getUrl ()Ljava/lang/String;
public fun getUrl ()Ljava/lang/String;
public final fun get_codec ()I
public fun hashCode ()I
public final fun toAudio ()Lnet/mamoe/mirai/message/data/Audio;
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$Serializer;
public final class net/mamoe/mirai/message/data/Voice$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$$serializer;
public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Voice;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/Voice;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder {
@ -5429,14 +5507,8 @@ public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io
public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image;
public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage;
public static fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage;
@ -5504,14 +5576,8 @@ public final class net/mamoe/mirai/utils/ExternalResource$Companion {
public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image;
public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public final fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage;
public final fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage;

View File

@ -176,6 +176,11 @@ public abstract interface class net/mamoe/mirai/contact/AnonymousMember : net/ma
public fun uploadImage (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class net/mamoe/mirai/contact/AudioSupported : net/mamoe/mirai/contact/Contact {
public fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/OfflineAudio;
public abstract fun uploadAudio (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class net/mamoe/mirai/contact/BotIsBeingMutedException : java/lang/RuntimeException {
public fun <init> (Lnet/mamoe/mirai/contact/Group;)V
public final fun getTarget ()Lnet/mamoe/mirai/contact/Group;
@ -326,7 +331,7 @@ public abstract interface class net/mamoe/mirai/contact/FileSupported : net/mamo
public abstract fun getFilesRoot ()Lnet/mamoe/mirai/utils/RemoteFile;
}
public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/User, net/mamoe/mirai/contact/VoiceSupported {
public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/User {
public fun delete ()V
public abstract fun delete (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun nudge ()Lnet/mamoe/mirai/message/action/FriendNudge;
@ -338,7 +343,7 @@ public abstract interface class net/mamoe/mirai/contact/Friend : kotlinx/corouti
public abstract fun sendMessage (Lnet/mamoe/mirai/message/data/Message;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/FileSupported, net/mamoe/mirai/contact/VoiceSupported {
public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutines/CoroutineScope, net/mamoe/mirai/contact/AudioSupported, net/mamoe/mirai/contact/Contact, net/mamoe/mirai/contact/FileSupported {
public static final field Companion Lnet/mamoe/mirai/contact/Group$Companion;
public abstract fun contains (J)Z
public fun contains (Lnet/mamoe/mirai/contact/NormalMember;)Z
@ -365,6 +370,8 @@ public abstract interface class net/mamoe/mirai/contact/Group : kotlinx/coroutin
public fun setEssenceMessage (Lnet/mamoe/mirai/message/data/MessageSource;)Z
public abstract fun setEssenceMessage (Lnet/mamoe/mirai/message/data/MessageSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun setName (Ljava/lang/String;)V
public fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Voice;
public abstract fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class net/mamoe/mirai/contact/Group$Companion {
@ -560,11 +567,6 @@ public abstract interface class net/mamoe/mirai/contact/UserOrBot : net/mamoe/mi
public abstract fun nudge ()Lnet/mamoe/mirai/message/action/Nudge;
}
public abstract interface class net/mamoe/mirai/contact/VoiceSupported : net/mamoe/mirai/contact/Contact {
public fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;)Lnet/mamoe/mirai/message/data/Voice;
public abstract fun uploadVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class net/mamoe/mirai/contact/announcement/Announcement {
public static final field Companion Lnet/mamoe/mirai/contact/announcement/Announcement$Companion;
public abstract fun getContent ()Ljava/lang/String;
@ -3158,6 +3160,41 @@ public final class net/mamoe/mirai/message/data/AtAll : net/mamoe/mirai/message/
public fun toString ()Ljava/lang/String;
}
public abstract interface class net/mamoe/mirai/message/data/Audio : net/mamoe/mirai/message/data/MessageContent {
public static final field Key Lnet/mamoe/mirai/message/data/Audio$Key;
public fun contentToString ()Ljava/lang/String;
public abstract fun getCodec ()Lnet/mamoe/mirai/message/data/AudioCodec;
public abstract fun getExtraData ()[B
public abstract fun getFileMd5 ()[B
public abstract fun getFileSize ()J
public abstract fun getFilename ()Ljava/lang/String;
public abstract fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/Audio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
}
public final class net/mamoe/mirai/message/data/AudioCodec : java/lang/Enum {
public static final field AMR Lnet/mamoe/mirai/message/data/AudioCodec;
public static final field Companion Lnet/mamoe/mirai/message/data/AudioCodec$Companion;
public static final field SILK Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec;
public static final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun getFormatName ()Ljava/lang/String;
public final fun getId ()I
public static fun valueOf (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public static fun values ()[Lnet/mamoe/mirai/message/data/AudioCodec;
}
public final class net/mamoe/mirai/message/data/AudioCodec$Companion {
public final fun fromFormatName (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun fromFormatNameOrNull (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun fromId (I)Lnet/mamoe/mirai/message/data/AudioCodec;
public final fun fromIdOrNull (I)Lnet/mamoe/mirai/message/data/AudioCodec;
}
public abstract interface class net/mamoe/mirai/message/data/ConstrainSingle : net/mamoe/mirai/message/data/SingleMessage {
public abstract fun getKey ()Lnet/mamoe/mirai/message/data/MessageKey;
}
@ -4499,6 +4536,8 @@ public final class net/mamoe/mirai/message/data/MessageUtils {
public static final synthetic fun At (Lnet/mamoe/mirai/contact/UserOrBot;)Lnet/mamoe/mirai/message/data/At;
public static final synthetic fun FileMessage (Ljava/lang/String;ILjava/lang/String;J)Lnet/mamoe/mirai/message/data/FileMessage;
public static final synthetic fun Image (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/Image;
public static final synthetic fun OfflineAudio (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio;
public static final synthetic fun OfflineAudio (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio;
public static final synthetic fun UnsupportedMessage ([B)Lnet/mamoe/mirai/message/data/UnsupportedMessage;
public static final synthetic fun at (Lnet/mamoe/mirai/contact/Member;)Lnet/mamoe/mirai/message/data/At;
public static final fun buildMessageChain (ILkotlin/jvm/functions/Function1;)Lnet/mamoe/mirai/message/data/MessageChain;
@ -4541,6 +4580,7 @@ public final class net/mamoe/mirai/message/data/MessageUtils {
public static final synthetic fun toMessageChain ([Lnet/mamoe/mirai/message/data/Message;)Lnet/mamoe/mirai/message/data/MessageChain;
public static final fun toOfflineMessageSource (Lnet/mamoe/mirai/message/data/OnlineMessageSource;)Lnet/mamoe/mirai/message/data/OfflineMessageSource;
public static final synthetic fun toPlainText (Ljava/lang/String;)Lnet/mamoe/mirai/message/data/PlainText;
public static final synthetic fun toVoice (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice;
}
public final class net/mamoe/mirai/message/data/MusicKind : java/lang/Enum {
@ -4604,6 +4644,26 @@ public final class net/mamoe/mirai/message/data/MusicShare$Key : net/mamoe/mirai
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public abstract interface class net/mamoe/mirai/message/data/OfflineAudio : net/mamoe/mirai/message/data/Audio {
public static final field Key Lnet/mamoe/mirai/message/data/OfflineAudio$Key;
public static final field SERIAL_NAME Ljava/lang/String;
}
public abstract interface class net/mamoe/mirai/message/data/OfflineAudio$Factory {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE;
public abstract fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio;
public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio;
}
public final class net/mamoe/mirai/message/data/OfflineAudio$Factory$INSTANCE : net/mamoe/mirai/message/data/OfflineAudio$Factory {
public fun create (Ljava/lang/String;[BJLnet/mamoe/mirai/message/data/AudioCodec;[B)Lnet/mamoe/mirai/message/data/OfflineAudio;
public fun from (Lnet/mamoe/mirai/message/data/OnlineAudio;)Lnet/mamoe/mirai/message/data/OfflineAudio;
}
public final class net/mamoe/mirai/message/data/OfflineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public static final field SERIAL_NAME Ljava/lang/String;
}
public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/mamoe/mirai/message/data/MessageSource {
public static final field Key Lnet/mamoe/mirai/message/data/OfflineMessageSource$Key;
public fun <init> ()V
@ -4613,6 +4673,17 @@ public abstract class net/mamoe/mirai/message/data/OfflineMessageSource : net/ma
public final class net/mamoe/mirai/message/data/OfflineMessageSource$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
}
public abstract interface class net/mamoe/mirai/message/data/OnlineAudio : net/mamoe/mirai/message/data/Audio {
public static final field Key Lnet/mamoe/mirai/message/data/OnlineAudio$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public abstract fun getLength ()J
public abstract fun getUrlForDownload ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/OnlineAudio$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public static final field SERIAL_NAME Ljava/lang/String;
}
public abstract class net/mamoe/mirai/message/data/OnlineMessageSource : net/mamoe/mirai/message/data/MessageSource {
public static final field Key Lnet/mamoe/mirai/message/data/OnlineMessageSource$Key;
public abstract fun getBot ()Lnet/mamoe/mirai/Bot;
@ -5090,32 +5161,39 @@ public final class net/mamoe/mirai/message/data/VipFace$Kind$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage {
public class net/mamoe/mirai/message/data/Voice : net/mamoe/mirai/message/data/PttMessage {
public static final field Key Lnet/mamoe/mirai/message/data/Voice$Key;
public static final field SERIAL_NAME Ljava/lang/String;
public synthetic fun <init> (ILjava/lang/String;[BJILjava/lang/String;Ljava/lang/String;Lkotlinx/serialization/internal/SerializationConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;[BJILjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun contentToString ()Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public final fun getCodec ()I
public static final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice;
public fun getFileName ()Ljava/lang/String;
public fun getFileSize ()J
public fun getMd5 ()[B
public final fun getUrl ()Ljava/lang/String;
public fun getUrl ()Ljava/lang/String;
public final fun get_codec ()I
public fun hashCode ()I
public final fun toAudio ()Lnet/mamoe/mirai/message/data/Audio;
public fun toString ()Ljava/lang/String;
}
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Serializer : kotlinx/serialization/KSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$Serializer;
public final class net/mamoe/mirai/message/data/Voice$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lnet/mamoe/mirai/message/data/Voice$$serializer;
public static final synthetic field descriptor Lkotlinx/serialization/descriptors/SerialDescriptor;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lnet/mamoe/mirai/message/data/Voice;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lnet/mamoe/mirai/message/data/Voice;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/Voice$Key : net/mamoe/mirai/message/data/AbstractPolymorphicMessageKey {
public final fun fromAudio (Lnet/mamoe/mirai/message/data/Audio;)Lnet/mamoe/mirai/message/data/Voice;
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class net/mamoe/mirai/message/data/XmlMessageBuilder$ItemBuilder {
@ -5429,14 +5507,8 @@ public abstract interface class net/mamoe/mirai/utils/ExternalResource : java/io
public static fun uploadAsImage (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Image;
public static fun uploadAsImage (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public static fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage;
public static fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage;
@ -5504,14 +5576,8 @@ public final class net/mamoe/mirai/utils/ExternalResource$Companion {
public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/File;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;ILjava/lang/Object;)Lnet/mamoe/mirai/message/data/Image;
public static synthetic fun uploadAsImage$default (Lnet/mamoe/mirai/utils/ExternalResource$Companion;Ljava/io/InputStream;Lnet/mamoe/mirai/contact/Contact;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
public final fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Ljava/io/File;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Ljava/io/InputStream;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/Contact;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;)Lnet/mamoe/mirai/message/data/Voice;
public final fun uploadAsVoice (Lnet/mamoe/mirai/utils/ExternalResource;Lnet/mamoe/mirai/contact/VoiceSupported;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;)Lnet/mamoe/mirai/message/data/FileMessage;
public final fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun uploadTo (Ljava/io/File;Lnet/mamoe/mirai/contact/FileSupported;Ljava/lang/String;Lnet/mamoe/mirai/utils/RemoteFile$ProgressionCallback;)Lnet/mamoe/mirai/message/data/FileMessage;

View File

@ -15,9 +15,7 @@ import kotlinx.coroutines.Job
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.contact.*
import net.mamoe.mirai.data.*
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.WeakRef
import kotlin.annotation.AnnotationTarget.*
@ -219,18 +217,4 @@ public interface LowLevelApiAccessor {
groupId: Long,
seconds: Int,
)
/**
* 序列化 [Voice.pttInternalInstance]
*/
@LowLevelApi
@MiraiInternalApi // For Voice serialize
public fun serializePttElem(ptt: Any?): String
/**
* 反序列化 [Voice.pttInternalInstance]
*/
@LowLevelApi
@MiraiInternalApi // For Voice serialize
public fun deserializePttElem(ptt: String): Any?
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:JvmBlockingBridge
package net.mamoe.mirai.contact
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.message.data.Audio
import net.mamoe.mirai.message.data.OfflineAudio
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.OverFileSizeMaxException
/**
* 支持发送语音的 [Contact]
*
* @since 2.7
*/
@NotStableForInheritance
public interface AudioSupported : Contact {
/**
* 上传一个语音文件以备发送. [resource] 需要调用方[关闭][ExternalResource.close].
*
* 多次调用 [uploadAudio] 使用同一个 [resource] , 将会发生多次上传, 且有可能产生不同的 [OfflineAudio] 对象, 因为服务器不会提供有关文件是否已经存在于服务器的信息.
*
* 返回的 [OfflineAudio] 支持序列化, 可以保存后在将来使用, 而不需要立即[发送][Contact.sendMessage]. 但不建议保存太久, 无法确定服务器保留一个文件的时间.
*
* 建议使用同一个 [Contact] 进行 [uploadAudio] [sendMessage]. 目标对象不同时的行为是不确定的.
*
* 要获取更多语音相关的信息, 参阅 [Audio].
*
* @throws OverFileSizeMaxException 当语音文件过大而被服务器拒绝上传时. (最大大小约为 1 MB)
* **注意**: 由于服务器不一定会检查大小, 该异常就不一定会因大小超过 1MB 而抛出.
*
* @since 2.7
*/
public suspend fun uploadAudio(resource: ExternalResource): OfflineAudio
}

View File

@ -34,7 +34,7 @@ import net.mamoe.mirai.utils.NotStableForInheritance
* @see FriendMessageEvent
*/
@NotStableForInheritance
public interface Friend : User, CoroutineScope, VoiceSupported {
public interface Friend : User, CoroutineScope, AudioSupported {
/**
* 向这个对象发送消息.
*

View File

@ -19,6 +19,7 @@ import net.mamoe.mirai.contact.announcement.Announcements
import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.NotStableForInheritance
@ -52,7 +53,7 @@ import net.mamoe.mirai.utils.NotStableForInheritance
* ##
*/
@NotStableForInheritance
public interface Group : Contact, CoroutineScope, FileSupported, VoiceSupported {
public interface Group : Contact, CoroutineScope, FileSupported, AudioSupported {
/**
* 群名称.
*
@ -184,6 +185,17 @@ public interface Group : Contact, CoroutineScope, FileSupported, VoiceSupported
this.sendMessage(message.toPlainText())
/**
* 上传一个语音消息以备发送. 该方法已弃用且将在未来版本删除, 请使用 [uploadAudio].
*/
@Suppress("DEPRECATION")
@Deprecated(
"use uploadAudio",
replaceWith = ReplaceWith("uploadAudio(resource)"),
level = DeprecationLevel.WARNING
)
public suspend fun uploadVoice(resource: ExternalResource): Voice
/**
* 将一条消息设置为群精华消息, 需要管理员或群主权限.
* 操作成功返回 `true`.

View File

@ -1,39 +0,0 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.contact
import net.mamoe.kjbb.JvmBlockingBridge
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsVoice
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.OverFileSizeMaxException
/**
* 支持发送语音的 [Contact]
*
* @since 2.7
*/
@NotStableForInheritance
public interface VoiceSupported : Contact {
/**
* 上传一个语音消息以备发送.
*
* - **请手动关闭 [resource]**
* - 请使用 amr silk 格式
*
* @since 2.7
* @see ExternalResource.uploadAsVoice
* @throws OverFileSizeMaxException 当语音文件过大而被服务器拒绝上传时. (最大大小约为 1 MB)
*/
@JvmBlockingBridge
public suspend fun uploadVoice(resource: ExternalResource): Voice
}

View File

@ -13,14 +13,12 @@ import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.modules.PolymorphicModuleBuilder
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.overwriteWith
import kotlinx.serialization.modules.polymorphic
import kotlinx.serialization.modules.*
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.message.MessageSerializers
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.lateinitMutableProperty
import net.mamoe.mirai.utils.map
import net.mamoe.mirai.utils.takeElementsFrom
import kotlin.reflect.KClass
@ -121,6 +119,7 @@ private val builtInSerializersModule by lazy {
subclass(SimpleServiceMessage::class, SimpleServiceMessage.serializer())
// subclass(PttMessage::class, PttMessage.serializer())
@Suppress("DEPRECATION")
subclass(Voice::class, Voice.serializer())
// subclass(HummerMessage::class, HummerMessage.serializer())
@ -188,31 +187,58 @@ private val builtInSerializersModule by lazy {
// Tests:
// net.mamoe.mirai.internal.message.data.MessageSerializationTest
internal object MessageSerializersImpl : MessageSerializers {
@Volatile
private var serializersModuleField: SerializersModule? = null
private var serializersModuleField: SerializersModule by lateinitMutableProperty {
builtInSerializersModule
}
override val serializersModule: SerializersModule
get() {
Mirai // ensure registered, for tests
return serializersModuleField ?: builtInSerializersModule
return serializersModuleField
}
@Synchronized
override fun <M : SingleMessage> registerSerializer(type: KClass<M>, serializer: KSerializer<M>) {
serializersModuleField = serializersModule.overwriteWith(SerializersModule {
// contextual(type, serializer)
for (superclass in type.allSuperclasses) {
if (superclass.isFinal) continue
if (!superclass.isSubclassOf(SingleMessage::class)) continue
@Suppress("UNCHECKED_CAST")
polymorphic(superclass as KClass<Any>) {
subclass(type, serializer)
}
}
})
serializersModuleField = serializersModule.overwritePolymorphicWith(type, serializer)
}
@Synchronized
override fun registerSerializers(serializersModule: SerializersModule) {
serializersModuleField = serializersModule.overwriteWith(serializersModule)
}
}
internal fun <M : Any> SerializersModule.overwritePolymorphicWith(
type: KClass<M>,
serializer: KSerializer<M>
): SerializersModule {
return overwriteWith(SerializersModule {
// contextual(type, serializer)
for (superclass in type.allSuperclasses) {
if (superclass.isFinal) continue
if (!superclass.isSubclassOf(SingleMessage::class)) continue
@Suppress("UNCHECKED_CAST")
polymorphic(superclass as KClass<Any>) {
subclass(type, serializer)
}
}
})
}
private inline fun <reified M : SingleMessage> SerializersModuleBuilder.hierarchicallyPolymorphic(serializer: KSerializer<M>) =
hierarchicallyPolymorphic(M::class, serializer)
private fun <M : SingleMessage> SerializersModuleBuilder.hierarchicallyPolymorphic(
type: KClass<M>,
serializer: KSerializer<M>
) {
// contextual(type, serializer)
for (superclass in type.allSuperclasses) {
if (superclass.isFinal) continue
if (!superclass.isSubclassOf(SingleMessage::class)) continue
@Suppress("UNCHECKED_CAST")
polymorphic(superclass as KClass<Any>) {
subclass(type, serializer)
}
}
}

View File

@ -0,0 +1,274 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:Suppress("NOTHING_TO_INLINE", "unused")
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
package net.mamoe.mirai.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.builtins.serializer
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.utils.*
/**
* 语音消息.
*
* [Audio] 分为 [OnlineAudio] [OfflineAudio]. 在本地上传的, 或手动构造的语音为 [OfflineAudio]. 从服务器接收的语音为 [OnlineAudio].
*
* ## 上传和发送语音
*
* 使用 [AudioSupported.uploadAudio] 上传语音到服务器并取得 [Audio] 消息实例, 然后通过 [Contact.sendMessage] 发送.
*
* Java 示例:
* ```
* Audio audio;
* try {
* audio = group.uploadAudio(resource); // 上传文件得到语音实例
* } finally {
* resource.close(); // 保证资源正常关闭
* }
* group.sendMessage(audio); // 发送语音消息
* ```
*
* ## 下载语音
*
* 使用 [OnlineAudio.urlForDownload] 获取文件下载链接.
*
* ## [Audio] [Voice] 的转换
*
* [Voice] 已弃用故不推荐进行兼容转换. [Audio] 将有稳定性保证, 请尽量使用新的 [Audio].
*
* [Audio] 转为 [Voice]: [Voice.fromAudio]
* [Voice] 转为 [Audio]: [Voice.toAudio]
*
* @since 2.7
*/
public sealed interface Audio : MessageContent {
public companion object Key :
AbstractPolymorphicMessageKey<MessageContent, Audio>(MessageContent, { it.safeCast() })
/**
* 文件名称. 通常为 `XXX.amr`. 服务器要求文件名后缀必须为 ".amr", 但其[编码方式][codec]也有可能是非 [AudioCodec.AMR].
*/
public val filename: String
/**
* 文件 MD5. 16 bytes.
*/
public val fileMd5: ByteArray
/**
* 文件大小 bytes. 官方客户端支持最大文件大小约为 1MB, 过大的文件**可能**可以正常上传, 但在官方客户端无法收听 (显示文件损坏).
*/
public val fileSize: Long
/**
* 编码方式.
*
* - 若语音文件真实编码方式为 [AudioCodec.SILK], 而该属性为 [AudioCodec.AMR], 语音文件将会被服务器压缩为低音质 [AudioCodec.AMR] 格式.
* - 若语音文件真实编码方式为 [AudioCodec.AMR], 而该属性为 [AudioCodec.SILK], 语音也可以正常发送并在客户端收听, 音质随文件真实格式而决定.
*
* 因此在发送时 [codec] 通常可以总是使用 [AudioCodec.SILK] (这也是 [AudioSupported.uploadAudio] 的默认行为).
*/
public val codec: AudioCodec
/**
* 文件的额外数据. 该数据由服务器提供, 可能会影响语音音质等属性.
* [extraData] `null` 时也可以发送语音, 但不确定发送和客户端收听是否会正常.
*
* [extraData] 可能随服务器更新而更新, 因此请不要尝试解析该数据.
*
* [extraData] 向下兼容, 即旧版本的 [extraData] 可以在新版本构造 [OfflineAudio] ([OfflineAudio.Factory.create]).
*/
public val extraData: ByteArray?
/**
* @return `"[mirai:audio:${filename}]"`
*/
public override fun toString(): String
public override fun contentToString(): String = "[语音消息]"
}
/**
* 在线语音消息, 即从消息事件中接收到的语音消息.
*
* [OnlineAudio] 可以获取[语音长度][length]以及[下载链接][urlForDownload].
*
* [OnlineAudio] 仅可以从事件中的[消息链][MessageChain]接收, 不可手动构造. 若需要手动构造, 请使用 [OfflineAudio.Factory.create] 构造 [离线语音][OfflineAudio].
*
* ### 序列化支持
*
* [OnlineAudio] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString].
* 也可以在 [MessageSerializers.serializersModule] 获取到 [OnlineAudio] [KSerializer].
*
* 要获取更多有关序列化的信息, 参阅 [MessageSerializers].
*
* ### 不建议自行实现该接口
*
* [OnlineAudio] 不稳定, 将来可能会增加新的抽象属性或方法而导致不兼容. 仅可以使用该接口而不能继承或实现它.
*
* @since 2.7
* @see OfflineAudio
*/
@NotStableForInheritance
public interface OnlineAudio : Audio { // 协议实现
/**
* 下载链接 HTTP URL.
* @return `"http://xxx"`
*/
public val urlForDownload: String
/**
* 语音长度秒数
*/
public val length: Long
public companion object Key :
AbstractPolymorphicMessageKey<Audio, OnlineAudio>(Audio, { it.safeCast() }) {
public const val SERIAL_NAME: String = "OnlineAudio"
}
}
/**
* 离线语音消息.
*
* [OfflineAudio] 仅拥有协议上必要的五个属性:
* - 文件名 [filename]
* - 文件 MD5 [fileMd5]
* - 文件大小 [fileSize]
* - 编码方式 [codec]
* - 额外数据 [extraData]
*
* [OfflineAudio] 可由本地 [ExternalResource] 经过 [AudioSupported.uploadAudio] 上传到服务器得到, 故无[下载链接][OnlineAudio.urlForDownload].
*
* [OfflineAudio] 同时还可以用做自定义构造 [Audio] 实例, 使用 [OfflineAudio.Factory.create] 可通过上述五个必要参数获得 [OfflineAudio] 实例.
*
* ### 序列化支持
*
* [OfflineAudio] 支持序列化. 可使用 [MessageChain.serializeToJsonString] 以及 [MessageChain.deserializeFromJsonString].
* 也可以在 [MessageSerializers.serializersModule] 获取到 [OfflineAudio] [KSerializer].
*
* 要获取更多有关序列化的信息, 参阅 [MessageSerializers].
*
* ### 不建议自行实现该接口
*
* [OfflineAudio] 不稳定, 将来可能会增加新的抽象属性或方法而导致不兼容. 仅可以使用该接口而不能继承或实现它.
*
* @since 2.7
*/
@NotStableForInheritance
public interface OfflineAudio : Audio {
public companion object Key :
AbstractPolymorphicMessageKey<Audio, OfflineAudio>(Audio, { it.safeCast() }) {
public const val SERIAL_NAME: String = "OfflineAudio"
}
public interface Factory {
/**
* 构造 [OfflineAudio]. 有关参数的含义, 参考 [Audio].
*
* Kotlin 可以使用类构造器的函数 [OfflineAudio]: `OfflineAudio(...)`
*/
public fun create(
filename: String,
fileMd5: ByteArray,
fileSize: Long,
codec: AudioCodec,
extraData: ByteArray?,
): OfflineAudio
/**
* 使用 [OnlineAudio] 的信息构造 [OfflineAudio].
*
* Kotlin 可以使用类构造器的函数 [OfflineAudio]: `OfflineAudio(...)`
*/
public fun from(onlineAudio: OnlineAudio): OfflineAudio = onlineAudio.run {
create(filename, fileMd5, fileSize, codec, extraData)
}
public companion object INSTANCE :
Factory by loadService("net.mamoe.mirai.internal.message.OfflineAudioFactoryImpl")
}
}
/**
* 构造 [OfflineAudio]. 有关参数的含义, 参考 [Audio].
* @since 2.7
*/
@JvmSynthetic
public inline fun OfflineAudio(
filename: String,
fileMd5: ByteArray,
fileSize: Long,
codec: AudioCodec,
extraData: ByteArray?,
): OfflineAudio = OfflineAudio.Factory.create(filename, fileMd5, fileSize, codec, extraData)
/**
* 使用 [OnlineAudio] 的信息构造 [OfflineAudio].
* @since 2.7
*/
@JvmSynthetic
public inline fun OfflineAudio(
onlineAudio: OnlineAudio
): OfflineAudio = OfflineAudio.Factory.from(onlineAudio)
/**
* 语音编码方式.
*
* @since 2.7
*/
@Serializable(AudioCodec.AsIntSerializer::class)
public enum class AudioCodec(
public val id: Int,
public val formatName: String,
) {
/**
* 低音质编码格式
*/
AMR(0, "amr"),
/**
* 高音质编码格式
*/
SILK(1, "silk");
public companion object {
private val VALUES = values()
@JvmStatic
public fun fromId(id: Int): AudioCodec = VALUES.first { it.id == id }
@JvmStatic
public fun fromFormatName(formatName: String): AudioCodec = VALUES.first { it.formatName == formatName }
@JvmStatic
public fun fromIdOrNull(id: Int): AudioCodec? = VALUES.find { it.id == id }
@JvmStatic
public fun fromFormatNameOrNull(formatName: String): AudioCodec? =
VALUES.find { it.formatName == formatName }
}
internal object AsIntSerializer : KSerializer<AudioCodec> by Int.serializer().map(
Int.serializer().descriptor,
deserialize = { fromId(it) },
serialize = { id }
)
}

View File

@ -7,16 +7,20 @@
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
@file:JvmMultifileClass
@file:JvmName("MessageUtils")
@file:Suppress("NOTHING_TO_INLINE")
package net.mamoe.mirai.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.utils.*
import net.mamoe.mirai.utils.ExternalResource.Companion.uploadAsVoice
import net.mamoe.mirai.utils.MiraiExperimentalApi
import net.mamoe.mirai.utils.MiraiInternalApi
import net.mamoe.mirai.utils.NotStableForInheritance
import net.mamoe.mirai.utils.safeCast
/**
@ -49,56 +53,68 @@ public abstract class PttMessage : MessageContent {
@MiraiInternalApi
@Transient
public var pttInternalInstance: Any? = null
set(value) {
field = value
_pttInternalInstanceSerializeCache = null
}
@MiraiInternalApi
protected val pttInternalInstanceSerializeCache: String
get() {
_pttInternalInstanceSerializeCache?.let { return it }
return Mirai.serializePttElem(pttInternalInstance).also {
_pttInternalInstanceSerializeCache = it
}
}
@Transient
private var _pttInternalInstanceSerializeCache: String? = null
}
/**
* 语音消息, 目前只支持接收和转发
* 已弃用的旧版本语音消息.
*
* 目前, 使用 [Voice] 类型是稳定的, 但调用 [Voice] 中的属性 [fileName], [md5], [fileSize] 是不稳定的. 语音的序列化也可能会在未来有变动.
* [Voice] 由于有设计缺陷已弃用且可能会在将来版本删除, 请使用 [Audio].
*
* ## 使用语音
* ## 迁移指南
*
* 可以通过 [ExternalResource.uploadAsVoice] 或者 [Group.uploadVoice] 上传语音文件到服务器, 得到 [Voice] 实例. 但这不会发送给目标群.
* 上传后需要通过 [Group.sendMessage] 发送 [Voice] 实例.
*
* [Voice] 实例可以通过序列化方式保存. 下次可以用它发送因而不需要上传. 但可能由于未来服务器更新, 这项功能就不稳定. 因此建议总是上传音频文件而不要保存 [Voice].
* - 将使用的 [Voice] 类型替换为 [Audio] 类型
* - [Group.uploadVoice] 替换为 [Group.uploadAudio]
* - 如果有必须使用旧 [Voice] 类型的情况, 请使用 [Audio.toVoice]
*/
@Suppress("DuplicatedCode")
@Serializable(Voice.Serializer::class) // experimental
@Suppress("DuplicatedCode", "DEPRECATION")
@Serializable
@SerialName(Voice.SERIAL_NAME)
public class Voice @MiraiInternalApi constructor(
@Deprecated(
"Please use Audio instead.",
replaceWith = ReplaceWith("Audio", "net.mamoe.mirai.message.data.Audio"),
level = DeprecationLevel.WARNING
)
public open class Voice @MiraiInternalApi constructor(
@MiraiExperimentalApi public override val fileName: String,
@MiraiExperimentalApi public override val md5: ByteArray,
@MiraiExperimentalApi public override val fileSize: Long,
@MiraiInternalApi public val codec: Int = 0,
@SerialName("codec") @MiraiInternalApi public val _codec: Int = 0,
private val _url: String
) : PttMessage() {
public companion object Key : AbstractPolymorphicMessageKey<PttMessage, Voice>(PttMessage, { it.safeCast() }) {
public const val SERIAL_NAME: String = "Voice"
/**
* 2.7 新增的 [Audio] 转为旧版本的 [Voice], 以兼容某些情况.
*
* @see Audio.toVoice
* @since 2.7
*/
@Suppress("DeprecatedCallableAddReplaceWith")
@Deprecated(
"Please consider migrating to Audio",
level = DeprecationLevel.WARNING
)
@JvmStatic
public fun fromAudio(audio: Audio): Voice {
audio.run {
return Voice(
filename,
fileMd5,
fileSize,
codec.id,
if (this is OnlineAudio) kotlin.runCatching { urlForDownload }.getOrElse { "" } else ""
)
}
}
}
/**
* 下载链接 HTTP URL.
*/
public val url: String?
public open val url: String?
get() = when {
_url.isBlank() -> null
_url.startsWith("http") -> _url
@ -115,69 +131,54 @@ public class Voice @MiraiInternalApi constructor(
public override fun contentToString(): String = "[语音消息]"
/**
* 转换为 2.7 新增的 [Audio], 以兼容某些无法迁移的情况.
*
* @since 2.7
*/
public fun toAudio(): Audio {
val voice = this
return OfflineAudio(
voice.fileName,
voice.md5,
voice.fileSize,
AudioCodec.fromIdOrNull(voice._codec) ?: AudioCodec.SILK,
byteArrayOf()
)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Voice) return false
if (this.pttInternalInstance != null && other.pttInternalInstance != null) {
if (this.pttInternalInstance == other.pttInternalInstance)
return true
// strict
return this.pttInternalInstanceSerializeCache == other.pttInternalInstanceSerializeCache
}
if (fileName != other.fileName) return false
if (!md5.contentEquals(other.md5)) return false
if (fileSize != other.fileSize) return false
if (codec != other.codec) return false
if (_codec != other._codec) return false
if (_url != other._url) return false
return true
}
override fun hashCode(): Int {
if (pttInternalInstance != null)
return pttInternalInstanceSerializeCache.hashCode()
var result = fileName.hashCode()
result = 12 * result + md5.contentHashCode()
result = 54 * result + fileSize.hashCode()
result = 33 * result + codec
result = 33 * result + _codec
result = 15 * result + _url.hashCode()
return result
}
}
public object Serializer : KSerializer<Voice> by VoiceS.serializer().map(
resultantDescriptor = VoiceS.serializer().descriptor.copy(SERIAL_NAME),
deserialize = {
Voice(
fileName = it.fileName,
md5 = it.md5,
fileSize = it.fileSize,
codec = it.codec,
_url = it._url,
).also { v -> v.pttInternalInstance = Mirai.deserializePttElem(it.ptt) }
},
serialize = {
VoiceS(
fileName = it.fileName,
md5 = it.md5,
fileSize = it.fileSize,
_url = it._url,
codec = it.codec,
ptt = Mirai.serializePttElem(it.pttInternalInstance)
)
}
) {
@Serializable
@SerialName(SERIAL_NAME)
private class VoiceS(
val fileName: String,
val md5: ByteArray,
val fileSize: Long,
val codec: Int,
val _url: String,
val ptt: String = "",
)
}
}
/**
* 2.7 新增的 [Audio] 转为旧版本的 [Voice], 以兼容某些情况.
*
* @since 2.7
*/
@Suppress("DEPRECATION", "DeprecatedCallableAddReplaceWith")
@Deprecated(
"Please migrate to Audio",
level = DeprecationLevel.WARNING
)
@JvmSynthetic
public inline fun Audio.toVoice(): Voice = Voice.fromAudio(this)

View File

@ -19,13 +19,12 @@ import net.mamoe.mirai.contact.Contact
import net.mamoe.mirai.contact.Contact.Companion.sendImage
import net.mamoe.mirai.contact.Contact.Companion.uploadImage
import net.mamoe.mirai.contact.FileSupported
import net.mamoe.mirai.contact.VoiceSupported
import net.mamoe.mirai.contact.Group
import net.mamoe.mirai.internal.utils.ExternalResourceImplByByteArray
import net.mamoe.mirai.internal.utils.ExternalResourceImplByFile
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.FileMessage
import net.mamoe.mirai.message.data.Image
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.message.data.sendTo
import net.mamoe.mirai.utils.ExternalResource.Companion.sendAsImageTo
import net.mamoe.mirai.utils.ExternalResource.Companion.toExternalResource
@ -416,58 +415,15 @@ public interface ExternalResource : Closeable {
// region uploadAsVoice
///////////////////////////////////////////////////////////////////////////
/**
* 将文件作为语音上传后构造 [Voice]. 上传后只会得到 [Voice] 实例, 而不会将语音发送到目标群或好友.
*
* **服务器仅支持音频格式 `silk` `amr`**. 需要调用方手动[关闭资源][ExternalResource.close].
*
* @throws OverFileSizeMaxException
* @since 2.7
* @see VoiceSupported.uploadVoice
*/
@Suppress("DEPRECATION")
@JvmBlockingBridge
@JvmStatic
public suspend fun ExternalResource.uploadAsVoice(contact: VoiceSupported): Voice {
return contact.uploadVoice(this)
}
@JvmBlockingBridge
@JvmStatic
@Deprecated("For binary compatibility", level = DeprecationLevel.WARNING)
@JvmName("uploadAsVoice")
public suspend fun ExternalResource.uploadAsVoice(contact: Contact): Voice {
if (contact is VoiceSupported) return contact.uploadVoice(this)
@Deprecated("Use `contact.uploadAudio(resource)` instead", level = DeprecationLevel.WARNING)
public suspend fun ExternalResource.uploadAsVoice(contact: Contact): net.mamoe.mirai.message.data.Voice {
@Suppress("DEPRECATION")
if (contact is Group) return contact.uploadVoice(this)
else throw UnsupportedOperationException("Contact `$contact` is not supported uploading voice")
}
/**
* 读取 [InputStream] 到临时文件并将其作为语音上传至 [contact] 后构造 [Voice],
* 上传后只会得到 [Voice] 实例, 而不会将语音发送到目标群或好友
*
* 注意本函数不会关闭流.
*
* @since 2.7
* @throws OverFileSizeMaxException
* @see VoiceSupported.uploadVoice
*/
@JvmStatic
@JvmBlockingBridge
public suspend fun InputStream.uploadAsVoice(contact: VoiceSupported): Voice =
runBIO { toExternalResource() }.withUse { uploadAsVoice(contact) }
/**
* 将文件作为语音上传后构造 [Voice].
* 上传后只会得到 [Voice] 实例, 而不会将语音发送到目标群或好友
*
* @since 2.7
* @throws OverFileSizeMaxException
* @see VoiceSupported.uploadVoice
*/
@JvmStatic
@JvmBlockingBridge
public suspend fun File.uploadAsVoice(contact: VoiceSupported): Voice =
toExternalResource().withUse { uploadAsVoice(contact) }
// endregion
}
}

View File

@ -149,7 +149,7 @@ public fun UByteArray.toUHexString(separator: String = " ", offset: Int = 0, len
}
public inline fun ByteArray.encodeToString(offset: Int = 0, charset: Charset = Charsets.UTF_8): String =
kotlinx.io.core.String(this, charset = charset, offset = offset, length = this.size - offset)
String(this, charset = charset, offset = offset, length = this.size - offset)
public expect fun ByteArray.encodeBase64(): String
public expect fun String.decodeBase64(): ByteArray

View File

@ -124,6 +124,14 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
UnsupportedMessageImpl::class,
UnsupportedMessageImpl.serializer()
)
MessageSerializers.registerSerializer(
OnlineAudioImpl::class,
OnlineAudioImpl.serializer()
)
MessageSerializers.registerSerializer(
OfflineAudioImpl::class,
OfflineAudioImpl.serializer()
)
}
}
@ -965,14 +973,4 @@ internal open class MiraiImpl : IMirai, LowLevelApiAccessor {
}
}
}
override fun serializePttElem(ptt: Any?): String {
if (ptt !is ImMsgBody.Ptt) return ""
return ptt.toByteArray(ImMsgBody.Ptt.serializer()).toUHexString()
}
override fun deserializePttElem(ptt: String): Any? {
if (ptt.isBlank()) return null
return ptt.hexToBytes().loadAs(ImMsgBody.Ptt.serializer())
}
}

View File

@ -9,11 +9,7 @@
@file:OptIn(LowLevelApi::class)
@file:Suppress(
"EXPERIMENTAL_API_USAGE",
"DEPRECATION_ERROR",
"NOTHING_TO_INLINE",
"INVISIBLE_MEMBER",
"INVISIBLE_REFERENCE"
)
package net.mamoe.mirai.internal.contact
@ -28,18 +24,19 @@ import net.mamoe.mirai.event.events.FriendMessagePostSendEvent
import net.mamoe.mirai.event.events.FriendMessagePreSendEvent
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.info.FriendInfoImpl
import net.mamoe.mirai.internal.message.OfflineAudioImpl
import net.mamoe.mirai.internal.network.highway.*
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x346
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec
import net.mamoe.mirai.internal.network.protocol.packet.list.FriendList
import net.mamoe.mirai.internal.utils.C2CPkgMsgParsingCache
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.MessageReceipt
import net.mamoe.mirai.message.data.Message
import net.mamoe.mirai.message.data.Voice
import net.mamoe.mirai.message.data.OfflineAudio
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.recoverCatchingSuppressed
import net.mamoe.mirai.utils.toByteArray
@ -78,10 +75,9 @@ internal class FriendImpl(
"Friend ${this.id} had already been deleted"
}
bot.network.run {
FriendList.DelFriend.invoke(bot.client, this@FriendImpl)
.sendAndExpect<FriendList.DelFriend.Response>().also {
check(it.isSuccess) { "delete friend failed: ${it.resultCode}" }
}
FriendList.DelFriend.invoke(bot.client, this@FriendImpl).sendAndExpect().also {
check(it.isSuccess) { "delete friend failed: ${it.resultCode}" }
}
}
}
@ -95,19 +91,13 @@ internal class FriendImpl(
override fun toString(): String = "Friend($id)"
override suspend fun uploadVoice(resource: ExternalResource): Voice = bot.network.run {
val voice = Voice(
"${resource.md5.toUHexString("")}.amr",
resource.md5,
resource.size,
resource.voiceCodec,
""
)
override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio = bot.network.run {
var audio: OfflineAudioImpl? = null
kotlin.runCatching {
val resp = Highway.uploadResourceBdh(
bot = bot,
resource = resource,
kind = ResourceKind.PRIVATE_VOICE,
kind = ResourceKind.PRIVATE_AUDIO,
commandId = 26,
extendInfo = PttStore.C2C.createC2CPttStoreBDHExt(bot, this@FriendImpl.uin, resource)
.toByteArray(Cmd0x346.ReqBody.serializer())
@ -117,14 +107,20 @@ internal class FriendImpl(
if (c346resp.msgApplyUploadRsp == null) {
error("Upload failed")
}
voice.pttInternalInstance = ImMsgBody.Ptt(
fileType = 4,
srcUin = bot.uin,
fileUuid = c346resp.msgApplyUploadRsp.uuid,
audio = OfflineAudioImpl(
filename = "${resource.md5.toUHexString("")}.amr",
fileMd5 = resource.md5,
fileName = resource.md5 + ".amr".toByteArray(),
fileSize = resource.size.toInt(),
boolValid = true,
fileSize = resource.size,
codec = resource.audioCodec,
originalPtt = ImMsgBody.Ptt(
fileType = 4,
srcUin = bot.uin,
fileUuid = c346resp.msgApplyUploadRsp.uuid,
fileMd5 = resource.md5,
fileName = resource.md5 + ".amr".toByteArray(),
fileSize = resource.size.toInt(),
boolValid = true,
)
)
}.recoverCatchingSuppressed {
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect<Any>()) {
@ -133,24 +129,30 @@ internal class FriendImpl(
bot,
resp.uploadIpList.zip(resp.uploadPortList),
resource.size,
ResourceKind.GROUP_VOICE,
ResourceKind.GROUP_AUDIO,
ChannelKind.HTTP
) { ip, port ->
Mirai.Http.postPtt(ip, port, resource, resp.uKey, resp.fileKey)
}
voice.pttInternalInstance = ImMsgBody.Ptt(
fileType = 4,
srcUin = bot.uin,
fileUuid = resp.fileId.toByteArray(),
audio = OfflineAudioImpl(
filename = "${resource.md5.toUHexString("")}.amr",
fileMd5 = resource.md5,
fileName = resource.md5 + ".amr".toByteArray(),
fileSize = resource.size.toInt(),
boolValid = true,
fileSize = resource.size,
codec = resource.audioCodec,
originalPtt = ImMsgBody.Ptt(
fileType = 4,
srcUin = bot.uin,
fileUuid = resp.fileId.toByteArray(),
fileMd5 = resource.md5,
fileName = resource.md5 + ".amr".toByteArray(),
fileSize = resource.size.toInt(),
boolValid = true,
)
)
}
}
}.getOrThrow()
return voice
return audio!!
}
}

View File

@ -23,22 +23,25 @@ import net.mamoe.mirai.event.events.*
import net.mamoe.mirai.internal.QQAndroidBot
import net.mamoe.mirai.internal.contact.announcement.AnnouncementsImpl
import net.mamoe.mirai.internal.contact.info.MemberInfoImpl
import net.mamoe.mirai.internal.message.OfflineAudioImpl
import net.mamoe.mirai.internal.message.OfflineGroupImage
import net.mamoe.mirai.internal.network.components.BdhSession
import net.mamoe.mirai.internal.network.handler.NetworkHandler
import net.mamoe.mirai.internal.network.handler.logger
import net.mamoe.mirai.internal.network.highway.ChannelKind
import net.mamoe.mirai.internal.network.highway.Highway
import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_AUDIO
import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_IMAGE
import net.mamoe.mirai.internal.network.highway.ResourceKind.GROUP_VOICE
import net.mamoe.mirai.internal.network.highway.postPtt
import net.mamoe.mirai.internal.network.highway.tryServersUpload
import net.mamoe.mirai.internal.network.protocol.data.proto.Cmd0x388
import net.mamoe.mirai.internal.network.protocol.packet.chat.TroopEssenceMsgManager
import net.mamoe.mirai.internal.network.protocol.packet.chat.image.ImgStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.PttStore
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.audioCodec
import net.mamoe.mirai.internal.network.protocol.packet.chat.voice.voiceCodec
import net.mamoe.mirai.internal.network.protocol.packet.list.ProfileService
import net.mamoe.mirai.internal.network.protocol.packet.sendAndExpect
import net.mamoe.mirai.internal.utils.GroupPkgMsgParsingCache
import net.mamoe.mirai.internal.utils.RemoteFileImpl
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
@ -204,32 +207,10 @@ internal class GroupImpl(
}
}
@Suppress("OverridingDeprecatedMember", "DEPRECATION")
override suspend fun uploadVoice(resource: ExternalResource): Voice {
return bot.network.run {
kotlin.runCatching {
val (_) = Highway.uploadResourceBdh(
bot = bot,
resource = resource,
kind = GROUP_VOICE,
commandId = 29,
extendInfo = PttStore.GroupPttUp.createTryUpPttPack(bot.id, id, resource)
.toByteArray(Cmd0x388.ReqBody.serializer()),
)
}.recoverCatchingSuppressed {
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect<Any>()) {
is PttStore.GroupPttUp.Response.RequireUpload -> {
tryServersUpload(
bot,
resp.uploadIpList.zip(resp.uploadPortList),
resource.size,
GROUP_VOICE,
ChannelKind.HTTP
) { ip, port ->
Mirai.Http.postPtt(ip, port, resource, resp.uKey, resp.fileKey)
}
}
}
}.getOrThrow()
uploadAudioResource(resource)
// val body = resp?.loadAs(Cmd0x388.RspBody.serializer())
// ?.msgTryupPttRsp
@ -243,6 +224,51 @@ internal class GroupImpl(
""
)
}
}
private suspend fun uploadAudioResource(resource: ExternalResource) {
kotlin.runCatching {
val (_) = Highway.uploadResourceBdh(
bot = bot,
resource = resource,
kind = GROUP_AUDIO,
commandId = 29,
extendInfo = PttStore.GroupPttUp.createTryUpPttPack(bot.id, id, resource)
.toByteArray(Cmd0x388.ReqBody.serializer()),
)
}.recoverCatchingSuppressed {
when (val resp = PttStore.GroupPttUp(bot.client, bot.id, id, resource).sendAndExpect(bot)) {
is PttStore.GroupPttUp.Response.RequireUpload -> {
tryServersUpload(
bot,
resp.uploadIpList.zip(resp.uploadPortList),
resource.size,
GROUP_AUDIO,
ChannelKind.HTTP
) { ip, port ->
Mirai.Http.postPtt(ip, port, resource, resp.uKey, resp.fileKey)
}
}
}
}.getOrThrow()
}
override suspend fun uploadAudio(resource: ExternalResource): OfflineAudio {
return bot.network.run {
uploadAudioResource(resource)
// val body = resp?.loadAs(Cmd0x388.RspBody.serializer())
// ?.msgTryupPttRsp
// ?.singleOrNull()?.fileKey ?: error("Group voice highway transfer succeed but failed to find fileKey")
OfflineAudioImpl(
filename = "${resource.md5.toUHexString("")}.amr",
fileMd5 = resource.md5,
fileSize = resource.size,
codec = resource.audioCodec,
originalPtt = null,
)
}
}

View File

@ -0,0 +1,302 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.message
import kotlinx.io.core.toByteArray
import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils.io.ProtoBuf
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.toByteArray
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.copy
import net.mamoe.mirai.utils.map
/**
* ## Audio Implementation Overview
*
* ```
* (api)Audio
* |
* /------------------\
* (api)OnlineAudio (api)OfflineAudio
* | |
* | |
* (core)OnlineAudioImpl (core)OfflineAudioImpl
* ```
*
* - [OnlineAudioImpl]: 实现从 [ImMsgBody.Ptt] 解析
* - [OfflineAudioImpl]: 支持用户手动构造
*
* ## Equality
*
* - [OnlineAudio] != [OfflineAudio]
*
* ## Converting [Audio] to [ImMsgBody.Ptt]
*
* Always call [Audio.toPtt]
*/
internal interface AudioPttSupport : MessageContent { // Audio is sealed in mirai-core-api
/**
* 原协议数据. 用于在接受到其他用户发送的语音时能按照原样发回.
*/
val originalPtt: ImMsgBody.Ptt?
}
@Serializable
internal class AudioExtraData(
@ProtoNumber(1) val ptt: ImMsgBody.Ptt?,
) : ProtoBuf {
fun toByteArray(): ByteArray {
return Wrapper(CURRENT_VERSION, this).toByteArray(Wrapper.serializer())
}
companion object {
@Serializable
class Wrapper(
@ProtoNumber(1) val version: Int,
@ProtoNumber(2) val v1: AudioExtraData,
) : ProtoBuf
private const val CURRENT_VERSION = 1
fun loadFrom(byteArray: ByteArray?): AudioExtraData? {
byteArray ?: return null
return kotlin.runCatching {
byteArray.loadAs(Wrapper.serializer()).v1
}.getOrNull()
}
}
}
internal fun Audio.toPtt(): ImMsgBody.Ptt {
if (this is AudioPttSupport) {
this.originalPtt?.let { return it }
}
return ImMsgBody.Ptt(
fileName = this.filename.toByteArray(),
fileMd5 = this.fileMd5,
boolValid = true,
fileSize = this.fileSize.toInt(),
fileType = 4,
pbReserve = byteArrayOf(0),
format = this.codec.id
)
}
@SerialName(OnlineAudio.SERIAL_NAME)
@Serializable(OnlineAudioImpl.Serializer::class)
internal class OnlineAudioImpl(
override val filename: String,
override val fileMd5: ByteArray,
override val fileSize: Long,
override val codec: AudioCodec,
url: String,
override val length: Long,
override val originalPtt: ImMsgBody.Ptt?,
) : OnlineAudio, AudioPttSupport {
private val _url = refineUrl(url)
override val extraData: ByteArray? by lazy {
AudioExtraData(originalPtt).toByteArray()
}
override val urlForDownload: String
get() = _url.takeIf { it.isNotBlank() }
?: throw UnsupportedOperationException("Could not fetch URL for audio $filename")
private val _stringValue: String by lazy { "[mirai:audio:${filename}]" }
override fun toString(): String = _stringValue
@Suppress("DuplicatedCode")
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as OnlineAudioImpl
if (filename != other.filename) return false
if (!fileMd5.contentEquals(other.fileMd5)) return false
if (fileSize != other.fileSize) return false
if (_url != other._url) return false
if (codec != other.codec) return false
if (length != other.length) return false
if (originalPtt != other.originalPtt) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + filename.hashCode()
result = 31 * result + fileMd5.contentHashCode()
result = 31 * result + fileSize.hashCode()
result = 31 * result + _url.hashCode()
result = 31 * result + codec.hashCode()
result = 31 * result + length.hashCode()
result = 31 * result + originalPtt.hashCode()
return result
}
companion object {
fun refineUrl(url: String) = when {
url.isBlank() -> ""
url.startsWith("http") -> url
url.startsWith("/") -> "$DOWNLOAD_URL$url"
else -> "$DOWNLOAD_URL/$url"
}
@Suppress("HttpUrlsUsage")
const val DOWNLOAD_URL = "http://grouptalk.c2c.qq.com"
}
object Serializer : KSerializer<OnlineAudioImpl> by Surrogate.serializer().map(
resultantDescriptor = Surrogate.serializer().descriptor.copy(OnlineAudio.SERIAL_NAME),
deserialize = {
OnlineAudioImpl(
filename = filename,
fileMd5 = fileMd5,
fileSize = fileSize,
url = urlForDownload,
codec = codec,
length = length,
originalPtt = AudioExtraData.loadFrom(extraData)?.ptt
)
},
serialize = {
Surrogate(
filename = filename,
fileMd5 = fileMd5,
fileSize = fileSize,
urlForDownload = urlForDownload,
codec = codec,
length = length,
extraData = extraData
)
}
) {
@Serializable
@SerialName(OnlineAudio.SERIAL_NAME)
private class Surrogate(
override val filename: String,
override val fileMd5: ByteArray,
override val fileSize: Long,
override val codec: AudioCodec,
override val length: Long,
override val extraData: ByteArray?,
override val urlForDownload: String,
) : OnlineAudio {
override fun toString(): String {
return "Surrogate(filename='$filename', fileMd5=${fileMd5.contentToString()}, fileSize=$fileSize, codec=$codec, length=$length, extraData=${extraData.contentToString()}, urlForDownload='$urlForDownload')"
}
}
}
}
@SerialName(OfflineAudio.SERIAL_NAME)
@Serializable(OfflineAudioImpl.Serializer::class)
internal class OfflineAudioImpl(
override val filename: String,
override val fileMd5: ByteArray,
override val fileSize: Long,
override val codec: AudioCodec,
override val originalPtt: ImMsgBody.Ptt?,
) : OfflineAudio, AudioPttSupport {
constructor(
filename: String,
fileMd5: ByteArray,
fileSize: Long,
codec: AudioCodec,
extraData: ByteArray?,
) : this(filename, fileMd5, fileSize, codec, AudioExtraData.loadFrom(extraData)?.ptt)
override val extraData: ByteArray? by lazy {
AudioExtraData(originalPtt).toByteArray()
}
private val _stringValue: String by lazy { "[mirai:audio:${filename}]" }
override fun toString(): String = _stringValue
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as OfflineAudioImpl
if (filename != other.filename) return false
if (!fileMd5.contentEquals(other.fileMd5)) return false
if (fileSize != other.fileSize) return false
if (codec != other.codec) return false
if (originalPtt != other.originalPtt) return false
return true
}
override fun hashCode(): Int {
var result = filename.hashCode()
result = 31 * result + fileMd5.contentHashCode()
result = 31 * result + fileSize.hashCode()
result = 31 * result + codec.hashCode()
result = 31 * result + originalPtt.hashCode()
return result
}
object Serializer : KSerializer<OfflineAudioImpl> by Surrogate.serializer().map(
resultantDescriptor = Surrogate.serializer().descriptor.copy(OfflineAudio.SERIAL_NAME),
deserialize = {
OfflineAudioImpl(
filename = filename,
fileMd5 = fileMd5,
fileSize = fileSize,
codec = codec,
extraData = extraData,
)
},
serialize = {
Surrogate(
filename = filename,
fileMd5 = fileMd5,
fileSize = fileSize,
codec = codec,
extraData = extraData,
)
}
) {
@Serializable
@SerialName(OfflineAudio.SERIAL_NAME)
private class Surrogate(
override val filename: String,
override val fileMd5: ByteArray,
override val fileSize: Long,
override val codec: AudioCodec,
override val extraData: ByteArray?,
) : OfflineAudio {
override fun toString(): String {
return "OfflineAudio(filename='$filename', fileMd5=${fileMd5.contentToString()}, fileSize=$fileSize, codec=$codec, extraData=${extraData.contentToString()})"
}
}
}
}
@PublishedApi
internal class OfflineAudioFactoryImpl : OfflineAudio.Factory {
override fun create(
filename: String,
fileMd5: ByteArray,
fileSize: Long,
codec: AudioCodec,
extraData: ByteArray?
): OfflineAudio = OfflineAudioImpl(filename, fileMd5, fileSize, codec, extraData)
}

View File

@ -18,15 +18,12 @@ import net.mamoe.mirai.internal.message.DeepMessageRefiner.refineDeep
import net.mamoe.mirai.internal.message.LightMessageRefiner.refineLight
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.cleanupRubbishMessageElements
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.joinToMessageChain
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toVoice
import net.mamoe.mirai.internal.message.ReceiveMessageTransformer.toAudio
import net.mamoe.mirai.internal.network.protocol.data.proto.*
import net.mamoe.mirai.internal.utils.io.serialization.loadAs
import net.mamoe.mirai.internal.utils.io.serialization.readProtoBuf
import net.mamoe.mirai.message.data.*
import net.mamoe.mirai.utils.encodeToString
import net.mamoe.mirai.utils.read
import net.mamoe.mirai.utils.toUHexString
import net.mamoe.mirai.utils.unzip
import net.mamoe.mirai.utils.*
/**
* 只在手动构造 [OfflineMessageSource] 时调用
@ -91,7 +88,7 @@ private fun List<MsgComm.Msg>.toMessageChain(
joinToMessageChain(elements, groupIdOrZero, messageSourceKind, bot, builder)
for (msg in messageList) {
msg.msgBody.richText.ptt?.toVoice()?.let { builder.add(it) }
msg.msgBody.richText.ptt?.toAudio()?.let { builder.add(it) }
}
return builder.build().cleanupRubbishMessageElements()
@ -516,11 +513,13 @@ internal object ReceiveMessageTransformer {
}
}
fun ImMsgBody.Ptt.toVoice() = Voice(
kotlinx.io.core.String(fileName),
fileMd5,
fileSize.toLong(),
format,
kotlinx.io.core.String(downPara)
).also { it.pttInternalInstance = this }
fun ImMsgBody.Ptt.toAudio() = OnlineAudioImpl(
filename = fileName.encodeToString(),
fileMd5 = fileMd5,
fileSize = fileSize.toLongUnsigned(),
codec = AudioCodec.fromId(format),
url = downPara.encodeToString(),
length = time.toLongUnsigned(),
originalPtt = this,
)
}

View File

@ -122,8 +122,8 @@ internal enum class ResourceKind(
) {
PRIVATE_IMAGE("private image"),
GROUP_IMAGE("group image"),
PRIVATE_VOICE("private voice"),
GROUP_VOICE("group voice"),
PRIVATE_AUDIO("private audio"),
GROUP_AUDIO("group audio"),
GROUP_FILE("group file"),

View File

@ -765,7 +765,69 @@ internal class ImMsgBody : ProtoBuf {
@ProtoNumber(30) @JvmField val pbReserve: ByteArray = EMPTY_BYTE_ARRAY,
@ProtoNumber(31) @JvmField val bytesPttUrls: List<ByteArray> = emptyList(),
@ProtoNumber(32) @JvmField val downloadFlag: Int = 0,
) : ProtoBuf
) : ProtoBuf {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Ptt
if (fileType != other.fileType) return false
if (srcUin != other.srcUin) return false
if (!fileUuid.contentEquals(other.fileUuid)) return false
if (!fileMd5.contentEquals(other.fileMd5)) return false
if (!fileName.contentEquals(other.fileName)) return false
if (fileSize != other.fileSize) return false
if (!reserve.contentEquals(other.reserve)) return false
if (fileId != other.fileId) return false
if (serverIp != other.serverIp) return false
if (serverPort != other.serverPort) return false
if (boolValid != other.boolValid) return false
if (!signature.contentEquals(other.signature)) return false
if (!shortcut.contentEquals(other.shortcut)) return false
if (!fileKey.contentEquals(other.fileKey)) return false
if (magicPttIndex != other.magicPttIndex) return false
if (voiceSwitch != other.voiceSwitch) return false
if (!pttUrl.contentEquals(other.pttUrl)) return false
if (!groupFileKey.contentEquals(other.groupFileKey)) return false
if (time != other.time) return false
if (!downPara.contentEquals(other.downPara)) return false
if (format != other.format) return false
if (!pbReserve.contentEquals(other.pbReserve)) return false
if (bytesPttUrls != other.bytesPttUrls) return false
if (downloadFlag != other.downloadFlag) return false
return true
}
override fun hashCode(): Int {
var result = fileType
result = 31 * result + srcUin.hashCode()
result = 31 * result + fileUuid.contentHashCode()
result = 31 * result + fileMd5.contentHashCode()
result = 31 * result + fileName.contentHashCode()
result = 31 * result + fileSize
result = 31 * result + reserve.contentHashCode()
result = 31 * result + fileId
result = 31 * result + serverIp
result = 31 * result + serverPort
result = 31 * result + boolValid.hashCode()
result = 31 * result + signature.contentHashCode()
result = 31 * result + shortcut.contentHashCode()
result = 31 * result + fileKey.contentHashCode()
result = 31 * result + magicPttIndex
result = 31 * result + voiceSwitch
result = 31 * result + pttUrl.contentHashCode()
result = 31 * result + groupFileKey.contentHashCode()
result = 31 * result + time
result = 31 * result + downPara.contentHashCode()
result = 31 * result + format
result = 31 * result + pbReserve.contentHashCode()
result = 31 * result + bytesPttUrls.hashCode()
result = 31 * result + downloadFlag
return result
}
}
@Serializable
internal class PubAccInfo(

View File

@ -145,7 +145,8 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
return response
}
internal fun PttMessage.toPtt() = run {
// old Voice
private fun PttMessage.toPtt() = run {
(this.pttInternalInstance as? ImMsgBody.Ptt)?.let { return it }
ImMsgBody.Ptt(
fileName = fileName.toByteArray(),
@ -155,8 +156,9 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
fileType = 4,
pbReserve = byteArrayOf(0),
format = let {
@Suppress("DEPRECATION")
if (it is Voice) {
it.codec
it._codec
} else {
0
}
@ -244,7 +246,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = subChain.toRichTextElems(messageTarget = targetFriend, withGeneralFlags = true),
ptt = subChain[PttMessage]?.toPtt(),
ptt = subChain.findPtt(),
)
)
},
@ -361,7 +363,7 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
ImMsgBody.MsgBody(
richText = ImMsgBody.RichText(
elems = subChain.toRichTextElems(messageTarget = targetGroup, withGeneralFlags = true),
ptt = subChain[PttMessage]?.toPtt()
ptt = subChain.findPtt()
)
)
@ -409,6 +411,9 @@ internal object MessageSvcPbSendMsg : OutgoingPacketFactory<MessageSvcPbSendMsg.
doFragmented = fragmented
)
}
private fun MessageChain.findPtt() =
findIsInstance<Audio>()?.toPtt() ?: this[PttMessage]?.toPtt()
/*
= buildOutgoingUniPacket(client) {
///writeFully("0A 08 0A 06 08 89 FC A6 8C 0B 12 06 08 01 10 00 18 00 1A 1F 0A 1D 12 08 0A 06 0A 04 F0 9F 92 A9 12 11 AA 02 0E 88 01 00 9A 01 08 78 00 F8 01 00 C8 02 00 20 9B 7A 28 F4 CA 9B B8 03 32 34 08 92 C2 C4 F1 05 10 92 C2 C4 F1 05 18 E6 ED B9 C3 02 20 89 FE BE A4 06 28 89 84 F9 A2 06 48 DE 8C EA E5 0E 58 D9 BD BB A0 09 60 1D 68 92 C2 C4 F1 05 70 00 40 01".hexToBytes())

View File

@ -22,18 +22,21 @@ 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
import net.mamoe.mirai.internal.utils.toIpV4AddressString
import net.mamoe.mirai.message.data.AudioCodec
import net.mamoe.mirai.utils.EMPTY_BYTE_ARRAY
import net.mamoe.mirai.utils.ExternalResource
import net.mamoe.mirai.utils.encodeToString
import net.mamoe.mirai.utils.toUHexString
internal val ExternalResource.voiceCodec: Int
internal inline val ExternalResource.voiceCodec: Int get() = audioCodec.id
internal val ExternalResource.audioCodec: AudioCodec
get() {
return when (formatName) {
// 实际上 amr 是 0, 但用 1 也可以发. 为了避免 silk 错被以 amr 发送导致降音质就都用 1
"amr" -> 1 // amr
"silk" -> 1 // silk V3
else -> 1 // use amr by default
"amr" -> AudioCodec.SILK
"silk" -> AudioCodec.SILK
else -> AudioCodec.AMR // use amr by default
}
}

View File

@ -0,0 +1,10 @@
#
# Copyright 2019-2021 Mamoe Technologies and contributors.
#
# 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
# Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
#
# https://github.com/mamoe/mirai/blob/dev/LICENSE
#
net.mamoe.mirai.internal.message.OfflineAudioFactoryImpl

View File

@ -0,0 +1,75 @@
/*
* Copyright 2019-2021 Mamoe Technologies and contributors.
*
* 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证.
* Use of this source code is governed by the GNU AGPLv3 license that can be found through the following link.
*
* https://github.com/mamoe/mirai/blob/dev/LICENSE
*/
package net.mamoe.mirai.internal.message
import net.mamoe.mirai.internal.message.OnlineAudioImpl.Companion.DOWNLOAD_URL
import net.mamoe.mirai.internal.message.OnlineAudioImpl.Companion.refineUrl
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.test.AbstractTest
import net.mamoe.mirai.message.data.AudioCodec
import net.mamoe.mirai.message.data.OfflineAudio
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertEquals
import kotlin.test.assertFalse
internal class AudioTest : AbstractTest() {
@Test
fun `test factory`() {
assertEquals(
OfflineAudio("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf()),
OfflineAudio("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf())
)
}
@Test
fun `invalid extraData is refreshed`() {
assertContentEquals(
OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, null).extraData,
OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf(1, 2, 3)).extraData,
)
}
@Test
fun `test equality`() {
assertEquals(
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null),
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null)
)
assertEquals(
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null),
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null)
)
assertEquals(
OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, originalPtt = null),
OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, originalPtt = null)
)
assertEquals(
OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf()),
OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf())
)
assertEquals(
OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, ImMsgBody.Ptt(srcUin = 2)),
OfflineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, ImMsgBody.Ptt(srcUin = 2))
)
}
@Test
fun `test refineUrl`() {
assertFalse { DOWNLOAD_URL.endsWith("/") }
assertEquals("", refineUrl(""))
assertEquals("$DOWNLOAD_URL/test", refineUrl("/test"))
assertEquals("$DOWNLOAD_URL/test", refineUrl("test"))
assertEquals("https://custom.com", refineUrl("https://custom.com"))
assertEquals("http://localhost", refineUrl("http://localhost"))
}
}

View File

@ -12,13 +12,12 @@ package net.mamoe.mirai.internal.message.data
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Polymorphic
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.*
import kotlinx.serialization.serializer
import net.mamoe.mirai.Mirai
import net.mamoe.mirai.internal.message.FileMessageImpl
import net.mamoe.mirai.internal.message.MarketFaceImpl
import net.mamoe.mirai.internal.message.OnlineAudioImpl
import net.mamoe.mirai.internal.message.UnsupportedMessageImpl
import net.mamoe.mirai.internal.network.protocol.data.proto.ImMsgBody
import net.mamoe.mirai.internal.utils._miraiContentToString
@ -220,39 +219,100 @@ internal class MessageSerializationTest {
@Serializable
data class V(
val msg: Voice
val msg: Audio
)
@Serializable
data class AudioTestStandard(
val online: OnlineAudio,
val offline: OfflineAudio,
val onlineAsRec: Audio,
val offlineAsRec: Audio,
)
@Test
fun `test Voice serialization`() {
val v = V(Voice("4517", byteArrayOf(14), 50, 3, "https://github.com"))
println(v.serialize(V.serializer()))
fun `test Audio standard`() {
val origin = AudioTestStandard(
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null),
OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, null),
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, null),
OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, null),
)
assertEquals(
v.serialize(V.serializer()),
v.serialize(V.serializer())
.deserialize(V.serializer())
.serialize(V.serializer())
AudioCodec.SILK.id,
format.encodeToJsonElement(origin).jsonObject["offline"]!!.jsonObject["codec"]!!.jsonPrimitive.content.toInt()
) // use custom serializer
assertEquals(
AudioCodec.SILK.id,
format.encodeToJsonElement(origin).jsonObject["online"]!!.jsonObject["codec"]!!.jsonPrimitive.content.toInt()
) // use custom serializer
assertEquals(
"OnlineAudio",
format.encodeToJsonElement(origin).jsonObject["online"]!!.jsonObject["type"]!!.jsonPrimitive.content
)
assertEquals(
v,
v.serialize(V.serializer()).deserialize(V.serializer())
"OfflineAudio",
format.encodeToJsonElement(origin).jsonObject["offline"]!!.jsonObject["type"]!!.jsonPrimitive.content
)
v.msg.pttInternalInstance = ImMsgBody.Ptt(
srcUin = 1234567890,
fileMd5 = byteArrayOf(14, 81, 37, 14),
boolValid = true,
format = 90,
)
println(v.serialize(V.serializer()))
assertEquals(
v.serialize(V.serializer()),
v.serialize(V.serializer())
.deserialize(V.serializer())
.serialize(V.serializer())
"OnlineAudio",
format.encodeToJsonElement(origin).jsonObject["onlineAsRec"]!!.jsonObject["type"]!!.jsonPrimitive.content
)
assertEquals(
v,
v.serialize(V.serializer()).deserialize(V.serializer())
"OfflineAudio",
format.encodeToJsonElement(origin).jsonObject["offlineAsRec"]!!.jsonObject["type"]!!.jsonPrimitive.content
)
val result = origin.serialize().deserialize<AudioTestStandard>()
assertEquals(origin.online::class, result.online::class)
assertEquals(origin.offline::class, result.offline::class)
assertEquals(origin.onlineAsRec::class, result.onlineAsRec::class)
assertEquals(origin.offlineAsRec::class, result.offlineAsRec::class)
assertEquals(origin.online, result.online)
assertEquals(origin.offline, result.offline)
assertEquals(origin.onlineAsRec, result.onlineAsRec)
assertEquals(origin.offlineAsRec, result.offlineAsRec)
assertEquals(origin, result)
}
@Serializable
data class AudioTestWithPtt(
val online: OnlineAudio,
val offline: OfflineAudio,
val onlineAsRec: Audio,
val offlineAsRec: Audio,
)
@Test
fun `test Audio with ptt`() {
val origin = AudioTestWithPtt(
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, ImMsgBody.Ptt(1)),
OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf(1, 2)),
OnlineAudioImpl("name", byteArrayOf(), 1, AudioCodec.SILK, "url", 2, ImMsgBody.Ptt(1)),
OfflineAudio("test", byteArrayOf(), 1, AudioCodec.SILK, byteArrayOf(1, 2)),
)
val result = origin.serialize().deserialize<AudioTestWithPtt>()
assertEquals(origin.online::class, result.online::class)
assertEquals(origin.offline::class, result.offline::class)
assertEquals(origin.onlineAsRec::class, result.onlineAsRec::class)
assertEquals(origin.offlineAsRec::class, result.offlineAsRec::class)
assertEquals(origin.online, result.online)
assertEquals(origin.offline, result.offline)
assertEquals(origin.onlineAsRec, result.onlineAsRec)
assertEquals(origin.offlineAsRec, result.offlineAsRec)
assertEquals(origin, result)
}
}