From e8362691b65b6207ac1c951b6764ac883cedca9f Mon Sep 17 00:00:00 2001 From: wdvxdr Date: Fri, 25 Sep 2020 21:35:37 +0800 Subject: [PATCH] feature .get_tts ttsApi --- client/pb/richmedia/tts.pb.go | 311 ++++++++++++++++++++++++++++++++++ client/pb/richmedia/tts.proto | 21 +++ client/tts.go | 44 +++++ client/vip.go | 12 +- utils/http.go | 29 ++++ 5 files changed, 411 insertions(+), 6 deletions(-) create mode 100644 client/pb/richmedia/tts.pb.go create mode 100644 client/pb/richmedia/tts.proto create mode 100644 client/tts.go diff --git a/client/pb/richmedia/tts.pb.go b/client/pb/richmedia/tts.pb.go new file mode 100644 index 00000000..d4a10880 --- /dev/null +++ b/client/pb/richmedia/tts.pb.go @@ -0,0 +1,311 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.25.0 +// protoc v3.13.0 +// source: tts.proto + +package richmedia + +import ( + proto "github.com/golang/protobuf/proto" + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + +type TtsRspBody struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + RetCode uint32 `protobuf:"varint,1,opt,name=ret_code,json=retCode,proto3" json:"ret_code,omitempty"` + SessionId string `protobuf:"bytes,2,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` + OutSeq uint32 `protobuf:"varint,3,opt,name=out_seq,json=outSeq,proto3" json:"out_seq,omitempty"` + VoiceData []*TtsVoiceItem `protobuf:"bytes,4,rep,name=voice_data,json=voiceData,proto3" json:"voice_data,omitempty"` + Islast bool `protobuf:"varint,5,opt,name=islast,proto3" json:"islast,omitempty"` + PcmSampleRate uint32 `protobuf:"varint,6,opt,name=pcm_sample_rate,json=pcmSampleRate,proto3" json:"pcm_sample_rate,omitempty"` + OpusSampleRate uint32 `protobuf:"varint,7,opt,name=opus_sample_rate,json=opusSampleRate,proto3" json:"opus_sample_rate,omitempty"` + OpusChannels uint32 `protobuf:"varint,8,opt,name=opus_channels,json=opusChannels,proto3" json:"opus_channels,omitempty"` + OpusBitRate uint32 `protobuf:"varint,9,opt,name=opus_bit_rate,json=opusBitRate,proto3" json:"opus_bit_rate,omitempty"` + OpusFrameSize uint32 `protobuf:"varint,10,opt,name=opus_frame_size,json=opusFrameSize,proto3" json:"opus_frame_size,omitempty"` +} + +func (x *TtsRspBody) Reset() { + *x = TtsRspBody{} + if protoimpl.UnsafeEnabled { + mi := &file_tts_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TtsRspBody) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TtsRspBody) ProtoMessage() {} + +func (x *TtsRspBody) ProtoReflect() protoreflect.Message { + mi := &file_tts_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TtsRspBody.ProtoReflect.Descriptor instead. +func (*TtsRspBody) Descriptor() ([]byte, []int) { + return file_tts_proto_rawDescGZIP(), []int{0} +} + +func (x *TtsRspBody) GetRetCode() uint32 { + if x != nil { + return x.RetCode + } + return 0 +} + +func (x *TtsRspBody) GetSessionId() string { + if x != nil { + return x.SessionId + } + return "" +} + +func (x *TtsRspBody) GetOutSeq() uint32 { + if x != nil { + return x.OutSeq + } + return 0 +} + +func (x *TtsRspBody) GetVoiceData() []*TtsVoiceItem { + if x != nil { + return x.VoiceData + } + return nil +} + +func (x *TtsRspBody) GetIslast() bool { + if x != nil { + return x.Islast + } + return false +} + +func (x *TtsRspBody) GetPcmSampleRate() uint32 { + if x != nil { + return x.PcmSampleRate + } + return 0 +} + +func (x *TtsRspBody) GetOpusSampleRate() uint32 { + if x != nil { + return x.OpusSampleRate + } + return 0 +} + +func (x *TtsRspBody) GetOpusChannels() uint32 { + if x != nil { + return x.OpusChannels + } + return 0 +} + +func (x *TtsRspBody) GetOpusBitRate() uint32 { + if x != nil { + return x.OpusBitRate + } + return 0 +} + +func (x *TtsRspBody) GetOpusFrameSize() uint32 { + if x != nil { + return x.OpusFrameSize + } + return 0 +} + +type TtsVoiceItem struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Voice []byte `protobuf:"bytes,1,opt,name=voice,proto3" json:"voice,omitempty"` + Seq uint32 `protobuf:"varint,2,opt,name=seq,proto3" json:"seq,omitempty"` +} + +func (x *TtsVoiceItem) Reset() { + *x = TtsVoiceItem{} + if protoimpl.UnsafeEnabled { + mi := &file_tts_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *TtsVoiceItem) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TtsVoiceItem) ProtoMessage() {} + +func (x *TtsVoiceItem) ProtoReflect() protoreflect.Message { + mi := &file_tts_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TtsVoiceItem.ProtoReflect.Descriptor instead. +func (*TtsVoiceItem) Descriptor() ([]byte, []int) { + return file_tts_proto_rawDescGZIP(), []int{1} +} + +func (x *TtsVoiceItem) GetVoice() []byte { + if x != nil { + return x.Voice + } + return nil +} + +func (x *TtsVoiceItem) GetSeq() uint32 { + if x != nil { + return x.Seq + } + return 0 +} + +var File_tts_proto protoreflect.FileDescriptor + +var file_tts_proto_rawDesc = []byte{ + 0x0a, 0x09, 0x74, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xe8, 0x02, 0x0a, 0x0a, + 0x54, 0x74, 0x73, 0x52, 0x73, 0x70, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x19, 0x0a, 0x08, 0x72, 0x65, + 0x74, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x72, 0x65, + 0x74, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x73, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x65, 0x71, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x06, 0x6f, 0x75, 0x74, 0x53, 0x65, 0x71, 0x12, 0x2c, 0x0a, + 0x0a, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0d, 0x2e, 0x54, 0x74, 0x73, 0x56, 0x6f, 0x69, 0x63, 0x65, 0x49, 0x74, 0x65, 0x6d, + 0x52, 0x09, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x44, 0x61, 0x74, 0x61, 0x12, 0x16, 0x0a, 0x06, 0x69, + 0x73, 0x6c, 0x61, 0x73, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x06, 0x69, 0x73, 0x6c, + 0x61, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x70, 0x63, 0x6d, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x70, 0x63, + 0x6d, 0x53, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x28, 0x0a, 0x10, 0x6f, + 0x70, 0x75, 0x73, 0x5f, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0e, 0x6f, 0x70, 0x75, 0x73, 0x53, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x52, 0x61, 0x74, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x6f, 0x70, 0x75, 0x73, 0x5f, 0x63, 0x68, + 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0c, 0x6f, 0x70, + 0x75, 0x73, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x73, 0x12, 0x22, 0x0a, 0x0d, 0x6f, 0x70, + 0x75, 0x73, 0x5f, 0x62, 0x69, 0x74, 0x5f, 0x72, 0x61, 0x74, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x0d, 0x52, 0x0b, 0x6f, 0x70, 0x75, 0x73, 0x42, 0x69, 0x74, 0x52, 0x61, 0x74, 0x65, 0x12, 0x26, + 0x0a, 0x0f, 0x6f, 0x70, 0x75, 0x73, 0x5f, 0x66, 0x72, 0x61, 0x6d, 0x65, 0x5f, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x0d, 0x6f, 0x70, 0x75, 0x73, 0x46, 0x72, 0x61, + 0x6d, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x22, 0x36, 0x0a, 0x0c, 0x54, 0x74, 0x73, 0x56, 0x6f, 0x69, + 0x63, 0x65, 0x49, 0x74, 0x65, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, + 0x73, 0x65, 0x71, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x03, 0x73, 0x65, 0x71, 0x42, 0x0d, + 0x5a, 0x0b, 0x2e, 0x3b, 0x72, 0x69, 0x63, 0x68, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x62, 0x06, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_tts_proto_rawDescOnce sync.Once + file_tts_proto_rawDescData = file_tts_proto_rawDesc +) + +func file_tts_proto_rawDescGZIP() []byte { + file_tts_proto_rawDescOnce.Do(func() { + file_tts_proto_rawDescData = protoimpl.X.CompressGZIP(file_tts_proto_rawDescData) + }) + return file_tts_proto_rawDescData +} + +var file_tts_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_tts_proto_goTypes = []interface{}{ + (*TtsRspBody)(nil), // 0: TtsRspBody + (*TtsVoiceItem)(nil), // 1: TtsVoiceItem +} +var file_tts_proto_depIdxs = []int32{ + 1, // 0: TtsRspBody.voice_data:type_name -> TtsVoiceItem + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_tts_proto_init() } +func file_tts_proto_init() { + if File_tts_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_tts_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TtsRspBody); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_tts_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*TtsVoiceItem); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_tts_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_tts_proto_goTypes, + DependencyIndexes: file_tts_proto_depIdxs, + MessageInfos: file_tts_proto_msgTypes, + }.Build() + File_tts_proto = out.File + file_tts_proto_rawDesc = nil + file_tts_proto_goTypes = nil + file_tts_proto_depIdxs = nil +} diff --git a/client/pb/richmedia/tts.proto b/client/pb/richmedia/tts.proto new file mode 100644 index 00000000..b7ab8218 --- /dev/null +++ b/client/pb/richmedia/tts.proto @@ -0,0 +1,21 @@ +syntax = "proto3"; + +option go_package = ".;richmedia"; + +message TtsRspBody { + uint32 ret_code = 1; + string session_id = 2; + uint32 out_seq = 3; + repeated TtsVoiceItem voice_data = 4; + bool islast = 5; + uint32 pcm_sample_rate = 6; + uint32 opus_sample_rate = 7; + uint32 opus_channels = 8; + uint32 opus_bit_rate = 9; + uint32 opus_frame_size = 10; +} + +message TtsVoiceItem { + bytes voice = 1; + uint32 seq = 2; +} \ No newline at end of file diff --git a/client/tts.go b/client/tts.go new file mode 100644 index 00000000..46b465a5 --- /dev/null +++ b/client/tts.go @@ -0,0 +1,44 @@ +package client + +import ( + "fmt" + "github.com/Mrs4s/MiraiGo/binary" + "github.com/Mrs4s/MiraiGo/client/pb/richmedia" + "github.com/Mrs4s/MiraiGo/utils" + "github.com/golang/protobuf/proto" +) + +func (c *QQClient) GetTts(text string) ([]byte, error) { + url := "https://textts.qq.com/cgi-bin/tts" + data := "{\"appid\": \"201908021016\",\"text\": \"" + text + "\"}" + rsp, err := utils.HttpPostBytesWithCookie(url, []byte(data), c.getCookies()) + if err != nil { + return nil, err + } + ttsReader := binary.NewReader(rsp) + ttsWriter := binary.NewWriter() + for { + // 数据格式 69e(字符串) 十六进制 数据长度 0 为结尾 + // 0D 0A (分隔符) payload 0D 0A + var dataLen []byte + for b := ttsReader.ReadByte(); b != byte(0x0d); b = ttsReader.ReadByte() { + dataLen = append(dataLen, b) + } + ttsReader.ReadByte() + var length int + _, _ = fmt.Sscan("0x"+string(dataLen), &length) + if length == 0 { + break + } + ttsRsp := &richmedia.TtsRspBody{} + err := proto.Unmarshal(ttsReader.ReadBytes(length), ttsRsp) + if err != nil { + return nil, err + } + for _, voiceItem := range ttsRsp.VoiceData { + ttsWriter.Write(voiceItem.Voice) + } + ttsReader.ReadBytes(2) + } + return ttsWriter.Bytes(), nil +} diff --git a/client/vip.go b/client/vip.go index b92d3f19..532d563c 100644 --- a/client/vip.go +++ b/client/vip.go @@ -2,12 +2,12 @@ package client type ( VipInfo struct { - Uin int64 - Name string - Level int - LevelSpeed float64 - VipLevel string + Uin int64 + Name string + Level int + LevelSpeed float64 + VipLevel string VipGrowthSpeed int VipGrowthTotal int } -) \ No newline at end of file +) diff --git a/utils/http.go b/utils/http.go index 436c92bf..4dbf319b 100644 --- a/utils/http.go +++ b/utils/http.go @@ -62,3 +62,32 @@ func HttpPostBytes(url string, data []byte) ([]byte, error) { } return body, nil } + +func HttpPostBytesWithCookie(url string, data []byte, cookie string) ([]byte, error) { + req, err := http.NewRequest("POST", url, bytes.NewReader(data)) + if err != nil { + return nil, err + } + req.Header["User-Agent"] = []string{"Dalvik/2.1.0 (Linux; U; Android 7.1.2; PCRT00 Build/N2G48H)"} + req.Header["Content-Type"] = []string{"application/json"} + if cookie != "" { + req.Header["Cookie"] = []string{cookie} + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + if strings.Contains(resp.Header.Get("Content-Encoding"), "gzip") { + buffer := bytes.NewBuffer(body) + r, _ := gzip.NewReader(buffer) + defer r.Close() + unCom, err := ioutil.ReadAll(r) + return unCom, err + } + return body, nil +}