mirror of
https://github.com/bbernhard/signal-cli-rest-api.git
synced 2026-08-29 06:16:05 +00:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2fe9eb5bf3 | ||
|
|
1491a52285 | ||
|
|
a08db23a31 | ||
|
|
5cc379fde0 | ||
|
|
1a2da6c79b | ||
|
|
54507eba20 | ||
|
|
6a225e62c1 | ||
|
|
0e36e15f60 | ||
|
|
7138ffde7c | ||
|
|
7c12204889 | ||
|
|
bef11316e3 | ||
|
|
2d03a6c0ce | ||
|
|
d258f01d59 | ||
|
|
b13a74f43e | ||
|
|
be0842fdb2 | ||
|
|
17b86412f3 | ||
|
|
2ee3907d9e | ||
|
|
af64b772cd | ||
|
|
e34cd5af87 | ||
|
|
fe9df012f1 | ||
|
|
ede08b64d8 | ||
|
|
6e1698e820 |
38
.github/workflows/deploy-pages.yml
vendored
Normal file
38
.github/workflows/deploy-pages.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
name: Deploy API Docs to GitHub Pages
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pages: write
|
||||||
|
id-token: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: pages
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Setup Pages
|
||||||
|
uses: actions/configure-pages@v6
|
||||||
|
|
||||||
|
- name: Build static documentation bundle
|
||||||
|
run: |
|
||||||
|
src/docs/build-static-bundle.sh site "${{ github.event.release.tag_name }}"
|
||||||
|
|
||||||
|
- name: Upload Pages artifact
|
||||||
|
uses: actions/upload-pages-artifact@v5
|
||||||
|
with:
|
||||||
|
path: site
|
||||||
|
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v5
|
||||||
27
Dockerfile
27
Dockerfile
@ -1,5 +1,5 @@
|
|||||||
ARG SIGNAL_CLI_VERSION=0.14.5
|
ARG SIGNAL_CLI_VERSION=0.14.7
|
||||||
ARG LIBSIGNAL_CLIENT_VERSION=0.94.4
|
ARG LIBSIGNAL_CLIENT_VERSION=0.99.1
|
||||||
|
|
||||||
ARG SWAG_VERSION=1.16.4
|
ARG SWAG_VERSION=1.16.4
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ RUN dpkg-reconfigure debconf --frontend=noninteractive \
|
|||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
&& apt-get -y install --no-install-recommends \
|
&& apt-get -y install --no-install-recommends \
|
||||||
wget git locales zip unzip \
|
wget git locales zip unzip \
|
||||||
file build-essential libz-dev zlib1g-dev binutils openjdk-25-jdk \
|
file build-essential libz-dev zlib1g-dev binutils \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
#COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
|
#COPY ext/libraries/libsignal-client/v${LIBSIGNAL_CLIENT_VERSION} /tmp/libsignal-client-libraries
|
||||||
@ -40,15 +40,6 @@ ENV JAVA_OPTS="-Djdk.lang.Process.launchMechanism=vfork"
|
|||||||
|
|
||||||
ENV LANG en_US.UTF-8
|
ENV LANG en_US.UTF-8
|
||||||
|
|
||||||
# building the jsonSchemas for armv7l unfortunately doesn't work, so as a temporary fix, do not build the json schema
|
|
||||||
# for armv7. should be removed as soon as https://github.com/AsamK/signal-cli/pull/2040 is merged
|
|
||||||
RUN if [ "$(uname -m)" != "armv7l" ]; then \
|
|
||||||
cd /tmp \
|
|
||||||
&& git clone https://github.com/AsamK/signal-cli.git --branch v${SIGNAL_CLI_VERSION} --single-branch signal-cli-source \
|
|
||||||
&& cd signal-cli-source \
|
|
||||||
&& ./gradlew jsonSchemas; \
|
|
||||||
fi;
|
|
||||||
|
|
||||||
RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION}
|
RUN go install github.com/swaggo/swag/cmd/swag@v${SWAG_VERSION}
|
||||||
|
|
||||||
RUN cd /tmp/ \
|
RUN cd /tmp/ \
|
||||||
@ -112,13 +103,11 @@ RUN cd /tmp/signal-cli-rest-api-src && ${GOPATH}/bin/swag init --requiredByDefau
|
|||||||
|
|
||||||
|
|
||||||
# manually add the json schemas for the receive V1 endpoint to the docs
|
# manually add the json schemas for the receive V1 endpoint to the docs
|
||||||
# (building the jsonSchemas for armv7l unfortunately doesn't work, so as a temporary fix, do not build the json schema
|
RUN cd /tmp/signal-cli-rest-api-src/docs \
|
||||||
# for armv7. should be removed as soon as https://github.com/AsamK/signal-cli/pull/2040 is merged)
|
&& wget https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz \
|
||||||
RUN if [ "$(uname -m)" != "armv7l" ]; then \
|
&& mkdir signal-cli-schemas \
|
||||||
cd /tmp/signal-cli-rest-api-src/docs \
|
&& tar xf signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz -C signal-cli-schemas \
|
||||||
&& cp -r /tmp/signal-cli-source/build/generated/META-INF/schemas signal-cli-schemas \
|
&& go run add_v1_receive_schemas.go signal-cli-schemas
|
||||||
&& go run add_v1_receive_schemas.go signal-cli-schemas; \
|
|
||||||
fi;
|
|
||||||
|
|
||||||
# build signal-cli-rest-api
|
# build signal-cli-rest-api
|
||||||
RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go
|
RUN cd /tmp/signal-cli-rest-api-src && go build -o signal-cli-rest-api main.go
|
||||||
|
|||||||
@ -173,10 +173,6 @@ type TrustIdentityRequest struct {
|
|||||||
TrustAllKnownKeys *bool `json:"trust_all_known_keys,omitempty" example:"false"`
|
TrustAllKnownKeys *bool `json:"trust_all_known_keys,omitempty" example:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendMessageResponse struct {
|
|
||||||
Timestamp string `json:"timestamp"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RemoteDeleteResponse struct {
|
type RemoteDeleteResponse struct {
|
||||||
Timestamp string `json:"timestamp"`
|
Timestamp string `json:"timestamp"`
|
||||||
}
|
}
|
||||||
@ -476,7 +472,6 @@ func (a *Api) VerifyRegisteredNumber(c *gin.Context) {
|
|||||||
// @Router /v1/send [post]
|
// @Router /v1/send [post]
|
||||||
// @Deprecated
|
// @Deprecated
|
||||||
func (a *Api) Send(c *gin.Context) {
|
func (a *Api) Send(c *gin.Context) {
|
||||||
|
|
||||||
var req SendMessageV1
|
var req SendMessageV1
|
||||||
err := c.BindJSON(&req)
|
err := c.BindJSON(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -489,12 +484,12 @@ func (a *Api) Send(c *gin.Context) {
|
|||||||
base64Attachments = append(base64Attachments, req.Base64Attachment)
|
base64Attachments = append(base64Attachments, req.Base64Attachment)
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp, err := a.signalClient.SendV1(req.Number, req.Message, req.Recipients, base64Attachments, req.IsGroup)
|
resp, err := a.signalClient.SendV1(req.Number, req.Message, req.Recipients, base64Attachments, req.IsGroup)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, Error{Msg: err.Error()})
|
c.JSON(400, Error{Msg: err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(201, SendMessageResponse{Timestamp: strconv.FormatInt(timestamp.Timestamp, 10)})
|
c.JSON(201, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Summary Send a signal message.
|
// @Summary Send a signal message.
|
||||||
@ -502,7 +497,7 @@ func (a *Api) Send(c *gin.Context) {
|
|||||||
// @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.
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Success 201 {object} SendMessageResponse
|
// @Success 201 {object} ds.SendMessageResponse
|
||||||
// @Failure 400 {object} SendMessageError
|
// @Failure 400 {object} SendMessageError
|
||||||
// @Param data body SendMessageV2 true "Input Data"
|
// @Param data body SendMessageV2 true "Input Data"
|
||||||
// @Router /v2/send [post]
|
// @Router /v2/send [post]
|
||||||
@ -574,7 +569,7 @@ func (a *Api) SendV2(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(201, SendMessageResponse{Timestamp: strconv.FormatInt((*data)[0].Timestamp, 10)})
|
c.JSON(201, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Api) handleSignalReceive(ws *websocket.Conn, number string, stop chan struct{}) {
|
func (a *Api) handleSignalReceive(ws *websocket.Conn, number string, stop chan struct{}) {
|
||||||
|
|||||||
@ -119,6 +119,7 @@ type GroupEntry struct {
|
|||||||
InternalId string `json:"internal_id"`
|
InternalId string `json:"internal_id"`
|
||||||
Members []string `json:"members"`
|
Members []string `json:"members"`
|
||||||
Blocked bool `json:"blocked"`
|
Blocked bool `json:"blocked"`
|
||||||
|
Member bool `json:"member"`
|
||||||
PendingInvites []string `json:"pending_invites"`
|
PendingInvites []string `json:"pending_invites"`
|
||||||
PendingRequests []string `json:"pending_requests"`
|
PendingRequests []string `json:"pending_requests"`
|
||||||
InviteLink string `json:"invite_link"`
|
InviteLink string `json:"invite_link"`
|
||||||
@ -143,6 +144,7 @@ type ExpandedGroupEntry struct {
|
|||||||
InternalId string `json:"internal_id"`
|
InternalId string `json:"internal_id"`
|
||||||
Members []GroupMember `json:"members"`
|
Members []GroupMember `json:"members"`
|
||||||
Blocked bool `json:"blocked"`
|
Blocked bool `json:"blocked"`
|
||||||
|
Member bool `json:"member"`
|
||||||
PendingInvites []GroupMember `json:"pending_invites"`
|
PendingInvites []GroupMember `json:"pending_invites"`
|
||||||
PendingRequests []GroupMember `json:"pending_requests"`
|
PendingRequests []GroupMember `json:"pending_requests"`
|
||||||
InviteLink string `json:"invite_link"`
|
InviteLink string `json:"invite_link"`
|
||||||
@ -477,8 +479,8 @@ func validateLinkPreview(message string, linkPreview *ds.LinkPreviewType) error
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*SendResponse, error) {
|
func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*ds.SendMessageResponse, error) {
|
||||||
var resp SendResponse
|
var rawData string
|
||||||
var linkPreviewAttachmentEntry *AttachmentEntry = nil
|
var linkPreviewAttachmentEntry *AttachmentEntry = nil
|
||||||
|
|
||||||
if len(signalCliSendRequest.Recipients) == 0 {
|
if len(signalCliSendRequest.Recipients) == 0 {
|
||||||
@ -613,23 +615,13 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*Send
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rawData, err := jsonRpc2Client.getRaw("send", &signalCliSendRequest.Number, request)
|
rawData, err = jsonRpc2Client.getRaw("send", &signalCliSendRequest.Number, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal([]byte(rawData), &resp)
|
|
||||||
if err != nil {
|
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
|
||||||
|
|
||||||
if strings.Contains(err.Error(), signalCliV2GroupError) {
|
|
||||||
return nil, errors.New("Cannot send message to group - please first update your profile.")
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
cmd := []string{"--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"}
|
cmd := []string{"--output", "json", "--config", s.signalCliConfig, "-a", signalCliSendRequest.Number, "send", "--message-from-stdin"}
|
||||||
if signalCliSendRequest.RecipientType == ds.Number {
|
if signalCliSendRequest.RecipientType == ds.Number {
|
||||||
cmd = append(cmd, signalCliSendRequest.Recipients...)
|
cmd = append(cmd, signalCliSendRequest.Recipients...)
|
||||||
} else if signalCliSendRequest.RecipientType == ds.Group {
|
} else if signalCliSendRequest.RecipientType == ds.Group {
|
||||||
@ -717,23 +709,48 @@ func (s *SignalClient) send(signalCliSendRequest ds.SignalCliSendRequest) (*Send
|
|||||||
cmd = append(cmd, "--view-once")
|
cmd = append(cmd, "--view-once")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawData, err := s.cliClient.Execute(true, cmd, signalCliSendRequest.Message)
|
rawData, err = s.cliClient.Execute(true, cmd, signalCliSendRequest.Message)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignalCliSendResponse struct {
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Results []struct {
|
||||||
|
RecipientAddress struct {
|
||||||
|
Uuid string `json:"uuid"`
|
||||||
|
Number string `json:"number"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
} `json:"recipientAddress"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
} `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var signalCliSendResponse SignalCliSendResponse
|
||||||
|
err = json.Unmarshal([]byte(rawData), &signalCliSendResponse)
|
||||||
|
if err != nil {
|
||||||
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
|
|
||||||
if strings.Contains(err.Error(), signalCliV2GroupError) {
|
if strings.Contains(err.Error(), signalCliV2GroupError) {
|
||||||
return nil, errors.New("Cannot send message to group - please first update your profile.")
|
return nil, errors.New("Cannot send message to group - please first update your profile.")
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
resp.Timestamp, err = strconv.ParseInt(strings.TrimSuffix(rawData, "\n"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
|
||||||
return nil, errors.New(strings.Replace(rawData, "\n", "", -1)) //in case we can't parse the timestamp, it means signal-cli threw an error. So instead of returning the parsing error, return the actual error from signal-cli
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
cleanupAttachmentEntries(attachmentEntries, linkPreviewAttachmentEntry)
|
||||||
|
|
||||||
|
resp := ds.SendMessageResponse{Timestamp: strconv.FormatInt(signalCliSendResponse.Timestamp, 10)}
|
||||||
|
for _, entry := range signalCliSendResponse.Results {
|
||||||
|
if entry.Type != "SUCCESS" {
|
||||||
|
if resp.Errors == nil {
|
||||||
|
resp.Errors = &ds.SendMessageErrors{}
|
||||||
|
}
|
||||||
|
sendMessageError := ds.SendMessageError{Uuid: entry.RecipientAddress.Uuid, Number: entry.RecipientAddress.Number, Username: entry.RecipientAddress.Username, Reason: entry.Type}
|
||||||
|
resp.Errors.Recipients = append(resp.Errors.Recipients, sendMessageError)
|
||||||
|
}
|
||||||
|
}
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -899,7 +916,7 @@ 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) (*ds.SendMessageResponse, error) {
|
||||||
recipientType := ds.Number
|
recipientType := ds.Number
|
||||||
if isGroup {
|
if isGroup {
|
||||||
recipientType = ds.Group
|
recipientType = ds.Group
|
||||||
@ -908,8 +925,8 @@ func (s *SignalClient) SendV1(number string, message string, recipients []string
|
|||||||
signalCliSendRequest := ds.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, LinkPreview: nil}
|
QuoteMentions: nil, TextMode: nil, EditTimestamp: nil, LinkPreview: nil}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
return timestamp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) {
|
func (s *SignalClient) getJsonRpc2Client() (*JsonRpc2Client, error) {
|
||||||
@ -929,7 +946,7 @@ func (s *SignalClient) getJsonRpc2Clients() []*JsonRpc2Client {
|
|||||||
|
|
||||||
func (s *SignalClient) SendV2(number string, message string, recps []string, base64Attachments []string, sticker string, mentions []ds.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 []ds.MessageMention, textMode *string, editTimestamp *int64, notifySelf *bool,
|
quoteTimestamp *int64, quoteAuthor *string, quoteMessage *string, quoteMentions []ds.MessageMention, textMode *string, editTimestamp *int64, notifySelf *bool,
|
||||||
linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]SendResponse, error) {
|
linkPreview *ds.LinkPreviewType, viewOnce *bool) (*[]ds.SendMessageResponse, 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")
|
||||||
}
|
}
|
||||||
@ -975,17 +992,17 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
|
|||||||
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{}
|
responses := []ds.SendMessageResponse{}
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments,
|
signalCliSendRequest := ds.SignalCliSendRequest{Number: number, Message: message, Recipients: []string{group}, Base64Attachments: base64Attachments,
|
||||||
RecipientType: ds.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, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamps = append(timestamps, *timestamp)
|
responses = append(responses, *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(numbers) > 0 {
|
if len(numbers) > 0 {
|
||||||
@ -993,11 +1010,11 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
|
|||||||
RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
RecipientType: ds.Number, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
||||||
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
QuoteAuthor: quoteAuthor, QuoteMessage: quoteMessage, QuoteMentions: quoteMentions,
|
||||||
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamps = append(timestamps, *timestamp)
|
responses = append(responses, *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(usernames) > 0 {
|
if len(usernames) > 0 {
|
||||||
@ -1005,14 +1022,14 @@ func (s *SignalClient) SendV2(number string, message string, recps []string, bas
|
|||||||
RecipientType: ds.Username, Sticker: sticker, Mentions: mentions, QuoteTimestamp: quoteTimestamp,
|
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, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
TextMode: textMode, EditTimestamp: editTimestamp, NotifySelf: notifySelf, LinkPreview: linkPreview, ViewOnce: viewOnce}
|
||||||
timestamp, err := s.send(signalCliSendRequest)
|
resp, err := s.send(signalCliSendRequest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
timestamps = append(timestamps, *timestamp)
|
responses = append(responses, *resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ×tamps, nil
|
return &responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SignalClient) Receive(number string, timeout int64, ignoreAttachments bool, ignoreStories bool, ignoreAvatars bool, ignoreStickers bool, maxMessages int64, sendReadReceipts bool) (string, error) {
|
func (s *SignalClient) Receive(number string, timeout int64, ignoreAttachments bool, ignoreStories bool, ignoreAvatars bool, ignoreStickers bool, maxMessages int64, sendReadReceipts bool) (string, error) {
|
||||||
@ -1330,6 +1347,25 @@ func (s *SignalClient) RemoveAdminsFromGroup(number string, groupId string, admi
|
|||||||
return s.updateGroupAdmins(number, groupId, admins, false)
|
return s.updateGroupAdmins(number, groupId, admins, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry SignalCliGroupEntry) ExpandedGroupEntry {
|
||||||
|
var groupEntry ExpandedGroupEntry
|
||||||
|
groupEntry.InternalId = signalCliGroupEntry.Id
|
||||||
|
groupEntry.Name = signalCliGroupEntry.Name
|
||||||
|
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
|
||||||
|
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
|
||||||
|
groupEntry.Member = signalCliGroupEntry.IsMember
|
||||||
|
groupEntry.Description = signalCliGroupEntry.Description
|
||||||
|
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
||||||
|
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
||||||
|
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
|
||||||
|
groupEntry.Members = signalCliGroupEntry.Members
|
||||||
|
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
|
||||||
|
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
|
||||||
|
groupEntry.Admins = signalCliGroupEntry.Admins
|
||||||
|
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
|
||||||
|
return groupEntry
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) {
|
func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, error) {
|
||||||
groupEntries := []ExpandedGroupEntry{}
|
groupEntries := []ExpandedGroupEntry{}
|
||||||
|
|
||||||
@ -1360,22 +1396,7 @@ func (s *SignalClient) GetGroupsExpanded(number string) ([]ExpandedGroupEntry, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, signalCliGroupEntry := range signalCliGroupEntries {
|
for _, signalCliGroupEntry := range signalCliGroupEntries {
|
||||||
var groupEntry ExpandedGroupEntry
|
groupEntries = append(groupEntries, signalCliGroupEntryToExpandedGroupEntry(signalCliGroupEntry))
|
||||||
groupEntry.InternalId = signalCliGroupEntry.Id
|
|
||||||
groupEntry.Name = signalCliGroupEntry.Name
|
|
||||||
groupEntry.Id = convertInternalGroupIdToGroupId(signalCliGroupEntry.Id)
|
|
||||||
groupEntry.Blocked = signalCliGroupEntry.IsBlocked
|
|
||||||
groupEntry.Description = signalCliGroupEntry.Description
|
|
||||||
groupEntry.Permissions.SendMessages = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
|
||||||
groupEntry.Permissions.EditGroup = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionSendMessage)
|
|
||||||
groupEntry.Permissions.AddMembers = signalCliGroupPermissionToRestApiGroupPermission(signalCliGroupEntry.PermissionAddMember)
|
|
||||||
groupEntry.Members = signalCliGroupEntry.Members
|
|
||||||
groupEntry.PendingInvites = signalCliGroupEntry.PendingMembers
|
|
||||||
groupEntry.PendingRequests = signalCliGroupEntry.RequestingMembers
|
|
||||||
groupEntry.Admins = signalCliGroupEntry.Admins
|
|
||||||
groupEntry.InviteLink = signalCliGroupEntry.GroupInviteLink
|
|
||||||
|
|
||||||
groupEntries = append(groupEntries, groupEntry)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return groupEntries, nil
|
return groupEntries, nil
|
||||||
@ -1390,7 +1411,7 @@ func (s *SignalClient) GetGroups(number string) ([]GroupEntry, error) {
|
|||||||
groupEntries := []GroupEntry{}
|
groupEntries := []GroupEntry{}
|
||||||
for _, expandedGroupEntry := range expandedGroupEntries {
|
for _, expandedGroupEntry := range expandedGroupEntries {
|
||||||
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
|
groupEntry := GroupEntry{InternalId: expandedGroupEntry.InternalId, Name: expandedGroupEntry.Name,
|
||||||
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Description: expandedGroupEntry.Description,
|
Id: expandedGroupEntry.Id, Blocked: expandedGroupEntry.Blocked, Member: expandedGroupEntry.Member, Description: expandedGroupEntry.Description,
|
||||||
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
|
Permissions: expandedGroupEntry.Permissions, InviteLink: expandedGroupEntry.InviteLink}
|
||||||
|
|
||||||
members := []string{}
|
members := []string{}
|
||||||
|
|||||||
93
src/client/groups_test.go
Normal file
93
src/client/groups_test.go
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sampleListGroupsJSON mirrors the shape of signal-cli's `listGroups` JSON output
|
||||||
|
// (see signal-cli's ListGroupsCommand / the jsonrpc man page). It contains a group
|
||||||
|
// the account is still in (isMember=true), a group it has left or been removed from
|
||||||
|
// (isMember=false) — the "ghost" group that the REST API previously could not
|
||||||
|
// distinguish — and a blocked-but-still-member group to verify the two flags are
|
||||||
|
// mapped independently.
|
||||||
|
const sampleListGroupsJSON = `[
|
||||||
|
{
|
||||||
|
"id": "Pmpi+EfPWmsxiomLe9Nx2XF9HOE483p6iKiFj65iMwI=",
|
||||||
|
"name": "Current Group",
|
||||||
|
"description": "still a member",
|
||||||
|
"isMember": true,
|
||||||
|
"isBlocked": false,
|
||||||
|
"members": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
|
||||||
|
"pendingMembers": [],
|
||||||
|
"requestingMembers": [],
|
||||||
|
"admins": [{"number": "+15551230001", "uuid": "11111111-1111-1111-1111-111111111111"}],
|
||||||
|
"groupInviteLink": "",
|
||||||
|
"permissionAddMember": "EVERY_MEMBER",
|
||||||
|
"permissionSendMessage": "EVERY_MEMBER"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "Zm9vYmFyYmF6cXV4MTIzNDU2Nzg5MGFiY2RlZmdoaWo=",
|
||||||
|
"name": "Left Group",
|
||||||
|
"description": "removed or left",
|
||||||
|
"isMember": false,
|
||||||
|
"isBlocked": false,
|
||||||
|
"members": [],
|
||||||
|
"pendingMembers": [],
|
||||||
|
"requestingMembers": [],
|
||||||
|
"admins": [],
|
||||||
|
"groupInviteLink": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "YmxvY2tlZGdyb3VwaWQwMDAwMDAwMDAwMDAwMDAwMDA=",
|
||||||
|
"name": "Blocked But Member",
|
||||||
|
"description": "blocked yet still a member",
|
||||||
|
"isMember": true,
|
||||||
|
"isBlocked": true,
|
||||||
|
"members": [],
|
||||||
|
"pendingMembers": [],
|
||||||
|
"requestingMembers": [],
|
||||||
|
"admins": [],
|
||||||
|
"groupInviteLink": ""
|
||||||
|
}
|
||||||
|
]`
|
||||||
|
|
||||||
|
// TestSignalCliGroupEntryToExpandedGroupEntry verifies that the signal-cli isMember
|
||||||
|
// flag is carried through to the REST ExpandedGroupEntry.Member field, independently
|
||||||
|
// of the isBlocked -> Blocked mapping.
|
||||||
|
func TestSignalCliGroupEntryToExpandedGroupEntry(t *testing.T) {
|
||||||
|
var entries []SignalCliGroupEntry
|
||||||
|
if err := json.Unmarshal([]byte(sampleListGroupsJSON), &entries); err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal sample listGroups JSON: %v", err)
|
||||||
|
}
|
||||||
|
if len(entries) != 3 {
|
||||||
|
t.Fatalf("expected 3 group entries, got %d", len(entries))
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
wantMember bool
|
||||||
|
wantBlocked bool
|
||||||
|
}{
|
||||||
|
{"Current Group", true, false},
|
||||||
|
{"Left Group", false, false},
|
||||||
|
{"Blocked But Member", true, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, c := range cases {
|
||||||
|
got := signalCliGroupEntryToExpandedGroupEntry(entries[i])
|
||||||
|
|
||||||
|
if got.Name != c.name {
|
||||||
|
t.Errorf("entry %d: Name = %q, want %q", i, got.Name, c.name)
|
||||||
|
}
|
||||||
|
if got.Member != c.wantMember {
|
||||||
|
t.Errorf("%s: Member = %v, want %v (must reflect signal-cli isMember)", c.name, got.Member, c.wantMember)
|
||||||
|
}
|
||||||
|
if got.Blocked != c.wantBlocked {
|
||||||
|
t.Errorf("%s: Blocked = %v, want %v", c.name, got.Blocked, c.wantBlocked)
|
||||||
|
}
|
||||||
|
if got.InternalId != entries[i].Id {
|
||||||
|
t.Errorf("%s: InternalId = %q, want %q", c.name, got.InternalId, entries[i].Id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -186,6 +186,8 @@ func (r *JsonRpc2Client) getRaw(command string, account *string, args interface{
|
|||||||
ChallengeTokens: challengeTokens,
|
ChallengeTokens: challengeTokens,
|
||||||
Err: errors.New(resp.Err.Message),
|
Err: errors.New(resp.Err.Message),
|
||||||
}
|
}
|
||||||
|
} else if resp.Err.Code == -4 {
|
||||||
|
return "", errors.New("Unable to complete request due to untrusted recipient keys")
|
||||||
}
|
}
|
||||||
return "", errors.New(resp.Err.Message)
|
return "", errors.New(resp.Err.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -58,3 +58,19 @@ type GroupPermissions struct {
|
|||||||
EditGroup string `json:"edit_group" enums:"only-admins,every-member"`
|
EditGroup string `json:"edit_group" enums:"only-admins,every-member"`
|
||||||
SendMessages string `json:"send_messages" enums:"only-admins,every-member"`
|
SendMessages string `json:"send_messages" enums:"only-admins,every-member"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SendMessageError struct {
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Number string `json:"number,omitempty"`
|
||||||
|
Uuid string `json:"uuid,omitempty"`
|
||||||
|
Reason string `json:"reason,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendMessageErrors struct {
|
||||||
|
Recipients []SendMessageError `json:"recipients,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SendMessageResponse struct {
|
||||||
|
Timestamp string `json:"timestamp"`
|
||||||
|
Errors *SendMessageErrors `json:"errors,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@ -45,7 +45,7 @@ Install [go](https://go.dev/).
|
|||||||
cd docs
|
cd docs
|
||||||
```
|
```
|
||||||
1. Add the signal-cli receive V1 schemas
|
1. Add the signal-cli receive V1 schemas
|
||||||
* Download the `signal-cli-x.y.z-json-schemas.tar.gz` schema files from https://github.com/Gara-Dorta/signal-cli/releases
|
* Download the `signal-cli-x.y.z-json-schemas.tar.gz` schema files from https://github.com/AsamK/signal-cli/releases
|
||||||
* Extract the files
|
* Extract the files
|
||||||
* Run the script to add the schemas
|
* Run the script to add the schemas
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
24
src/docs/build-static-bundle.sh
Executable file
24
src/docs/build-static-bundle.sh
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
DOCS_DIR="$ROOT_DIR/src/docs"
|
||||||
|
INDEX_FILE="$DOCS_DIR/index.html"
|
||||||
|
OUT_DIR="${1:?output directory argument is required}"
|
||||||
|
DOCS_VERSION="${2:?docs version argument is required}"
|
||||||
|
NORMALIZED_DOCS_VERSION="${DOCS_VERSION#v}"
|
||||||
|
|
||||||
|
echo "Normalized docs version: $NORMALIZED_DOCS_VERSION"
|
||||||
|
|
||||||
|
rm -rf "$OUT_DIR"
|
||||||
|
mkdir -p "$OUT_DIR"
|
||||||
|
|
||||||
|
cp "$DOCS_DIR/swagger.json" "$OUT_DIR/swagger.json"
|
||||||
|
cp "$INDEX_FILE" "$OUT_DIR/index.html"
|
||||||
|
touch "$OUT_DIR/.nojekyll"
|
||||||
|
|
||||||
|
jq --arg version "$NORMALIZED_DOCS_VERSION" '.info.version = $version' "$OUT_DIR/swagger.json" > "$OUT_DIR/swagger.json.tmp"
|
||||||
|
mv "$OUT_DIR/swagger.json.tmp" "$OUT_DIR/swagger.json"
|
||||||
|
|
||||||
|
echo "Static bundle created at: $OUT_DIR"
|
||||||
@ -806,6 +806,9 @@ const docTemplate = `{
|
|||||||
"invite_link": {
|
"invite_link": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"member": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"members": {
|
"members": {
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -838,6 +841,7 @@ const docTemplate = `{
|
|||||||
"id",
|
"id",
|
||||||
"internal_id",
|
"internal_id",
|
||||||
"invite_link",
|
"invite_link",
|
||||||
|
"member",
|
||||||
"members",
|
"members",
|
||||||
"name",
|
"name",
|
||||||
"pending_invites",
|
"pending_invites",
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
// Begin Swagger UI call region
|
// Begin Swagger UI call region
|
||||||
const ui = SwaggerUIBundle({
|
const ui = SwaggerUIBundle({
|
||||||
url: "src/docs/swagger.json", //Location of Open API spec in the repo
|
url: "swagger.json",
|
||||||
dom_id: '#swagger-ui',
|
dom_id: '#swagger-ui',
|
||||||
deepLinking: true,
|
deepLinking: true,
|
||||||
presets: [
|
presets: [
|
||||||
@ -801,6 +801,9 @@
|
|||||||
"invite_link": {
|
"invite_link": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"member": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"members": {
|
"members": {
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -833,6 +836,7 @@
|
|||||||
"id",
|
"id",
|
||||||
"internal_id",
|
"internal_id",
|
||||||
"invite_link",
|
"invite_link",
|
||||||
|
"member",
|
||||||
"members",
|
"members",
|
||||||
"name",
|
"name",
|
||||||
"pending_invites",
|
"pending_invites",
|
||||||
|
|||||||
14
src/go.mod
14
src/go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/bbernhard/signal-cli-rest-api
|
module github.com/bbernhard/signal-cli-rest-api
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bbernhard/gluasql v0.2.0
|
github.com/bbernhard/gluasql v0.2.0
|
||||||
@ -9,7 +9,7 @@ require (
|
|||||||
github.com/gabriel-vasile/mimetype v1.4.8
|
github.com/gabriel-vasile/mimetype v1.4.8
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/gofrs/uuid v4.4.0+incompatible
|
github.com/gofrs/uuid v4.4.0+incompatible
|
||||||
github.com/gorilla/websocket v1.5.0
|
github.com/gorilla/websocket v1.5.3
|
||||||
github.com/h2non/filetype v1.1.3
|
github.com/h2non/filetype v1.1.3
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/sirupsen/logrus v1.9.1
|
github.com/sirupsen/logrus v1.9.1
|
||||||
@ -57,11 +57,11 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.14.0 // indirect
|
golang.org/x/arch v0.14.0 // indirect
|
||||||
golang.org/x/crypto v0.45.0 // indirect
|
golang.org/x/crypto v0.52.0 // indirect
|
||||||
golang.org/x/net v0.47.0 // indirect
|
golang.org/x/net v0.55.0 // indirect
|
||||||
golang.org/x/sys v0.38.0 // indirect
|
golang.org/x/sys v0.45.0 // indirect
|
||||||
golang.org/x/text v0.31.0 // indirect
|
golang.org/x/text v0.37.0 // indirect
|
||||||
golang.org/x/tools v0.38.0 // indirect
|
golang.org/x/tools v0.44.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.4 // indirect
|
google.golang.org/protobuf v1.36.4 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
36
src/go.sum
36
src/go.sum
@ -71,8 +71,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN
|
|||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
|
||||||
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
|
||||||
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
|
||||||
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
|
||||||
@ -170,21 +170,21 @@ golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
|||||||
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM=
|
||||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8=
|
||||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@ -194,10 +194,10 @@ golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8 h1:LvzTn0GQhWuvKH/kVRS3R3bVAsdQWI7hvfLHGgh9+lU=
|
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa h1:efT73AJZfAAUV7SOip6pWGkwJDzIGiKBZGVzHYa+ve4=
|
||||||
golang.org/x/telemetry v0.0.0-20251008203120-078029d740a8/go.mod h1:Pi4ztBfryZoJEkyFTI5/Ocsu2jXyDr6iSdgJiYE/uwE=
|
golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -205,13 +205,13 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ=
|
golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c=
|
||||||
golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs=
|
golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
|
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2 h1:pl8qT5D+48655f14yDURpIZwSPvMWuuekfAP+gxtjvk=
|
||||||
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
google.golang.org/genproto v0.0.0-20210506142907-4a47615972c2/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
|
||||||
|
|||||||
@ -23,6 +23,7 @@ const (
|
|||||||
MonoSpaceBegin = 6
|
MonoSpaceBegin = 6
|
||||||
StrikethroughBegin = 8
|
StrikethroughBegin = 8
|
||||||
SpoilerBegin = 9
|
SpoilerBegin = 9
|
||||||
|
BoldItalicBegin = 10
|
||||||
)
|
)
|
||||||
|
|
||||||
const EscapeCharacter rune = '\\'
|
const EscapeCharacter rune = '\\'
|
||||||
@ -119,7 +120,14 @@ func (l *TextstyleParser) handleToken(tokenType int, signalCliStylingType string
|
|||||||
} else {
|
} else {
|
||||||
if l.tokens.Peek().Token == tokenType {
|
if l.tokens.Peek().Token == tokenType {
|
||||||
tokenBeginState := l.tokens.Pop()
|
tokenBeginState := l.tokens.Pop()
|
||||||
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(getUtf16StringLength(l.fullString)-tokenBeginState.BeginPos)+":"+signalCliStylingType)
|
length := getUtf16StringLength(l.fullString) - tokenBeginState.BeginPos
|
||||||
|
|
||||||
|
if tokenType == BoldItalicBegin {
|
||||||
|
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+Bold)
|
||||||
|
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+Italic)
|
||||||
|
} else {
|
||||||
|
l.signalCliFormatStrings = append(l.signalCliFormatStrings, strconv.Itoa(tokenBeginState.BeginPos)+":"+strconv.Itoa(length)+":"+signalCliStylingType)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
l.tokens.Push(TokenState{BeginPos: getUtf16StringLength(l.fullString), Token: tokenType})
|
l.tokens.Push(TokenState{BeginPos: getUtf16StringLength(l.fullString), Token: tokenType})
|
||||||
}
|
}
|
||||||
@ -137,14 +145,27 @@ func (l *TextstyleParser) Parse() (string, []string) {
|
|||||||
nextRune := l.peek()
|
nextRune := l.peek()
|
||||||
|
|
||||||
if c == '*' {
|
if c == '*' {
|
||||||
if nextRune == '*' { //Bold
|
if nextRune == '*' {
|
||||||
l.next()
|
l.next()
|
||||||
|
|
||||||
|
if l.peek() == '*' { // Check for *** before treating it as **
|
||||||
|
l.next()
|
||||||
|
|
||||||
|
if prevChar == EscapeCharacter {
|
||||||
|
prevChar = c
|
||||||
|
l.fullString += "***"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
l.handleToken(BoldItalicBegin, "BOLD_ITALIC")
|
||||||
|
} else {
|
||||||
if prevChar == EscapeCharacter {
|
if prevChar == EscapeCharacter {
|
||||||
prevChar = c
|
prevChar = c
|
||||||
l.fullString += "**"
|
l.fullString += "**"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
l.handleToken(BoldBegin, Bold)
|
l.handleToken(BoldBegin, Bold)
|
||||||
|
}
|
||||||
} else { //Italic
|
} else { //Italic
|
||||||
if prevChar == EscapeCharacter {
|
if prevChar == EscapeCharacter {
|
||||||
prevChar = c
|
prevChar = c
|
||||||
|
|||||||
@ -155,5 +155,11 @@ func TestEscapeNew(t *testing.T) {
|
|||||||
message, signalCliFormatStrings := textstyleParser.Parse()
|
message, signalCliFormatStrings := textstyleParser.Parse()
|
||||||
expectMessageEqual(t, message, "Test ** * ~ Escape")
|
expectMessageEqual(t, message, "Test ** * ~ Escape")
|
||||||
expectFormatStringsEqual(t, signalCliFormatStrings, []string{})
|
expectFormatStringsEqual(t, signalCliFormatStrings, []string{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBoldItalic(t *testing.T) {
|
||||||
|
textstyleParser := NewTextstyleParser("***Bold Italic Text***")
|
||||||
|
message, signalCliFormatStrings := textstyleParser.Parse()
|
||||||
|
expectMessageEqual(t, message, "Bold Italic Text")
|
||||||
|
expectFormatStringsEqual(t, signalCliFormatStrings, []string{"0:16:BOLD", "0:16:ITALIC"})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user