Merge adf0ee16e1231e5b8fff5ad82b54a44eaf4e87c5 into 787d204e155d0c73840a615a5e90c697bacaa6f1

This commit is contained in:
Gara Dorta 2026-10-22 22:04:50 +00:00 committed by GitHub
commit e0df77cabf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 74 additions and 11 deletions

View File

@ -138,6 +138,7 @@ type SendMessageV2 struct {
QuoteMessage *string `json:"quote_message,omitempty"`
QuoteMentions []ds.MessageMention `json:"quote_mentions,omitempty"`
TextMode *string `json:"text_mode,omitempty" enums:"normal,styled"`
TextStyles []string `json:"text_styles,omitempty" example:"0:5:BOLD,10:3:ITALIC"`
EditTimestamp *int64 `json:"edit_timestamp,omitempty"`
NotifySelf *bool `json:"notify_self,omitempty"`
LinkPreview *ds.LinkPreviewType `json:"link_preview,omitempty"`
@ -508,7 +509,7 @@ func (a *Api) Send(c *gin.Context) {
// @Summary Send a signal message.
// @Tags Messages
// @Description Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \*italic text\*, \*\*bold text\*\*, ~strikethrough text~, ||spoiler||, \`monospace\`. If you want to escape a formatting character, prefix it with two backslashes.
// @Description Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \*italic text\*, \*\*bold text\*\*, ~strikethrough text~, ||spoiler||, \`monospace\`. If you want to escape a formatting character, prefix it with two backslashes. Alternatively, pass the styles directly via text_styles in signal-cli's 'start:length:STYLE' format (STYLE is one of BOLD, ITALIC, SPOILER, STRIKETHROUGH, MONOSPACE); text_styles can't be combined with text_mode 'styled'.
// @Accept json
// @Produce json
// @Success 201 {array} ds.SendMessageResponse
@ -547,8 +548,21 @@ func (a *Api) SendV2(c *gin.Context) {
return
}
if len(req.TextStyles) > 0 && req.TextMode != nil && *req.TextMode == "styled" {
c.JSON(400, Error{Msg: "'text_styles' can't be combined with text_mode 'styled'!"})
return
}
for _, textStyle := range req.TextStyles {
if !utils.IsValidTextStyle(textStyle) {
c.JSON(400, Error{Msg: "Invalid text style '" + textStyle + "' - expected format is 'start:length:STYLE' (e.g. '0:5:BOLD')"})
return
}
}
// the default text mode doesn't apply when the text styles are passed directly
textMode := req.TextMode
if textMode == nil {
if textMode == nil && len(req.TextStyles) == 0 {
defaultSignalTextMode := utils.GetEnv("DEFAULT_SIGNAL_TEXT_MODE", "normal")
if defaultSignalTextMode == "styled" {
styledStr := "styled"
@ -564,7 +578,7 @@ func (a *Api) SendV2(c *gin.Context) {
data, err := a.signalClient.SendV2(
req.Number, req.Message, req.Recipients, req.Base64Attachments, req.Sticker,
req.Mentions, req.QuoteTimestamp, req.QuoteAuthor, req.QuoteMessage, req.QuoteMentions,
textMode, req.EditTimestamp, req.NotifySelf, req.LinkPreview, req.ViewOnce)
textMode, req.TextStyles, req.EditTimestamp, req.NotifySelf, req.LinkPreview, req.ViewOnce)
if err != nil {
switch err.(type) {
case *client.RateLimitErrorType:

View File

@ -501,8 +501,8 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*ds.S
return nil, err
}
signalCliTextFormatStrings := []string{}
if signalCliSendRequest.TextMode != nil && *signalCliSendRequest.TextMode == "styled" {
signalCliTextFormatStrings := signalCliSendRequest.TextStyles
if len(signalCliTextFormatStrings) == 0 && signalCliSendRequest.TextMode != nil && *signalCliSendRequest.TextMode == "styled" {
textstyleParser := utils.NewTextstyleParser(signalCliSendRequest.Message)
signalCliSendRequest.Message, signalCliTextFormatStrings = textstyleParser.Parse()
}
@ -954,7 +954,7 @@ func (s *SignalClient) getJsonRpc2Clients() []*JsonRpc2Client {
}
func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []ds.MessageMention,
quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, editTimestamp *int64, notifySelf *bool,
quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, textStyles []string, editTimestamp *int64, notifySelf *bool,
linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]ds.SendMessageResponse, error) {
if len(recps) == 0 {
return nil, errors.New("Please provide at least one recipient")
@ -1006,7 +1006,7 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments,
RecipientType: ds.Group, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
TextMode: textMode, TextStyles: textStyles, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
resp, err := s.send(signalCliSendRequest)
if err != nil {
return nil, err
@ -1018,7 +1018,7 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: numbers, Base64Attachments: base64Attachments,
RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
TextMode: textMode, TextStyles: textStyles, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
resp, err := s.send(signalCliSendRequest)
if err != nil {
return nil, err
@ -1030,7 +1030,7 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
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,
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
TextMode: textMode, TextStyles: textStyles, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
resp, err := s.send(signalCliSendRequest)
if err != nil {
return nil, err

View File

@ -47,6 +47,7 @@ type SignalCliSendRequest struct {
QuoteMessage *string
QuoteMentions []MessageMention
TextMode *string
TextStyles []string
EditTimestamp *int64
NotifySelf *bool
LinkPreview *LinkPreviewType

View File

@ -468,6 +468,16 @@ const docTemplate = `{
],
"type": "string"
},
"text_styles": {
"example": [
"0:5:BOLD",
"10:3:ITALIC"
],
"items": {
"type": "string"
},
"type": "array"
},
"view_once": {
"type": "boolean"
}
@ -5113,7 +5123,7 @@ const docTemplate = `{
"consumes": [
"application/json"
],
"description": "Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \\*italic text\\*, \\*\\*bold text\\*\\*, ~strikethrough text~, ||spoiler||, \\` + "`" + `monospace\\` + "`" + `. If you want to escape a formatting character, prefix it with two backslashes.",
"description": "Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \\*italic text\\*, \\*\\*bold text\\*\\*, ~strikethrough text~, ||spoiler||, \\` + "`" + `monospace\\` + "`" + `. If you want to escape a formatting character, prefix it with two backslashes. Alternatively, pass the styles directly via text_styles in signal-cli's 'start:length:STYLE' format (STYLE is one of BOLD, ITALIC, SPOILER, STRIKETHROUGH, MONOSPACE); text_styles can't be combined with text_mode 'styled'.",
"parameters": [
{
"description": "Input Data",

View File

@ -463,6 +463,16 @@
],
"type": "string"
},
"text_styles": {
"example": [
"0:5:BOLD",
"10:3:ITALIC"
],
"items": {
"type": "string"
},
"type": "array"
},
"view_once": {
"type": "boolean"
}
@ -5108,7 +5118,7 @@
"consumes": [
"application/json"
],
"description": "Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \\*italic text\\*, \\*\\*bold text\\*\\*, ~strikethrough text~, ||spoiler||, \\`monospace\\`. If you want to escape a formatting character, prefix it with two backslashes.",
"description": "Send a signal message. Set the text_mode to 'styled' in case you want to add formatting to your text message. Styling Options: \\*italic text\\*, \\*\\*bold text\\*\\*, ~strikethrough text~, ||spoiler||, \\`monospace\\`. If you want to escape a formatting character, prefix it with two backslashes. Alternatively, pass the styles directly via text_styles in signal-cli's 'start:length:STYLE' format (STYLE is one of BOLD, ITALIC, SPOILER, STRIKETHROUGH, MONOSPACE); text_styles can't be combined with text_mode 'styled'.",
"parameters": [
{
"description": "Input Data",

View File

@ -2,6 +2,7 @@ package utils
import (
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"
)
@ -28,6 +29,20 @@ const (
const EscapeCharacter rune = '\\'
// IsValidTextStyle checks a signal-cli text style in the format start:length:STYLE
func IsValidTextStyle(textStyle string) bool {
parts := strings.SplitN(textStyle, ":", 3)
if len(parts) != 3 {
return false
}
for _, num := range parts[:2] {
if _, err := strconv.ParseUint(num, 10, 32); err != nil {
return false
}
}
return StringInSlice(parts[2], []string{Bold, Italic, Monospace, Strikethrough, Spoiler})
}
func getUtf16StringLength(s string) int {
runes := []rune(s) //turn string to slice

View File

@ -163,3 +163,16 @@ func TestBoldItalic(t *testing.T) {
expectMessageEqual(t, message, "Bold Italic Text")
expectFormatStringsEqual(t, signalCliFormatStrings, []string{"0:16:BOLD", "0:16:ITALIC"})
}
func TestIsValidTextStyle(t *testing.T) {
for _, textStyle := range []string{"0:5:BOLD", "10:3:ITALIC", "2:1:SPOILER", "0:4:STRIKETHROUGH", "7:2:MONOSPACE"} {
if !IsValidTextStyle(textStyle) {
t.Errorf("expected %q to be valid", textStyle)
}
}
for _, textStyle := range []string{"", "foo", "0:5", "0:5:bold", "0:5:NORMAL", "-1:5:BOLD", "+1:5:BOLD", "a:5:BOLD", "0:5:BOLD:X"} {
if IsValidTextStyle(textStyle) {
t.Errorf("expected %q to be invalid", textStyle)
}
}
}