Remove unnecessary route definitions
This commit is contained in:
parent
e7a6c3f7af
commit
1d32fd14da
14 changed files with 125 additions and 289 deletions
|
@ -50,6 +50,8 @@ type Client struct {
|
||||||
evHandler EventHandler
|
evHandler EventHandler
|
||||||
sessionHandler *SessionHandler
|
sessionHandler *SessionHandler
|
||||||
|
|
||||||
|
conversationsFetchedOnce bool
|
||||||
|
|
||||||
AuthData *AuthData
|
AuthData *AuthData
|
||||||
|
|
||||||
proxy Proxy
|
proxy Proxy
|
||||||
|
|
|
@ -5,15 +5,16 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) ListConversations(count int64, folder gmproto.ListConversationsRequest_Folder) (*gmproto.ListConversationsResponse, error) {
|
func (c *Client) ListConversations(count int64, folder gmproto.ListConversationsRequest_Folder) (*gmproto.ListConversationsResponse, error) {
|
||||||
payload := &gmproto.ListConversationsRequest{Count: count, Folder: folder}
|
msgType := gmproto.MessageType_BUGLE_MESSAGE
|
||||||
//var actionType gmproto.ActionType
|
if !c.conversationsFetchedOnce {
|
||||||
//if !c.synced {
|
msgType = gmproto.MessageType_BUGLE_ANNOTATION
|
||||||
// actionType = gmproto.ActionType_LIST_CONVERSATIONS_SYNC
|
c.conversationsFetchedOnce = true
|
||||||
// c.synced = true
|
}
|
||||||
//} else {
|
return typedResponse[*gmproto.ListConversationsResponse](c.sessionHandler.sendMessageWithParams(SendMessageParams{
|
||||||
actionType := gmproto.ActionType_LIST_CONVERSATIONS
|
Action: gmproto.ActionType_LIST_CONVERSATIONS,
|
||||||
|
Data: &gmproto.ListConversationsRequest{Count: count, Folder: folder},
|
||||||
return typedResponse[*gmproto.ListConversationsResponse](c.sessionHandler.sendMessage(actionType, payload))
|
MessageType: msgType,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) ListContacts() (*gmproto.ListContactsResponse, error) {
|
func (c *Client) ListContacts() (*gmproto.ListContactsResponse, error) {
|
||||||
|
@ -55,13 +56,13 @@ func (c *Client) GetConversation(conversationID string) (*gmproto.Conversation,
|
||||||
return resp.GetConversation(), nil
|
return resp.GetConversation(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) FetchMessages(conversationID string, count int64, cursor *gmproto.Cursor) (*gmproto.FetchMessagesResponse, error) {
|
func (c *Client) FetchMessages(conversationID string, count int64, cursor *gmproto.Cursor) (*gmproto.ListMessagesResponse, error) {
|
||||||
payload := &gmproto.FetchMessagesRequest{ConversationID: conversationID, Count: count}
|
payload := &gmproto.ListMessagesRequest{ConversationID: conversationID, Count: count}
|
||||||
if cursor != nil {
|
if cursor != nil {
|
||||||
payload.Cursor = cursor
|
payload.Cursor = cursor
|
||||||
}
|
}
|
||||||
actionType := gmproto.ActionType_LIST_MESSAGES
|
actionType := gmproto.ActionType_LIST_MESSAGES
|
||||||
return typedResponse[*gmproto.FetchMessagesResponse](c.sessionHandler.sendMessage(actionType, payload))
|
return typedResponse[*gmproto.ListMessagesResponse](c.sessionHandler.sendMessage(actionType, payload))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SendMessage(payload *gmproto.SendMessageRequest) (*gmproto.SendMessageResponse, error) {
|
func (c *Client) SendMessage(payload *gmproto.SendMessageRequest) (*gmproto.SendMessageResponse, error) {
|
||||||
|
|
|
@ -7,8 +7,6 @@ import (
|
||||||
|
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/routes"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,6 +20,24 @@ type IncomingRPCMessage struct {
|
||||||
DecryptedMessage proto.Message
|
DecryptedMessage proto.Message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var responseType = map[gmproto.ActionType]proto.Message{
|
||||||
|
gmproto.ActionType_IS_BUGLE_DEFAULT: &gmproto.IsBugleDefaultResponse{},
|
||||||
|
gmproto.ActionType_GET_UPDATES: &gmproto.UpdateEvents{},
|
||||||
|
gmproto.ActionType_LIST_CONVERSATIONS: &gmproto.ListConversationsResponse{},
|
||||||
|
gmproto.ActionType_NOTIFY_DITTO_ACTIVITY: &gmproto.NotifyDittoActivityResponse{},
|
||||||
|
gmproto.ActionType_GET_CONVERSATION_TYPE: &gmproto.GetConversationTypeResponse{},
|
||||||
|
gmproto.ActionType_GET_CONVERSATION: &gmproto.GetConversationResponse{},
|
||||||
|
gmproto.ActionType_LIST_MESSAGES: &gmproto.ListMessagesResponse{},
|
||||||
|
gmproto.ActionType_SEND_MESSAGE: &gmproto.SendMessageResponse{},
|
||||||
|
gmproto.ActionType_SEND_REACTION: &gmproto.SendReactionResponse{},
|
||||||
|
gmproto.ActionType_DELETE_MESSAGE: &gmproto.DeleteMessageResponse{},
|
||||||
|
gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: &gmproto.GetParticipantThumbnailResponse{},
|
||||||
|
gmproto.ActionType_LIST_CONTACTS: &gmproto.ListContactsResponse{},
|
||||||
|
gmproto.ActionType_LIST_TOP_CONTACTS: &gmproto.ListTopContactsResponse{},
|
||||||
|
gmproto.ActionType_GET_OR_CREATE_CONVERSATION: &gmproto.GetOrCreateConversationResponse{},
|
||||||
|
gmproto.ActionType_UPDATE_CONVERSATION: &gmproto.UpdateConversationResponse{},
|
||||||
|
}
|
||||||
|
|
||||||
func (r *RPC) decryptInternalMessage(data *gmproto.IncomingRPCMessage) (*IncomingRPCMessage, error) {
|
func (r *RPC) decryptInternalMessage(data *gmproto.IncomingRPCMessage) (*IncomingRPCMessage, error) {
|
||||||
msg := &IncomingRPCMessage{
|
msg := &IncomingRPCMessage{
|
||||||
IncomingRPCMessage: data,
|
IncomingRPCMessage: data,
|
||||||
|
@ -39,16 +55,20 @@ func (r *RPC) decryptInternalMessage(data *gmproto.IncomingRPCMessage) (*Incomin
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
responseStruct, ok := responseType[msg.Message.GetAction()]
|
||||||
|
if ok {
|
||||||
|
msg.DecryptedMessage = responseStruct.ProtoReflect().New().Interface()
|
||||||
|
}
|
||||||
if msg.Message.EncryptedData != nil {
|
if msg.Message.EncryptedData != nil {
|
||||||
msg.DecryptedData, err = r.client.AuthData.RequestCrypto.Decrypt(msg.Message.EncryptedData)
|
msg.DecryptedData, err = r.client.AuthData.RequestCrypto.Decrypt(msg.Message.EncryptedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
responseStruct := routes.Routes[msg.Message.GetAction()].ResponseStruct
|
if msg.DecryptedMessage != nil {
|
||||||
msg.DecryptedMessage = responseStruct.ProtoReflect().New().Interface()
|
err = proto.Unmarshal(msg.DecryptedData, msg.DecryptedMessage)
|
||||||
err = proto.Unmarshal(msg.DecryptedData, msg.DecryptedMessage)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -70,7 +90,7 @@ func (r *RPC) deduplicateHash(hash [32]byte) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RPC) logContent(res *IncomingRPCMessage) {
|
func (r *RPC) logContent(res *IncomingRPCMessage) {
|
||||||
if r.client.Logger.Trace().Enabled() && res.DecryptedData != nil {
|
if r.client.Logger.Trace().Enabled() && (res.DecryptedData != nil || res.DecryptedMessage != nil) {
|
||||||
evt := r.client.Logger.Trace()
|
evt := r.client.Logger.Trace()
|
||||||
if res.DecryptedMessage != nil {
|
if res.DecryptedMessage != nil {
|
||||||
evt.Str("proto_name", string(res.DecryptedMessage.ProtoReflect().Descriptor().FullName()))
|
evt.Str("proto_name", string(res.DecryptedMessage.ProtoReflect().Descriptor().FullName()))
|
||||||
|
|
|
@ -1181,7 +1181,7 @@ func (x *Cursor) GetLastItemTimestamp() int64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type FetchMessagesRequest struct {
|
type ListMessagesRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
@ -1191,8 +1191,8 @@ type FetchMessagesRequest struct {
|
||||||
Cursor *Cursor `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
|
Cursor *Cursor `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesRequest) Reset() {
|
func (x *ListMessagesRequest) Reset() {
|
||||||
*x = FetchMessagesRequest{}
|
*x = ListMessagesRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_client_proto_msgTypes[16]
|
mi := &file_client_proto_msgTypes[16]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
@ -1200,13 +1200,13 @@ func (x *FetchMessagesRequest) Reset() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesRequest) String() string {
|
func (x *ListMessagesRequest) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*FetchMessagesRequest) ProtoMessage() {}
|
func (*ListMessagesRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *FetchMessagesRequest) ProtoReflect() protoreflect.Message {
|
func (x *ListMessagesRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_client_proto_msgTypes[16]
|
mi := &file_client_proto_msgTypes[16]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
@ -1218,33 +1218,33 @@ func (x *FetchMessagesRequest) ProtoReflect() protoreflect.Message {
|
||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use FetchMessagesRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListMessagesRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*FetchMessagesRequest) Descriptor() ([]byte, []int) {
|
func (*ListMessagesRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_client_proto_rawDescGZIP(), []int{16}
|
return file_client_proto_rawDescGZIP(), []int{16}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesRequest) GetConversationID() string {
|
func (x *ListMessagesRequest) GetConversationID() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.ConversationID
|
return x.ConversationID
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesRequest) GetCount() int64 {
|
func (x *ListMessagesRequest) GetCount() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Count
|
return x.Count
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesRequest) GetCursor() *Cursor {
|
func (x *ListMessagesRequest) GetCursor() *Cursor {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Cursor
|
return x.Cursor
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type FetchMessagesResponse struct {
|
type ListMessagesResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
@ -1255,8 +1255,8 @@ type FetchMessagesResponse struct {
|
||||||
Cursor *Cursor `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
|
Cursor *Cursor `protobuf:"bytes,5,opt,name=cursor,proto3" json:"cursor,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesResponse) Reset() {
|
func (x *ListMessagesResponse) Reset() {
|
||||||
*x = FetchMessagesResponse{}
|
*x = ListMessagesResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_client_proto_msgTypes[17]
|
mi := &file_client_proto_msgTypes[17]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
@ -1264,13 +1264,13 @@ func (x *FetchMessagesResponse) Reset() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesResponse) String() string {
|
func (x *ListMessagesResponse) String() string {
|
||||||
return protoimpl.X.MessageStringOf(x)
|
return protoimpl.X.MessageStringOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*FetchMessagesResponse) ProtoMessage() {}
|
func (*ListMessagesResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *FetchMessagesResponse) ProtoReflect() protoreflect.Message {
|
func (x *ListMessagesResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_client_proto_msgTypes[17]
|
mi := &file_client_proto_msgTypes[17]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
@ -1282,33 +1282,33 @@ func (x *FetchMessagesResponse) ProtoReflect() protoreflect.Message {
|
||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use FetchMessagesResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListMessagesResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*FetchMessagesResponse) Descriptor() ([]byte, []int) {
|
func (*ListMessagesResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_client_proto_rawDescGZIP(), []int{17}
|
return file_client_proto_rawDescGZIP(), []int{17}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesResponse) GetMessages() []*Message {
|
func (x *ListMessagesResponse) GetMessages() []*Message {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Messages
|
return x.Messages
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesResponse) GetSomeBytes() []byte {
|
func (x *ListMessagesResponse) GetSomeBytes() []byte {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.SomeBytes
|
return x.SomeBytes
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesResponse) GetTotalMessages() int64 {
|
func (x *ListMessagesResponse) GetTotalMessages() int64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.TotalMessages
|
return x.TotalMessages
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *FetchMessagesResponse) GetCursor() *Cursor {
|
func (x *ListMessagesResponse) GetCursor() *Cursor {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Cursor
|
return x.Cursor
|
||||||
}
|
}
|
||||||
|
@ -3212,8 +3212,8 @@ var file_client_proto_goTypes = []interface{}{
|
||||||
(*GetContactsThumbnailRequest)(nil), // 19: client.GetContactsThumbnailRequest
|
(*GetContactsThumbnailRequest)(nil), // 19: client.GetContactsThumbnailRequest
|
||||||
(*ThumbnailData)(nil), // 20: client.ThumbnailData
|
(*ThumbnailData)(nil), // 20: client.ThumbnailData
|
||||||
(*Cursor)(nil), // 21: client.Cursor
|
(*Cursor)(nil), // 21: client.Cursor
|
||||||
(*FetchMessagesRequest)(nil), // 22: client.FetchMessagesRequest
|
(*ListMessagesRequest)(nil), // 22: client.ListMessagesRequest
|
||||||
(*FetchMessagesResponse)(nil), // 23: client.FetchMessagesResponse
|
(*ListMessagesResponse)(nil), // 23: client.ListMessagesResponse
|
||||||
(*ListContactsRequest)(nil), // 24: client.ListContactsRequest
|
(*ListContactsRequest)(nil), // 24: client.ListContactsRequest
|
||||||
(*ListTopContactsRequest)(nil), // 25: client.ListTopContactsRequest
|
(*ListTopContactsRequest)(nil), // 25: client.ListTopContactsRequest
|
||||||
(*ListContactsResponse)(nil), // 26: client.ListContactsResponse
|
(*ListContactsResponse)(nil), // 26: client.ListContactsResponse
|
||||||
|
@ -3274,9 +3274,9 @@ var file_client_proto_depIdxs = []int32{
|
||||||
18, // 10: client.GetParticipantThumbnailResponse.thumbnail:type_name -> client.ParticipantThumbnail
|
18, // 10: client.GetParticipantThumbnailResponse.thumbnail:type_name -> client.ParticipantThumbnail
|
||||||
20, // 11: client.ParticipantThumbnail.data:type_name -> client.ThumbnailData
|
20, // 11: client.ParticipantThumbnail.data:type_name -> client.ThumbnailData
|
||||||
61, // 12: client.ThumbnailData.dimensions:type_name -> conversations.Dimensions
|
61, // 12: client.ThumbnailData.dimensions:type_name -> conversations.Dimensions
|
||||||
21, // 13: client.FetchMessagesRequest.cursor:type_name -> client.Cursor
|
21, // 13: client.ListMessagesRequest.cursor:type_name -> client.Cursor
|
||||||
62, // 14: client.FetchMessagesResponse.messages:type_name -> conversations.Message
|
62, // 14: client.ListMessagesResponse.messages:type_name -> conversations.Message
|
||||||
21, // 15: client.FetchMessagesResponse.cursor:type_name -> client.Cursor
|
21, // 15: client.ListMessagesResponse.cursor:type_name -> client.Cursor
|
||||||
63, // 16: client.ListContactsResponse.contacts:type_name -> conversations.Contact
|
63, // 16: client.ListContactsResponse.contacts:type_name -> conversations.Contact
|
||||||
63, // 17: client.ListTopContactsResponse.contacts:type_name -> conversations.Contact
|
63, // 17: client.ListTopContactsResponse.contacts:type_name -> conversations.Contact
|
||||||
3, // 18: client.ListConversationsRequest.folder:type_name -> client.ListConversationsRequest.Folder
|
3, // 18: client.ListConversationsRequest.folder:type_name -> client.ListConversationsRequest.Folder
|
||||||
|
@ -3511,7 +3511,7 @@ func file_client_proto_init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_client_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
file_client_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*FetchMessagesRequest); i {
|
switch v := v.(*ListMessagesRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -3523,7 +3523,7 @@ func file_client_proto_init() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_client_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
file_client_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*FetchMessagesResponse); i {
|
switch v := v.(*ListMessagesResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
|
|
Binary file not shown.
|
@ -94,14 +94,14 @@ message Cursor {
|
||||||
int64 lastItemTimestamp = 2;
|
int64 lastItemTimestamp = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message FetchMessagesRequest {
|
message ListMessagesRequest {
|
||||||
string conversationID = 2;
|
string conversationID = 2;
|
||||||
int64 count = 3;
|
int64 count = 3;
|
||||||
|
|
||||||
Cursor cursor = 5;
|
Cursor cursor = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
message FetchMessagesResponse {
|
message ListMessagesResponse {
|
||||||
repeated conversations.Message messages = 2;
|
repeated conversations.Message messages = 2;
|
||||||
bytes someBytes = 3;
|
bytes someBytes = 3;
|
||||||
int64 totalMessages = 4;
|
int64 totalMessages = 4;
|
||||||
|
|
|
@ -25,22 +25,22 @@ const (
|
||||||
type BugleRoute int32
|
type BugleRoute int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BugleRoute_UNKNOWN_BUGLE_ROUTE BugleRoute = 0
|
BugleRoute_Unknown BugleRoute = 0
|
||||||
BugleRoute_DataEvent BugleRoute = 19
|
BugleRoute_DataEvent BugleRoute = 19
|
||||||
BugleRoute_PairEvent BugleRoute = 14
|
BugleRoute_PairEvent BugleRoute = 14
|
||||||
)
|
)
|
||||||
|
|
||||||
// Enum value maps for BugleRoute.
|
// Enum value maps for BugleRoute.
|
||||||
var (
|
var (
|
||||||
BugleRoute_name = map[int32]string{
|
BugleRoute_name = map[int32]string{
|
||||||
0: "UNKNOWN_BUGLE_ROUTE",
|
0: "Unknown",
|
||||||
19: "DataEvent",
|
19: "DataEvent",
|
||||||
14: "PairEvent",
|
14: "PairEvent",
|
||||||
}
|
}
|
||||||
BugleRoute_value = map[string]int32{
|
BugleRoute_value = map[string]int32{
|
||||||
"UNKNOWN_BUGLE_ROUTE": 0,
|
"Unknown": 0,
|
||||||
"DataEvent": 19,
|
"DataEvent": 19,
|
||||||
"PairEvent": 14,
|
"PairEvent": 14,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ func (x *IncomingRPCMessage) GetBugleRoute() BugleRoute {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.BugleRoute
|
return x.BugleRoute
|
||||||
}
|
}
|
||||||
return BugleRoute_UNKNOWN_BUGLE_ROUTE
|
return BugleRoute_Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *IncomingRPCMessage) GetStartExecute() string {
|
func (x *IncomingRPCMessage) GetStartExecute() string {
|
||||||
|
@ -907,7 +907,7 @@ func (x *OutgoingRPCMessage_Data) GetBugleRoute() BugleRoute {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.BugleRoute
|
return x.BugleRoute
|
||||||
}
|
}
|
||||||
return BugleRoute_UNKNOWN_BUGLE_ROUTE
|
return BugleRoute_Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *OutgoingRPCMessage_Data) GetMessageData() []byte {
|
func (x *OutgoingRPCMessage_Data) GetMessageData() []byte {
|
||||||
|
|
Binary file not shown.
|
@ -1,66 +0,0 @@
|
||||||
package routes
|
|
||||||
|
|
||||||
import "go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
|
||||||
|
|
||||||
var LIST_CONVERSATIONS_WITH_UPDATES = Route{
|
|
||||||
Action: gmproto.ActionType_LIST_CONVERSATIONS,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_ANNOTATION,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.ListConversationsResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var LIST_CONVERSATIONS = Route{
|
|
||||||
Action: gmproto.ActionType_LIST_CONVERSATIONS,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.ListConversationsResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var GET_CONVERSATION_TYPE = Route{
|
|
||||||
Action: gmproto.ActionType_GET_CONVERSATION_TYPE,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.GetConversationTypeResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var GET_CONVERSATION = Route{
|
|
||||||
Action: gmproto.ActionType_GET_CONVERSATION,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.GetConversationResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var GET_PARTICIPANT_THUMBNAIL = Route{
|
|
||||||
Action: gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.ParticipantThumbnail{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var UPDATE_CONVERSATION = Route{
|
|
||||||
Action: gmproto.ActionType_UPDATE_CONVERSATION,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.UpdateConversationResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var TYPING_UPDATES = Route{
|
|
||||||
Action: gmproto.ActionType_TYPING_UPDATES,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: nil,
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package routes
|
|
||||||
|
|
||||||
import (
|
|
||||||
"google.golang.org/protobuf/proto"
|
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Route struct {
|
|
||||||
Action gmproto.ActionType
|
|
||||||
MessageType gmproto.MessageType
|
|
||||||
BugleRoute gmproto.BugleRoute
|
|
||||||
ResponseStruct proto.Message
|
|
||||||
UseSessionID bool
|
|
||||||
UseTTL bool
|
|
||||||
}
|
|
||||||
|
|
||||||
var Routes = map[gmproto.ActionType]Route{
|
|
||||||
gmproto.ActionType_IS_BUGLE_DEFAULT: IS_BUGLE_DEFAULT,
|
|
||||||
gmproto.ActionType_GET_UPDATES: GET_UPDATES,
|
|
||||||
gmproto.ActionType_LIST_CONVERSATIONS: LIST_CONVERSATIONS,
|
|
||||||
gmproto.ActionType_LIST_CONVERSATIONS_SYNC: LIST_CONVERSATIONS_WITH_UPDATES,
|
|
||||||
gmproto.ActionType_MESSAGE_READ: MESSAGE_READ,
|
|
||||||
gmproto.ActionType_NOTIFY_DITTO_ACTIVITY: NOTIFY_DITTO_ACTIVITY,
|
|
||||||
gmproto.ActionType_GET_CONVERSATION_TYPE: GET_CONVERSATION_TYPE,
|
|
||||||
gmproto.ActionType_GET_CONVERSATION: GET_CONVERSATION,
|
|
||||||
gmproto.ActionType_LIST_MESSAGES: LIST_MESSAGES,
|
|
||||||
gmproto.ActionType_SEND_MESSAGE: SEND_MESSAGE,
|
|
||||||
gmproto.ActionType_SEND_REACTION: SEND_REACTION,
|
|
||||||
gmproto.ActionType_DELETE_MESSAGE: DELETE_MESSAGE,
|
|
||||||
gmproto.ActionType_TYPING_UPDATES: TYPING_UPDATES,
|
|
||||||
gmproto.ActionType_GET_PARTICIPANTS_THUMBNAIL: GET_PARTICIPANT_THUMBNAIL,
|
|
||||||
gmproto.ActionType_LIST_CONTACTS: LIST_CONTACTS,
|
|
||||||
gmproto.ActionType_LIST_TOP_CONTACTS: LIST_TOP_CONTACTS,
|
|
||||||
gmproto.ActionType_GET_OR_CREATE_CONVERSATION: GET_OR_CREATE_CONVERSATION,
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package routes
|
|
||||||
|
|
||||||
import "go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
|
||||||
|
|
||||||
var LIST_MESSAGES = Route{
|
|
||||||
Action: gmproto.ActionType_LIST_MESSAGES,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.FetchMessagesResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var SEND_MESSAGE = Route{
|
|
||||||
Action: gmproto.ActionType_SEND_MESSAGE,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.SendMessageResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var SEND_REACTION = Route{
|
|
||||||
Action: gmproto.ActionType_SEND_REACTION,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.SendReactionResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var DELETE_MESSAGE = Route{
|
|
||||||
Action: gmproto.ActionType_DELETE_MESSAGE,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.DeleteMessageResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var MESSAGE_READ = Route{
|
|
||||||
Action: gmproto.ActionType_MESSAGE_READ,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: nil,
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package routes
|
|
||||||
|
|
||||||
import "go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
|
||||||
|
|
||||||
var IS_BUGLE_DEFAULT = Route{
|
|
||||||
Action: gmproto.ActionType_IS_BUGLE_DEFAULT,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.IsBugleDefaultResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var GET_UPDATES = Route{
|
|
||||||
Action: gmproto.ActionType_GET_UPDATES,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.UpdateEvents{},
|
|
||||||
UseSessionID: true,
|
|
||||||
UseTTL: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
var NOTIFY_DITTO_ACTIVITY = Route{
|
|
||||||
Action: gmproto.ActionType_NOTIFY_DITTO_ACTIVITY,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: nil,
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var LIST_CONTACTS = Route{
|
|
||||||
Action: gmproto.ActionType_LIST_CONTACTS,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.ListContactsResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var LIST_TOP_CONTACTS = Route{
|
|
||||||
Action: gmproto.ActionType_LIST_TOP_CONTACTS,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.ListTopContactsResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var GET_OR_CREATE_CONVERSATION = Route{
|
|
||||||
Action: gmproto.ActionType_GET_OR_CREATE_CONVERSATION,
|
|
||||||
MessageType: gmproto.MessageType_BUGLE_MESSAGE,
|
|
||||||
BugleRoute: gmproto.BugleRoute_DataEvent,
|
|
||||||
ResponseStruct: &gmproto.GetOrCreateConversationResponse{},
|
|
||||||
UseSessionID: false,
|
|
||||||
UseTTL: true,
|
|
||||||
}
|
|
|
@ -6,8 +6,11 @@ import (
|
||||||
|
|
||||||
func (c *Client) SetActiveSession() error {
|
func (c *Client) SetActiveSession() error {
|
||||||
c.sessionHandler.ResetSessionID()
|
c.sessionHandler.ResetSessionID()
|
||||||
actionType := gmproto.ActionType_GET_UPDATES
|
return c.sessionHandler.sendMessageNoResponse(SendMessageParams{
|
||||||
return c.sessionHandler.sendMessageNoResponse(actionType, nil)
|
Action: gmproto.ActionType_GET_UPDATES,
|
||||||
|
OmitTTL: true,
|
||||||
|
UseSessionID: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) IsBugleDefault() (*gmproto.IsBugleDefaultResponse, error) {
|
func (c *Client) IsBugleDefault() (*gmproto.IsBugleDefaultResponse, error) {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import (
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
"go.mau.fi/mautrix-gmessages/libgm/pblite"
|
||||||
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
"go.mau.fi/mautrix-gmessages/libgm/gmproto"
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/routes"
|
|
||||||
"go.mau.fi/mautrix-gmessages/libgm/util"
|
"go.mau.fi/mautrix-gmessages/libgm/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -35,8 +34,8 @@ func (s *SessionHandler) ResetSessionID() {
|
||||||
s.sessionID = uuid.NewString()
|
s.sessionID = uuid.NewString()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionHandler) sendMessageNoResponse(actionType gmproto.ActionType, encryptedData proto.Message) error {
|
func (s *SessionHandler) sendMessageNoResponse(params SendMessageParams) error {
|
||||||
_, payload, err := s.buildMessage(actionType, encryptedData)
|
_, payload, err := s.buildMessage(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -45,8 +44,8 @@ func (s *SessionHandler) sendMessageNoResponse(actionType gmproto.ActionType, en
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionHandler) sendAsyncMessage(actionType gmproto.ActionType, encryptedData proto.Message) (<-chan *IncomingRPCMessage, error) {
|
func (s *SessionHandler) sendAsyncMessage(params SendMessageParams) (<-chan *IncomingRPCMessage, error) {
|
||||||
requestID, payload, err := s.buildMessage(actionType, encryptedData)
|
requestID, payload, err := s.buildMessage(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -73,8 +72,8 @@ func typedResponse[T proto.Message](resp *IncomingRPCMessage, err error) (casted
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionHandler) sendMessage(actionType gmproto.ActionType, encryptedData proto.Message) (*IncomingRPCMessage, error) {
|
func (s *SessionHandler) sendMessageWithParams(params SendMessageParams) (*IncomingRPCMessage, error) {
|
||||||
ch, err := s.sendAsyncMessage(actionType, encryptedData)
|
ch, err := s.sendAsyncMessage(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -83,30 +82,45 @@ func (s *SessionHandler) sendMessage(actionType gmproto.ActionType, encryptedDat
|
||||||
return <-ch, nil
|
return <-ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SessionHandler) buildMessage(actionType gmproto.ActionType, data proto.Message) (string, []byte, error) {
|
func (s *SessionHandler) sendMessage(actionType gmproto.ActionType, encryptedData proto.Message) (*IncomingRPCMessage, error) {
|
||||||
|
return s.sendMessageWithParams(SendMessageParams{
|
||||||
|
Action: actionType,
|
||||||
|
Data: encryptedData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendMessageParams struct {
|
||||||
|
Action gmproto.ActionType
|
||||||
|
Data proto.Message
|
||||||
|
|
||||||
|
UseSessionID bool
|
||||||
|
OmitTTL bool
|
||||||
|
MessageType gmproto.MessageType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SessionHandler) buildMessage(params SendMessageParams) (string, []byte, error) {
|
||||||
var requestID string
|
var requestID string
|
||||||
var err error
|
var err error
|
||||||
sessionID := s.client.sessionHandler.sessionID
|
sessionID := s.client.sessionHandler.sessionID
|
||||||
|
|
||||||
routeInfo, ok := routes.Routes[actionType]
|
if params.UseSessionID {
|
||||||
if !ok {
|
|
||||||
return "", nil, fmt.Errorf("failed to build message: could not find route %d", actionType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if routeInfo.UseSessionID {
|
|
||||||
requestID = s.sessionID
|
requestID = s.sessionID
|
||||||
} else {
|
} else {
|
||||||
requestID = uuid.NewString()
|
requestID = uuid.NewString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if params.MessageType == 0 {
|
||||||
|
params.MessageType = gmproto.MessageType_BUGLE_MESSAGE
|
||||||
|
}
|
||||||
|
|
||||||
message := &gmproto.OutgoingRPCMessage{
|
message := &gmproto.OutgoingRPCMessage{
|
||||||
Mobile: s.client.AuthData.Mobile,
|
Mobile: s.client.AuthData.Mobile,
|
||||||
Data: &gmproto.OutgoingRPCMessage_Data{
|
Data: &gmproto.OutgoingRPCMessage_Data{
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
BugleRoute: routeInfo.BugleRoute,
|
BugleRoute: gmproto.BugleRoute_DataEvent,
|
||||||
MessageTypeData: &gmproto.OutgoingRPCMessage_Data_Type{
|
MessageTypeData: &gmproto.OutgoingRPCMessage_Data_Type{
|
||||||
EmptyArr: &gmproto.EmptyArr{},
|
EmptyArr: &gmproto.EmptyArr{},
|
||||||
MessageType: routeInfo.MessageType,
|
MessageType: params.MessageType,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Auth: &gmproto.OutgoingRPCMessage_Auth{
|
Auth: &gmproto.OutgoingRPCMessage_Auth{
|
||||||
|
@ -116,10 +130,13 @@ func (s *SessionHandler) buildMessage(actionType gmproto.ActionType, data proto.
|
||||||
},
|
},
|
||||||
EmptyArr: &gmproto.EmptyArr{},
|
EmptyArr: &gmproto.EmptyArr{},
|
||||||
}
|
}
|
||||||
|
if !params.OmitTTL {
|
||||||
|
message.TTL = s.client.AuthData.TachyonTTL
|
||||||
|
}
|
||||||
var encryptedData []byte
|
var encryptedData []byte
|
||||||
if data != nil {
|
if params.Data != nil {
|
||||||
var serializedData []byte
|
var serializedData []byte
|
||||||
serializedData, err = proto.Marshal(data)
|
serializedData, err = proto.Marshal(params.Data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", nil, err
|
||||||
}
|
}
|
||||||
|
@ -130,7 +147,7 @@ func (s *SessionHandler) buildMessage(actionType gmproto.ActionType, data proto.
|
||||||
}
|
}
|
||||||
message.Data.MessageData, err = proto.Marshal(&gmproto.OutgoingRPCData{
|
message.Data.MessageData, err = proto.Marshal(&gmproto.OutgoingRPCData{
|
||||||
RequestID: requestID,
|
RequestID: requestID,
|
||||||
Action: actionType,
|
Action: params.Action,
|
||||||
EncryptedProtoData: encryptedData,
|
EncryptedProtoData: encryptedData,
|
||||||
SessionID: sessionID,
|
SessionID: sessionID,
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue