added username support to /v2/send endpoint

This commit is contained in:
Bernhard B
2024-05-22 19:21:22 +02:00
parent 07a3beaa97
commit bbd088fc0b
7 changed files with 147 additions and 127 deletions

View File

@@ -130,6 +130,7 @@ RUN cd /tmp/ \
COPY src/api /tmp/signal-cli-rest-api-src/api COPY src/api /tmp/signal-cli-rest-api-src/api
COPY src/client /tmp/signal-cli-rest-api-src/client COPY src/client /tmp/signal-cli-rest-api-src/client
COPY src/datastructs /tmp/signal-cli-rest-api-src/datastructs
COPY src/utils /tmp/signal-cli-rest-api-src/utils COPY src/utils /tmp/signal-cli-rest-api-src/utils
COPY src/scripts /tmp/signal-cli-rest-api-src/scripts COPY src/scripts /tmp/signal-cli-rest-api-src/scripts
COPY src/main.go /tmp/signal-cli-rest-api-src/ COPY src/main.go /tmp/signal-cli-rest-api-src/

View File

@@ -16,6 +16,7 @@ import (
"github.com/bbernhard/signal-cli-rest-api/client" "github.com/bbernhard/signal-cli-rest-api/client"
utils "github.com/bbernhard/signal-cli-rest-api/utils" utils "github.com/bbernhard/signal-cli-rest-api/utils"
ds "github.com/bbernhard/signal-cli-rest-api/datastructs"
) )
const ( const (
@@ -112,11 +113,11 @@ type SendMessageV2 struct {
Message string `json:"message"` Message string `json:"message"`
Base64Attachments []string `json:"base64_attachments" example:"<BASE64 ENCODED DATA>,data:<MIME-TYPE>;base64<comma><BASE64 ENCODED DATA>,data:<MIME-TYPE>;filename=<FILENAME>;base64<comma><BASE64 ENCODED DATA>"` Base64Attachments []string `json:"base64_attachments" example:"<BASE64 ENCODED DATA>,data:<MIME-TYPE>;base64<comma><BASE64 ENCODED DATA>,data:<MIME-TYPE>;filename=<FILENAME>;base64<comma><BASE64 ENCODED DATA>"`
Sticker string `json:"sticker"` Sticker string `json:"sticker"`
Mentions []client.MessageMention `json:"mentions"` Mentions []ds.MessageMention `json:"mentions"`
QuoteTimestamp *int64 `json:"quote_timestamp"` QuoteTimestamp *int64 `json:"quote_timestamp"`
QuoteAuthor *string `json:"quote_author"` QuoteAuthor *string `json:"quote_author"`
QuoteMessage *string `json:"quote_message"` QuoteMessage *string `json:"quote_message"`
QuoteMentions []client.MessageMention `json:"quote_mentions"` QuoteMentions []ds.MessageMention `json:"quote_mentions"`
TextMode *string `json:"text_mode" enums:"normal,styled"` TextMode *string `json:"text_mode" enums:"normal,styled"`
EditTimestamp *int64 `json:"edit_timestamp"` EditTimestamp *int64 `json:"edit_timestamp"`
} }

View File

@@ -18,6 +18,7 @@ import (
uuid "github.com/gofrs/uuid" uuid "github.com/gofrs/uuid"
qrcode "github.com/skip2/go-qrcode" qrcode "github.com/skip2/go-qrcode"
ds "github.com/bbernhard/signal-cli-rest-api/datastructs"
utils "github.com/bbernhard/signal-cli-rest-api/utils" utils "github.com/bbernhard/signal-cli-rest-api/utils"
) )
@@ -102,12 +103,6 @@ func (g GroupLinkState) FromString(input string) GroupLinkState {
return DefaultGroupLinkState return DefaultGroupLinkState
} }
type MessageMention struct {
Start int64 `json:"start"`
Length int64 `json:"length"`
Author string `json:"author"`
}
type GroupEntry struct { type GroupEntry struct {
Name string `json:"name"` Name string `json:"name"`
Id string `json:"id"` Id string `json:"id"`
@@ -190,30 +185,6 @@ type ListInstalledStickerPacksResponse struct {
Author string `json:"author"` Author string `json:"author"`
} }
type RecpType int
const (
Number RecpType = iota + 1
Username
Group
)
type SignalCliSendRequest struct {
Number string
Message string
Recipients []string
Base64Attachments []string
RecipientType RecpType
Sticker string
Mentions []MessageMention
QuoteTimestamp *int64
QuoteAuthor *string
QuoteMessage *string
QuoteMentions []MessageMention
TextMode *string
EditTimestamp *int64
}
func cleanupTmpFiles(paths []string) { func cleanupTmpFiles(paths []string) {
for _, path := range paths { for _, path := range paths {
os.Remove(path) os.Remove(path)
@@ -308,6 +279,35 @@ func getSignalCliModeString(signalCliMode SignalCliMode) string {
return "unknown" return "unknown"
} }
func getRecipientType(s string) (ds.RecpType, error) {
// check if the provided recipient is of type 'group'
if strings.HasPrefix(s, groupPrefix) { // if the recipient starts with 'group.' it is either a group or a username that starts with 'group.'
// in order to find out whether it is a Signal group or a username that starts with 'group.',
// we remove the prefix and attempt to base64 decode the group name twice (in case it is a Signal group, the group name was base64 encoded
// twice - once in the REST API wrapper and once in signal-cli). If the decoded Signal Group is 32 in length, we know that it is a Signal Group.
// A Signal Group is exactly 32 elements long (see https://github.com/signalapp/libsignal/blob/1086531d798fb4bde25dfaba51ecb59500e0715f/rust/zkgroup/src/api/groups/group_params.rs#L69), whereas the Signal Username Discriminator can be at most 10 digits long (see https://signal.miraheze.org/wiki/Usernames#Discriminator).
// So in case the group name is 32 elements long we know for sure that it is a Signal Group.
s1 := strings.TrimPrefix(s, groupPrefix)
signalCliBase64EncodedGroupId, err := base64.StdEncoding.DecodeString(s1)
if err == nil {
signalCliGroupId, err := base64.StdEncoding.DecodeString(string(signalCliBase64EncodedGroupId))
if err == nil {
if len(signalCliGroupId) == 32 {
return ds.Group, nil
} else {
return ds.Group, errors.New("Invalid Signal group size (" + strconv.Itoa(len(signalCliGroupId)))
}
}
} else if len(s1) <= 10 {
return ds.Username, nil
}
return ds.Group, errors.New("Invalid identifier " + s)
} else if utils.IsPhoneNumber(s) {
return ds.Number, nil
}
return ds.Username, nil
}
type SignalClient struct { type SignalClient struct {
signalCliConfig string signalCliConfig string
attachmentTmpDir string attachmentTmpDir string
@@ -369,11 +369,7 @@ func (s *SignalClient) Init() error {
return nil return nil
} }
func (s *MessageMention) toString() string { func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*SendResponse, error) {
return fmt.Sprintf("%d:%d:%s", s.Start, s.Length, s.Author)
}
func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendResponse, error) {
var resp SendResponse var resp SendResponse
if len(signalCliSendRequest.Recipients) == 0 { if len(signalCliSendRequest.Recipients) == 0 {
@@ -386,7 +382,7 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
} }
var groupId string = "" var groupId string = ""
if signalCliSendRequest.RecipientType == Group { if signalCliSendRequest.RecipientType == ds.Group {
if len(signalCliSendRequest.Recipients) > 1 { if len(signalCliSendRequest.Recipients) > 1 {
return nil, errors.New("More than one recipient is currently not allowed") return nil, errors.New("More than one recipient is currently not allowed")
} }
@@ -419,6 +415,7 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
type Request struct { type Request struct {
Recipients []string `json:"recipient,omitempty"` Recipients []string `json:"recipient,omitempty"`
Usernames []string `json:"username,omitempty"`
Message string `json:"message"` Message string `json:"message"`
GroupId string `json:"group-id,omitempty"` GroupId string `json:"group-id,omitempty"`
Attachments []string `json:"attachment,omitempty"` Attachments []string `json:"attachment,omitempty"`
@@ -434,12 +431,12 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
} }
request := Request{Message: signalCliSendRequest.Message} request := Request{Message: signalCliSendRequest.Message}
if signalCliSendRequest.RecipientType == Group { if signalCliSendRequest.RecipientType == ds.Group {
request.GroupId = groupId request.GroupId = groupId
} else if signalCliSendRequest.RecipientType == Number { } else if signalCliSendRequest.RecipientType == ds.Number {
request.Recipients = signalCliSendRequest.Recipients request.Recipients = signalCliSendRequest.Recipients
} else if signalCliSendRequest.RecipientType == Username { } else if signalCliSendRequest.RecipientType == ds.Username {
//TODO: fix for username request.Usernames = signalCliSendRequest.Recipients
} }
for _, attachmentEntry := range attachmentEntries { for _, attachmentEntry := range attachmentEntries {
request.Attachments = append(request.Attachments, attachmentEntry.toDataForSignal()) request.Attachments = append(request.Attachments, attachmentEntry.toDataForSignal())
@@ -451,7 +448,7 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
if signalCliSendRequest.Mentions != nil { if signalCliSendRequest.Mentions != nil {
request.Mentions = make([]string, len(signalCliSendRequest.Mentions)) request.Mentions = make([]string, len(signalCliSendRequest.Mentions))
for i, mention := range signalCliSendRequest.Mentions { for i, mention := range signalCliSendRequest.Mentions {
request.Mentions[i] = mention.toString() request.Mentions[i] = mention.ToString()
} }
} else { } else {
request.Mentions = nil request.Mentions = nil
@@ -462,7 +459,7 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
if signalCliSendRequest.QuoteMentions != nil { if signalCliSendRequest.QuoteMentions != nil {
request.QuoteMentions = make([]string, len(signalCliSendRequest.QuoteMentions)) request.QuoteMentions = make([]string, len(signalCliSendRequest.QuoteMentions))
for i, mention := range signalCliSendRequest.QuoteMentions { for i, mention := range signalCliSendRequest.QuoteMentions {
request.QuoteMentions[i] = mention.toString() request.QuoteMentions[i] = mention.ToString()
} }
} else { } else {
request.QuoteMentions = nil request.QuoteMentions = nil
@@ -490,12 +487,13 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
} }
} else { } else {
cmd := []string{"--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"} cmd := []string{"--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"}
if signalCliSendRequest.RecipientType == Number { if signalCliSendRequest.RecipientType == ds.Number {
cmd = append(cmd, signalCliSendRequest.Recipients...) cmd = append(cmd, signalCliSendRequest.Recipients...)
} else if signalCliSendRequest.RecipientType == Group { } else if signalCliSendRequest.RecipientType == ds.Group {
cmd = append(cmd, []string{"-g", groupId}...) cmd = append(cmd, []string{"-g", groupId}...)
} else if signalCliSendRequest.RecipientType == Username { } else if signalCliSendRequest.RecipientType == ds.Username {
//TODO fix for usernames cmd = append(cmd, "-u")
cmd = append(cmd, signalCliSendRequest.Recipients...)
} }
if len(signalCliTextFormatStrings) > 0 { if len(signalCliTextFormatStrings) > 0 {
@@ -512,7 +510,7 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
for _, mention := range signalCliSendRequest.Mentions { for _, mention := range signalCliSendRequest.Mentions {
cmd = append(cmd, "--mention") cmd = append(cmd, "--mention")
cmd = append(cmd, mention.toString()) cmd = append(cmd, mention.ToString())
} }
if signalCliSendRequest.Sticker != "" { if signalCliSendRequest.Sticker != "" {
@@ -537,7 +535,7 @@ func (s *SignalClient) send(signalCliSendRequest SignalCliSendRequest) (*SendRes
for _, mention := range signalCliSendRequest.QuoteMentions { for _, mention := range signalCliSendRequest.QuoteMentions {
cmd = append(cmd, "--quote-mention") cmd = append(cmd, "--quote-mention")
cmd = append(cmd, mention.toString()) cmd = append(cmd, mention.ToString())
} }
if signalCliSendRequest.EditTimestamp != nil { if signalCliSendRequest.EditTimestamp != nil {
@@ -674,12 +672,12 @@ func (s *SignalClient) VerifyRegisteredNumber(number string, token string, pin s
} }
func (s *SignalClient) SendV1(number string, message string, recipients []string, base64Attachments []string, isGroup bool) (*SendResponse, error) { func (s *SignalClient) SendV1(number string, message string, recipients []string, base64Attachments []string, isGroup bool) (*SendResponse, error) {
recipientType := Number recipientType := ds.Number
if isGroup { if isGroup {
recipientType = Group recipientType = ds.Group
} }
signalCliSendRequest := SignalCliSendRequest{Number: number, Message: message, Recipients: recipients, Base64Attachments: base64Attachments, signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: recipients, Base64Attachments: base64Attachments,
RecipientType: recipientType, Sticker: "", Mentions: nil, QuoteTimestamp: nil, QuoteAuthor: nil, QuoteMessage: nil, RecipientType: recipientType, Sticker: "", Mentions: nil, QuoteTimestamp: nil, QuoteAuthor: nil, QuoteMessage: nil,
QuoteMentions: nil, TextMode: nil, EditTimestamp: nil} QuoteMentions: nil, TextMode: nil, EditTimestamp: nil}
timestamp, err := s.send(signalCliSendRequest) timestamp, err := s.send(signalCliSendRequest)
@@ -701,8 +699,8 @@ func (s *SignalClient) getJsonRpc2Clients() []*JsonRpc2Client {
return jsonRpc2Clients return jsonRpc2Clients
} }
func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []MessageMention, func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []ds.MessageMention,
quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []MessageMention, textMode *string, editTimestamp *int64) (*[]SendResponse, error) { quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, editTimestamp *int64) (*[]SendResponse, error) {
if len(recps) == 0 { if len(recps) == 0 {
return nil, errors.New("Please provide at least one recipient") return nil, errors.New("Please provide at least one recipient")
} }
@@ -712,28 +710,46 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
} }
groups := []string{} groups := []string{}
recipients := []string{} numbers := []string{}
usernames := []string{}
for _, recipient := range recps { for _, recipient := range recps {
if strings.HasPrefix(recipient, groupPrefix) { recipientType, err := getRecipientType(recipient)
if err != nil {
return nil, err
}
if recipientType == ds.Group {
groups = append(groups, strings.TrimPrefix(recipient, groupPrefix)) groups = append(groups, strings.TrimPrefix(recipient, groupPrefix))
} else if recipientType == ds.Number {
numbers = append(numbers, recipient)
} else if recipientType == ds.Username {
usernames = append(usernames, recipient)
} else { } else {
recipients = append(recipients, recipient) return nil, errors.New("Invalid recipient type")
} }
} }
if len(recipients) > 0 && len(groups) > 0 { if len(numbers) > 0 && len(groups) > 0 {
return nil, errors.New("Signal Messenger Groups and phone numbers cannot be specified together in one request! Please split them up into multiple REST API calls.") return nil, errors.New("Signal Messenger Groups and phone numbers cannot be specified together in one request! Please split them up into multiple REST API calls.")
} }
if len(usernames) > 0 && len(groups) > 0 {
return nil, errors.New("Signal Messenger Groups and usernames cannot be specified together in one request! Please split them up into multiple REST API calls.")
}
if len(numbers) > 0 && len(usernames) > 0 {
return nil, errors.New("Signal Messenger phone numbers and usernames cannot be specified together in one request! Please split them up into multiple REST API calls.")
}
if len(groups) > 1 { if len(groups) > 1 {
return nil, errors.New("A signal message cannot be sent to more than one group at once! Please use multiple REST API calls for that.") return nil, errors.New("A signal message cannot be sent to more than one group at once! Please use multiple REST API calls for that.")
} }
timestamps := []SendResponse{} timestamps := []SendResponse{}
for _, group := range groups { for _, group := range groups {
signalCliSendRequest := SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments, signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments,
RecipientType: Group, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp, RecipientType: ds.Group, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions, QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp} TextMode: textMode, EditTimestamp: editTimestamp}
timestamp, err := s.send(signalCliSendRequest) timestamp, err := s.send(signalCliSendRequest)
@@ -743,9 +759,21 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
timestamps = append(timestamps, *timestamp) timestamps = append(timestamps, *timestamp)
} }
if len(recipients) > 0 { if len(numbers) > 0 {
signalCliSendRequest := SignalCliSendRequest{Number: number, Message: message, Recipients: recipients, Base64Attachments: base64Attachments, signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: numbers, Base64Attachments: base64Attachments,
RecipientType: Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp, RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp}
timestamp, err := s.send(signalCliSendRequest)
if err != nil {
return nil, err
}
timestamps = append(timestamps, *timestamp)
}
if len(usernames) > 0 {
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: usernames, Base64Attachments: base64Attachments,
RecipientType: ds.Username, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions, QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp} TextMode: textMode, EditTimestamp: editTimestamp}
timestamp, err := s.send(signalCliSendRequest) timestamp, err := s.send(signalCliSendRequest)
@@ -1654,7 +1682,6 @@ func (s *SignalClient) SendReaction(number string, recipient string, emoji strin
return err return err
} }
func (s *SignalClient) SendReceipt(number string, recipient string, receipt_type string, timestamp int64) error { func (s *SignalClient) SendReceipt(number string, recipient string, receipt_type string, timestamp int64) error {
// see https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc#sendreceipt // see https://github.com/AsamK/signal-cli/blob/master/man/signal-cli.1.adoc#sendreceipt
var err error var err error
@@ -1662,9 +1689,9 @@ func (s *SignalClient) SendReceipt(number string, recipient string, receipt_type
if s.signalCliMode == JsonRpc { if s.signalCliMode == JsonRpc {
type Request struct { type Request struct {
Recipient string `json:"recipient,omitempty"` Recipient string `json:"recipient,omitempty"`
ReceiptType string `json:"receipt-type"` ReceiptType string `json:"receipt-type"`
Timestamp int64 `json:"target-timestamp"` Timestamp int64 `json:"target-timestamp"`
} }
request := Request{} request := Request{}
request.Recipient = recp request.Recipient = recp

View File

@@ -0,0 +1,44 @@
package data
import (
"fmt"
)
type RecpType int
const (
Number RecpType = iota + 1
Username
Group
)
type MessageMention struct {
Start int64 `json:"start"`
Length int64 `json:"length"`
Author string `json:"author"`
}
func (s *MessageMention) ToString() string {
return fmt.Sprintf("%d:%d:%s", s.Start, s.Length, s.Author)
}
type SendMessageRecipient struct {
Identifier string `json:"identifier"`
Type string `json:"type"`
}
type SignalCliSendRequest struct {
Number string
Message string
Recipients []string
Base64Attachments []string
RecipientType RecpType
Sticker string
Mentions []MessageMention
QuoteTimestamp *int64
QuoteAuthor *string
QuoteMessage *string
QuoteMentions []MessageMention
TextMode *string
EditTimestamp *int64
}

View File

@@ -2217,10 +2217,7 @@ var doc = `{
"type": "integer" "type": "integer"
}, },
"mentions": { "mentions": {
"type": "array", "type": "string"
"items": {
"$ref": "#/definitions/client.MessageMention"
}
}, },
"message": { "message": {
"type": "string" "type": "string"
@@ -2232,10 +2229,7 @@ var doc = `{
"type": "string" "type": "string"
}, },
"quote_mentions": { "quote_mentions": {
"type": "array", "type": "string"
"items": {
"$ref": "#/definitions/client.MessageMention"
}
}, },
"quote_message": { "quote_message": {
"type": "string" "type": "string"
@@ -2490,20 +2484,6 @@ var doc = `{
} }
} }
}, },
"client.MessageMention": {
"type": "object",
"properties": {
"author": {
"type": "string"
},
"length": {
"type": "integer"
},
"start": {
"type": "integer"
}
}
},
"client.SetUsernameResponse": { "client.SetUsernameResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -2201,10 +2201,7 @@
"type": "integer" "type": "integer"
}, },
"mentions": { "mentions": {
"type": "array", "type": "string"
"items": {
"$ref": "#/definitions/client.MessageMention"
}
}, },
"message": { "message": {
"type": "string" "type": "string"
@@ -2216,10 +2213,7 @@
"type": "string" "type": "string"
}, },
"quote_mentions": { "quote_mentions": {
"type": "array", "type": "string"
"items": {
"$ref": "#/definitions/client.MessageMention"
}
}, },
"quote_message": { "quote_message": {
"type": "string" "type": "string"
@@ -2474,20 +2468,6 @@
} }
} }
}, },
"client.MessageMention": {
"type": "object",
"properties": {
"author": {
"type": "string"
},
"length": {
"type": "integer"
},
"start": {
"type": "integer"
}
}
},
"client.SetUsernameResponse": { "client.SetUsernameResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -171,9 +171,7 @@ definitions:
edit_timestamp: edit_timestamp:
type: integer type: integer
mentions: mentions:
items: type: string
$ref: '#/definitions/client.MessageMention'
type: array
message: message:
type: string type: string
number: number:
@@ -181,9 +179,7 @@ definitions:
quote_author: quote_author:
type: string type: string
quote_mentions: quote_mentions:
items: type: string
$ref: '#/definitions/client.MessageMention'
type: array
quote_message: quote_message:
type: string type: string
quote_timestamp: quote_timestamp:
@@ -349,15 +345,6 @@ definitions:
url: url:
type: string type: string
type: object type: object
client.MessageMention:
properties:
author:
type: string
length:
type: integer
start:
type: integer
type: object
client.SetUsernameResponse: client.SetUsernameResponse:
properties: properties:
username: username: